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() 方法,用来执行目标对象的方法。
除了 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,两者的表现不同。
-
使用 JDK 动态代理产生的代理类(this)是继承了 java.lang.reflect.Proxy 后再实现了被代理的接口的,产生的代理类的代码里面会去调用目标类(target),再结合 InvocationHandler 来产生代理效果。
-
使用 Cglib 动态代理产生的代理类(this)是直接继承了被代理的目标类的(target)。
例子:
public interface AccountService {...}
public class AccountServiceImpl {...}
开启 AOP 的方式:
-
@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
这些都是与注解一起使用的。
注意:@annotation 只能用来匹配方法,而 @target、@within 可以用来匹配类。(粒度不同)
bean
bean(tradeService) // 匹配 bean 的名字叫 tradeService 的类
bean(*Service) // 匹配 bean 的名字以 Service 结尾的
通过 && 、||、! 来组合
上面所有的表达式都可以通过 && 、||、! 来组合使用。
处理泛型&给 Advice 传参
public interface Sample<T> {
void sampleGenericMethod(T param);
void sampleGenericCollectionMethod(Collection<T> param);
}