Spring 中的AOP是如何运作的

前言

这篇文章是从《图与循环依赖、死锁(一):为何我的循环依赖时好时坏?》拆出来的,在文章提到解决循环依赖的手段,其实还可以通过MethodHandle来解决循环依赖,但不在这篇讲述是因为又重新思考了一下Spring 中 AOP是运行机制,重新复习了一下AOP,放在《图与循环依赖、死锁(一):为何我的循环依赖时好时坏?》里面感觉篇幅有些大, 于是将这篇文章迁移到了这里。每当我回顾我旧有的文章,总是发现可以写的更好,有写的不到位的地方。

我在《图与循环依赖、死锁(一):为何我的循环依赖时好时坏?》里面提到如何解决循环依赖的时候,事实上有想通过MethodHandles来解决的,但是获取MethodHandler的时候,我写成了下面这样:

php 复制代码
@Component
public class A02TestService {
​
    private static final MethodHandle METHOD_HANDLE;    
    // A01TestService 是接口,有一个sayHello02方法,void是返回值
    // 里面的实现就是打印一个hello world
    // A01TestServiceImpl是A01TestService的实现类
    static {
        try {
            METHOD_HANDLE = MethodHandles.lookup().findVirtual(A01TestServiceImpl.class, "sayHello02", MethodType.methodType(void.class));
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }
    public void sayHello01()   {
        A01TestService a01TestService = SpringUtil.getBean(A01TestService.class);
        try {
            METHOD_HANDLE.invoke(a01TestService);
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }
}

然后报了这个错, java.lang.ClassCastException: Cannot cast jdk.proxy2.$Proxy71 to com.example.faiz.controller.A01TestServiceImpl。

本篇看的代码基于Spring Boot 3.0 + JDK 17,思想是可以通用的。

代理的原理实现

原因也在我们的意料之中,我们在sayHello02这个方法上打上了@Async这个注解,这代表我们希望在这个方法执行的时候由同步执行转为异步执行。我们知道Java的代码都在确定的线程里面执行,执行到这个方法如果不开启线程,那么这个方法就进入当前线程的方法栈执行。那sayHello01在调用sayHello02的时候,并没有显式的开启线程,那么执行到sayHello02必然是在执行的时候获得了增强,那么怎么不改动方法对方法进行增强呢? 这也就是AOP了,AOP底层的实现是动态代理。

AOP的核心概念

  • Aspect 切面:

    • Join point连接点: 方法的执行时机, 执行前、执行后、发生异常
    • Advice 通知: 特定连接点的执行逻辑
    • Pointcut 切点: 用来判断是否是连接点的表达式
  • Weaving 织入时机

    • 编译期织入
    • 加载期织入
    • 运行期织入
  • Joint Point 连接点

    • 通知,也就是增强逻辑的执行时机
  • Introduction : Spring AOP 允许把 新的接口及其对应的默认实现 动态引入到任何被增强的对象中。 例如,可通过 Introduction 让某个 Bean 额外实现 IsModified 接口,以简化缓存逻辑。 (在 AspectJ 社区,Introduction 也称 inter-type declaration,即"类型间声明"。)

常见实现AOP的方式大致可以分为两种,一种是在连接点直接将通知的字节码织入,一种是通过生成目标的代理对象,核心还是做向下转型实现对目标的方法的增强。

字节码植入

csharp 复制代码
// 这里只是说明思路,不代表真实实现
public void sayHello02(){
    new Thread(()->{
       System.out.println("hello world");
    })
}

常见的库是AspectJ,织入的时机有:

  • 编译时织入: AspectJ 自带了一个编译器,可以将通知织入到连接点。
  • 编译后织入: 就是在javac编译完成后,用AspectJ自带的编译器处理class,将通知织入连接点。
  • 加载时织入:通过java agent机制在内存中操作类文件,可以不需要ajc的支持做到动态织入

这里简单介绍一下java agent,Java Agent 是一种特殊的Java程序,可以作为插件挂载到任何目标Java应用上,在Java加载类的字节码文件之前,获得拦截和修改字节码的机会,从而实现对目标应用的无侵入式功能增强。

运行期实现

另一种实现AOP的方式是在运行期产生代理类,代理类是被代理类的子类,利用的是向下转型的机制,分为两种一种是基于接口的代理,一种是基于继承的。两种实现本质上都是通过子类型重写目标方法来实现增强的。

基于子类的代理

基于子类的代理被为人熟知的是cglib, 但注意这个已经是魔改过的cglib库了,Spring自己fork了一个分支,一些LLM会告诉你,Spring 用ByteBuddy 替换了实现。但这是胡编的,详情可以看参考文档[2] 下面是用cglib做动态代理的代码:

java 复制代码
public class Car {
    public void start() {
        System.out.println("start");
    }
}
public class SuperMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        Object returnValue = methodProxy.invokeSuper(obj, args);
        return returnValue;
    }
}
public class MyMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("方法执行前执行");
        System.out.println(Arrays.toString(args));
        Object returnValue = methodProxy.invokeSuper(obj, args);
        System.out.println("方法执行后执行");
        return returnValue;
    }
}
Enhancer enhancer = new Enhancer();
// 设置需要增强的类。
enhancer.setSuperclass(Car.class);
// 设置通知
enhancer.setCallbacks(new Callback[]{new SuperMethodInterceptor(),new MyMethodInterceptor()});
// 设置切点,因为通知会有多个
// 所以我们需要通过填入callBackFilter来判断,让方法走哪个通知
// 我们这里只增强start的方法, 其他还是调方法本身
enhancer.setCallbackFilter(new CallbackFilter() {
    @Override
    public int accept(Method method) {
        if(method.getName().equals("start")){
            return 1;
        }
        return 0;
    }
});
// 创建实际的代理对象
Car car = (Car) enhancer.create();
car.start();

基于接口的代理

下面是基于接口的动态代理示例:

typescript 复制代码
public interface ICar {
    void start();
}
public class Car implements ICar {
    public void start() {
        System.out.println("start");
    }
}
private static <T> T getProxyObject(Object target, final Class<T> interfaceType) {
    if (!interfaceType.isInterface()) {
        throw new IllegalArgumentException("interfaceType 必须是一个接口类型");
    }
    Object proxy = Proxy.newProxyInstance(target.getClass().getClassLoader(), new Class<?>[]{interfaceType}, new InvocationHandler() {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            for (Method interfaceTypeMethod : interfaceType.getMethods()) {
                if (interfaceTypeMethod.getName().equals("start")) {
                    System.out.println("增强start接口开始");
                    Object obj = method.invoke(target, args);
                    System.out.println("只增强start接口");
                    return obj;
                }
            }
            return  method.invoke(target, args);
        }
    });
    return (T) proxy;
}
Car car = new Car();
ICar proxyObject = getProxyObject(car, ICar.class);
proxyObject.start();

这里简单的谈一下本质, JDK的动态代理本质上基于接口做了一个匿名对象,这个匿名对象和需要增强的对象实现了相同的接口,或者说这个匿名对象本质上就是用接口创建的。这个匿名对象里面的成员变量就是我们传入的new InvocationHandler()。

回到上面的讨论:

我们在MethodHandlers拿到的是A01TestServiceImpl的sayHello02,但是我们我们从容器里面获取的时候,获取的是代理Bean,代理Bean不是A01TestServiceImpl。就报了类型转换错误。 动态代理本质上用的就是向下转型的思想,扩展父类的方法。那其实该上面的问题也比较简单,代理类仍然是A01TestService的子类,所以我们去拿接口的方法句柄即可:

php 复制代码
private static final MethodHandle METHOD_HANDLE = MethodHandleUtils.findVirtual(A01TestService.class, A01TestService::sayHello02,MethodType.methodType(void.class));
​
    public void sayHello01()   {
        A01TestService a01TestService1 = SpringUtil.getBean(A01TestService.class);
        try {
            METHOD_HANDLE.invoke(a01TestService1);
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
 }

但有时候你会发现你的代理没有开启成功向下面这样:

less 复制代码
@Component
public class A01TestServiceImpl implements A01TestService {
​
    @Autowired
    private A02TestService a02TestService;
​
    @Async
    @Override
    public void sayHello02(String currentThreadName){
        if (Thread.currentThread().getName().equals(currentThreadName)){
            System.out.println("代理未成功开启");
        }
        System.out.println("hello world");
    }
​
    @Override
    public void sayHello01(){
        this.sayHello02(Thread.currentThread().getName());
    }
}

如果你调用的时候是从sayHello01开始的,这样就会输出代理未成功开启,这让我思考在Spring中AOP是如何发挥作用的。

我的猜想

我的第一个猜想是外部的Bean在通过A01TestService的实例来调用sayHello01的时候,判断sayHello01不满足AOP的条件,于是不为sayHello01()开启代理。那仔细想想这个猜想也有些不合理,如果不是代理对象,怎么做额外的判断逻辑,也就是说如你没有增强A01TestService这个类。我们看到上面的cglib的CallbackFilter只能返回一个通知,那我们如果有多个切面该怎么办呢? 可以将CallbackFilter里面做文章,也就是说我们制定的通知里面,传入当前Bean的切面,然后遍历这个切面即可。这是我的猜想,下面让我们去看源码。

AOP是如何发挥作用的

现在我们将验证我们的猜想,即Spring中注入的变量已经是代理对象了,不是原来的Bean。这里我的验证思路首先就是观察堆栈法,在执行到对应Bean的时候判断观察方法调用链路:

kotlin 复制代码
@RestController
public class TestController {
​
    @Autowired
    private A01TestService a01TestService;
​
    @GetMapping("/test")
    public String test() {
        a01TestService.sayHello02("sss"); // 这里打断点
        return "hello world";
    }
}

观察到这一点之后,我们就可以看代理的开启流程了,在IDEA的方法栈里面:

我们接着看这个obj是从哪里获取的即可:

其实是在InvocableHandlerMethod里面,截图的时候看错了,InvocableHandlerMethod是HandlerMethod的子类,getBean在HandlerMethod里面:

typescript 复制代码
public Object getBean() {
    return this.bean;
}

而这个Bean是在HandlerMethod初始化的时候赋值的:

ini 复制代码
public HandlerMethod(Object bean, String methodName, Class<?>... parameterTypes) throws NoSuchMethodException {
    super(bean.getClass().getMethod(methodName, parameterTypes));
    this.bean = bean;
    this.beanFactory = null;
    this.messageSource = null;
    this.beanType = ClassUtils.getUserClass(bean);
    this.validateArguments = false;
    this.validateReturnValue = false;
    evaluateResponseStatus();
    this.description = initDescription(this.beanType, getMethod());
}

现在我们只需要观察HandlerMethod 的初始化链路即可。沿着链路往上追在AbstractHandlerMethodMapping的getHandlerInternal方法我们可以看到查找Bean的逻辑:

java 复制代码
@Override
@Nullable
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
    String lookupPath = initLookupPath(request);
    this.mappingRegistry.acquireReadLock();
    try {
       HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
       //在 createWithResolvedBean方法中我们可以看到这个查找Bean的逻辑
       return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
    }
    finally {
       this.mappingRegistry.releaseReadLock();
    }
}

在 createWithResolvedBean方法中我们可以看到这个查找Bean的逻辑:

kotlin 复制代码
public HandlerMethod createWithResolvedBean() {
    Object handler = this.bean;
    if (this.bean instanceof String beanName) {
       Assert.state(this.beanFactory != null, "Cannot resolve bean name without BeanFactory");     
       handler = this.beanFactory.getBean(beanName);
    }
    Assert.notNull(handler, "No handler instance");
    return new HandlerMethod(this, handler, false);
}

观察handler可以看到,这里其实TestController里面已经是代理的Bean了,这里已经准备增强了嘛? 那我们这么想,如果一个方法上有多个增强声明,比如@Transactional、@Retryable,那应该怎么依次代理呢

进入代理的时机

我们在a01TestService调用sayHello02这一行,step Into 会发现进入到了DynamicAdvisedInterceptor这个类里面, DynamicAdvisedInterceptor实现了MethodInterceptor, MethodInterceptor是一个环绕通知(我们上面讲过的),方法在执行的时候会进入到intercept。

那现在我们有通知有了,去哪里找切点呢? 于是我下意识的找这个类在哪里被引用,然后没找到有点泄气。是不是要转换下思路了呢? 在我的意识里面,写切点,写通知,写连接点。然后就能AOP了,Spring帮我们做了这一切。那如果让我们站在Spring的开发者的角度呢? 那是不是应该在创建Bean的时候就要为这个类来创建代理类呢? 然后方法在执行的时候进入对应的代理链,看是否需要代理。

于是我们观察TestController成员变量a01TestService的结构:

可以看到TestController在注入的时候就是代理类对象,于是我们猜想A01TestService在创建Bean的时候,判断如果这个类里面存在需要代理,或者就给所有的Bean生成代理类,只是在执行Bean对应方法的时候,经过一个代理器链,或者说代理类里面就放了代理器链过一道执行即可。这是我们的猜想,现在让我们来验证这一点。注意观察a01TestService, 里面的成员变量,结合上面我们介绍的动态代理接口实现,应该是通过接口代理的形式传入的代理对象。

DynamicAdvisedInterceptor 的创建时间

我们知道使用类的普通方法的时候,一定要通过对象来进行调用,那既然我们在调用A01TestService中的方法的时候进入到了这个代理类,那么我们不妨看看这个DynamicAdvisedInterceptor 是在什么时候初始化的, DynamicAdvisedInterceptor是CglibAopProxy这个类的子类,而且是私有内部类,于是我们可以看到DynamicAdvisedInterceptor的初始化在CglibAopProxy的getCallbacks里面对DynamicAdvisedInterceptor进行了初始化:

我们在这一行打上断点观察即可,启动Spring Boot 可以观察到在启动的时候,断点悬停到了这里:

观察线程堆栈可以发现里面有createBean, doCreateBean这个逻辑:

scss 复制代码
AbstractBeanFactory.doGetBean(...)              // 获取或创建 Bean
 └── AbstractAutowireCapableBeanFactory.createBean(...)
     └── AbstractAutowireCapableBeanFactory.doCreateBean(...)
         └── AbstractAutowireCapableBeanFactory.initializeBean(...)
             └── AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsAfterInitialization(...)
                 └── AbstractAutoProxyCreator.postProcessAfterInitialization(...)
                     └── AbstractAutoProxyCreator.wrapIfNecessary(...)
                         └── AbstractAutoProxyCreator.createProxy(...)
                             └── AbstractAutoProxyCreator.buildProxy(...)
                                 └── ProxyFactory.getProxy(...)
                                     └── CglibAopProxy.getProxy(...)
                                         └── CglibAopProxy.buildProxy(...)
                                             └── CglibAopProxy.getCallbacks(...)

顺着这个方法链路, 在CglibAopProxy的buildProxy可以看到熟悉的Cglib代理类:

scss 复制代码
Enhancer enhancer = createEnhancer();
            if (classLoader != null) {
                enhancer.setClassLoader(classLoader);
                if (classLoader instanceof SmartClassLoader smartClassLoader &&
                        smartClassLoader.isClassReloadable(proxySuperClass)) {
                    enhancer.setUseCache(false);
                }
            }
            enhancer.setSuperclass(proxySuperClass);
            enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
            enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
            enhancer.setAttemptLoad(true);
            enhancer.setStrategy(new ClassLoaderAwareGeneratorStrategy(classLoader));
​
            Callback[] callbacks = getCallbacks(rootClass);
            Class<?>[] types = new Class<?>[callbacks.length];
            for (int x = 0; x < types.length; x++) {
                types[x] = callbacks[x].getClass();
            }
            // fixedInterceptorMap only populated at this point, after getCallbacks call above
            ProxyCallbackFilter filter = new ProxyCallbackFilter(
                    this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset);
            // 注意这个callBackFilter
            enhancer.setCallbackFilter(filter);
            enhancer.setCallbackTypes(types);
​
            // Generate the proxy class and create a proxy instance.
            // ProxyCallbackFilter has method introspection capability with Advisor access.
            try {
                return (classOnly ? createProxyClass(enhancer) : createProxyClassAndInstance(enhancer, callbacks));
            }
            finally {
                // Reduce ProxyCallbackFilter to key-only state for its class cache role
                // in the CGLIB$CALLBACK_FILTER field, not leaking any Advisor state...
                filter.advised.reduceToAdvisorKey();
            }
        }
        catch (CodeGenerationException | IllegalArgumentException ex) {
            throw new AopConfigException("Could not generate CGLIB subclass of " + this.advised.getTargetClass() +
                    ": Common causes of this problem include using a final class or a non-visible class",
                    ex);
        }
        catch (Throwable ex) {
            // TargetSource.getTarget() failed
            throw new AopConfigException("Unexpected AOP exception", ex);
        }

callBackFilter的逻辑

回到getCallbacks的逻辑:

scss 复制代码
Callback[] mainCallbacks = new Callback[] {
      aopInterceptor,  // for normal advice
      targetInterceptor,  // invoke target without considering advice, if optimized
      new SerializableNoOp(),  // no override for methods mapped to this
      targetDispatcher, this.advisedDispatcher,
      new EqualsInterceptor(this.advised),
      new HashCodeInterceptor(this.advised)
};

这个aopInterceptor 就是DynamicAdvisedInterceptor:

然后我们观察这个代理链是如何执行的就可以了,也就是CglibMethodInvocation中proceed的逻辑:

kotlin 复制代码
@Override
@Nullable
public Object proceed() throws Throwable {
   // We start with an index of -1 and increment early.
   if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
      return invokeJoinpoint();
   }
​
   Object interceptorOrInterceptionAdvice =
         this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
   if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher dm) {
      // Evaluate dynamic method matcher here: static part will already have
      // been evaluated and found to match.
      Class<?> targetClass = (this.targetClass != null ? this.targetClass : this.method.getDeclaringClass());
      if (dm.matcher().matches(this.method, targetClass, this.arguments)) {
         return dm.interceptor().invoke(this);
      }
      else {
         // Dynamic matching failed.
         // Skip this interceptor and invoke the next in the chain.
         return proceed();
      }
   }
   else {
      // It's an interceptor, so we just invoke it: The pointcut will have
      // been evaluated statically before this object was constructed.
      return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
   }
}

这里我们看到在调用AOP链路。于是我们得到了我们想要的结果,那DynamicAdvisedInterceptor中的代理快照是什么时候传入的。

切面传入的时机

我们观察方法链路可以看到, 在AbstractAutoProxyCreator的wrapIfNecessary,我们可以看到在创建Bean的时候,这个方法拿到Bean对应的通知,然后一路传给DynamicAdvisedInterceptor:

kotlin 复制代码
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
    if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
       return bean;
    }
    if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
       return bean;
    }
    if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
       this.advisedBeans.put(cacheKey, Boolean.FALSE);
       return bean;
    }
​
    // 这里判断需要拦截
    Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
    if (specificInterceptors != DO_NOT_PROXY) {
       this.advisedBeans.put(cacheKey, Boolean.TRUE);
       Object proxy = createProxy(
             bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
       this.proxyTypes.put(cacheKey, proxy.getClass());
       return proxy;
    }
​
    this.advisedBeans.put(cacheKey, Boolean.FALSE);
    return bean;
}

specificInterceptors获取Bean上对应的切面,然后传递给createProxy方法:

less 复制代码
private Object buildProxy(Class<?> beanClass, @Nullable String beanName,
       @Nullable Object[] specificInterceptors, TargetSource targetSource, boolean classOnly) {
​
    if (this.beanFactory instanceof ConfigurableListableBeanFactory clbf) {
       AutoProxyUtils.exposeTargetClass(clbf, beanName, beanClass);
    }
​
    ProxyFactory proxyFactory = new ProxyFactory();
    proxyFactory.copyFrom(this);
​
    if (proxyFactory.isProxyTargetClass()) {
       // Explicit handling of JDK proxy targets and lambdas (for introduction advice scenarios)
       if (Proxy.isProxyClass(beanClass) || ClassUtils.isLambdaClass(beanClass)) {
          // Must allow for introductions; can't just set interfaces to the proxy's interfaces only.
          for (Class<?> ifc : beanClass.getInterfaces()) {
             proxyFactory.addInterface(ifc);
          }
       }
    }
    else {
       // No proxyTargetClass flag enforced, let's apply our default checks...
       if (shouldProxyTargetClass(beanClass, beanName)) {
          proxyFactory.setProxyTargetClass(true);
       }
       else {
          evaluateProxyInterfaces(beanClass, proxyFactory);
       }
    }
    // 注意这里 Advisor包含切点,切面,连接点
    // proxyFactory 是代理工厂, 将切面放置到对应的连接点上
    Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
    proxyFactory.addAdvisors(advisors);
    proxyFactory.setTargetSource(targetSource);
    customizeProxyFactory(proxyFactory);
    proxyFactory.setFrozen(this.freezeProxy);
    if (advisorsPreFiltered()) {
       proxyFactory.setPreFiltered(true);
    }
​
    // Use original ClassLoader if bean class not locally loaded in overriding class loader
    ClassLoader classLoader = getProxyClassLoader();
    if (classLoader instanceof SmartClassLoader smartClassLoader && classLoader != beanClass.getClassLoader()) {
       classLoader = smartClassLoader.getOriginalClassLoader();
    }
    return (classOnly ? proxyFactory.getProxyClass(classLoader) : proxyFactory.getProxy(classLoader));
}

我们现在已经明白了Spring 中AOP是如何发挥作用的,底层还是Cglib,创建Bean的时候看看Bean身上是否有请求代理的声明,如果有进入到创建代理的逻辑,其实注入的已经是代理对象了,代理对象在执行目标方法的时候,会经过一道又一道的回调过滤器,满足对应的过滤器,会进入到对应的增强方法中,触发代理逻辑。

注意也不是所有的增强都是在AbstractAutoProxyCreator中完成代理的创建,比如@Async对应的增强,这一点我是在《再探Spring IOC的生命周期》观测到的。@Async的增强我们将会在一篇新的文章中讲述。

总结一下

我们到现在已经对Spring 中AOP的执行机制有了一个大致的了解,在创建Bean的时候根据Bean的元信息是否产生代理对象,放入到容器里面。这样目标方法在执行的时候,就依次进入到代理链,每个链都会判断当前方法是否要被代理。其实写这篇文章的时候思路倒又被重新梳理了一下。 这篇在梳理的时候是零零散散的,刚开始我以为AOP是在调用方法的时候判断切点是否满足,后面又觉得这个不太合适,因为判断是否满足条件就在方法逻辑之外,这个逻辑不属于原方法的逻辑。除非是通过字节码植入的方式进行增强的。

但我的判断并非没有依据,因为想到了内部调用代理不生效的这个场景,尝试为这个生效场景做解释,也就是下面这样:

typescript 复制代码
@Service
public class UserService {
    @Transactional      // 会被 AOP 拦截
    public void createUser(){ ... }
​
    public void doBusiness(){
        // 直接调用 -> 实际是 this.createUser(),绕过代理
        createUser();   // 事务不生效
    }
}

所以我理解应当是从外部调用的时候,已经是代理对象了,只不过首次在进入Bean的方法调用的时候才会判断是否开启代理方法。 像上面的代码调用,doBusiness调用的时候,这个this已经是目标Bean了,无法经过代理。那怎么解决呢? 我们可以请求容器暴露代理对象:

ini 复制代码
@EnableAspectJAutoProxy(exposeProxy = true)

然后可以通过如下的方式拿到代理对象:

scss 复制代码
public void doBusiness(){
    // 拿到代理再调用,切面继续生效
    ((UserService) AopContext.currentProxy()).createUser();
}

参考资料

1\]Aopalliance-03-原生 AspectJ 3 种织入方式及 spring-aop 原理分析 [houbb.github.io/2019/02/26/...](https://link.juejin.cn?target=https%3A%2F%2Fhoubb.github.io%2F2019%2F02%2F26%2Faopalliance-03-aspectj-02-3ways "https://houbb.github.io/2019/02/26/aopalliance-03-aspectj-02-3ways") \[2\] Support for ByteBuddy as an alternative to CGLIB [github.com/spring-proj...](https://link.juejin.cn?target=https%3A%2F%2Fgithub.com%2Fspring-projects%2Fspring-framework%2Fissues%2F12840 "https://github.com/spring-projects/spring-framework/issues/12840")

相关推荐
weixin_46244623几秒前
使用 Go 实现 SSE 流式推送 + 打字机效果(模拟 Coze Chat)
开发语言·后端·golang
JIngJaneIL19 分钟前
基于springboot + vue古城景区管理系统(源码+数据库+文档)
java·开发语言·前端·数据库·vue.js·spring boot·后端
小信啊啊41 分钟前
Go语言切片slice
开发语言·后端·golang
Victor3562 小时前
Netty(20)如何实现基于Netty的WebSocket服务器?
后端
缘不易2 小时前
Springboot 整合JustAuth实现gitee授权登录
spring boot·后端·gitee
Kiri霧3 小时前
Range循环和切片
前端·后端·学习·golang
WizLC3 小时前
【Java】各种IO流知识详解
java·开发语言·后端·spring·intellij idea
Victor3563 小时前
Netty(19)Netty的性能优化手段有哪些?
后端
爬山算法3 小时前
Netty(15)Netty的线程模型是什么?它有哪些线程池类型?
java·后端
白宇横流学长3 小时前
基于SpringBoot实现的冬奥会科普平台设计与实现【源码+文档】
java·spring boot·后端