问题

Spring的AOP在什么场景下会失效?

答案

1. 核心原理回顾

Spring AOP基于代理模式实现,只有通过代理对象调用方法时,AOP增强才会生效。

// 代理对象调用链
客户端  代理对象AOP增强  目标对象业务逻辑

失效本质:没有经过代理对象,直接调用了目标对象的方法。

2. 失效场景详解

场景1:同类内部方法调用(最常见)

@Service
public class UserService {
    
    // 方法A有AOP增强
    @Transactional
    public void methodA() {
        System.out.println("Method A");
        // 内部调用methodB - AOP失效!
        methodB();
    }
    
    @Transactional
    public void methodB() {
        System.out.println("Method B");
    }
}

// 客户端调用
@Autowired
private UserService userService;

public void test() {
    // methodA的AOP生效(通过代理对象调用)
    userService.methodA();
    
    // methodB的AOP失效(methodA内部直接调用,this.methodB())
}

失效原因

// Spring代理的实际调用过程
UserService proxy = ...;  // 代理对象
UserService target = ...;  // 目标对象

// 外部调用methodA - 经过代理
proxy.methodA() {
    // AOP增强(事务开启)
    target.methodA() {
        // 内部调用methodB - 直接调用目标对象
        this.methodB();  // this = target,不是proxy!
    }
    // AOP增强(事务提交)
}

解决方案

方案1:拆分到不同类(推荐)

@Service
public class UserService {
    
    @Autowired
    private UserHelper userHelper;
    
    @Transactional
    public void methodA() {
        System.out.println("Method A");
        // 通过Spring注入的bean调用,走代理
        userHelper.methodB();
    }
}

@Service
public class UserHelper {
    
    @Transactional
    public void methodB() {
        System.out.println("Method B");
    }
}

方案2:通过ApplicationContext获取代理对象

@Service
public class UserService implements ApplicationContextAware {
    
    private ApplicationContext applicationContext;
    
    @Override
    public void setApplicationContext(ApplicationContext context) {
        this.applicationContext = context;
    }
    
    @Transactional
    public void methodA() {
        System.out.println("Method A");
        // 获取代理对象
        UserService proxy = applicationContext.getBean(UserService.class);
        proxy.methodB();
    }
    
    @Transactional
    public void methodB() {
        System.out.println("Method B");
    }
}

方案3:使用AopContext.currentProxy()(需要配置)

// 启用exposeProxy
@EnableAspectJAutoProxy(exposeProxy = true)
@Configuration
public class AopConfig {
}

@Service
public class UserService {
    
    @Transactional
    public void methodA() {
        System.out.println("Method A");
        // 获取当前代理对象
        UserService proxy = (UserService) AopContext.currentProxy();
        proxy.methodB();
    }
    
    @Transactional
    public void methodB() {
        System.out.println("Method B");
    }
}

方案4:使用自注入(Spring 4.3+)

@Service
public class UserService {
    
    @Autowired
    @Lazy  // 防止循环依赖
    private UserService self;
    
    @Transactional
    public void methodA() {
        System.out.println("Method A");
        // 通过self调用,走代理
        self.methodB();
    }
    
    @Transactional
    public void methodB() {
        System.out.println("Method B");
    }
}

场景2:方法不是public

@Service
public class UserService {
    
    // protected方法 - AOP失效(CGLIB可以代理,但Spring默认不代理)
    @Transactional
    protected void protectedMethod() {
        System.out.println("Protected method");
    }
    
    // private方法 - AOP完全失效
    @Transactional
    private void privateMethod() {
        System.out.println("Private method");
    }
    
    // 包访问权限 - AOP失效
    @Transactional
    void packageMethod() {
        System.out.println("Package method");
    }
}

失效原因

  • JDK动态代理:只能代理接口的public方法
  • CGLIB代理:可以代理protected/包方法,但Spring默认只对public方法应用AOP
  • private方法:无法被子类重写,CGLIB也无法代理

解决方案

// 改为public方法
@Service
public class UserService {
    
    @Transactional
    public void publicMethod() {
        System.out.println("Public method");
    }
}

场景3:方法被final修饰

@Service
public class UserService {
    
    // final方法 - CGLIB无法代理
    @Transactional
    public final void finalMethod() {
        System.out.println("Final method");
    }
}

失效原因

  • CGLIB通过继承目标类生成代理
  • final方法无法被子类重写
  • JDK动态代理不受影响(代理接口)

解决方案

// 方案1:去掉final关键字
@Service
public class UserService {
    
    @Transactional
    public void normalMethod() {
        System.out.println("Normal method");
    }
}

// 方案2:使用接口 + JDK动态代理
public interface UserService {
    void method();
}

@Service
public class UserServiceImpl implements UserService {
    
    @Transactional
    @Override
    public void method() {
        System.out.println("Method");
    }
}

场景4:类被final修饰

// final类 - CGLIB无法代理
@Service
public final class FinalUserService {
    
    @Transactional
    public void method() {
        System.out.println("Method");
    }
}

失效原因:CGLIB无法继承final类。

解决方案

// 去掉final,或使用接口
public interface UserService {
    void method();
}

@Service
public class UserServiceImpl implements UserService {
    
    @Transactional
    @Override
    public void method() {
        System.out.println("Method");
    }
}

场景5:对象不是Spring管理的Bean

// 手动new的对象 - 不是Spring Bean
public class Test {
    
    public void test() {
        // 不是Spring Bean,AOP失效
        UserService userService = new UserService();
        userService.method();
    }
}

失效原因:Spring AOP只对Spring容器管理的Bean生效。

解决方案

// 通过Spring容器获取Bean
@Component
public class Test {
    
    @Autowired
    private UserService userService;
    
    public void test() {
        // 使用Spring注入的Bean
        userService.method();
    }
}

场景6:切点表达式不匹配

@Aspect
@Component
public class LogAspect {
    
    // 只匹配service包下的方法
    @Pointcut("execution(* com.example.service.*.*(..))")
    public void serviceLayer() {}
    
    @Before("serviceLayer()")
    public void logBefore(JoinPoint joinPoint) {
        System.out.println("Before: " + joinPoint.getSignature());
    }
}

// controller包下的方法不会被拦截
@RestController
public class UserController {
    
    public void method() {
        // 不在切点范围内,AOP失效
    }
}

解决方案

// 调整切点表达式
@Pointcut("execution(* com.example..*.*(..))")  // 匹配所有子包

场景7:异常被捕获(事务AOP场景)

@Service
public class UserService {
    
    @Transactional
    public void transferMoney(Long from, Long to, BigDecimal amount) {
        try {
            // 扣款
            accountDao.deduct(from, amount);
            
            // 模拟异常
            int i = 1 / 0;
            
            // 加款
            accountDao.add(to, amount);
        } catch (Exception e) {
            // 异常被捕获,事务不会回滚!
            log.error("转账失败", e);
        }
    }
}

失效原因:Spring事务AOP默认只在未捕获的异常抛出时回滚。

解决方案

// 方案1:不捕获异常,向上抛出
@Transactional
public void transferMoney(Long from, Long to, BigDecimal amount) {
    accountDao.deduct(from, amount);
    int i = 1 / 0;
    accountDao.add(to, amount);
    // 异常抛出,事务回滚
}

// 方案2:手动设置回滚
@Transactional
public void transferMoney(Long from, Long to, BigDecimal amount) {
    try {
        accountDao.deduct(from, amount);
        int i = 1 / 0;
        accountDao.add(to, amount);
    } catch (Exception e) {
        log.error("转账失败", e);
        // 手动触发回滚
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
    }
}

// 方案3:重新抛出异常
@Transactional
public void transferMoney(Long from, Long to, BigDecimal amount) {
    try {
        accountDao.deduct(from, amount);
        int i = 1 / 0;
        accountDao.add(to, amount);
    } catch (Exception e) {
        log.error("转账失败", e);
        throw e;  // 重新抛出
    }
}

场景8:异常类型不匹配(事务AOP场景)

@Service
public class UserService {
    
    // 默认只对RuntimeException和Error回滚
    @Transactional
    public void method() throws Exception {
        // 抛出检查异常 - 事务不会回滚!
        throw new Exception("Checked exception");
    }
}

失效原因@Transactional默认只回滚RuntimeExceptionError

解决方案

// 方案1:指定回滚异常类型
@Transactional(rollbackFor = Exception.class)
public void method() throws Exception {
    throw new Exception("Checked exception");
}

// 方案2:抛出RuntimeException
@Transactional
public void method() {
    throw new RuntimeException("Runtime exception");
}

场景9:多线程调用

@Service
public class UserService {
    
    @Transactional
    public void method() {
        // 在新线程中调用 - AOP失效
        new Thread(() -> {
            // 这里的代码不在事务中
            save();
        }).start();
    }
    
    @Transactional
    public void save() {
        userDao.save(new User());
    }
}

失效原因

  • 事务绑定在ThreadLocal中
  • 新线程无法获取父线程的事务上下文
  • 代理对象也是线程局部的

解决方案

// 方案1:不使用多线程(如果可能)
@Transactional
public void method() {
    save();
}

// 方案2:使用编程式事务
@Service
public class UserService {
    
    @Autowired
    private PlatformTransactionManager transactionManager;
    
    @Autowired
    private TransactionTemplate transactionTemplate;
    
    public void method() {
        new Thread(() -> {
            // 在新线程中开启新事务
            transactionTemplate.execute(status -> {
                save();
                return null;
            });
        }).start();
    }
    
    public void save() {
        userDao.save(new User());
    }
}

场景10:静态方法

@Service
public class UserService {
    
    // 静态方法 - AOP失效
    @Transactional
    public static void staticMethod() {
        System.out.println("Static method");
    }
}

失效原因

  • 代理对象是实例级别的
  • 静态方法属于类,不属于对象
  • 无法通过代理对象调用

解决方案

// 改为实例方法
@Service
public class UserService {
    
    @Transactional
    public void instanceMethod() {
        System.out.println("Instance method");
    }
}

场景11:@Async异步方法(事务场景)

@Service
public class UserService {
    
    @Transactional
    @Async
    public void asyncMethod() {
        // 事务可能失效(取决于线程池配置)
        userDao.save(new User());
    }
}

失效原因:异步方法在新线程执行,事务上下文丢失。

解决方案

// 方案1:分离异步和事务
@Service
public class UserService {
    
    @Async
    public void asyncMethod() {
        // 不需要事务的逻辑
        // 或调用另一个有事务的方法
        transactionalService.doInTransaction();
    }
}

@Service
public class TransactionalService {
    
    @Transactional
    public void doInTransaction() {
        userDao.save(new User());
    }
}

// 方案2:使用编程式事务
@Service
public class UserService {
    
    @Autowired
    private TransactionTemplate transactionTemplate;
    
    @Async
    public void asyncMethod() {
        transactionTemplate.execute(status -> {
            userDao.save(new User());
            return null;
        });
    }
}

3. 失效场景总结表

场景 失效原因 解决方案
同类内部调用 直接调用目标对象(this) 拆分类 / 自注入 / AopContext
非public方法 Spring默认不代理 改为public
final方法 CGLIB无法重写 去掉final / 使用接口
final类 CGLIB无法继承 去掉final / 使用接口
非Spring Bean 未经过代理 使用@Autowired注入
切点不匹配 表达式未覆盖 调整切点表达式
异常被捕获 事务无法感知 不捕获 / 手动回滚 / 重新抛出
异常类型不匹配 默认只回滚RuntimeException 指定rollbackFor
多线程 线程上下文隔离 编程式事务
静态方法 无法通过实例代理 改为实例方法
异步方法 新线程丢失上下文 分离异步和事务

4. 验证AOP是否生效

@Service
public class UserService {
    
    @Transactional
    public void method() {
        // 检查当前对象是否是代理对象
        System.out.println("Class: " + this.getClass().getName());
        // 如果是代理:com.example.UserService$$EnhancerBySpringCGLIB$$xxxxx
        // 如果不是代理:com.example.UserService
        
        // 检查是否在事务中
        boolean inTransaction = TransactionSynchronizationManager.isActualTransactionActive();
        System.out.println("In transaction: " + inTransaction);
    }
}

5. 面试答题要点

标准回答结构

  1. 核心原因:AOP基于代理模式,未经过代理对象的调用会失效

  2. 最常见场景
    • 同类内部方法调用(最常见)
    • 方法不是public
    • 异常被捕获(事务场景)
  3. 代理限制
    • final类/方法(CGLIB限制)
    • 静态方法
    • 非Spring Bean
  4. 线程问题
    • 多线程
    • 异步方法
  5. 解决方案
    • 拆分类(推荐)
    • 自注入
    • 改为public
    • 指定rollbackFor

加分点

  • 能说明代理原理(JDK/CGLIB)
  • 了解AopContext.currentProxy()
  • 知道事务传播行为
  • 能举实际踩坑案例
  • 了解编程式事务作为备选方案

6. 最佳实践

(1)设计原则

// ✅ 好的设计:职责分离
@Service
public class OrderService {
    
    @Autowired
    private InventoryService inventoryService;
    
    @Transactional
    public void createOrder(Order order) {
        orderDao.save(order);
        // 调用其他Service,走代理
        inventoryService.deduct(order.getProductId(), order.getQuantity());
    }
}

@Service
public class InventoryService {
    
    @Transactional
    public void deduct(Long productId, Integer quantity) {
        inventoryDao.deduct(productId, quantity);
    }
}

// ❌ 不好的设计:同类内部调用
@Service
public class OrderService {
    
    @Transactional
    public void createOrder(Order order) {
        orderDao.save(order);
        // 内部调用,AOP失效
        this.deductInventory(order.getProductId(), order.getQuantity());
    }
    
    @Transactional
    public void deductInventory(Long productId, Integer quantity) {
        inventoryDao.deduct(productId, quantity);
    }
}

(2)事务管理

// 统一异常处理
@Transactional(rollbackFor = Exception.class)  // 指定所有异常回滚
public void method() {
    // 不捕获异常,让事务AOP处理
}

(3)日志记录

// 记录AOP拦截日志
@Aspect
@Component
@Slf4j
public class LogAspect {
    
    @Before("serviceLayer()")
    public void logBefore(JoinPoint joinPoint) {
        // 检查是否是代理对象
        Object target = joinPoint.getTarget();
        Object proxy = joinPoint.getThis();
        log.debug("Target: {}, Proxy: {}", target.getClass(), proxy.getClass());
    }
}

7. 总结

记忆口诀

  • 代理是核心,调用要走代理
  • 内部调用坑,拆分最靠谱
  • public才生效,final要避免
  • Bean才管理,容器来注入
  • 异常要抛出,类型要匹配
  • 线程要注意,上下文隔离

避免踩坑

  • 优先拆分类,避免内部调用
  • 方法设计为public
  • 避免使用final
  • 统一异常处理策略
  • 多线程场景使用编程式事务

理解Spring AOP的失效场景和解决方案,是掌握Spring事务管理和AOP应用的关键,也是面试高频考点。