基于本文回答

播面 播面

文图音视,全方位拆解八股文
0
评论

同一个 Service 类内部的 A 方法直接调用同类内部的 B 方法,B 方法上的 AOP 切面会生效吗?为什么?

知识点图片

不会生效。

这是一个在 Spring 开发中非常经典的面试题和易踩坑点(最常见的场景就是 @Transactional 事务注解失效,或者自定义日志注解失效)。

为什么不会生效?

根本原因在于 Spring AOP 的底层实现机制是“动态代理”

  1. 代理模式的工作原理
    当你在 Spring 中为一个 Service 类配置了 AOP(比如事务、日志等)时,Spring 容器在初始化这个 Bean 时,不会直接把原始对象(目标对象 Target)交给调用方,而是动态生成一个代理对象(Proxy)
    这个代理对象包装了原始对象,并在调用的前后插入了 AOP 的逻辑(如开启事务、记录日志)。

  2. 外部调用的情况(AOP 生效)
    当 Controller 调用 Service 的 A 方法时,实际上调用的是代理对象的 A 方法
    链路是:Controller -> 代理对象.A() -> 执行 AOP 逻辑 -> 原始对象.A()。此时 AOP 正常生效。

  3. 内部调用的情况(AOP 失效)
    当在 A 方法内部直接调用同类的 B 方法时,代码写的是 B(),但在 Java 语义中,它等同于 this.B()
    这里的 this 指向的是原始对象(Target)本身,而不是代理对象(Proxy)!
    因为绕过了代理对象,直接在原始对象上调用了 B 方法,所以 B 方法上的 AOP 切面逻辑根本没有机会被执行。


代码示例说明

java
@Service
public class OrderService {

    // 外部调用 A 方法,A 方法的 AOP 会生效(如果 A 上有注解的话)
    public void A() {
        System.out.println("执行 A 方法");
        // 这里隐式调用了 this.B(),this 是原始对象,不是代理对象!
        B(); 
    }

    // AOP 注解(比如事务控制),此时如果被 A 直接调用,事务将不会开启
    @Transactional 
    public void B() {
        System.out.println("执行 B 方法");
        // 数据库操作...
    }
}

如何解决这个问题?

如果你必须在 A 方法中调用 B 方法,并且要求 B 方法的 AOP 生效,有以下几种常用解决方案:

方案一:通过 AopContext 获取当前代理对象(推荐,侵入性适中)

利用 Spring 提供的 AopContext 获取当前类的代理对象,然后用代理对象去调用 B 方法。
注意:需要在启动类或配置类上加上 @EnableAspectJAutoProxy(exposeProxy = true) 才能暴露代理对象。

java
public void A() {
    // 获取当前类的代理对象,再调用 B 方法
    OrderService proxy = (OrderService) AopContext.currentProxy();
    proxy.B();
}

方案二:自己注入自己(Spring 4.3+ 支持,最常用)

在类内部通过 @Autowired 注入自己。为了避免循环依赖报错,通常建议加上 @Lazy 注解。

java
@Service
public class OrderService {

    @Autowired
    @Lazy // 延迟注入,解决可能出现的循环依赖问题
    private OrderService self;

    public void A() {
        // 使用注入的代理对象调用 B 方法
        self.B();
    }

    @Transactional
    public void B() { ... }
}

方案三:重构代码(最佳实践)

内部方法调用导致 AOP 失效,很多时候说明类的职责过重。
最好的解决方法是进行代码重构:将 B 方法抽离到一个全新的 Service 类中(比如 OrderExtService),然后在原类中注入这个新 Service,通过外部调用的方式来执行 B 方法。这不仅解决了 AOP 失效问题,也符合面向对象设计的单一职责原则

方案四:从 ApplicationContext 中获取 Bean(不推荐,代码太重)

注入 ApplicationContext,在 A 方法中通过 context.getBean(OrderService.class) 拿到代理对象再调用 B 方法。这种方式与 Spring API 强耦合,不够优雅。

00:00
00:00