Spring AOP & 切面表达式

SpringAOP 和 AspectJ 的关系:
它们是两种不同的编程风格, SpringAOP 使用 xml 配置的形式配置 aop。而 AspectJ 使用 AspectJ 的注解来配置 aop

aspect、JoinPoint、Pointcut、Weaving、Advice
JoinPoint: 连接点。表示目标对象中的方法
Pointcut: 切点。表示连接点的集合
Weaving: 织入。把代理逻辑加入到目标对象上的过程叫织入
Advice: 通知。包括 “around”, “before” and “after 等


this、target
this: 产生的代理对象
target: 被代理的原始对象

JoinPoint、ProceedingJoinPoint
JoinPoint 接口可以拿到连接点的相关信息,比如:方法签名、方法参数、this、target
ProceedingJoinPoint 继承自 JoinPoint,它是用来支持环绕(around)通知的,多暴露了一个 proceed() 方法,用来执行目标对象的方法。

 

切面表达式:https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#aop-pointcuts-examples

execution

除了 ret-type-pattern, name-pattern, param-pattern 以外,其他的部分都是可选的(? 表示可选项)。

* 最常用作返回类型格式(ret-type-pattern),它表示匹配任何返回类型。
* 也可以用作名称格式(name-pattern)的全部或一部分。
参数格式(param-pattern)稍微复杂一些:
()   : 匹配不带参数的方法
(..) : 匹配任意数量(零个或多个)的参数
(*)  : 匹配任何类型的一个参数的方法
(*,String) : 匹配两个参数的方法,第一个可以是任何类型,而第二个必须是字符串

更多语法可以参考 AspectJ 的文档:https://www.eclipse.org/aspectj/doc/released/progguide/semantics-pointcuts.html

 

execution 与 within 的区别

execution 与 within 的区别:粒度不一样 execution 的最小粒度可以控制到方法级别; within 的最小粒度只能控制到类级别;

例子:

execution(public * *(..))  // 匹配任意公共方法
execution(* set*(..))      // 匹配任意名称为 set 开头的方法
execution(* com.xyz.service.AccountService.*(..))    // 匹配 AccountService 类中的任意方法
execution(* com.xyz.service.*.*(..))    // 匹配 com.xyz.service 包下的任意类的任意方法
execution(* com.xyz.service..*.*(..))    // 匹配 com.xyz.service 包及子包下的任意类的任意方法

within(com.xyz.service.*)    // 匹配 com.xyz.service 包下的任意类的任意方法
within(com.xyz.service..*)   // 匹配 com.xyz.service 包及子包下的任意类的任意方法

 

this 和 target 的区别

this 匹配的是代理对象,即从 SpringIOC 容器中拿出来的代理对象;
target 匹配的是目标对象,即被代理的原始对象。
this(com.xyz.service.AccountService) // 匹配代理对象的所有方法,且代理对象实现了 AccountService
target(com.xyz.service.AccountService) // 匹配目标对象,且目标对象实现了 AccountService
注意: 使用 this 和 target 时,要注意代理实现是 JDK 还是 Cglib,两者的表现不同。

  1. 使用 JDK 动态代理产生的代理类(this)是继承了 java.lang.reflect.Proxy 后再实现了被代理的接口的,产生的代理类的代码里面会去调用目标类(target),再结合 InvocationHandler 来产生代理效果。

  2. 使用 Cglib 动态代理产生的代理类(this)是直接继承了被代理的目标类的(target)。

例子:

public interface AccountService {...}
public class AccountServiceImpl {...}
开启 AOP 的方式: @EnableAspectJAutoProxy(proxyTargetClass=false)  // 默认走 JDK 的动态代理


public class Test {
@Autowired
AccountService accountService;
publict test1(){
 // 通过自动注入的方法获取到 AccountService,来调用它里面的方法
 accountService.m1();
}

publict test2(){
 // 通过从 applicationContext 中获取 AccountService 的 bean,来调用它里面的方法
 AccountService accountService2 = applicationContext.getBean(AccountService.class);
 accountService2.m1();
}
}

 

  • @PointCut("this(com.xyz.service.AccountServiceImpl)") : 它表示匹配代理对象的所有方法,且代理对象是 AccountServiceImpl 的实例,即 "代理对象 instanceof AccountServiceImpl == true"。 如果 AccountService 是代理 bean 的话,也会是 JDK 动态代理产生的代理 bean,那么它的 proxyClass 是一个继承了 java.lang.reflect.Proxy 并且实现了 AccountService 接口的一个代理类,类似于 public class $proxy extend Proxy implements AccountService {},所以,这个代理类不是 AccountServiceImpl 的实例。故这个切面不起作用。

  • @PointCut("target(com.xyz.service.AccountServiceImpl)") : 它表示匹配目标对象的所有方法,且目标对象是 AccountServiceImpl 的实例。显然,在 Test 类中目标对象就是 AccountServiceImpl,所以切面会起作用。

  • 将代理改为默认使用 Cglib : @EnableAspectJAutoProxy(proxyTargetClass=true) 由于 Cglib 动态代理是采用继承目标类的方式来实现的,所以,@PointCut("this(com.xyz.service.AccountServiceImpl)") 与 @PointCut("target(com.xyz.service.AccountServiceImpl)") 两个切面都会生效。

 

args

匹配指定参数类型和参数数据的方法。

args(java.io.Serializable)   // 匹配只有一个参数的方法,且参数类型为 Serializable

通过这个表达式还可以为切面通知传参,具体参考:https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#aop-ataspectj-advice-params-generics

 

@target、@within、@annotation、@args

这些都是与注解一起使用的。

@target(org.springframework.transaction.annotation.Transactional)   // 匹配目标对象上有 @Transactional
@within(org.springframework.transaction.annotation.Transactional)   // 与上面类似
@annotation(org.springframework.transaction.annotation.Transactional)  // 匹配方法上面带有 @Transactional
@args(com.xyz.security.Classified)    // 匹配只有一个参数的方法,且参数上有 @Classified  

注意:@annotation 只能用来匹配方法,而 @target、@within 可以用来匹配类。(粒度不同)

 

bean

 bean(tradeService)    // 匹配 bean 的名字叫 tradeService 的类
bean(*Service)    // 匹配 bean 的名字以 Service 结尾的

 

通过 && 、||、! 来组合

上面所有的表达式都可以通过 && 、||、! 来组合使用。

 

处理泛型&给 Advice 传参

参考: https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#aop-ataspectj-advice-params-generics

public interface Sample<T> {
   void sampleGenericMethod(T param);
   void sampleGenericCollectionMethod(Collection<T> param);
}

@Before("execution(* ..Sample+.sampleGenericCollectionMethod(*)) && args(param)")
public void beforeSampleMethod(Collection<MyType> param) {
   // Advice implementation
}

 

 

 

 

posted on 2020-02-05 17:54  快鸟  阅读(1767)  评论(0编辑  收藏  举报