在文章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() {} // 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接口的目标类中的所有方法
this
和target
的区别究竟在哪呢?举例说明。
下述所有类均定义在包com.qyh.test.aspectj
中。
- 定义一个接口TargetClassInterface
public interface TargetClassInterface {
void sayHello();
}
- 定义一个TargetClassInterface接口的实现类TargetClass
@Component("targetClass")
public class TargetClass implements TargetClassInterface {
@Override
public void sayHello() {
System.out.println("Hello World!");
}
}
- 定义Spring的Java配置类AppConfig
@Configuration
@EnableAspectJAutoProxy
@ComponentScan
public class AppConfig {}
- 定义切面类AspectClass
@Component
@Aspect
public class AspectClass {
@Pointcut("this(com.qyh.test.aspectj.TargetClass)")
public void pointCutInTargetClass() {}
@Before("pointCutInTargetClass()")
public void beforeAdvice() {
System.out.println("我是前置增强");
}
}
- 定义测试类Test
public class Test {
public static void main(String[] args) {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
TargetClassInterface targetClass = applicationContext.getBean("targetClass", TargetClassInterface.class);
targetClass.sayHello();
}
}
- 测试结果1
Hello World!
- 将切面类AspectClass修改如下
@Component
@Aspect
public class AspectClass {
@Pointcut("target(com.qyh.test.aspectj.TargetClass)")
public void pointCutInTargetClass() {}
@Before("pointCutInTargetClass()")
public void beforeAdvice() {
System.out.println("我是前置增强");
}
}
- 测试结果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 {
public void originalMethod(String name, int age) {
System.out.println("我的名字叫" + name + ",我今年" + age + "岁了!");
}
public String sayHello(Serializable name) throws Exception {
return "Hello World!" + name;
}
}
- 定义Spring的Java配置类AppConfig
@Configuration
@EnableAspectJAutoProxy
@ComponentScan
public class AppConfig {}
- 定义切面类AspectClass
@Component
@Aspect
public class AspectClass {
@Pointcut("execution(* *(java.io.Serializable))")
public void pointCutInTargetClass1() {}
@Pointcut("args(java.lang.String)")
public void pointCutInTargetClass2() {}
@Before("pointCutInTargetClass1()")
public void beforeAdvice() {
System.out.println("我是前置增强");
}
@AfterReturning(value = "pointCutInTargetClass2()")
public void afterReturningAdvice() {
System.out.println("我是后置增强");
}
}
- 定义测试类Test
public class Test {
public static void main(String[] args) throws Exception {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
TargetClass targetClass = applicationContext.getBean("targetClass", TargetClass.class);
System.out.println(targetClass.sayHello("小钱"));
}
}
- 测试结果
我是前置增强
我是后置增强
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 {}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface MyAnnotation2 {}
- 定义3个实体类GrandFather、Father和Son
@MyAnnotation1
public class GrandFather {
public void say() {
System.out.println("GrandFather say");
}
public void run() {
System.out.println("GrandFather run");
}
}
@MyAnnotation2
public class Father extends GrandFather {
@Override
public void say() {
System.out.println("Father say");
}
}
@Component
public class Son extends Father {
@Override
public void say() {
System.out.println("Son say");
}
}
- 定义Spring的Java配置类AppConfig
@Configuration
@EnableAspectJAutoProxy
@ComponentScan
public class AppConfig {}
- 定义切面类AspectClass
@Aspect
@Component
public class AspectClass {
@Before("@within(com.qyh.test.aspectj.withinandtargetannotation.MyAnnotation1)")
public void beforeAdviceWithinMyAnnotation1() {
System.out.println("beforeAdviceWithinMyAnnotation1");
}
@Before("@within(com.qyh.test.aspectj.withinandtargetannotation.MyAnnotation2)")
public void beforeAdviceWithinMyAnnotation2() {
System.out.println("beforeAdviceWithinMyAnnotation2");
}
@Before("@target(com.qyh.test.aspectj.withinandtargetannotation.MyAnnotation1)")
public void beforeAdviceTargetMyAnnotation1() {
System.out.println("beforeAdviceTargetMyAnnotation1");
}
@Before("@target(com.qyh.test.aspectj.withinandtargetannotation.MyAnnotation2)")
public void beforeAdviceTargetMyAnnotation2() {
System.out.println("beforeAdviceTargetMyAnnotation2");
}
}
- 定义测试类Test
public class Test {
public static void main(String[] args) {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
GrandFather grandFather = applicationContext.getBean("grandFather", GrandFather.class);
grandFather.say();
grandFather.run();
Father father = applicationContext.getBean("father", Father.class);
father.say();
father.run();
Son son = applicationContext.getBean("son", Son.class);
son.say();
son.run();
}
}
- 测试结果
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:
this
,target
和@annotation
。
其中,Kinded designators和Contextual designators的性能较差,而Scoping designators的性能最好,故在开发中应尽可能地使用within
指示符来定义pointcut
。
pointcut expression表达式
通配符语法
*
:匹配任何数量字符。..
:匹配任何数量字符的重复,如在类型模式中匹配任何数量子包;而在方法参数模式中匹配任何数量参数。+
:匹配指定类型的子类型,仅能作为后缀放在类型模式后边。
模式匹配语法
以execution
指示符为例:
execution(修饰符类型? 返回值类型 类型模式?方法名称(参数类型) 异常类型?)
References
Spring Framework 5.2.5 Reference Doc.
Spring–@within和@target的区别
02-05 AOP学习之@within和@target使用示例及对比分析