SpringBoot 的 AOP
参考:Spring AOP何时使用JDK动态代理?何时使用Cglib ?默认是哪种?
参考:Spring AOP源码分析-代理方式的选择
JDK 和 CGLIB 选择
DefaultAopProxyFactory#createAopProxy
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (
config.isOptimize()
// 配置强制走 CGLIB 代理
|| config.isProxyTargetClass()
// 没有继承用户的接口(Spring 代理的会继承 SpringProxy 标记接口)
|| hasNoUserSuppliedProxyInterfaces(config)) {
Class<?> targetClass = config.getTargetClass();
// 是接口、已经是代理类、Labmbda 则走 JDK
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass) || ClassUtils.isLambdaClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
// 走 CGLIB
return new ObjenesisCglibAopProxy(config);
}
else {
return new JdkDynamicAopProxy(config);
}
}
也即差不多可以理解为继承了接口则走 JDK,没有则走 CGLIB;已被代理过走 JDK,Lambda 走 JDK。
JDK 生成的代理类已经继承了 Proxy 类,因此只能代理接口(单继承、多实现)。
配置使用 CGLIB
@EnableAspectJAutoProxy
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {
// 优先使用 CGLIB
boolean proxyTargetClass() default false;
// 能使用 AopContext.currentProxy() 获取
// 应该是织入了 Spring 自己的逻辑, AOP 前后对 ThreadLocal 进行操作
boolean exposeProxy() default false;
}
手动创建代理
最简单的应该是这种了吧,主要是 AspectJProxyFactory
public class SmartThreadPoolTaskExecutor extends ThreadPoolTaskExecutor {
public SmartThreadPoolTaskExecutor() {
SmartThreadPoolTaskExecutorShutdownHook.addHook(this);
}
public static SmartThreadPoolTaskExecutor warp(ThreadPoolTaskExecutor executor) {
AspectJProxyFactory proxyFactory = new AspectJProxyFactory(new SmartThreadPoolTaskExecutor());
proxyFactory.setProxyTargetClass(true);
proxyFactory.addAspect(new ExecutorAspect(executor));
return proxyFactory.getProxy();
}
@Aspect
private static class ExecutorAspect {
private final ThreadPoolTaskExecutor executor;
public ExecutorAspect(ThreadPoolTaskExecutor executor) {
this.executor = executor;
}
@Pointcut("execution(* org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor.*(..))")
public void pointcut() {
}
@Around("pointcut()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
String name = pjp.getSignature().getName();
Class<?>[] args = Arrays.stream(pjp.getArgs())
.map(Object::getClass)
.collect(Collectors.toList())
.toArray(new Class<?>[]{});
Method method = executor.getClass().getMethod(name, args.length == 0 ? null : args);
return method.invoke(executor, pjp.getArgs());
}
}
}
代理范围
Spring AOP 代理只能代理 public 的方法,可能是考虑 public 的方法是稳定的,而 protected、private 方法可能不稳定。
其次内部调用方法是不会被代理的,其实现方式差不多是这样的,有一个 target 实例要被代理,新建一个 proxy 代理类实例(继承接口或类)返回给你使用,proxy 持有 target 实例,于是你调用实际上是在调用 proxy,proxy 执行相关增强逻辑然后调用 target 对应方法。当你是 target 内部调用时,即便 proxy 是继承了 target 类的,但是 proxy 和 target 是不同的实例,target 内部调用显然不会被增强。
LTW
普通的 Spring AOP 代理只是使用了 aspectj 的一些语法,如 @AspectJ、@Pointcut 等,但是并没有使用 aspectj 进行代理实现。
参考:详解 Spring AOP LoadTimeWeaving (LTW)
参考:Spring AOP and AspectJ Load-Time Weaving: Around advice will be invoked twice for private methods
参考:Java Agent简介及使用Byte Buddy和AspectJ LTW监控方法执行耗时
LTW:Load Time Weaving,在类加载期通过字节码编辑技术将切面织入目标类。
直接 Agent 的这种方法感觉比较适合那种通用的 AOP,之前云平台上发布的 Spring Cloud 程序好像都是使用了一个 Agent 的。
LTW 适合业务应用相关的。
AOP 切面织入时机
- 编译时:比如使用 AspectJ 编译器,Aspect 编译器支持独特的语法,感觉也可以使用类似 maven 插件的形式生成
- 类加载时:如 aspectj 的 LTW
- 运行时:常规 AOP,JDK 代理、CGLIB 代理
JDK新增的java.lang.instrument
JDK 只提供了相关接口,那么 JVM 是怎么知道要 transfer 的呢