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)

参考:Java 对类加载信息追踪

参考:Spring AOP and AspectJ Load-Time Weaving: Around advice will be invoked twice for private methods

参考:Java Agent 技术

参考:Java agent从0到1的踩坑过程

参考: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 的呢
image

其他参加:SpringBoot 的 LoadTimeWeaving 的 AOP

posted @ 2023-11-23 20:17  YangDanMua  阅读(36)  评论(0编辑  收藏  举报