Spring AOP之pointcut语法

在文章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接口的目标类中的所有方法
thistarget的区别究竟在哪呢?举例说明。

下述所有类均定义在包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:thistarget@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使用示例及对比分析

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值