Spring AOP之pointcut语法

墨蓝 2023-07-25 08:37 129阅读 0赞

在文章Spring AOP之术语简介中有提到,pointcut定义了一种模式,用于匹配join point。Spring AOP中使用了AspectJ的pointcut模式定义语言。

声明一个pointcut

一个pointcut由两部分组成:

  • Pointcut signature:pointcut签名,类似于方法签名,但该方法返回值必须是void
  • Pointcut expression:@Pointcut注解中的模式定义表达式

    @Pointcut(“execution(* transfer(..))”) // the pointcut expression
    private void anyOldTransfer() {

    1. } // the pointcut signature

pointcut expression指示符

pointcut expression指示符用来指示该表达式的目的,Spring AOP支持以下10种指示符:

  • execution:匹配指定方法。

    execution(public (..)) //任意public方法
    execution( set(..)) //任意名称以set开头的方法
    execution( com.xyz.service.AccountService.(..)) //com.xyz.service.AccountService接口中定义的任意方法
    execution( com.xyz.service..(..)) //com.xyz.service包中定义的任意方法
    execution(
    com.xyz.service...(..)) //com.xyz.service包中及其子包中定义的任意方法

  • within:匹配指定类内的所有join point(在Spring AOP中仅是方法执行点)。

    within(com.xyz.service.) //com.xyz.service包中的任意方法
    within(com.xyz.service..
    ) //com.xyz.service包中及其子包中的任意方法

  • this:匹配可以向上转型为this指定的类型的代理对象中的所有join point(在Spring AOP中仅是方法执行点)。

    this(com.xyz.service.AccountService) //实现了com.xyz.service.AccountService接口的代理类中的所有方法

  • target:匹配可以向上转型为target指定的类型的目标对象中的所有join point(在Spring AOP中仅是方法执行点)。

    target(com.xyz.service.AccountService) //实现了com.xyz.service.AccountService接口的目标类中的所有方法

thistarget的区别究竟在哪呢?举例说明。

下述所有类均定义在包com.qyh.test.aspectj中。

  • 定义一个接口TargetClassInterface

    public interface TargetClassInterface {

    1. void sayHello();

    }

  • 定义一个TargetClassInterface接口的实现类TargetClass

    @Component(“targetClass”)
    public class TargetClass implements TargetClassInterface {

    1. @Override
    2. public void sayHello() {
    3. System.out.println("Hello World!");
    4. }

    }

  • 定义Spring的Java配置类AppConfig

    @Configuration
    @EnableAspectJAutoProxy
    @ComponentScan
    public class AppConfig {

    1. }
  • 定义切面类AspectClass

    @Component
    @Aspect
    public class AspectClass {

    1. @Pointcut("this(com.qyh.test.aspectj.TargetClass)")
    2. public void pointCutInTargetClass() {
    3. }
    4. @Before("pointCutInTargetClass()")
    5. public void beforeAdvice() {
    6. System.out.println("我是前置增强");
    7. }

    }

  • 定义测试类Test

    public class Test {

    1. public static void main(String[] args) {
    2. ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
    3. TargetClassInterface targetClass = applicationContext.getBean("targetClass", TargetClassInterface.class);
    4. targetClass.sayHello();
    5. }

    }

  • 测试结果1

    Hello World!

  • 将切面类AspectClass修改如下

    @Component
    @Aspect
    public class AspectClass {

    1. @Pointcut("target(com.qyh.test.aspectj.TargetClass)")
    2. public void pointCutInTargetClass() {
    3. }
    4. @Before("pointCutInTargetClass()")
    5. public void beforeAdvice() {
    6. System.out.println("我是前置增强");
    7. }

    }

  • 测试结果2

    我是前置增强
    Hello World!

  • 结果分析
    由于Spring AOP默认使用的是JDK的动态代理来为接口生成代理对象,其代理对象实现了接口TargetClassInterface,与TargetClass没有关系,使用this关键字时,该代理对象无法向上转型为TargetClass,因此该切面失效,没有应用前置增强。使用target关键字时,目标对象可以向上转型为TargetClass(目标对象本身就是TargetClass),因此该切面生效,应用前置增强。

  • args:用方法参数去匹配方法

    args(java.io.Serializable) //只有一个参数,且参数的运行时类型是java.io.Serializable的方法

args(java.io.Serializable)execution(* *(java.io.Serializable))的在哪呢?举例说明。

下述所有类均定义在包com.qyh.test.aspectj中。

  • 定义一个目标类TargetClass

    @Component(“targetClass”)
    public class TargetClass {

    1. public void originalMethod(String name, int age) {
    2. System.out.println("我的名字叫" + name + ",我今年" + age + "岁了!");
    3. }
    4. public String sayHello(Serializable name) throws Exception {
    5. return "Hello World!" + name;
    6. }

    }

  • 定义Spring的Java配置类AppConfig

    @Configuration
    @EnableAspectJAutoProxy
    @ComponentScan
    public class AppConfig {

    1. }
  • 定义切面类AspectClass

    @Component
    @Aspect
    public class AspectClass {

    1. @Pointcut("execution(* *(java.io.Serializable))")
    2. public void pointCutInTargetClass1() {
    3. }
    4. @Pointcut("args(java.lang.String)")
    5. public void pointCutInTargetClass2() {
    6. }
    7. @Before("pointCutInTargetClass1()")
    8. public void beforeAdvice() {
    9. System.out.println("我是前置增强");
    10. }
    11. @AfterReturning(value = "pointCutInTargetClass2()")
    12. public void afterReturningAdvice() {
    13. System.out.println("我是后置增强");
    14. }

    }

  • 定义测试类Test

    public class Test {

    1. public static void main(String[] args) throws Exception {
    2. ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
    3. TargetClass targetClass = applicationContext.getBean("targetClass", TargetClass.class);
    4. System.out.println(targetClass.sayHello("小钱"));
    5. }

    }

  • 测试结果

    我是前置增强
    我是后置增强
    Hello World!小钱

  • 结果分析
    在本例中,sayHello(java.io.Serializable)方法的参数声明类型是java.io.Serializable,运行时类型是java.lang.String,因此execution(* *(java.io.Serializable))args(java.lang.String)均能对该方法进行增强。这个例子说明args(java.io.Serializable)execution(* *(java.io.Serializable))是不一样的。args(java.io.Serializable)指参数的运行时类型是java.io.Serializable,而execution(* *(java.io.Serializable))指参数的声明类型是java.io.Serializable

  • @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.

    @target(org.springframework.transaction.annotation.Transactional) //Any join point (method execution only in Spring AOP) where the target object has a @Transactional annotation

  • @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).

    @within(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

@target@within的区别究竟在哪呢?举例说明。

下述所有类均定义在包com.qyh.test.aspectj.withinandtargetannotation中。

  • 定义两个注解类MyAnnotation1和MyAnnotation2

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Component
    public @interface MyAnnotation1 {

    1. }

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Component
    public @interface MyAnnotation2 {

    1. }
  • 定义3个实体类GrandFather、Father和Son

    @MyAnnotation1
    public class GrandFather {

    1. public void say() {
    2. System.out.println("GrandFather say");
    3. }
    4. public void run() {
    5. System.out.println("GrandFather run");
    6. }

    }

    @MyAnnotation2
    public class Father extends GrandFather {

    1. @Override
    2. public void say() {
    3. System.out.println("Father say");
    4. }

    }

    @Component
    public class Son extends Father {

    1. @Override
    2. public void say() {
    3. System.out.println("Son say");
    4. }

    }

  • 定义Spring的Java配置类AppConfig

    @Configuration
    @EnableAspectJAutoProxy
    @ComponentScan
    public class AppConfig {

    1. }
  • 定义切面类AspectClass

    @Aspect
    @Component
    public class AspectClass {

    1. @Before("@within(com.qyh.test.aspectj.withinandtargetannotation.MyAnnotation1)")
    2. public void beforeAdviceWithinMyAnnotation1() {
    3. System.out.println("beforeAdviceWithinMyAnnotation1");
    4. }
    5. @Before("@within(com.qyh.test.aspectj.withinandtargetannotation.MyAnnotation2)")
    6. public void beforeAdviceWithinMyAnnotation2() {
    7. System.out.println("beforeAdviceWithinMyAnnotation2");
    8. }
    9. @Before("@target(com.qyh.test.aspectj.withinandtargetannotation.MyAnnotation1)")
    10. public void beforeAdviceTargetMyAnnotation1() {
    11. System.out.println("beforeAdviceTargetMyAnnotation1");
    12. }
    13. @Before("@target(com.qyh.test.aspectj.withinandtargetannotation.MyAnnotation2)")
    14. public void beforeAdviceTargetMyAnnotation2() {
    15. System.out.println("beforeAdviceTargetMyAnnotation2");
    16. }

    }

  • 定义测试类Test

    public class Test {

    1. public static void main(String[] args) {
    2. ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
    3. GrandFather grandFather = applicationContext.getBean("grandFather", GrandFather.class);
    4. grandFather.say();
    5. grandFather.run();
    6. Father father = applicationContext.getBean("father", Father.class);
    7. father.say();
    8. father.run();
    9. Son son = applicationContext.getBean("son", Son.class);
    10. son.say();
    11. son.run();
    12. }

    }

  • 测试结果

    beforeAdviceTargetMyAnnotation1
    beforeAdviceWithinMyAnnotation1
    GrandFather say
    beforeAdviceTargetMyAnnotation1
    beforeAdviceWithinMyAnnotation1
    GrandFather run
    beforeAdviceTargetMyAnnotation2
    beforeAdviceWithinMyAnnotation2
    Father say
    beforeAdviceTargetMyAnnotation2
    beforeAdviceWithinMyAnnotation1
    GrandFather run
    Son say
    beforeAdviceWithinMyAnnotation1
    GrandFather run

  • 结果分析
    父类有注解A,子类没有注解,子类中不是在父类中定义的方法不会被@within(A)@target(A)拦截。
    父类有注解A,子类没有注解,子类中在父类中定义的方法会被@within(A)拦截,但不会被@target(A)拦截
    父类有注解A,子类有注解B,子类中不是在父类中定义的方法会被注解@within(B)@target(B)拦截,但不会被@within(A)@target(A)拦截。
    父类有注解A,子类有注解B,子类中在父类中定义的方法会被@within(A)@target(B)拦截,但不会被@within(B)@target(A)拦截。

  • @args:匹配方法,该方法的参数的运行时类型带有指定的注解

    @args(com.xyz.security.Classified) //只有一个参数,且该参数的运行时类型具有com.xyz.security.Classified注解的方法

  • @annotation:匹配方法,该方法带有指定的注解

    @annotation(org.springframework.transaction.annotation.Transactional) //被标注了org.springframework.transaction.annotation.Transactional注解的所有方法

  • bean:匹配bean实例内的所有join point(在Spring AOP中仅是方法执行点)。

    bean(tradeService) //id或name为tradeService的bean实例的所有方法
    bean(*Service) //id或name以Service结尾的bean实例的所有方法

指示符分类
  • Kinded designators:execution
  • Scoping designators:within
  • Contextual designators:thistarget@annotation
    其中,Kinded designators和Contextual designators的性能较差,而Scoping designators的性能最好,故在开发中应尽可能地使用within指示符来定义pointcut

pointcut expression表达式

通配符语法
  • *:匹配任何数量字符。
  • ..:匹配任何数量字符的重复,如在类型模式中匹配任何数量子包;而在方法参数模式中匹配任何数量参数。
  • +:匹配指定类型的子类型,仅能作为后缀放在类型模式后边。
模式匹配语法

execution指示符为例:

  1. execution(修饰符类型? 返回值类型 类型模式?方法名称(参数类型) 异常类型?)

References

Spring Framework 5.2.5 Reference Doc.
Spring–@within和@target的区别
02-05 AOP学习之@within和@target使用示例及对比分析

发表评论

表情:
评论列表 (有 0 条评论,129人围观)

还没有评论,来说两句吧...

相关阅读