SpringAOP @PointCut 切点解析

本文深入解析Spring AOP中切点表达式的应用,包括execution、within、this、target、args等功能,以及如何使用逻辑运算和注解增强切点匹配。通过实例展示如何精确控制AOP行为。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

进行Spring AOP编程,首先要声明一个切面,里面包含连接点和通知。然后把连接点作用到通知上面去,通知方法内部就是通知的逻辑。这个流程已经在博客【详解什么是Spring AOP】中给出过,而且在里面详细的解释了,什么是AOP。但是其中还是有很多可以深入了解的部分,比如我们这次题目中的@PointCut注解就是一个,所以我们直接走去官网看下@PointCut是怎么说的。更多Spring内容进入【Spring解读系列目录】

切点声明的表达式

要了解切点都有哪些表达式,最好的地方就是官网【Supported Pointcut Designators】以下英文内容摘自官网。从官网上我们知道@PointCut 一共有execution、within、this、target、args、@target、@args、@within和@annotation这9个,我们重点关注execution、within、this、target、args前五个,因为后面的注解和前面的功能上是一样的。

Spring AOP supports the following AspectJ pointcut designators (PCD) for use in pointcut expressions:
• execution: For matching method execution join points. This is the primary pointcut designator to use when working with Spring AOP.
• within: Limits matching to join points within certain types (the execution of a method declared within a matching type when using Spring AOP).
• this: Limits matching to join points (the execution of methods when using Spring AOP) where the bean reference (Spring AOP proxy) is an instance of the given type.
• target: Limits matching to join points (the execution of methods when using Spring AOP) where the target object (application object being proxied) is an instance of the given type.
• args: Limits matching to join points (the execution of methods when using Spring AOP) where the arguments are instances of the given types.
• @target: Limits matching to join points (the execution of methods when using Spring AOP) where the class of the executing object has an annotation of the given type.
• @args: Limits matching to join points (the execution of methods when using Spring AOP) where the runtime type of the actual arguments passed have annotations of the given types.
• @within: Limits matching to join points within types that have the given annotation (the execution of methods declared in types with the given annotation when using Spring AOP).
• @annotation: Limits matching to join points where the subject of the join point (the method being executed in Spring AOP) has the given annotation.

在解释这些方法之前,我们先写一个小例子,要引入的maven依赖和配置类请参考【详解什么是Spring AOP】。首先要有一个切面,包含一个切点和一个通知。

@Component
@Aspect  
public class DemoAspect {
	//这里的意思是使用execution匹配com.demo.dao包下的所有类的所有方法
    @Pointcut("execution(* com.demo.dao.*.*(..))") 
    public void myPointcutExecution(){
    }
    @Before("myPointcutExecution()")
    public void beforeDao(){
        System.out.println("before print");
    }
}

然后我们有一个接口TargetDao和一个实现类DemoDao,我们的验证就基于这些打印。

public interface TargetDao{
    public void print();
    public void print(String a);
    public void print(Integer a);
}
@Repository("demodao")
public class DemoDao implements TargetDao{
    public void print(){ System.out.println("print null"); }
    public void print(String print){ System.out.println("print String"); }
    public void print(Integer print){  System.out.println("print Integer"); }
}

最后我们写一个测试入口DemoTest。

public class DemoTest {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext anno=new AnnotationConfigApplicationContext(Appconfig.class);
        TargetDao dao = (TargetDao) anno.getBean("demodao");
        dao.print(1);
        dao.print();
        dao.print("1");
    }
}

我们直接运行,就会打印这六句话。。那么下面就先解释我们例子中使用的execution是干嘛的,以及怎么用的。

before print		-----> beforeDao()输出
print Integer		-----> print(Integer a)方法输出
before print		-----> beforeDao()输出
print null			-----> print()方法输出
before print		-----> beforeDao()输出
print String		-----> print(String a)方法输出
execution

execution:用于匹配方法执行 join points连接点。官网同时也说execution是Spring AOP主要的(primary)切点指定器。就是说大多数时候用这个就能搞定。"execution"非常的强大,它可以精确的控制到最小粒度。能够控制修饰符、返回类型、声明的类型、包名、类名、方法名字、参数、抛出的异常等等,想匹配什么就匹配什么。下面就是官方给的匹配模式,这里问号表示当前项可以有也可以没有,我们还是按照例子中的execution(* com.demo.dao.*.*(..))对每一个参数的语义进行解读。

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)

[modifiers-pattern]:方法的可见性,如public,protected,private等等,省略不加这个的话,就代表所有的方法:
execution(private * com.demo.dao.*.*(..)):就是只匹配私有方法。
execution(* com.demo.dao.*.*(..)):把private删掉,就是匹配全部的方法。

[ret-type-pattern]:方法的返回值类型,如int,void等,也可以用*代替,就是所有的返回类型:
execution(String com.demo.dao.*.*(..)):匹配返回类型为String的方法。
execution(* com.demo.dao.*.*(..)):所有返回类型的方法都匹配,包括void。

[declaring-type-pattern]:方法所在类的全路径名,如com.demo.dao.SomeClass,如匹配包下所有的类就是com.demo.dao.*:
execution(String com.demo.dao.*.*(..)):匹配com.demo.dao包下的所有类的所有方法。
execution(String com.demo.dao.SomeClass.*(..)):匹配com.demo.dao包下的SomeClass类的所有方法。

[name-pattern]:方法名类型,如test(),或者用*表示代表所有方法:
execution(* com.demo.dao.SomeClass.test(..)):只匹配test方法。
execution(* com.demo.dao.SomeClass.*(..)):匹配SomeClass下面的所有方法。

[param-pattern]:方法的参数类型,如匹配java.lang.String:
execution(* com.demo.dao.SomeClass.test(String)):只匹配一个String参数
execution(* com.demo.dao.SomeClass.test(..)):匹配所有参数
execution(* com.demo.dao.SomeClass.test(String,..)):甚至只匹配第一个是String参数的方法。

[throws-pattern]:方法抛出的异常类型,如java.lang.Exception;
execution(private * com.demo.dao.*.*(..) throws Exception):匹配dao包下的所有抛出的Exception,也是可选的能够省略。

within

within:限定匹配一个确定的类型中的连接点,换成人话就是能匹配确定类中的方法。也就是within的最小粒度只能定义到一个类,或者一个包下的所有类的方法。Spring文档里所说的type就是指类,不要被它的术语搞糊涂了。所以我们可以理解within就是execution的一个辅助,用来简化我们写代码用的,但是有一点要注意,within不能够匹配接口,只能匹配目标类。比如我们在DemoAspect类中追加一个Pointcut的within方法,然后把通知指向这个方法,那么这个效果是一样的,同样会打印出六句话before print、print Integer、before print、print null、before print和print String。

@Pointcut("within(com.demo.dao.*)")
public void myPointcutWithin(){ }
    
@Before("myPointcutWithin()")
public void beforeDao(){ System.out.println("before print"); }
args

args:限定匹配连接点,其中参数是执行类型的实例。基本上也是听不懂,那么我们看一个官网的例子是怎么定义的。

args(java.io.Serializable)

一样不好理解,我们换一个好理解的例子 @Pointcut("args(java.lang.Integer)")。其实args就是在匹配传参为Integer的所有方法。它匹配指定参数类型和指定参数数量的方法,与包名和类名无关。这个例子里只要满足传入的参数是Integer型,一旦调用起来,都会被匹配到。

@Pointcut("args(java.lang.Integer)")
public void myPointcutArgs(){ }

@Before("myPointcutArgs()")
public void beforeDao(){ System.out.println("before print"); }

如果是使用args,那么打印出来的内容就只有4句话。因为我们限定了匹配Integer型的参数,所以只有print(Integer a)之前的beforeDao()方法输出了,其他都没有被匹配。

before print		-----> beforeDao()输出
print Integer		-----> print(Integer a)方法输出
print null			-----> print()方法输出
print String		-----> print(String a)方法输出
this

this:限定匹配连接点,其中bean的引用是一个指定类的实例,换句话说就是匹配代理对象。也就是说当代理对象这个类型的时候就去执行,这个this就是当前生成的代理对象。

target

target:限定匹配连接点,其中目标对象是一个指定类的实例。也就说目标对象等于这个接口的时候就去执行。目标对象就是原对象永远不会变。

这两个的概念涉及到了动态代理的底层原理了,所以我们要放在一起讲,同样也是添加新的Pointcut方法。至于代理对象和目标对象的详细解释,可以参考【详解什么是Spring AOP】

@Pointcut("this(com.demo.dao.DemoDao)") //this表示的是代理对象
public void myPointcutThis(){ }

@Pointcut("target(com.demo.dao.DemoDao)") //target表示的是目标对象
public void myPointcutTarget(){ }

如果我们按照现在的例子,直接让通知使用this。

@Before("myPointcutThis()")
public void beforeDao(){ System.out.println("before print"); }

那么打印的结果就是只有三行,什么都没有匹配到。这是因为在JDK原生的动态代理中,代理对象和原对象不是一个对象。在我们的例子中DemoDao是TargetDao的实现,因此如果直接使用DemoDao就相当于直接使用了目标对象。而在JDK原生动态代理里面生成的DemoDao将会extend Proxy然后再implement TargetDao。所以由JDK生成的代理对象只能识别Proxy和TargetDao这两个类,因此我们想要命中DemoDao的时候就会出现一个也命中不了的局面。

print Integer		-----> print(Integer a)方法输出
print null			-----> print()方法输出
print String		-----> print(String a)方法输出

因此如果想要命中的话,有三种修改的方法,三种根据场景取其一即可:

第一:修改this为真正的动态代理对象类,也就是TargetDao。此时因为DemoDao实现了TargetDao这个代理,所以方法的执行也会交给DemoDao这个类中的方法去执行。

@Pointcut("this(com.demo.dao.TargetDao)") //this获取真正的代理对象
public void myPointcutThis(){ }

第二:修改通知方法的匹配为target语句执行,此时我们用的就是目标对象,也就是原对象,所以DemoDao自然可以被匹配到。

@Before("myPointcutTarget()")
public void beforeDao(){ System.out.println("before print"); }

第三:去Appconfig的配置类中加入@EnableAspectJAutoProxy(proxyTargetClass = true),把动态代理的执行交给CGLIB,因为CGLIB的动态代理是基于继承做的,所以这里TargetDao和DemoDao可以被看作一个对象,因此也能匹配到。

@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)
@ComponentScan("com")
public class Appconfig {
}

逻辑运算表达式的用法

除了这些以外,我们还可以使用逻辑符号对匹配使用逻辑运算,官网给出了可以用与&&, 或||和非!这三种表达式来完成我们更进一步的需求,比如我们想要匹配dao包下面的所有方法,但是传入Integer参数的除外,就可以在通知上这样写:

@Before("myPointcutExecution()&&!myPointcutArgs()") 
public void beforeDao(){ System.out.println("before print"); }
运行结果:只有Integer前面的没有输出,因为被屏蔽了
print Integer		-----> print(Integer a)方法输出
before print		-----> beforeDao()输出
print null			-----> print()方法输出
before print		-----> beforeDao()输出
print String		-----> print(String a)方法输出

加注解的语义

我们已经详细的解释了execution、within、this、target、args这个五个常用的语义,其实最常用的就只有一个execution,而这些带有注解的@target、@args、@within和@annotation四个,其实是Spring给我们提供的自定义的部分。随便找@annotation举个例子。我们先创建一个注解类:

@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnno { }

然后给DemoDao中的print(Integer print)加上标注。就意味着我们讲只对这一个方法做匹配。

@Repository("demodao")
public class DemoDao implements TargetDao{
    public void print(){ System.out.println("print null"); }
    public void print(String print){ System.out.println("print String"); }
    @MyAnno   //只对这个方法做匹配
    public void print(Integer print){ System.out.println("print Integer"); }
}

最后返回AspectDemo类里面添加一个@annotation的切点方法,并且加上通知。

@Pointcut("@annotation(com.demo.anno.MyAnno)") //annotation表示的是目标对象
public void myPointcutAnno(){  }

@Before("myPointcutAnno()")
public void beforeDao(){
    System.out.println("before print");
}

运行结果和预期一样,只有print(Integer a)前面的before()方法输出了:
before print		-----> beforeDao()输出
print Integer		-----> print(Integer a)方法输出
print null			-----> print()方法输出
print String		-----> print(String a)方法输出

为什么我说自定义呢,因为这些有@注解的语义就是让用户自己定义内容的,如果要用@target,那就在DemoDao类上做注解。

@Repository("demodao")
@MyAnno   //只对这个类做匹配
public class DemoDao implements TargetDao{
    public void print(){ System.out.println("print null"); }
    public void print(String print){ System.out.println("print String"); }
    public void print(Integer print){ System.out.println("print Integer"); }
}

AspectDemo类里面添加一个@target的切点方法,并且加上通知。

@Pointcut("@target(com.demo.anno.MyAnno)") 
public void myPointcutAnno(){ }

@Before("myPointcutAnno()")
public void beforeDao(){
    System.out.println("before print");
}

运行结果,这里就是把定义的DemoDao里面的所有方法输出了:
before print		-----> beforeDao()输出
print Integer		-----> print(Integer a)方法输出
before print		-----> beforeDao()输出
print null			-----> print()方法输出
before print		-----> beforeDao()输出
print String		-----> print(String a)方法输出

@args无非就是自己定义的类型,@within也是自己定义的包中的类,一样的道理就不占篇幅了。

附 切点的表达式

关于切点的位置,官方文档给的非常之详细,这里为了方便大家搬运过来。以下全部可以放在@Pointcut(" ")里面。当然以下摘自官方文档。

The following examples show some common pointcut expressions:
• The execution of any public method:
执行所有public方法
execution(public * *(..))

• The execution of any method with a name that begins with set:
执行所有的set命名开头的方法,这个不一定是set,也可以自己命名,比如我写的小例子中就可以这样@Pointcut("execution(* prin*(..))"),一样效果。
execution(* set*(..))

• The execution of any method defined by the AccountService interface:
执行在AccountService 接口中定义的所有方法,也是支持自定义。当然这里要加上了包名才能识别位置。
execution(* com.xyz.service.AccountService.*(..))

• The execution of any method defined in the service package:
执行在com.xyz.service包下的所有方法,就是我们例子里用的。
execution(* com.xyz.service.*.*(..))

• The execution of any method defined in the service package or one of its sub-packages:
执行定义在service报下或者它的一个子包下的所有方法。
execution(* com.xyz.service..*.*(..))

• Any join point (method execution only in Spring AOP) within the service package:
执行在service包下的所有连接点,方法的执行只能在SpringAOP框架内
within(com.xyz.service.*)

• Any join point (method execution only in Spring AOP) within the service package or one of its sub-packages:
执行在service包或者其子包下的所有连接点,方法的执行只能在SpringAOP框架内
within(com.xyz.service..*)

• Any join point (method execution only in Spring AOP) where the proxy implements the AccountService interface:
执行所有那些用代理实现的AccountService 接口的连接点。
this(com.xyz.service.AccountService)

• Any join point (method execution only in Spring AOP) where the target object implements the AccountService interface:
执行所有那些用目标实现的AccountService 接口的连接点。
target(com.xyz.service.AccountService)

• Any join point (method execution only in Spring AOP) that takes a single parameter and where the argument passed at runtime is Serializable:
对某一个类型的参数执行匹配。
args(java.io.Serializable)

• Any join point (method execution only in Spring AOP) where the target object has a @Transactional annotation:
对于任何连接点的目标对象有一个Transactional(自定义)注解的。
@target(org.springframework.transaction.annotation.Transactional)


• Any join point (method execution only in Spring AOP) where the declared type of the target object has an @Transactional annotation:
对于任何连接点的目标对象的声明类型具有Transactional(自定义)注解的。
@within(org.springframework.transaction.annotation.Transactional)


• Any join point (method execution only in Spring AOP) where the executing method has an @Transactional annotation:
对于任何连接点的执行方法具有Transactional(自定义)注解的。
@annotation(org.springframework.transaction.annotation.Transactional)

• Any join point (method execution only in Spring AOP) which takes a single parameter, and where the runtime type of the argument passed has the @Classified annotation:
对于任何具有单个参数的连接点,其运行时传递的参数类型有Classified(自定义)注解的。
@args(com.xyz.security.Classified)

• Any join point (method execution only in Spring AOP) on a Spring bean named tradeService:
匹配任意在tradeService这个bean下的连接点。
bean(tradeService)

• Any join point (method execution only in Spring AOP) on Spring beans having names that match the wildcard expression *Service:
广泛的匹配Spring bean有Service后缀的连接点。
bean(*Service)
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值