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")

相关推荐
_風箏22 分钟前
Zabbix【问题 01】安装问题 (比 zabbix-release-5.0-1.el7.noarch 还要新) 问题处理
后端
卓码软件测评28 分钟前
网站测评-利用缓存机制实现XSS的分步测试方法
java·后端·安全·spring·可用性测试·软件需求
星星电灯猴30 分钟前
一次真实的 TF 上架协作案例,从证书到分发的多工具配合流程
后端
Cosolar1 小时前
玩转 WSL:Windows 一键开启 Linux 子系统,轻松实现 SSH 远程连接
后端
rannn_1111 小时前
【Linux学习|黑马笔记|Day4】IP地址、主机名、网络请求、下载、端口、进程管理、主机状态监控、环境变量、文件的上传和下载、压缩和解压
linux·笔记·后端·学习
惜鸟1 小时前
如何让大模型输出结构化数据
后端
ApeAssistant1 小时前
windows 端口占用解决方案
服务器·后端
阿湯哥1 小时前
SkyPilot 的产生背景
后端·python·flask
吴佳浩2 小时前
Python 环境管理工具完全指南
后端·python
JohnYan2 小时前
工作笔记 - 一次微信认证集成迁移
后端·安全·微信