AOP原理相关问题

总结摘要
总结AOP原理相关知识点可能提的问题

针对AOP原理相关问题,我准备了一个“一句话原理 + 一句话源码 + 一句话项目/场景”的结构化回答,体现深度同时展现实战能力。

基础概念

什么是 AOP(面向切面编程)?它的应用场景有哪些?(日志、事务、权限等)

AOP核心原理

一句话原理:AOP(Aspect-Oriented Programming)通过动态代理技术,将横切关注点(如日志、事务、权限)从业务代码中分离出来,在不修改源码的情况下,实现统一增强,遵循开闭原则(对扩展开放,对修改关闭)。

一句话源码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
/**
 * AOP核心概念源码级解析
 */
// 1. 传统OOP方式:横切关注点散落在各处
public class UserService {
    // 日志、事务、权限代码混在业务逻辑中
    public void createUser(User user) {
        // 权限检查
        if (!SecurityContext.hasPermission("user:create")) {
            throw new SecurityException("无权限");
        }
        
        // 日志记录
        log.info("开始创建用户: {}", user);
        
        // 开启事务
        Transaction tx = transactionManager.beginTransaction();
        try {
            // 核心业务逻辑
            userDao.save(user);
            
            // 提交事务
            tx.commit();
            
            // 日志记录
            log.info("用户创建成功: {}", user.getId());
        } catch (Exception e) {
            // 回滚事务
            tx.rollback();
            log.error("用户创建失败", e);
            throw e;
        }
    }
}

// 2. AOP方式:横切关注点被抽取为切面
@Aspect
@Component
public class ServiceAspect {
    
    // 权限切面
    @Before("@annotation(RequirePermission)")
    public void checkPermission(JoinPoint joinPoint) {
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        RequirePermission ann = method.getAnnotation(RequirePermission.class);
        SecurityContext.checkPermission(ann.value());
    }
    
    // 日志切面
    @Around("@annotation(LogExecution)")
    public Object logExecution(ProceedingJoinPoint pjp) throws Throwable {
        String methodName = pjp.getSignature().toShortString();
        log.info("方法开始: {}", methodName);
        
        long start = System.currentTimeMillis();
        try {
            Object result = pjp.proceed();
            long cost = System.currentTimeMillis() - start;
            log.info("方法结束: {}, 耗时: {}ms", methodName, cost);
            return result;
        } catch (Exception e) {
            log.error("方法异常: {}", methodName, e);
            throw e;
        }
    }
    
    // 事务切面
    @Around("@annotation(Transactional)")
    public Object manageTransaction(ProceedingJoinPoint pjp) throws Throwable {
        Transaction tx = transactionManager.beginTransaction();
        try {
            Object result = pjp.proceed();
            tx.commit();
            return result;
        } catch (Exception e) {
            tx.rollback();
            throw e;
        }
    }
}

// 3. 纯净的业务代码(只有核心逻辑)
@Service
public class UserService {
    
    @RequirePermission("user:create")
    @LogExecution
    @Transactional
    public void createUser(User user) {
        userDao.save(user);  // 只有核心业务逻辑
    }
}

项目场景:在金融交易系统中,通过AOP将权限校验、交易日志、分布式事务等横切关注点从业务代码中剥离,业务开发只需关注核心逻辑,代码量减少60%,维护性大幅提升。

AOP五大核心概念

一句话原理:AOP的五大核心概念构成完整体系:切面(Aspect)封装横切关注点,连接点(JoinPoint)是程序执行点,通知(Advice)定义增强逻辑,切入点(Pointcut)匹配连接点,织入(Weaving)将切面应用到目标对象。

一句话源码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
/**
 * AOP五大核心概念源码示例
 */
@Aspect  // 1. 切面:封装横切关注点的模块
@Component
public class LoggingAspect {
    
    // 2. 切入点:匹配需要增强的方法
    @Pointcut("execution(* com.example.service.*.*(..))")
    public void serviceLayer() {}
    
    @Pointcut("@annotation(com.example.annotation.Loggable)")
    public void loggableMethods() {}
    
    @Pointcut("serviceLayer() && loggableMethods()")
    public void combinedPointcut() {}
    
    // 3. 通知:定义增强逻辑(5种类型)
    
    // 3.1 前置通知:在方法执行前执行
    @Before("serviceLayer()")
    public void beforeAdvice(JoinPoint joinPoint) {
        log.info("前置通知 - 方法名: {}", joinPoint.getSignature().getName());
    }
    
    // 3.2 后置通知:在方法执行后执行(无论是否异常)
    @After("serviceLayer()")
    public void afterAdvice(JoinPoint joinPoint) {
        log.info("后置通知 - 清理资源");
    }
    
    // 3.3 返回通知:方法成功返回后执行
    @AfterReturning(value = "serviceLayer()", returning = "result")
    public void afterReturningAdvice(JoinPoint joinPoint, Object result) {
        log.info("返回通知 - 结果: {}", result);
    }
    
    // 3.4 异常通知:方法抛出异常后执行
    @AfterThrowing(value = "serviceLayer()", throwing = "e")
    public void afterThrowingAdvice(JoinPoint joinPoint, Exception e) {
        log.error("异常通知 - 错误: {}", e.getMessage());
    }
    
    // 3.5 环绕通知:最强大的通知,控制方法执行
    @Around("@annotation(LogExecutionTime)")
    public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {
        long start = System.currentTimeMillis();
        
        try {
            // 前置处理
            log.info("环绕通知 - 开始执行: {}", pjp.getSignature().getName());
            
            // 执行目标方法
            Object result = pjp.proceed();
            
            // 后置处理
            long cost = System.currentTimeMillis() - start;
            log.info("环绕通知 - 执行完成,耗时: {}ms", cost);
            
            return result;
        } catch (Exception e) {
            log.error("环绕通知 - 执行异常", e);
            throw e;
        }
    }
}

// 4. 连接点:程序执行的特定点(被切面拦截的方法)
@Service
public class OrderService {
    
    @LogExecutionTime  // 这是一个连接点
    public Order createOrder(OrderRequest request) {
        // 业务逻辑
        return order;
    }
}

// 5. 织入:将切面应用到目标对象的过程
// Spring在运行时通过动态代理将切面织入

项目场景:在支付系统中,使用环绕通知实现分布式事务的TCC模式,使用异常通知统一记录异常并发送告警,使用后置通知清理ThreadLocal中的上下文,每种通知各司其职,共同构建健壮的支付体系。

五大应用场景实战

一句话原理:AOP的五大经典应用场景:日志记录(方法追踪)、事务管理(数据库一致性)、权限控制(安全校验)、性能监控(耗时统计)、缓存处理(结果缓存),覆盖了企业应用的大部分横切关注点。

一句话源码

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
/**
 * AOP五大应用场景实战
 */
@Aspect
@Component
public class ApplicationScenesAspect {
    
    private static final Logger log = LoggerFactory.getLogger(ApplicationScenesAspect.class);
    
    // ============ 场景1:日志记录 ============
    @Before("@annotation(LogOperation)")
    public void logOperation(JoinPoint joinPoint) {
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        LogOperation logOp = method.getAnnotation(LogOperation.class);
        
        String operation = logOp.value();
        String user = SecurityContext.getCurrentUser();
        String params = Arrays.toString(joinPoint.getArgs());
        
        // 记录操作日志到数据库
        log.info("用户: {} 执行操作: {}, 参数: {}", user, operation, params);
        operationLogService.save(new OperationLog(user, operation, params));
    }
    
    // ============ 场景2:事务管理 ============
    @Around("@annotation(Transactional)")
    public Object manageTransaction(ProceedingJoinPoint pjp) throws Throwable {
        TransactionStatus transaction = null;
        try {
            // 开启事务
            transaction = transactionManager.beginTransaction();
            log.debug("开启事务");
            
            // 执行业务方法
            Object result = pjp.proceed();
            
            // 提交事务
            transactionManager.commit(transaction);
            log.debug("提交事务");
            
            return result;
        } catch (Exception e) {
            // 回滚事务
            if (transaction != null) {
                transactionManager.rollback(transaction);
                log.error("回滚事务", e);
            }
            throw e;
        }
    }
    
    // ============ 场景3:权限控制 ============
    @Before("@annotation(RequirePermission)")
    public void checkPermission(JoinPoint joinPoint) {
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        RequirePermission perm = method.getAnnotation(RequirePermission.class);
        
        String requiredPerm = perm.value();
        String user = SecurityContext.getCurrentUser();
        
        // 检查用户权限
        if (!permissionService.hasPermission(user, requiredPerm)) {
            log.warn("用户: {} 无权执行操作: {}", user, requiredPerm);
            throw new PermissionDeniedException("无权限执行此操作");
        }
        
        log.debug("权限验证通过: {}", requiredPerm);
    }
    
    // ============ 场景4:性能监控 ============
    @Around("@annotation(MonitorPerformance)")
    public Object monitorPerformance(ProceedingJoinPoint pjp) throws Throwable {
        String methodName = pjp.getSignature().toShortString();
        
        long start = System.nanoTime();
        try {
            return pjp.proceed();
        } finally {
            long cost = (System.nanoTime() - start) / 1_000_000; // 毫秒
            log.info("性能监控 - {} 耗时: {}ms", methodName, cost);
            
            // 上报到监控系统
            if (cost > 500) {  // 超过500ms的慢方法
                metricsCollector.recordSlowMethod(methodName, cost);
            }
        }
    }
    
    // ============ 场景5:缓存处理 ============
    @Around("@annotation(Cacheable)")
    public Object handleCache(ProceedingJoinPoint pjp) throws Throwable {
        Method method = ((MethodSignature) pjp.getSignature()).getMethod();
        Cacheable cacheable = method.getAnnotation(Cacheable.class);
        
        String key = generateKey(method, pjp.getArgs());
        
        // 尝试从缓存获取
        Object cachedValue = cacheManager.get(key);
        if (cachedValue != null) {
            log.debug("缓存命中: {}", key);
            return cachedValue;
        }
        
        // 缓存未命中,执行方法
        log.debug("缓存未命中,执行方法: {}", key);
        Object result = pjp.proceed();
        
        // 存入缓存
        cacheManager.put(key, result, cacheable.ttl());
        
        return result;
    }
    
    private String generateKey(Method method, Object[] args) {
        // 生成缓存key
        return method.getDeclaringClass().getSimpleName() + "." + 
               method.getName() + ":" + Arrays.hashCode(args);
    }
}

项目场景:在电商系统中,通过AOP实现了五大核心功能:日志切面记录所有操作日志;事务切面保证订单数据一致性;权限切面拦截未授权访问;监控切面统计接口性能;缓存切面加速商品查询。各切面协同工作,让业务代码保持简洁。

完整实战指南

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
/**
 * AOP完整实战指南
 */
public class AOPCompleteGuide {
    
    /**
     * 1. 自定义注解实现声明式AOP
     */
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface RateLimit {
        int value() default 10;  // 每秒允许的请求数
    }
    
    @Aspect
    @Component
    public class RateLimitAspect {
        
        private final Map<String, RateLimiter> limiters = new ConcurrentHashMap<>();
        
        @Around("@annotation(rateLimit)")
        public Object rateLimit(ProceedingJoinPoint pjp, RateLimit rateLimit) throws Throwable {
            String key = pjp.getSignature().toShortString();
            
            // 获取或创建限流器
            RateLimiter limiter = limiters.computeIfAbsent(key, 
                k -> RateLimiter.create(rateLimit.value()));
            
            // 尝试获取令牌
            if (!limiter.tryAcquire()) {
                log.warn("接口限流: {}", key);
                throw new RateLimitException("请求太频繁,请稍后重试");
            }
            
            return pjp.proceed();
        }
    }
    
    /**
     * 2. 通知执行顺序
     */
    @Aspect
    @Order(1)  // 控制切面执行顺序
    @Component
    public class OrderedAspect {
        
        @Around("@annotation(OrderedAnnotation)")
        public Object around(ProceedingJoinPoint pjp) throws Throwable {
            // 执行顺序:@Order数值小的先执行around开始,后执行around结束
            try {
                System.out.println("Order 1 - before");
                return pjp.proceed();
            } finally {
                System.out.println("Order 1 - after");
            }
        }
    }
    
    /**
     * 3. 常见切入点表达式
     */
    public class PointcutExpressions {
        
        // 匹配特定包下的所有方法
        @Pointcut("execution(* com.example.service.*.*(..))")
        public void serviceLayer() {}
        
        // 匹配特定注解的方法
        @Pointcut("@annotation(com.example.annotation.Loggable)")
        public void loggableMethods() {}
        
        // 匹配特定类型的Bean
        @Pointcut("bean(*Service)")
        public void serviceBeans() {}
        
        // 匹配带有特定注解的Bean
        @Pointcut("@within(org.springframework.stereotype.Service)")
        public void serviceClasses() {}
        
        // 组合切入点
        @Pointcut("serviceLayer() && !loggableMethods()")
        public void serviceLayerWithoutLog() {}
    }
    
    /**
     * 4. 注意事项
     */
    public class Precautions {
        // 1. 内部方法调用不会触发AOP(需要通过代理对象调用)
        // 2. final方法无法被代理
        // 3. 私有方法无法被代理
        // 4. 同一个类中的方法调用不经过代理
        // 5. 性能开销:每个代理方法调用都有微小开销
    }
    
    /**
     * 5. 调试技巧
     */
    public class DebugTips {
        // 1. 查看代理类:-Dproxy.debug=true
        // 2. 断点位置:JdkDynamicAopProxy.invoke() 或 CglibAopProxy.intercept()
        // 3. 查看切面应用情况:@EnableAspectJAutoProxy(proxyTargetClass = true)
    }
}

/**
 * AOP应用场景总结表
 * 
 * 场景        实现方式               作用                    业务价值
 * 日志记录    @Before/@After        方法追踪                问题排查
 * 事务管理    @Around                数据一致性              防止数据损坏
 * 权限控制    @Before                安全校验                防止越权
 * 性能监控    @Around                耗时统计                性能优化
 * 缓存处理    @Around                结果缓存                响应加速
 * 限流降级    @Around                流量控制                系统保护
 * 参数校验    @Before                输入验证                数据安全
 * 
 * 优势:
 * 1. 代码复用:横切逻辑集中管理
 * 2. 解耦合:业务逻辑与系统服务分离
 * 3. 可维护:修改切面影响所有切点
 * 4. 无侵入:不修改原有代码
 */

// 面试金句
// "AOP就像'装修公司'给房子统一安装'智能家居系统':
//  日志切面是'监控摄像头'(记录每个房间的活动),
//  事务切面是'中央空调'(保证温度一致性),
//  权限切面是'门禁系统'(检查谁能进入),
//  性能监控是'电表'(统计每个电器的能耗),
//  缓存切面是'冰箱'(暂时保存食物,下次直接用)。
//  在支付系统中,我用AOP统一处理分布式事务、幂等性检查、异常告警,
//  业务代码从3000行精简到800行,开发效率提升3倍。
//  理解AOP就能把系统服务像乐高积木一样灵活组合让业务代码回归纯粹"

Spring AOP 和 AspectJ 的区别?

核心原理本质区别

一句话原理:Spring AOP是基于动态代理的运行时增强,只能织入方法级别的切面;AspectJ是基于字节码操作的编译期/类加载期增强,支持方法、构造器、字段、静态初始化等更细粒度的切面,两者是"运行时代理"与"编译期织入"的本质区别。

一句话源码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
/**
 * 实现机制对比
 */
// ============ Spring AOP:基于动态代理 ============
@Service
public class UserService {
    public void createUser(User user) {
        // 业务逻辑
    }
}

@Aspect
@Component
public class LogAspect {
    @Before("execution(* com.example.service.*.*(..))")
    public void logBefore() {
        System.out.println("Spring AOP: 方法执行前记录日志");
    }
}

// Spring AOP在运行时创建代理对象
// JDK动态代理实现
public class JdkDynamicAopProxy implements AopProxy {
    public Object getProxy() {
        // 创建JDK动态代理
        return Proxy.newProxyInstance(classLoader, interfaces, this);
    }
    
    public Object invoke(Object proxy, Method method, Object[] args) {
        // 执行切面逻辑
        // 调用目标方法
    }
}

// CGLIB代理实现
public class CglibAopProxy implements AopProxy {
    public Object getProxy() {
        // 创建CGLIB代理
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(targetClass);
        enhancer.setCallback(new DynamicAdvisedInterceptor());
        return enhancer.create();
    }
}

// ============ AspectJ:基于字节码织入 ============
// AspectJ在编译期或类加载期修改字节码
public aspect LogAspect {
    // 编译期直接修改字节码,插入切面逻辑
    pointcut publicMethod(): execution(public * *(..));
    
    before(): publicMethod() {
        System.out.println("AspectJ: 方法执行前记录日志");
    }
}

// 编译后的字节码(反编译后)
public class UserService {
    public void createUser(User user) {
        // AspectJ编译时直接插入的代码
        LogAspect.aspectOf().before();  // 切面代码直接织入
        
        // 原始业务逻辑
        // ...
    }
}

项目场景:在性能要求极高的核心交易系统中,选择AspectJ实现零反射开销的切面;在普通的Web应用中,使用Spring AOP利用其简单性和与Spring的无缝集成。两种技术各有千秋,根据场景选择。

详细对比分析

一句话原理:Spring AOP和AspectJ在织入方式(运行时代理 vs 编译期织入)、支持粒度(方法级 vs 全方位)、性能(有代理开销 vs 无运行时开销)、易用性(简单集成 vs 复杂配置)四个维度有显著差异。

一句话源码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
/**
 * 详细对比分析表(代码注释形式)
 */
public class ComparisonDetail {
    
    // 1. 织入方式对比
    public class WeavingMethod {
        // Spring AOP: 运行时动态代理
        // - 在运行时创建代理对象
        // - 只有通过Spring容器获取的Bean才能被增强
        // - 同一个类内部方法调用不会触发增强
        
        // AspectJ: 编译期/类加载期织入
        // - 在编译时直接将切面代码织入字节码
        // - 所有方法调用都会触发增强(包括内部调用)
        // - 支持Load-Time Weaving(类加载时织入)
    }
    
    // 2. 支持粒度对比
    public class JoinPointTypes {
        // Spring AOP支持的连接点:
        // - 方法执行(仅此而已)
        
        // AspectJ支持的连接点:
        // - 方法执行、方法调用
        // - 构造器执行、构造器调用
        // - 字段读取、字段赋值
        // - 静态初始化器
        // - 对象初始化
        // - 前置/后置/环绕/异常通知等
    }
    
    // 3. 性能对比
    public class PerformanceComparison {
        // Spring AOP:
        // - 有代理对象的方法调用开销(反射)
        // - 每次调用都经过代理链
        // - 适合大部分业务场景
        
        // AspectJ:
        // - 零运行时开销(直接调用)
        // - 性能接近原生Java
        // - 适合高频调用场景
    }
    
    // 4. 配置复杂度
    public class ConfigurationComplexity {
        // Spring AOP:
        @Configuration
        @EnableAspectJAutoProxy  // 一行注解开启
        public class AopConfig {
            // 简单配置
        }
        
        // AspectJ:
        // - 需要AspectJ编译器(ajc)
        // - 或配置Load-Time Weaving(javaagent)
        // - Maven/Gradle插件配置
        // - 学习曲线较陡
    }
    
    // 5. 局限性对比
    public class Limitations {
        // Spring AOP局限性:
        // - 只能增强public方法
        // - 内部方法调用不增强
        // - 目标对象必须是Spring Bean
        // - 不能增强final类和方法
        
        // AspectJ局限性:
        // - 配置复杂
        // - 编译时间增加
        // - 调试相对困难
    }
}

项目场景:在开发统一日志框架时,使用AspectJ实现方法调用的全链路追踪,包括构造器、字段访问等细粒度切面;在业务系统中使用Spring AOP实现声明式事务和权限控制,简单高效。两者结合使用,各取所长。

Spring如何集成AspectJ

一句话原理:Spring通过@AspectJ注解支持AspectJ的切面语法,但底层仍然使用Spring AOP的动态代理实现,除非配置了<context:load-time-weaver/>启用真正的AspectJ织入

一句话源码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
/**
 * Spring集成AspectJ的两种方式
 */
@Configuration
public class AspectJIntegration {
    
    // ============ 方式1:使用Spring AOP实现AspectJ注解语法(默认) ============
    @EnableAspectJAutoProxy  // 启用@AspectJ注解支持(基于Spring AOP)
    public class SpringAopWithAspectJAnnotations {
        
        @Bean
        public LogAspect logAspect() {
            return new LogAspect();
        }
    }
    
    @Aspect  // 使用AspectJ注解,但底层是Spring AOP
    public class LogAspect {
        
        @Pointcut("execution(* com.example.service.*.*(..))")
        public void serviceLayer() {}
        
        @Before("serviceLayer()")
        public void beforeMethod() {
            // 这里用的是Spring AOP的动态代理
            System.out.println("方法执行前");
        }
    }
    
    // ============ 方式2:使用真正的AspectJ织入(Load-Time Weaving) ============
    @Configuration
    @EnableLoadTimeWeaving  // 启用加载时织入
    @EnableAspectJAutoProxy(proxyTargetClass = true)
    public class AspectJLTWConfig {
        
        @Bean
        public LoadTimeWeaver loadTimeWeaver() {
            return new InstrumentationLoadTimeWeaver();
        }
    }
    
    // JVM启动参数需要添加:
    // -javaagent:path/to/spring-instrument-5.3.23.jar
    // -javaagent:path/to/aspectjweaver-1.9.7.jar
    
    // ============ 方式3:编译期织入(CTW) ============
    // Maven插件配置
    /*
    <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>aspectj-maven-plugin</artifactId>
        <version>1.14.0</version>
        <configuration>
            <complianceLevel>11</complianceLevel>
            <source>11</source>
            <target>11</target>
        </configuration>
        <executions>
            <execution>
                <goals>
                    <goal>compile</goal>
                    <goal>test-compile</goal>
                </goals>
            </execution>
        </executions>
    </plugin>
    */
}

// 验证当前使用的AOP方式
@Component
public class AOPVerifier {
    
    @Autowired
    private ApplicationContext context;
    
    @PostConstruct
    public void checkAOP() {
        Object userService = context.getBean("userService");
        
        // 检查是否是代理对象
        if (AopUtils.isAopProxy(userService)) {
            System.out.println("使用Spring AOP动态代理");
            if (AopUtils.isCglibProxy(userService)) {
                System.out.println("CGLIB代理");
            } else {
                System.out.println("JDK动态代理");
            }
        } else {
            System.out.println("可能使用了AspectJ编译期织入");
        }
    }
}

项目场景:在开发基础架构组件时,使用AspectJ的编译期织入实现无侵入的监控探针;在业务系统中,使用Spring AOP配合@AspectJ注解实现声明式缓存和重试机制,既享受了AspectJ的语法便利,又保持了Spring AOP的简单性。

完整实战指南

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
/**
 * 选型指南完整实战
 */
public class AOPSelectionGuide {
    
    /**
     * 1. 选择决策树
     */
    public class DecisionTree {
        // 1. 是否需要细粒度切面(构造器、字段等)?
        //    ├─ 是 → AspectJ
        //    └─ 否 → 转到2
        
        // 2. 是否对性能有极致要求(零反射开销)?
        //    ├─ 是 → AspectJ
        //    └─ 否 → 转到3
        
        // 3. 是否需要增强非Spring管理的对象?
        //    ├─ 是 → AspectJ
        //    └─ 否 → 转到4
        
        // 4. 是否需要内部方法调用也触发切面?
        //    ├─ 是 → AspectJ
        //    └─ 否 → Spring AOP
        
        // 5. 项目是否已使用Spring?
        //    ├─ 是 → Spring AOP
        //    └─ 否 → 评估学习成本
    }
    
    /**
     * 2. 对比总结表
     */
    public class ComparisonTable {
        // 维度                Spring AOP                    AspectJ
        // 实现原理             动态代理                      字节码织入
        // 织入时机             运行时                        编译期/类加载期
        // 连接点支持           方法执行                       全方位(方法、构造器、字段)
        // 性能                 有代理开销                    零运行时开销
        // 内部调用增强          ❌                            ✅
        // 非Spring Bean增强     ❌                            ✅
        // 配置复杂度           简单                          复杂
        // 调试难度             容易                          较难
        // 与Spring集成         无缝                         需额外配置
    }
    
    /**
     * 3. 实战案例:混合使用
     */
    @Configuration
    @EnableAspectJAutoProxy
    @EnableLoadTimeWeaving
    public class HybridAOPConfig {
        
        // 事务管理:用Spring AOP(简单)
        @Bean
        public TransactionAspect transactionAspect() {
            return new TransactionAspect();
        }
        
        // 性能监控:用AspectJ(内部调用也需要监控)
        @Bean
        public PerformanceAspect performanceAspect() {
            return new PerformanceAspect();
        }
    }
    
    // 性能监控切面(需要AspectJ实现内部调用监控)
    @Aspect
    public class PerformanceAspect {
        
        @Around("execution(* com.example.service.*.*(..))")
        public Object monitor(ProceedingJoinPoint pjp) throws Throwable {
            long start = System.nanoTime();
            try {
                return pjp.proceed();
            } finally {
                long cost = (System.nanoTime() - start) / 1_000_000;
                System.out.println("方法耗时: " + cost + "ms");
            }
        }
    }
    
    /**
     * 4. 调试技巧
     */
    public class DebugTips {
        // Spring AOP调试:
        // -Dproxy.debug=true
        // logging.level.org.springframework.aop=DEBUG
        
        // AspectJ调试:
        // -Daj.weaving.verbose=true
        // -Dorg.aspectj.weaver.Dump.condition=true
    }
}

/**
 * 最终对比表
 * 
 * 特性           Spring AOP        AspectJ (LTW)      AspectJ (CTW)
 * 配置难度        ⭐                ⭐⭐⭐                ⭐⭐⭐⭐
 * 运行时开销      有代理开销         无                   无
 * 内部调用增强    ❌                 ✅                   ✅
 * 细粒度切面      ❌                 ✅                   ✅
 * 调试难度        ⭐                ⭐⭐                  ⭐⭐⭐
 * 适用场景        业务系统           监控框架              基础组件
 */

// 面试金句
// "Spring AOP和AspectJ的区别就像'家政服务'和'智能家居系统':
//  Spring AOP是'家政服务',只在需要的时候上门(方法调用时),
//  通过'代理'帮你做事(动态代理),简单灵活但有些地方去不了(内部方法);
//  AspectJ是'智能家居系统',在装修房子时(编译期)就把传感器装好(字节码织入),
//  每个角落都能监控(构造器、字段),一劳永逸但装修成本高。
//  在实际项目中,业务层的权限、事务用Spring AOP就够了;
//  而框架级的全链路追踪必须用AspectJ,确保每个内部调用都能被记录。
//  理解两者差异才能根据场景做出正确的技术选型"

什么是连接点(Join Point)、切点(Pointcut)、通知(Advice)、切面(Aspect)?

连接点(Join Point)—— 可插入的位置

一句话原理:连接点是程序执行过程中的特定点,如方法调用、方法执行、构造器调用、字段访问等,是AOP可以插入切面逻辑的位置,在Spring AOP中特指方法执行

一句话源码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/**
 * 连接点概念源码示例
 */
public class JoinPointExample {
    
    // 连接点示例:这些方法的执行都是连接点
    public class UserService {
        
        // 连接点1:createUser方法的执行
        public User createUser(String name, int age) {
            User user = new User(name, age);  // 连接点2:构造器执行
            user.setName(name);                // 连接点3:setName方法执行
            return user;                       // 连接点4:return语句(部分AOP支持)
        }
        
        // 连接点5:getUser方法的执行
        public User getUser(Long id) {
            return userRepository.findById(id); // 连接点6:findById方法调用
        }
    }
    
    // 在AspectJ中支持的连接点类型
    // - 方法执行:execution(* *.*(..))
    // - 方法调用:call(* *.*(..))
    // - 构造器执行:execution(new(..))
    // - 构造器调用:call(new(..))
    // - 字段读取:get(* *)
    // - 字段赋值:set(* *)
    // - 异常处理:handler(*)
    // - 静态初始化:staticinitialization(*)
    
    // Spring AOP中获取连接点信息
    @Before("execution(* com.example.service.*.*(..))")
    public void logJoinPoint(JoinPoint joinPoint) {
        // 获取连接点信息
        String methodName = joinPoint.getSignature().getName();  // 方法名
        Object[] args = joinPoint.getArgs();                      // 参数
        Object target = joinPoint.getTarget();                    // 目标对象
        Object proxy = joinPoint.getThis();                        // 代理对象
        
        System.out.println("连接点信息:");
        System.out.println("  方法名:" + methodName);
        System.out.println("  参数:" + Arrays.toString(args));
        System.out.println("  目标类:" + target.getClass().getSimpleName());
    }
}

项目场景:在监控系统中,通过获取连接点的方法名、参数等信息,动态记录每次API调用的详细信息,包括哪个用户调用了哪个方法、传入了什么参数,为问题排查提供完整上下文。

切点(Pointcut)—— 筛选连接点的规则

一句话原理:切点是一组连接点的集合,通过表达式(如execution(* service.*.*(..)))来匹配和筛选需要增强的连接点,定义了"在哪里“执行切面逻辑。

一句话源码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
/**
 * 切点概念源码示例
 */
@Aspect
@Component
public class PointcutExample {
    
    private static final Logger log = LoggerFactory.getLogger(PointcutExample.class);
    
    // ============ 切点表达式示例 ============
    
    // 1. 匹配特定包下的所有方法
    @Pointcut("execution(* com.example.service.*.*(..))")
    public void serviceLayer() {}
    
    // 2. 匹配特定注解的方法
    @Pointcut("@annotation(com.example.annotation.Loggable)")
    public void loggableMethods() {}
    
    // 3. 匹配特定返回类型
    @Pointcut("execution(com.example.entity.User *(..))")
    public void returningUser() {}
    
    // 4. 匹配特定参数
    @Pointcut("execution(* *.*(String, int))")
    public void stringAndIntParams() {}
    
    // 5. 匹配特定名称的Bean
    @Pointcut("bean(*Service)")
    public void serviceBeans() {}
    
    // 6. 匹配带有特定注解的类
    @Pointcut("@within(org.springframework.stereotype.Service)")
    public void serviceClasses() {}
    
    // 7. 组合切点(且、或、非)
    @Pointcut("serviceLayer() && loggableMethods()")
    public void serviceAndLoggable() {}
    
    @Pointcut("serviceLayer() || controllerLayer()")
    public void serviceOrController() {}
    
    @Pointcut("serviceLayer() && !loggableMethods()")
    public void serviceWithoutLog() {}
    
    // 8. 带参数的切点
    @Pointcut("execution(* *.*(..)) && args(userId,..)")
    public void firstParamIsLong(Long userId) {}
    
    // 9. 获取返回值
    @Pointcut("execution(* *.*(..))")
    public void anyMethod() {}
    
    @AfterReturning(value = "anyMethod()", returning = "result")
    public void afterReturning(Object result) {
        log.info("方法返回,结果: {}", result);
    }
    
    // 10. 获取异常
    @AfterThrowing(value = "serviceLayer()", throwing = "ex")
    public void afterThrowing(Exception ex) {
        log.error("方法异常: {}", ex.getMessage());
    }
    
    // ============ 使用切点 ============
    @Before("serviceLayer()")
    public void beforeServiceMethod(JoinPoint joinPoint) {
        log.info("执行Service方法: {}", joinPoint.getSignature().getName());
    }
    
    @Before("serviceAndLoggable()")
    public void beforeLoggableServiceMethod() {
        log.info("执行可记录的Service方法");
    }
    
    @Before("firstParamIsLong(userId)")
    public void beforeWithParam(Long userId) {
        log.info("第一个参数是userId: {}", userId);
    }
}

项目场景:在权限系统中,通过切点表达式匹配所有带有@RequirePermission注解的方法,自动进行权限校验。切点表达式灵活定义规则,让权限控制精确到每个需要保护的方法。

通知(Advice)—— 增强的逻辑

一句话原理:通知是切面在特定连接点执行的增强逻辑,定义了”什么时候"(Before/After/Around)和"做什么"(具体的处理代码),有五种类型:Before、After、AfterReturning、AfterThrowing、Around。

一句话源码

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
/**
 * 五种通知类型源码示例
 */
@Aspect
@Component
public class AdviceExample {
    
    private static final Logger log = LoggerFactory.getLogger(AdviceExample.class);
    
    // ============ 1. 前置通知(Before):方法执行前执行 ============
    @Before("execution(* com.example.service.*.*(..))")
    public void beforeAdvice(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        log.info("[前置通知] 方法 {} 即将执行,参数: {}", methodName, args);
        
        // 可以修改参数吗?不能直接修改,但可以通过ProceedingJoinPoint
        // 校验参数
        validateArgs(args);
    }
    
    // ============ 2. 后置通知(After):方法执行后执行(无论成功还是异常) ============
    @After("execution(* com.example.service.*.*(..))")
    public void afterAdvice(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        log.info("[后置通知] 方法 {} 执行完毕(清理资源)", methodName);
        
        // 清理ThreadLocal等资源
        ThreadLocalHolder.clear();
    }
    
    // ============ 3. 返回通知(AfterReturning):方法成功返回后执行 ============
    @AfterReturning(
        value = "execution(* com.example.service.*.*(..))",
        returning = "result"
    )
    public void afterReturningAdvice(JoinPoint joinPoint, Object result) {
        String methodName = joinPoint.getSignature().getName();
        log.info("[返回通知] 方法 {} 成功返回,结果: {}", methodName, result);
        
        // 可以修改返回值吗?不能直接修改,但可以通过Around
        // 记录成功日志
        successCounter.incrementAndGet();
    }
    
    // ============ 4. 异常通知(AfterThrowing):方法抛出异常后执行 ============
    @AfterThrowing(
        value = "execution(* com.example.service.*.*(..))",
        throwing = "exception"
    )
    public void afterThrowingAdvice(JoinPoint joinPoint, Exception exception) {
        String methodName = joinPoint.getSignature().getName();
        log.error("[异常通知] 方法 {} 抛出异常: {}", methodName, exception.getMessage(), exception);
        
        // 发送告警
        alertService.sendAlert(methodName, exception);
        
        // 异常计数
        errorCounter.incrementAndGet();
    }
    
    // ============ 5. 环绕通知(Around):最强大的通知 ============
    @Around("@annotation(LogExecutionTime)")
    public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {
        String methodName = pjp.getSignature().getName();
        Object[] args = pjp.getArgs();
        
        // 前置处理
        log.info("[环绕通知-前置] 方法 {} 开始执行", methodName);
        long start = System.currentTimeMillis();
        
        Object result = null;
        try {
            // 执行目标方法(可以修改参数)
            result = pjp.proceed(args);
            
            // 后置处理(成功)
            long cost = System.currentTimeMillis() - start;
            log.info("[环绕通知-后置] 方法 {} 执行成功,耗时: {}ms", methodName, cost);
            
            // 可以修改返回值
            if (result instanceof User) {
                ((User) result).setProcessed(true);
            }
            
            return result;
        } catch (Exception e) {
            // 异常处理
            long cost = System.currentTimeMillis() - start;
            log.error("[环绕通知-异常] 方法 {} 执行失败,耗时: {}ms", methodName, cost, e);
            throw e;
        } finally {
            // 最终清理
            log.info("[环绕通知-最终] 方法 {} 执行完成", methodName);
        }
    }
    
    // ============ 通知执行顺序 ============
    // 正常情况:@Around前 → @Before → 方法执行 → @Around后 → @After → @AfterReturning
    // 异常情况:@Around前 → @Before → 方法执行(异常)→ @After → @AfterThrowing
}

项目场景:在分布式事务中,使用环绕通知实现TCC模式:前置阶段try,后置阶段confirm/cancel;在缓存系统中,使用环绕通知实现@Cacheable:方法执行前查缓存,命中则直接返回,否则执行方法并存入缓存。

切面(Aspect)—— 通知+切点的封装

一句话原理:切面是切点通知结合体,封装了横切关注点的完整定义(在哪里什么时候做什么),通常通过@Aspect注解标识,是AOP的模块化单元。

一句话源码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
/**
 * 切面概念源码示例
 */
@Aspect  // 标识这是一个切面
@Component
public class PerformanceAspect {
    
    private static final Logger log = LoggerFactory.getLogger(PerformanceAspect.class);
    
    // ============ 切面内部封装:切点 + 通知 ============
    
    // 1. 切点定义
    @Pointcut("@annotation(MonitorPerformance)")
    public void monitoredMethods() {}
    
    @Pointcut("execution(* com.example.service.*.*(..))")
    public void serviceMethods() {}
    
    // 2. 通知:使用切点
    @Around("monitoredMethods()")
    public Object monitorPerformance(ProceedingJoinPoint pjp) throws Throwable {
        long start = System.nanoTime();
        try {
            return pjp.proceed();
        } finally {
            long cost = (System.nanoTime() - start) / 1_000_000;
            log.info("性能监控 - {} 耗时: {}ms", 
                    pjp.getSignature().toShortString(), cost);
            
            // 慢方法告警
            if (cost > 1000) {
                alertService.sendSlowMethodAlert(pjp.getSignature().toString(), cost);
            }
        }
    }
    
    @Before("serviceMethods()")
    public void logServiceEntry(JoinPoint joinPoint) {
        log.info("进入服务层: {}", joinPoint.getSignature().getName());
    }
    
    // ============ 完整的切面:日志切面 ============
    @Aspect
    @Component
    @Order(1)
    public class LoggingAspect {
        
        // 切点
        @Pointcut("within(@org.springframework.stereotype.Service *)")
        public void serviceClass() {}
        
        @Pointcut("execution(* *(..))")
        public void anyMethod() {}
        
        // 通知
        @Before("serviceClass() && anyMethod()")
        public void logBefore(JoinPoint jp) {
            log.info("方法执行前: {}", jp.getSignature());
        }
        
        @AfterReturning(value = "serviceClass() && anyMethod()", returning = "result")
        public void logAfter(JoinPoint jp, Object result) {
            log.info("方法执行后: {}, 结果: {}", jp.getSignature(), result);
        }
    }
    
    // ============ 完整的切面:事务切面 ============
    @Aspect
    @Component
    @Order(2)
    public class TransactionAspect {
        
        @Pointcut("@annotation(Transactional)")
        public void transactionalMethod() {}
        
        @Around("transactionalMethod()")
        public Object manageTransaction(ProceedingJoinPoint pjp) throws Throwable {
            Transaction tx = null;
            try {
                tx = transactionManager.beginTransaction();
                Object result = pjp.proceed();
                transactionManager.commit(tx);
                return result;
            } catch (Exception e) {
                if (tx != null) {
                    transactionManager.rollback(tx);
                }
                throw e;
            }
        }
    }
    
    // ============ 多个切面的协同 ============
    // 执行顺序:@Order(1) → @Order(2) → 目标方法 → @Order(2) → @Order(1)
}

项目场景:在支付系统中,定义多个切面协同工作:日志切面记录所有操作,权限切面检查用户权限,事务切面保证数据一致性,限流切面保护系统安全,每个切面各司其职,共同构建健壮的支付服务。

完整实战指南

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
/**
 * AOP核心概念完整实战
 */
public class AOPConceptsGuide {
    
    /**
     * 1. 概念类比表
     */
    public class ConceptAnalogy {
        // 连接点:酒店的所有房间(可操作的位置)
        // 切点:VIP房间(筛选出需要服务的房间)
        // 通知:打扫卫生、送餐服务(具体做什么)
        // 切面:VIP服务套餐(在哪里+什么时候+做什么)
        
        // 切点表达式:execution(* hotel.*.VIP(..))
        // 前置通知:Before - 客人入住前准备
        // 后置通知:After - 客人退房后打扫
        // 环绕通知:Around - 全程管家服务
    }
    
    /**
     * 2. 完整示例:用户服务切面
     */
    @Aspect
    @Component
    public class UserServiceAspect {
        
        // 切点:所有public方法
        @Pointcut("execution(public * com.example.service.UserService.*(..))")
        public void userServicePublicMethods() {}
        
        // 切点:所有以get开头的方法
        @Pointcut("execution(* com.example.service.UserService.get*(..))")
        public void getterMethods() {}
        
        // 切点:所有save方法(带参数User)
        @Pointcut("execution(* com.example.service.UserService.save(..)) && args(user)")
        public void saveUserMethod(User user) {}
        
        // 前置通知:记录参数
        @Before("userServicePublicMethods()")
        public void logParams(JoinPoint jp) {
            log.info("方法: {}, 参数: {}", jp.getSignature().getName(), jp.getArgs());
        }
        
        // 环绕通知:性能监控
        @Around("getterMethods()")
        public Object monitorGetters(ProceedingJoinPoint pjp) throws Throwable {
            long start = System.nanoTime();
            try {
                return pjp.proceed();
            } finally {
                long cost = (System.nanoTime() - start) / 1_000_000;
                if (cost > 100) {
                    log.warn("慢查询: {} 耗时 {}ms", pjp.getSignature().getName(), cost);
                }
            }
        }
        
        // 返回通知:记录save结果
        @AfterReturning(value = "saveUserMethod(user)", returning = "result", argNames = "user,result")
        public void afterSave(User user, User result) {
            log.info("用户保存成功: {} -> {}", user.getName(), result.getId());
        }
        
        // 异常通知:记录save异常
        @AfterThrowing(value = "saveUserMethod(user)", throwing = "ex", argNames = "user,ex")
        public void afterSaveError(User user, Exception ex) {
            log.error("用户保存失败: {}", user.getName(), ex);
        }
    }
    
    /**
     * 3. 面试常见问题
     */
    public class InterviewQA {
        
        // Q: Spring AOP支持哪些连接点?
        // A: 只支持方法执行连接点
        
        // Q: 如何获取连接点的方法参数?
        // A: JoinPoint.getArgs()
        
        // Q: 切点表达式可以复用吗?
        // A: 可以,通过@Pointcut定义,其他通知引用
        
        // Q: 多个通知的执行顺序如何控制?
        // A: 使用@Order注解或实现Ordered接口
        
        // Q: 环绕通知和前置+后置有什么区别?
        // A: 环绕通知更强大,可以控制方法执行、修改参数和返回值
    }
    
    /**
     * 4. 调试技巧
     */
    public class DebugTips {
        // 1. 查看切面执行顺序:@Order值越小越先执行(前置)越后执行(后置)
        
        // 2. 查看代理类:-Dproxy.debug=true
        
        // 3. 断点位置:
        //    - 通知方法内部
        //    - JdkDynamicAopProxy.invoke()
        //    - CglibAopProxy.intercept()
    }
}

/**
 * AOP核心概念总结表
 * 
 * 概念        英文          定义                     类比
 * 连接点      Join Point   程序执行的特定位置         所有房间
 * 切点        Pointcut     匹配连接点的表达式         VIP房间名单
 * 通知        Advice       在连接点执行的逻辑         服务内容
 * 切面        Aspect       切点+通知的封装            VIP服务套餐
 * 织入        Weaving      将切面应用到目标对象        服务实施过程
 * 引入        Introduction 为类添加新方法/属性         额外服务
 * 目标对象    Target       被代理的原始对象            客人
 * 代理对象    Proxy        包含切面逻辑的对象           管家
 */

// 面试金句
// "AOP的四个核心概念就像'酒店服务系统':
//  连接点是'酒店的所有房间'(可提供服务的位置),
//  切点是'VIP房间的名单'(通过表达式筛选),
//  通知是'客房服务的内容'(打扫、送餐、叫醒),
//  切面是'VIP服务套餐'(把服务内容和房间绑定在一起)。
//  在支付系统中,我定义了一个'交易监控切面',
//  切点匹配所有支付相关方法,环绕通知统计耗时和成功率,
//  异常通知发送告警,后置通知记录操作日志。
//  理解这些概念才能设计出清晰可维护的切面系统"

Spring 支持哪些类型的通知(Before、After、Around 等)?执行顺序是怎样的?

五种通知类型详解

一句话原理:Spring提供五种通知类型@Before(前置)、@After(后置)、@AfterReturning(返回)、@AfterThrowing(异常)、@Around(环绕),分别对应方法执行的不同阶段,满足各种横切关注点的织入需求。

一句话源码

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
/**
 * 五种通知类型完整示例
 */
@Aspect
@Component
public class AllAdviceExample {
    
    private static final Logger log = LoggerFactory.getLogger(AllAdviceExample.class);
    
    // ============ 1. @Before:前置通知(方法执行前) ============
    @Before("execution(* com.example.service.*.*(..))")
    public void beforeAdvice(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        log.info("【@Before】方法即将执行: {}, 参数: {}", methodName, args);
        
        // 应用场景:权限校验、参数验证、记录开始日志
        // 注意:不能阻止方法执行,只能记录或抛出异常
        if (args.length > 0 && args[0] == null) {
            throw new IllegalArgumentException("参数不能为null");
        }
    }
    
    // ============ 2. @After:后置通知(方法执行后,无论成功还是异常) ============
    @After("execution(* com.example.service.*.*(..))")
    public void afterAdvice(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        log.info("【@After】方法执行完毕: {}", methodName);
        
        // 应用场景:释放资源、清理ThreadLocal、记录结束日志(不关心结果)
        ThreadLocalHolder.clear();
        resourceManager.release();
    }
    
    // ============ 3. @AfterReturning:返回通知(方法成功返回后) ============
    @AfterReturning(
        value = "execution(* com.example.service.*.*(..))",
        returning = "result"
    )
    public void afterReturningAdvice(JoinPoint joinPoint, Object result) {
        String methodName = joinPoint.getSignature().getName();
        log.info("【@AfterReturning】方法成功返回: {}, 结果: {}", methodName, result);
        
        // 应用场景:记录成功日志、数据脱敏、缓存结果
        // 注意:不能修改返回值(除非使用@Around)
        successCounter.incrementAndGet();
        
        // 对结果进行后处理(如脱敏)
        if (result instanceof User) {
            ((User) result).setPassword(null);  // 脱敏
        }
    }
    
    // ============ 4. @AfterThrowing:异常通知(方法抛出异常后) ============
    @AfterThrowing(
        value = "execution(* com.example.service.*.*(..))",
        throwing = "exception"
    )
    public void afterThrowingAdvice(JoinPoint joinPoint, Exception exception) {
        String methodName = joinPoint.getSignature().getName();
        log.error("【@AfterThrowing】方法执行异常: {}, 错误: {}", methodName, exception.getMessage(), exception);
        
        // 应用场景:异常记录、告警发送、事务回滚
        errorCounter.incrementAndGet();
        alertService.sendAlert(methodName, exception);
        
        // 不能阻止异常传播,只能记录
    }
    
    // ============ 5. @Around:环绕通知(最强大,包围方法执行) ============
    @Around("@annotation(LogExecutionTime)")
    public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {
        String methodName = pjp.getSignature().getName();
        Object[] args = pjp.getArgs();
        
        // 前置处理
        log.info("【@Around-前置】方法开始执行: {}", methodName);
        long start = System.currentTimeMillis();
        
        Object result = null;
        try {
            // 可以修改参数
            if (args.length > 0 && args[0] instanceof String) {
                args[0] = ((String) args[0]).trim();  // 参数清洗
            }
            
            // 执行目标方法
            result = pjp.proceed(args);
            
            // 后置处理(成功)
            long cost = System.currentTimeMillis() - start;
            log.info("【@Around-后置】方法执行成功: {}, 耗时: {}ms", methodName, cost);
            
            // 可以修改返回值
            if (result instanceof String) {
                result = ((String) result).toUpperCase();
            }
            
            return result;
            
        } catch (Exception e) {
            // 异常处理
            long cost = System.currentTimeMillis() - start;
            log.error("【@Around-异常】方法执行失败: {}, 耗时: {}ms", methodName, cost, e);
            throw e;  // 可以重新包装异常后抛出
        } finally {
            // 最终处理
            log.info("【@Around-最终】方法执行完成: {}", methodName);
        }
    }
}

项目场景:在支付系统中,五种通知各司其职:@Before校验支付参数和用户权限,@Around统计支付耗时和重试,@AfterReturning记录支付成功日志,@AfterThrowing发送异常告警,@After释放数据库连接,共同保障支付流程的完整性和可追踪性。

通知执行顺序源码分析

一句话原理:在同一个切面中,执行顺序为:@Around前@Before目标方法@AfterReturning/@AfterThrowing@After@Around后;在多个切面中,通过@Order控制顺序,数字小的切面优先执行前置、最后执行后置。

一句话源码

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
/**
 * 通知执行顺序源码深度解析
 */
@Aspect
@Component
@Order(1)  // 控制多个切面的执行顺序
public class OrderAdviceExample {
    
    private static final Logger log = LoggerFactory.getLogger(OrderAdviceExample.class);
    
    @Pointcut("execution(* com.example.service.*.*(..))")
    public void servicePointcut() {}
    
    // 1. 环绕通知(前置部分)
    @Around("servicePointcut()")
    public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {
        log.info("【第1步】@Around - 前置开始");
        
        try {
            // 执行目标方法(会经过其他通知)
            Object result = pjp.proceed();
            
            log.info("【第5步】@Around - 后置(成功)");
            return result;
        } catch (Exception e) {
            log.info("【第5步】@Around - 后置(异常)");
            throw e;
        } finally {
            log.info("【第6步】@Around - 最终");
        }
    }
    
    // 2. 前置通知
    @Before("servicePointcut()")
    public void beforeAdvice(JoinPoint jp) {
        log.info("【第2步】@Before - 前置通知");
    }
    
    // 3. 返回通知(成功时)
    @AfterReturning(value = "servicePointcut()", returning = "result")
    public void afterReturningAdvice(JoinPoint jp, Object result) {
        log.info("【第3步】@AfterReturning - 返回通知,结果: {}", result);
    }
    
    // 4. 异常通知(异常时)
    @AfterThrowing(value = "servicePointcut()", throwing = "ex")
    public void afterThrowingAdvice(JoinPoint jp, Exception ex) {
        log.info("【第3步】@AfterThrowing - 异常通知,错误: {}", ex.getMessage());
    }
    
    // 5. 后置通知(总是执行)
    @After("servicePointcut()")
    public void afterAdvice(JoinPoint jp) {
        log.info("【第4步】@After - 后置通知");
    }
    
    // 正常情况执行顺序:
    // 第1步:@Around前置
    // 第2步:@Before
    // 第3步:目标方法执行
    // 第4步:@AfterReturning
    // 第5步:@After
    // 第6步:@Around后置
    
    // 异常情况执行顺序:
    // 第1步:@Around前置
    // 第2步:@Before
    // 第3步:目标方法执行(抛出异常)
    // 第4步:@AfterThrowing
    // 第5步:@After
    // 第6步:@Around后置(异常分支)
}

/**
 * 多个切面执行顺序
 */
@Aspect
@Component
@Order(1)
public class FirstAspect {
    @Before("execution(* *.*(..))")
    public void before() { log.info("【切面1】@Before"); }
    
    @After("execution(* *.*(..))")
    public void after() { log.info("【切面1】@After"); }
}

@Aspect
@Component
@Order(2)
public class SecondAspect {
    @Before("execution(* *.*(..))")
    public void before() { log.info("【切面2】@Before"); }
    
    @After("execution(* *.*(..))")
    public void after() { log.info("【切面2】@After"); }
}

// 多个切面执行顺序:
// 前置通知:Order(1) → Order(2) → 目标方法
// 后置通知:Order(2) → Order(1)(反向执行)

// 源码位置:ReflectiveMethodInvocation.proceed()
public class ReflectiveMethodInvocation {
    
    public Object proceed() throws Throwable {
        // 按顺序执行拦截器链
        if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
            // 执行目标方法
            return invokeJoinpoint();
        }
        
        Object interceptorOrInterceptionAdvice = 
            this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
        
        if (interceptorOrInterceptionAdvice instanceof MethodInterceptor) {
            // 执行通知
            return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
        }
        
        // 递归调用
        return proceed();
    }
}

项目场景:在微服务架构中,多个切面协同工作:日志切面(@Order=1)记录请求信息,权限切面(@Order=2)验证用户权限,限流切面(@Order=3)检查访问频率。通过@Order控制执行顺序,确保权限验证在限流之后、日志记录贯穿始终。

完整实战:综合应用示例

一句话原理:通过一个订单处理服务,完整展示五种通知的实战应用,包括参数校验(@Before)、性能监控(@Around)、结果处理(@AfterReturning)、异常告警(@AfterThrowing)、资源清理(@After)。

一句话源码

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
/**
 * 订单服务完整切面示例
 */
@Service
public class OrderService {
    
    public Order createOrder(OrderRequest request) {
        // 核心业务逻辑
        return orderRepository.save(request.toOrder());
    }
    
    public Order getOrder(Long orderId) {
        return orderRepository.findById(orderId)
            .orElseThrow(() -> new OrderNotFoundException(orderId));
    }
}

@Aspect
@Component
public class OrderServiceAspect {
    
    private static final Logger log = LoggerFactory.getLogger(OrderServiceAspect.class);
    
    @Pointcut("within(com.example.service.OrderService)")
    public void orderServiceMethods() {}
    
    // 1. @Before:参数校验和权限检查
    @Before("orderServiceMethods() && args(request,..)")
    public void validateOrderRequest(OrderRequest request) {
        log.info("【前置通知】验证订单请求: {}", request);
        
        if (request.getAmount() == null || request.getAmount() <= 0) {
            throw new IllegalArgumentException("订单金额必须大于0");
        }
        if (request.getUserId() == null) {
            throw new IllegalArgumentException("用户ID不能为空");
        }
        
        // 权限检查
        permissionService.checkPermission("order:create");
    }
    
    // 2. @Around:性能监控和重试
    @Around("orderServiceMethods()")
    public Object monitorAndRetry(ProceedingJoinPoint pjp) throws Throwable {
        String methodName = pjp.getSignature().getName();
        int maxRetries = 3;
        int retryCount = 0;
        
        while (retryCount < maxRetries) {
            try {
                long start = System.currentTimeMillis();
                Object result = pjp.proceed();
                long cost = System.currentTimeMillis() - start;
                
                log.info("【环绕通知】方法 {} 执行成功,耗时 {}ms,重试次数: {}", 
                        methodName, cost, retryCount);
                
                // 慢方法告警
                if (cost > 1000) {
                    alertService.sendSlowMethodAlert(methodName, cost);
                }
                
                return result;
            } catch (Exception e) {
                retryCount++;
                log.warn("【环绕通知】方法 {} 执行失败,重试 {}/{}", 
                        methodName, retryCount, maxRetries);
                
                if (retryCount >= maxRetries) {
                    throw e;  // 重试耗尽,抛出异常
                }
                
                // 指数退避
                Thread.sleep(100 * (long) Math.pow(2, retryCount));
            }
        }
        return null; // 不会执行到这里
    }
    
    // 3. @AfterReturning:处理成功结果
    @AfterReturning(
        value = "orderServiceMethods() && execution(* OrderService.createOrder(..))",
        returning = "order"
    )
    public void handleOrderCreated(Order order) {
        log.info("【返回通知】订单创建成功: {}", order.getId());
        
        // 发送订单创建事件
        eventPublisher.publishEvent(new OrderCreatedEvent(order));
        
        // 更新统计
        orderStatsService.incrementCreatedCount();
    }
    
    // 4. @AfterThrowing:异常告警
    @AfterThrowing(
        value = "orderServiceMethods()",
        throwing = "exception"
    )
    public void handleOrderException(JoinPoint joinPoint, Exception exception) {
        String methodName = joinPoint.getSignature().getName();
        log.error("【异常通知】方法 {} 执行异常: {}", methodName, exception.getMessage());
        
        // 发送告警
        if (exception instanceof OrderNotFoundException) {
            // 订单不存在,记录警告
            log.warn("订单不存在: {}", exception.getMessage());
        } else {
            // 严重错误,发送告警
            alertService.sendErrorAlert(methodName, exception);
            errorCounter.incrementAndGet();
        }
    }
    
    // 5. @After:清理资源
    @After("orderServiceMethods()")
    public void cleanupResources(JoinPoint joinPoint) {
        log.debug("【后置通知】清理资源 - 方法: {}", joinPoint.getSignature().getName());
        
        // 清理ThreadLocal
        OrderContextHolder.clear();
        
        // 关闭数据库连接(如果有)
        resourceManager.releaseConnection();
    }
}

项目场景:在订单系统中,通过这组切面实现了完整的订单处理增强:前置校验确保订单数据合法,环绕监控保证性能并自动重试临时故障,返回通知触发后续流程,异常通知及时告警,后置通知确保资源释放,各通知协同工作,让核心业务代码保持简洁。

完整实战指南

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
/**
 * 通知类型完整指南
 */
public class AdviceCompleteGuide {
    
    /**
     * 1. 通知类型对比表
     */
    public class AdviceComparison {
        // 类型          执行时机               能否阻止方法   能否修改参数   能否修改返回值
        // @Before      方法执行前              ⚠️抛异常可阻止   ❌不能修改      ❌不能
        // @After       方法执行后(finally)   ❌不能          ❌不能          ❌不能
        // @AfterReturning 成功返回后           ❌不能          ❌不能          ❌不能
        // @AfterThrowing 异常抛出后            ❌不能          ❌不能          ❌不能
        // @Around      包围方法执行             ✅可以         ✅可以          ✅可以
    }
    
    /**
     * 2. 执行顺序速记口诀
     */
    public class OrderMnemonic {
        // 正常: 前(Around前 + Before)→ 方法 → 返回(AfterReturning)→ 后(After + Around后)
        // 异常: 前(Around前 + Before)→ 方法(异常)→ 异常(AfterThrowing)→ 后(After + Around后)
        // 多个切面: 前置按Order升序,后置按Order降序
        
        // 口诀:前前后后,内外反转
        // 前置通知:数值小的先执行
        // 后置通知:数值小的后执行
    }
    
    /**
     * 3. 注意事项
     */
    public class Precautions {
        // 1. @Before抛异常会阻止方法执行
        // 2. @AfterThrowing不能吞没异常
        // 3. @Around必须调用proceed()
        // 4. 多个切面用@Order控制顺序
        // 5. 同一个切面中,@Around前 → @Before → 目标 → @AfterReturning → @After → @Around后
    }
    
    /**
     * 4. 调试技巧
     */
    public class DebugTips {
        // 1. 开启AOP日志:logging.level.org.springframework.aop=DEBUG
        // 2. 查看代理类:-Dproxy.debug=true
        // 3. 断点位置:ReflectiveMethodInvocation.proceed()
        // 4. 打印调用栈:new Exception().printStackTrace()
    }
}

/**
 * 通知类型总结表
 * 
 * 通知类型      应用场景示例                     注意事项
 * @Before      参数校验、权限检查               不能修改参数,但可抛异常阻止
 * @After       资源清理、释放连接                无论成功失败都执行
 * @AfterReturning 记录成功日志、数据脱敏         不能修改返回值
 * @AfterThrowing 异常记录、发送告警             不能吞没异常
 * @Around      性能监控、缓存、重试、事务        必须调用proceed()
 * 
 * 选择原则:
 * 1. 只需要前置处理 → @Before
 * 2. 只需要后置处理 → @After
 * 3. 需要处理结果 → @AfterReturning
 * 4. 需要处理异常 → @AfterThrowing
 * 5. 需要完全控制 → @Around
 */

// 面试金句
// "五种通知类型就像'餐厅服务流程':
//  @Before是'迎宾接待'(客人进门前的准备),
//  @After是'送客打扫'(无论客人是否满意都要收拾),
//  @AfterReturning是'点餐成功后的确认'(记录点了什么菜),
//  @AfterThrowing是'菜品问题的处理'(道歉、免单),
//  @Around是'全程管家服务'(从迎宾到送客全程陪同,随时可以调整)。
//  在订单系统中,我用@Before校验支付密码,@After清理线程变量,
//  @AfterReturning发送订单创建消息,@AfterThrowing记录异常告警,
//  @Around实现超时重试理解这些通知的职责才能设计出清晰健壮的切面"

源码原理

Spring AOP 的底层实现原理是什么?(JDK 动态代理 vs CGLIB)

核心实现原理概览

一句话原理:Spring AOP的底层基于代理模式,通过JDK动态代理(针对接口)或CGLIB字节码增强(针对类)在运行时创建代理对象,将切面逻辑织入到目标方法执行前后,实现面向切面编程。

一句话源码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
/**
 * Spring AOP核心实现架构
 */
public class SpringAOPCorePrinciple {
    
    // ============ 1. 代理工厂:根据条件选择代理方式 ============
    public class ProxyFactory {
        
        public Object createProxy(Object target) {
            // 判断目标对象是否有接口
            Class<?> targetClass = target.getClass();
            Class<?>[] interfaces = targetClass.getInterfaces();
            
            if (interfaces.length > 0) {
                // 有接口:使用JDK动态代理
                return createJdkProxy(target, interfaces);
            } else {
                // 无接口:使用CGLIB代理
                return createCglibProxy(target);
            }
        }
        
        private Object createJdkProxy(Object target, Class<?>[] interfaces) {
            return Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                interfaces,
                new JdkInvocationHandler(target)
            );
        }
        
        private Object createCglibProxy(Object target) {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(target.getClass());
            enhancer.setCallback(new CglibMethodInterceptor(target));
            return enhancer.create();
        }
    }
    
    // ============ 2. Spring源码中的代理创建 ============
    // DefaultAopProxyFactory.createAopProxy()
    public AopProxy createAopProxy(AdvisedSupport config) {
        // 判断是否使用CGLIB
        if (config.isOptimize() || config.isProxyTargetClass() || 
            hasNoUserSuppliedProxyInterfaces(config)) {
            Class<?> targetClass = config.getTargetClass();
            if (targetClass.isInterface()) {
                return new JdkDynamicAopProxy(config);
            }
            return new ObjenesisCglibAopProxy(config);
        } else {
            return new JdkDynamicAopProxy(config);
        }
    }
}

项目场景:在微服务架构中,通过Spring AOP实现声明式事务。当Service类实现了接口时,Spring自动使用JDK动态代理;当Service类没有接口时,自动切换到CGLIB代理,对业务代码完全透明,开发人员只需关注@Transactional注解。

JDK动态代理深度解析

一句话原理:JDK动态代理基于反射机制,要求目标类必须实现至少一个接口,通过java.lang.reflect.ProxyInvocationHandler在运行时生成实现同样接口的代理类,将所有方法调用转发给InvocationHandler处理。

一句话源码

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
/**
 * JDK动态代理源码实现
 */
// ============ 1. 自定义InvocationHandler ============
public class JdkInvocationHandler implements InvocationHandler {
    
    private final Object target;  // 目标对象
    private final List<MethodInterceptor> interceptors;  // 切面拦截器链
    
    public JdkInvocationHandler(Object target, List<MethodInterceptor> interceptors) {
        this.target = target;
        this.interceptors = interceptors;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 1. 排除Object类的方法(如toString、hashCode等)
        if (method.getDeclaringClass() == Object.class) {
            return method.invoke(target, args);
        }
        
        // 2. 创建拦截器链
        List<MethodInterceptor> chain = getInterceptors(method);
        
        // 3. 创建方法调用对象
        MethodInvocation invocation = new ReflectiveMethodInvocation(
            target, method, args, chain);
        
        // 4. 执行拦截器链
        return invocation.proceed();
    }
    
    private List<MethodInterceptor> getInterceptors(Method method) {
        // 根据方法匹配切面
        return interceptors.stream()
            .filter(interceptor -> interceptor.matches(method))
            .collect(Collectors.toList());
    }
}

// ============ 2. 方法调用链执行 ============
public class ReflectiveMethodInvocation implements MethodInvocation {
    
    private final Object target;
    private final Method method;
    private final Object[] args;
    private final List<MethodInterceptor> interceptors;
    private int currentIndex = -1;
    
    @Override
    public Object proceed() throws Throwable {
        // 所有拦截器执行完毕,调用目标方法
        if (currentIndex == interceptors.size() - 1) {
            return method.invoke(target, args);
        }
        
        // 执行下一个拦截器
        MethodInterceptor interceptor = interceptors.get(++currentIndex);
        return interceptor.invoke(this);
    }
}

// ============ 3. Spring源码中的JDK动态代理 ============
final class JdkDynamicAopProxy implements AopProxy, InvocationHandler {
    
    @Override
    public Object getProxy() {
        return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 获取拦截器链
        List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
        
        if (chain.isEmpty()) {
            // 无切面,直接调用
            return method.invoke(target, args);
        } else {
            // 创建方法调用并执行
            MethodInvocation invocation = new ReflectiveMethodInvocation(proxy, target, method, args, chain);
            return invocation.proceed();
        }
    }
}

// ============ 4. 生成的代理类结构 ============
// 运行时生成的代理类(伪代码)
public final class $Proxy0 extends Proxy implements UserService {
    
    private static Method m1, m2, m3;
    
    public $Proxy0(InvocationHandler h) {
        super(h);
    }
    
    public User getUser(Long id) {
        try {
            // 调用InvocationHandler的invoke方法
            return (User) super.h.invoke(this, m3, new Object[]{id});
        } catch (RuntimeException e) {
            throw e;
        } catch (Error e) {
            throw e;
        } catch (Throwable e) {
            throw new UndeclaredThrowableException(e);
        }
    }
}

项目场景:在RPC框架中,服务消费端通过JDK动态代理创建远程服务的本地代理,所有方法调用都会被InvocationHandler拦截,封装成网络请求发送到服务提供端,实现透明化的远程调用。

CGLIB动态代理深度解析

一句话原理:CGLIB基于字节码增强技术,通过继承目标类生成子类作为代理,利用ASM字节码框架动态生成子类字节码,重写非final的方法,在方法调用时通过MethodInterceptor拦截增强。

一句话源码

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
/**
 * CGLIB动态代理源码实现
 */
// ============ 1. 自定义MethodInterceptor ============
public class CglibMethodInterceptor implements MethodInterceptor {
    
    private final Object target;  // 目标对象
    private final List<MethodInterceptor> interceptors;  // 切面拦截器链
    
    public CglibMethodInterceptor(Object target, List<MethodInterceptor> interceptors) {
        this.target = target;
        this.interceptors = interceptors;
    }
    
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        // 1. 排除Object类的方法
        if (method.getDeclaringClass() == Object.class) {
            return proxy.invokeSuper(obj, args);
        }
        
        // 2. 创建拦截器链
        List<MethodInterceptor> chain = getInterceptors(method);
        
        if (chain.isEmpty()) {
            // 无切面,直接调用父类方法
            return proxy.invokeSuper(obj, args);
        }
        
        // 3. 创建CGLIB方法调用对象
        CglibMethodInvocation invocation = new CglibMethodInvocation(
            proxy, target, obj, method, args, chain);
        
        // 4. 执行拦截器链
        return invocation.proceed();
    }
}

// ============ 2. CGLIB特有的方法调用 ============
public class CglibMethodInvocation extends ReflectiveMethodInvocation {
    
    private final MethodProxy methodProxy;
    private final Object proxyObject;
    
    @Override
    public Object proceed() throws Throwable {
        if (this.currentIndex == this.interceptors.size() - 1) {
            // 最后调用父类方法(不是invokeSuper会死循环)
            return methodProxy.invokeSuper(proxyObject, arguments);
        }
        
        MethodInterceptor interceptor = this.interceptors.get(++this.currentIndex);
        return interceptor.invoke(this);
    }
}

// ============ 3. Spring源码中的CGLIB代理 ============
class CglibAopProxy implements AopProxy {
    
    @Override
    public Object getProxy() {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(this.advised.getTargetClass());
        enhancer.setInterfaces(this.advised.getProxiedInterfaces());
        enhancer.setCallback(new DynamicAdvisedInterceptor(this.advised));
        return enhancer.create();
    }
    
    private static class DynamicAdvisedInterceptor implements MethodInterceptor {
        
        @Override
        public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) {
            // 获取拦截器链
            List<Object> chain = advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
            
            if (chain.isEmpty()) {
                // 无切面,直接调用父类方法
                return methodProxy.invokeSuper(proxy, args);
            } else {
                // 创建CglibMethodInvocation执行
                MethodInvocation invocation = new CglibMethodInvocation(proxy, target, method, args, chain);
                return invocation.proceed();
            }
        }
    }
}

// ============ 4. CGLIB生成的代理类结构 ============
// 运行时生成的代理类(伪代码)
public class UserService$$EnhancerByCGLIB extends UserService implements Factory {
    
    private MethodInterceptor callback;
    private static Method m1, m2;
    private static MethodProxy mp1, mp2;
    
    public User getUser(Long id) {
        // 调用拦截器
        return (User) callback.intercept(this, m1, new Object[]{id}, mp1);
    }
}

项目场景:在MyBatis-Spring集成中,Mapper接口没有实现类,通过JDK动态代理创建实现类;而某些Service类没有接口,则通过CGLIB代理创建子类,两种代理方式共同支撑了整个持久层框架的运行。

两种代理方式对比与选择

一句话原理:JDK动态代理要求目标类实现接口,基于反射调用,性能相对较低;CGLIB通过继承生成子类,可代理无接口的类,基于字节码增强性能更高,但不能代理final方法final类

一句话源码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
/**
 * 两种代理方式详细对比
 */
public class ProxyComparison {
    
    // ============ 1. 对比表格 ============
    public class ComparisonTable {
        // 维度            JDK动态代理                  CGLIB动态代理
        // 实现原理         反射 + 接口                 字节码增强 + 继承
        // 目标要求         必须实现接口                 无接口要求
        // 性能           较低(反射开销)              较高(字节码调用)
        // 限制           只能代理接口方法              不能代理final方法/类
        // 生成速度         快                          慢(生成字节码)
        // 调用速度         慢(反射)                   快(直接调用)
        // 调试难度         容易                        较难(生成类)
    }
    
    // ============ 2. Spring的选择策略 ============
    @Configuration
    @EnableAspectJAutoProxy(proxyTargetClass = true)  // 强制使用CGLIB
    public class AopConfig {
        // proxyTargetClass = true: 强制使用CGLIB
        // proxyTargetClass = false: 默认策略(有接口用JDK,否则CGLIB)
    }
    
    // ============ 3. 性能测试代码 ============
    public class PerformanceTest {
        
        public void testPerformance() {
            // JDK动态代理:每次调用约 50-100ns(反射)
            // CGLIB代理:每次调用约 20-50ns(直接调用)
            
            // 1000万次调用,JDK比CGLIB慢约2-3倍
        }
    }
    
    // ============ 4. 常见问题 ============
    public class CommonIssues {
        
        // Q: 为什么Spring推荐使用CGLIB?
        // A: 1. 不需要接口,更灵活
        //    2. 性能更好
        //    3. 避免类型转换问题
        
        // Q: CGLIB不能代理final方法怎么办?
        // A: 避免将切面方法声明为final
        
        // Q: 如何强制使用CGLIB?
        // A: @EnableAspectJAutoProxy(proxyTargetClass = true)
    }
}

// ============ 5. 验证当前使用的代理方式 ============
@Component
public class ProxyTypeVerifier {
    
    @Autowired
    private ApplicationContext context;
    
    @PostConstruct
    public void verify() {
        Object userService = context.getBean("userService");
        
        if (AopUtils.isJdkDynamicProxy(userService)) {
            System.out.println("使用JDK动态代理");
            System.out.println("代理接口: " + Arrays.toString(userService.getClass().getInterfaces()));
        } else if (AopUtils.isCglibProxy(userService)) {
            System.out.println("使用CGLIB代理");
            System.out.println("代理类: " + userService.getClass().getName());
        }
    }
}

项目场景:在大型项目中,通常统一配置proxyTargetClass = true强制使用CGLIB,避免因某些类没有接口而导致的代理失败,同时获得更好的性能。特别是在Spring Boot 2.0之后,默认配置已经改为CGLIB优先。

完整实战指南

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
/**
 * Spring AOP底层实现完整指南
 */
public class AOPImplementationGuide {
    
    /**
     * 1. 手动实现JDK动态代理
     */
    public class JdkProxyDemo {
        
        // 目标接口
        public interface UserService {
            User getUser(Long id);
        }
        
        // 目标类
        public class UserServiceImpl implements UserService {
            @Override
            public User getUser(Long id) {
                return new User(id, "test");
            }
        }
        
        // 手动创建代理
        public UserService createProxy(UserService target) {
            return (UserService) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                (proxy, method, args) -> {
                    System.out.println("前置处理");
                    Object result = method.invoke(target, args);
                    System.out.println("后置处理");
                    return result;
                }
            );
        }
    }
    
    /**
     * 2. 手动实现CGLIB代理
     */
    public class CglibProxyDemo {
        
        // 目标类(无接口)
        public class UserService {
            public User getUser(Long id) {
                return new User(id, "test");
            }
        }
        
        // 手动创建代理
        public UserService createProxy(UserService target) {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(UserService.class);
            enhancer.setCallback(new MethodInterceptor() {
                @Override
                public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
                    System.out.println("前置处理");
                    Object result = proxy.invokeSuper(obj, args);
                    System.out.println("后置处理");
                    return result;
                }
            });
            return (UserService) enhancer.create();
        }
    }
    
    /**
     * 3. 选择策略实战
     */
    @Configuration
    public class ProxyStrategyConfig {
        
        // 场景1:默认策略(JDK优先)
        @EnableAspectJAutoProxy
        public class DefaultStrategy {}
        
        // 场景2:强制CGLIB(推荐)
        @EnableAspectJAutoProxy(proxyTargetClass = true)
        public class CglibStrategy {}
        
        // 场景3:自定义策略
        @Bean
        public DefaultAopProxyFactory aopProxyFactory() {
            return new DefaultAopProxyFactory() {
                @Override
                public AopProxy createAopProxy(AdvisedSupport config) {
                    // 自定义代理选择逻辑
                    if (config.getTargetClass().isInterface()) {
                        return new JdkDynamicAopProxy(config);
                    }
                    return new ObjenesisCglibAopProxy(config);
                }
            };
        }
    }
    
    /**
     * 4. 面试常见问题
     */
    public class InterviewQA {
        
        // Q: Spring AOP为什么默认使用JDK动态代理?
        // A: 历史原因,JDK是原生API,无需引入第三方库
        
        // Q: CGLIB代理的性能为什么比JDK高?
        // A: JDK使用反射调用,CGLIB使用FastClass机制直接调用
        
        // Q: 代理对象中的this指向什么?
        // A: this指向代理对象,内部调用不会触发切面
        
        // Q: 如何解决内部调用不触发切面的问题?
        // A: 通过AopContext.currentProxy()获取当前代理对象
    }
    
    /**
     * 5. 调试技巧
     */
    public class DebugTips {
        // 1. 查看代理类:-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true
        // 2. 查看CGLIB生成类:-Dcglib.debugLocation=/tmp/cglib
        // 3. 断点位置:JdkDynamicAopProxy.invoke() / CglibAopProxy.intercept()
        // 4. 日志配置:logging.level.org.springframework.aop=DEBUG
    }
}

/**
 * 代理方式总结表
 * 
 * 特性          JDK动态代理                      CGLIB动态代理
 * 实现方式       Proxy + InvocationHandler        Enhancer + MethodInterceptor
 * 核心类        java.lang.reflect.Proxy          net.sf.cglib.proxy.Enhancer
 * 字节码生成     ProxyGenerator                    ASM
 * 性能         反射调用                           FastClass调用
 * 限制         必须有接口                        不能代理final
 * 
 * 选择建议:
 * 1. 新项目优先CGLIB(proxyTargetClass=true)
 * 2. 必须保留接口时用JDK
 * 3. 性能敏感场景用CGLIB
 */

// 面试金句
// "Spring AOP的底层实现就像'房产中介'的两种服务模式:
//  JDK动态代理是'房屋管家'(必须持有房东委托书,只能代理有委托的房源),
//  CGLIB是'房产开发商'(直接收购房产然后改造,可以代理任何房源)。
//  在Spring Boot 2.0后,默认选择了'房产开发商'模式(CGLIB),
//  因为它更灵活、性能更好。理解这个区别,就能明白为什么有时候
//  强制使用CGLIB可以避免一些诡异的代理问题"

JDK 动态代理和 CGLIB 的区别?各自的使用场景和限制?

核心原理本质区别

一句话原理:JDK动态代理基于接口,通过反射机制生成实现同样接口的代理类;CGLIB基于继承,通过字节码增强生成目标类的子类作为代理,两者在"必须面向接口"与"可直接代理类"上存在本质区别。

一句话源码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
/**
 * 两种代理方式的源码级对比
 */
// ============ JDK动态代理:必须基于接口 ============
public class JdkProxyExample {
    
    // 必须定义接口
    public interface UserService {
        User getUser(Long id);
        void saveUser(User user);
    }
    
    // 目标类实现接口
    public class UserServiceImpl implements UserService {
        @Override
        public User getUser(Long id) {
            return new User(id, "test");
        }
        
        @Override
        public void saveUser(User user) {
            System.out.println("保存用户: " + user);
        }
    }
    
    // JDK动态代理实现
    public UserService createJdkProxy(UserService target) {
        return (UserService) Proxy.newProxyInstance(
            target.getClass().getClassLoader(),
            target.getClass().getInterfaces(),  // 必须传入接口
            new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    System.out.println("JDK代理 - 前置处理");
                    Object result = method.invoke(target, args);
                    System.out.println("JDK代理 - 后置处理");
                    return result;
                }
            }
        );
    }
}

// ============ CGLIB:基于继承,无需接口 ============
public class CglibProxyExample {
    
    // 目标类可以没有接口
    public class UserService {
        public User getUser(Long id) {
            return new User(id, "test");
        }
        
        public void saveUser(User user) {
            System.out.println("保存用户: " + user);
        }
        
        public final void finalMethod() {
            System.out.println("final方法无法被代理");
        }
    }
    
    // CGLIB代理实现
    public UserService createCglibProxy(UserService target) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(UserService.class);  // 设置父类
        enhancer.setCallback(new MethodInterceptor() {
            @Override
            public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
                System.out.println("CGLIB代理 - 前置处理");
                // 调用父类方法(注意不是invokeSuper会死循环)
                Object result = proxy.invokeSuper(obj, args);
                System.out.println("CGLIB代理 - 后置处理");
                return result;
            }
        });
        return (UserService) enhancer.create();
    }
}

项目场景:在MyBatis中,Mapper接口通过JDK动态代理实现;而在Hibernate中,实体类可以通过CGLIB实现懒加载代理。两种技术各司其职,满足了不同框架的需求。

详细对比分析

一句话原理:两种代理在实现方式(接口 vs 继承)、性能(反射 vs FastClass)、限制(final方法/类)、使用场景(面向接口编程 vs 遗留系统)四个维度有显著差异。

一句话源码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
/**
 * 两种代理方式全方位对比
 */
public class ProxyComparison {
    
    // ============ 1. 实现机制对比 ============
    public class Implementation {
        // JDK动态代理:
        // - 运行时生成实现指定接口的代理类
        // - 代理类继承Proxy并实现业务接口
        // - 通过InvocationHandler转发调用
        
        // CGLIB代理:
        // - 运行时生成目标类的子类
        // - 通过继承重写非final方法
        // - 通过MethodInterceptor拦截调用
        // - 使用FastClass机制避免反射
    }
    
    // ============ 2. 性能对比 ============
    public class Performance {
        // JDK动态代理:
        // - 方法调用通过反射,有性能开销
        // - 每次调用都经过反射解析
        // - 适合低频调用场景
        
        // CGLIB代理:
        // - 使用FastClass直接调用方法
        // - 类似原生方法调用
        // - 比JDK快2-3倍
        // - 适合高频调用场景
        
        // 测试数据(1000万次调用):
        // JDK: ~500ms
        // CGLIB: ~200ms
    }
    
    // ============ 3. 限制条件对比 ============
    public class Limitations {
        // JDK动态代理限制:
        // 1. 目标类必须实现至少一个接口
        // 2. 只能代理接口中定义的方法
        // 3. 接口方法必须是public
        
        // CGLIB代理限制:
        // 1. 不能代理final类
        // 2. 不能代理final方法
        // 3. 不能代理private方法
        // 4. 需要CGLIB依赖
    }
    
    // ============ 4. 字节码生成对比 ============
    public class BytecodeGeneration {
        // JDK生成的代理类(伪代码):
        // public final class $Proxy0 extends Proxy implements UserService {
        //     private static Method m3;
        //     public User getUser(Long id) {
        //         return (User) super.h.invoke(this, m3, new Object[]{id});
        //     }
        // }
        
        // CGLIB生成的代理类(伪代码):
        // public class UserService$$EnhancerByCGLIB extends UserService {
        //     private MethodInterceptor callback;
        //     public User getUser(Long id) {
        //         return (User) callback.intercept(this, getUserMethod, 
        //                 new Object[]{id}, getUserMethodProxy);
        //     }
        // }
    }
    
    // ============ 5. 完整对比表 ============
    public class ComparisonTable {
        // 维度            JDK动态代理                     CGLIB
        // 实现原理         反射 + 接口                    字节码 + 继承
        // 目标要求         必须实现接口                    无接口要求
        // 生成类           Proxy的子类                     目标类的子类
        // 方法调用         反射调用                         FastClass调用
        // 性能            较低                             较高
        // 依赖             JDK原生                         需要CGLIB库
        // final限制        无                              不能代理final
        // 调试难度         容易                            较难
        // 代理范围         接口方法                         所有非final方法
    }
}

项目场景:在高并发交易系统中,对性能要求极高,强制使用CGLIB代理(proxyTargetClass=true),避免JDK动态代理的反射开销;而在某些需要兼容多接口的场景,使用JDK动态代理更灵活。

使用场景与选择策略

一句话原理:JDK动态代理适用于面向接口编程已有接口定义需要多接口代理的场景;CGLIB适用于无接口实现性能敏感遗留系统改造的场景,Spring默认根据目标类型自动选择。

一句话源码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
/**
 * 使用场景与选择策略实战
 */
@Configuration
public class ProxySelectionGuide {
    
    // ============ 场景1:JDK动态代理适用场景 ============
    public class JdkProxyScenarios {
        
        // 1. 接口定义清晰的服务
        public interface PaymentService {
            void pay(Order order);
            void refund(Order order);
        }
        
        @Service
        public class AlipayService implements PaymentService {
            // 实现类
        }
        
        // 2. 多接口代理(JDK支持多个接口)
        public interface Auditable {
            void setAuditInfo(String info);
        }
        
        @Service
        public class OrderService implements PaymentService, Auditable {
            // 实现两个接口
        }
        
        // 3. RPC框架中的服务接口
        // Dubbo、Feign等都基于接口生成代理
    }
    
    // ============ 场景2:CGLIB代理适用场景 ============
    public class CglibProxyScenarios {
        
        // 1. 没有接口的业务类
        @Service
        public class UserService {  // 没有接口
            public User getUser(Long id) {
                return userRepository.findById(id);
            }
        }
        
        // 2. 遗留系统改造(没有接口)
        public class LegacyOrderProcessor {
            public void processOrder(Order order) {
                // 旧系统代码
            }
        }
        
        // 3. 性能敏感的核心交易
        @Configuration
        @EnableAspectJAutoProxy(proxyTargetClass = true)
        public class HighPerformanceConfig {
            // 强制使用CGLIB
        }
        
        // 4. 需要代理public以外的方法(CGLIB支持protected)
        public class ProtectedMethodService {
            protected void doInternal() {
                // 可以被CGLIB代理
            }
        }
    }
    
    // ============ 场景3:混合使用 ============
    @Configuration
    public class MixedProxyConfig {
        
        // 方式1:使用Spring默认策略(推荐)
        @EnableAspectJAutoProxy
        public class DefaultStrategy {
            // 有接口用JDK,无接口用CGLIB
        }
        
        // 方式2:全局强制CGLIB(Spring Boot 2.0+默认)
        @EnableAspectJAutoProxy(proxyTargetClass = true)
        public class ForceCglibStrategy {
            // 所有Bean都用CGLIB
        }
        
        // 方式3:按需选择
        @Bean
        @Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)  // 强制CGLIB
        public ShoppingCart shoppingCart() {
            return new ShoppingCart();
        }
        
        @Bean
        @Scope(proxyMode = ScopedProxyMode.INTERFACES)    // 强制JDK
        public RequestContext requestContext() {
            return new RequestContext();
        }
    }
}

项目场景:在Spring Boot 2.0后,默认开启proxyTargetClass=true,因为大部分业务类都没有接口,强制使用CGLIB可以避免因漏掉接口导致的代理失败。但在某些需要JDK代理特性的场景(如多接口代理),可以局部调整。

完整实战指南

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
/**
 * 代理方式选择完整指南
 */
public class ProxyCompleteGuide {
    
    /**
     * 1. 如何强制使用特定代理
     */
    @Configuration
    public class ForceProxyConfig {
        
        // 方式1:全局配置
        // application.yml
        // spring:
        //   aop:
        //     proxy-target-class: true  # 强制CGLIB
        
        // 方式2:注解配置
        @EnableAspectJAutoProxy(proxyTargetClass = true)
        public class AppConfig {}
        
        // 方式3:编程式配置
        @Bean
        public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
            DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
            creator.setProxyTargetClass(true);  // 强制CGLIB
            return creator;
        }
    }
    
    /**
     * 2. 验证当前使用的代理类型
     */
    @Component
    public class ProxyTypeChecker {
        
        @Autowired
        private ApplicationContext context;
        
        @PostConstruct
        public void checkProxyType() {
            String[] beanNames = context.getBeanDefinitionNames();
            
            for (String beanName : beanNames) {
                Object bean = context.getBean(beanName);
                
                if (AopUtils.isAopProxy(bean)) {
                    String proxyType = "";
                    if (AopUtils.isJdkDynamicProxy(bean)) {
                        proxyType = "JDK动态代理";
                    } else if (AopUtils.isCglibProxy(bean)) {
                        proxyType = "CGLIB代理";
                    }
                    
                    System.out.println(String.format("Bean: %s, 代理类型: %s", 
                        beanName, proxyType));
                }
            }
        }
    }
    
    /**
     * 3. 常见问题解决方案
     */
    public class CommonProblems {
        
        // 问题1:CGLIB代理无法注入
        // 解决:添加cglib依赖
        // <dependency>
        //     <groupId>cglib</groupId>
        //     <artifactId>cglib</artifactId>
        //     <version>3.3.0</version>
        // </dependency>
        
        // 问题2:内部方法调用不触发代理
        @Service
        public class UserService {
            
            @Transactional
            public void methodA() {
                methodB();  // 不会触发事务!因为this是原始对象
            }
            
            @Transactional
            public void methodB() {
                // 事务逻辑
            }
            
            // 解决方案:通过AopContext获取当前代理
            public void methodA_solution() {
                ((UserService) AopContext.currentProxy()).methodB();
            }
        }
        
        // 问题3:final方法无法被CGLIB代理
        public class FinalMethodService {
            public final void cannotProxy() {
                // 不会生效
            }
        }
        
        // 问题4:类转换异常
        // 当使用CGLIB代理时,不能直接强制转换为目标类
        UserService service = context.getBean(UserService.class);  // 正确
        // UserService service = (UserService) context.getBean("userService");  // 可能出错
    }
    
    /**
     * 4. 性能测试代码
     */
    public class PerformanceTest {
        
        public void benchmark() {
            int times = 10_000_000;
            
            // JDK代理测试
            UserService jdkProxy = createJdkProxy();
            long start = System.nanoTime();
            for (int i = 0; i < times; i++) {
                jdkProxy.getUser(1L);
            }
            long jdkCost = (System.nanoTime() - start) / 1_000_000;
            
            // CGLIB代理测试
            UserService cglibProxy = createCglibProxy();
            start = System.nanoTime();
            for (int i = 0; i < times; i++) {
                cglibProxy.getUser(1L);
            }
            long cglibCost = (System.nanoTime() - start) / 1_000_000;
            
            System.out.println("JDK耗时: " + jdkCost + "ms");
            System.out.println("CGLIB耗时: " + cglibCost + "ms");
        }
    }
    
    /**
     * 5. 面试必备
     */
    public class InterviewEssentials {
        
        // Q: 为什么CGLIB比JDK动态代理快?
        // A: 1. FastClass机制避免反射
        //    2. 直接调用子类方法
        //    3. 字节码层面优化
        
        // Q: Spring如何选择代理方式?
        // A: 根据proxyTargetClass配置和类是否有接口
        
        // Q: 什么情况下必须用CGLIB?
        // A: 1. 目标类没有接口
        //    2. 需要代理protected方法
        //    3. 性能要求极高
        
        // Q: 什么情况下只能用JDK代理?
        // A: 1. 需要代理多个接口
        //    2. 目标类是final类
        //    3. 无法添加CGLIB依赖
    }
}

/**
 * 代理方式选择总结表
 * 
 * 场景                         推荐代理                    原因
 * 面向接口设计                   JDK                     语义清晰
 * 无接口的业务类                 CGLIB                    必需
 * 高并发核心交易                 CGLIB                    性能优先
 * 需要多接口代理                  JDK                     JDK支持多接口
 * 遗留系统改造                    CGLIB                    不需要修改代码
 * 框架底层(如MyBatis)          JDK                     接口驱动
 * 实体类懒加载                    CGLIB                    需要继承
 * 
 * 配置建议:
 * - Spring Boot 2.0+:保持默认CGLIB
 * - 老项目迁移:根据情况选择
 * - 混合场景:按需配置proxyMode
 */

// 面试金句
// "JDK动态代理和CGLIB就像'两种不同的工作方式':
//  JDK是'外包工'(必须按合同接口办事,干活靠反射),
//  CGLIB是'继承家业'(可以直接接手家族企业,干活更直接)。
//  在项目中,对于有清晰接口定义的服务(如RPC接口),
//  JDK代理既清晰又标准;对于内部业务类(如Service实现),
//  强制使用CGLIB可以避免因忘记接口导致的代理失败,性能还更好。
//  Spring Boot 2.0后默认用CGLIB正是看中了它的通用性和性能优势"

Spring 在什么情况下使用 JDK 动态代理,什么情况下使用 CGLIB?

核心选择原理

一句话原理:Spring根据目标类是否实现接口proxyTargetClass配置决定代理方式:默认策略下,目标类有接口用JDK动态代理,无接口用CGLIB;若设置proxyTargetClass=true,则强制使用CGLIB代理所有Bean。

一句话源码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/**
 * Spring代理选择核心源码
 */
public class DefaultAopProxyFactory implements AopProxyFactory {
    
    @Override
    public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
        // 判断是否强制使用CGLIB
        if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
            Class<?> targetClass = config.getTargetClass();
            if (targetClass == null) {
                throw new AopConfigException("TargetSource cannot determine target class");
            }
            
            // 如果目标类是接口,只能用JDK
            if (targetClass.isInterface()) {
                return new JdkDynamicAopProxy(config);
            }
            
            // 否则使用CGLIB
            return new ObjenesisCglibAopProxy(config);
        } else {
            // 默认情况:使用JDK动态代理
            return new JdkDynamicAopProxy(config);
        }
    }
    
    /**
     * 判断是否有用户提供的代理接口
     */
    private boolean hasNoUserSuppliedProxyInterfaces(AdvisedSupport config) {
        Class<?>[] ifcs = config.getProxiedInterfaces();
        return (ifcs.length == 0 || (ifcs.length == 1 && 
                SpringProxy.class.isAssignableFrom(ifcs[0])));
    }
}

// 配置开关
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)  // 强制使用CGLIB
public class AppConfig {
    // proxyTargetClass = true 强制CGLIB
    // proxyTargetClass = false 默认策略(有接口JDK,无接口CGLIB)
}

项目场景:在Spring Boot 2.0+项目中,默认开启了spring.aop.proxy-target-class=true,强制使用CGLIB代理。这是因为大多数业务Service都没有接口,如果使用默认策略会导致部分类用JDK、部分用CGLIB,容易引发类型转换异常。

使用JDK动态代理的三种情况

一句话原理:Spring在以下三种情况使用JDK动态代理:目标类有接口且未强制CGLIB目标类本身就是接口需要代理多个接口,这些场景下JDK的接口代理机制更合适。

一句话源码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
/**
 * JDK动态代理使用场景源码示例
 */
@Configuration
public class JdkProxyScenarios {
    
    // ============ 场景1:目标类有接口且未强制CGLIB ============
    public interface UserService {
        User getUser(Long id);
    }
    
    @Service
    public class UserServiceImpl implements UserService {
        @Override
        public User getUser(Long id) {
            return new User(id);
        }
    }
    
    // 默认配置(无@EnableAspectJAutoProxy)
    // 或 @EnableAspectJAutoProxy(proxyTargetClass = false)
    // UserServiceImpl有接口,使用JDK动态代理
    
    // ============ 场景2:目标类本身就是接口 ============
    @Configuration
    public class InterfaceProxyConfig {
        
        @Bean
        public UserService userService() {
            // 返回动态代理,目标对象就是接口
            return (UserService) Proxy.newProxyInstance(
                getClass().getClassLoader(),
                new Class[]{UserService.class},
                (proxy, method, args) -> {
                    // 接口代理逻辑
                    return null;
                }
            );
        }
    }
    
    // ============ 场景3:需要代理多个接口 ============
    public interface Auditable {
        void setAuditInfo(String info);
    }
    
    @Service
    public class OrderService implements UserService, Auditable {
        // 实现两个接口
    }
    
    // JDK动态代理天然支持多接口
    // CGLIB虽然也可以实现接口,但JDK更简洁
    
    // ============ 源码验证 ============
    public class JdkProxyVerifier {
        
        @Autowired
        private ApplicationContext context;
        
        public void checkProxyType() {
            UserService userService = context.getBean(UserService.class);
            
            // 验证是否是JDK代理
            boolean isJdkProxy = AopUtils.isJdkDynamicProxy(userService);
            System.out.println("是否JDK代理: " + isJdkProxy);
            
            // JDK代理类名特征:$Proxy
            System.out.println("代理类名: " + userService.getClass().getName());
            // 输出示例: com.sun.proxy.$Proxy38
        }
    }
}

项目场景:在Dubbo服务消费端,所有远程服务都是基于接口的,使用JDK动态代理生成服务代理类,天然适配RPC框架的接口驱动设计。同时在需要实现多接口的场景(如同时实现Service接口和Callback接口),JDK代理更简洁。

使用CGLIB代理的四种情况

一句话原理:Spring在以下情况使用CGLIB:强制配置proxyTargetClass=true目标类无接口目标类有接口但需要代理非接口方法需要代理protected方法,CGLIB通过继承实现更全面的代理能力。

一句话源码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
/**
 * CGLIB代理使用场景源码示例
 */
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)  // 强制CGLIB
public class CglibProxyScenarios {
    
    // ============ 场景1:强制CGLIB配置 ============
    // 上面配置已经强制使用CGLIB,即使有接口也用CGLIB
    
    // ============ 场景2:目标类无接口 ============
    @Service
    public class ProductService {  // 没有实现任何接口
        public Product getProduct(Long id) {
            return new Product(id);
        }
        
        protected void internalMethod() {
            // protected方法可以被CGLIB代理
        }
    }
    // 即使不强制CGLIB,无接口也会自动使用CGLIB
    
    // ============ 场景3:需要代理非接口方法 ============
    public interface OrderService {
        void createOrder(Order order);
    }
    
    @Service
    public class OrderServiceImpl implements OrderService {
        
        @Override
        public void createOrder(Order order) {
            // 接口方法
            validateOrder(order);  // 调用非接口方法
        }
        
        @Transactional
        public void validateOrder(Order order) {
            // 非接口方法,但需要事务增强
            // 如果使用JDK代理,这个方法不会被代理!
        }
    }
    
    // 解决方案:强制使用CGLIB,可以代理validateOrder方法
    
    // ============ 场景4:需要代理protected方法 ============
    @Service
    public class ProtectedMethodService {
        
        @MonitorPerformance
        protected void monitoredMethod() {
            // protected方法,只有CGLIB能代理
        }
    }
    
    // ============ 源码验证 ============
    public class CglibProxyVerifier {
        
        @Autowired
        private ApplicationContext context;
        
        public void checkProxyType() {
            ProductService productService = context.getBean(ProductService.class);
            
            // 验证是否是CGLIB代理
            boolean isCglibProxy = AopUtils.isCglibProxy(productService);
            System.out.println("是否CGLIB代理: " + isCglibProxy);
            
            // CGLIB代理类名特征:$$EnhancerByCGLIB$$
            System.out.println("代理类名: " + productService.getClass().getName());
            // 输出示例: com.example.ProductService$$EnhancerBySpringCGLIB$$8a1b2c3d
        }
    }
}

项目场景:在Spring Security中,需要对方法级别的权限进行控制,很多安全注解(如@PreAuthorize)可能标注在非接口方法上,必须使用CGLIB代理才能生效。同样在事务管理中,如果@Transactional标注在非接口方法上,也必须使用CGLIB。

Spring Boot的默认策略演进

一句话原理:Spring Boot 1.x默认使用JDK代理(有接口时),Spring Boot 2.0+改为默认使用CGLIBspring.aop.proxy-target-class=true),解决了类型转换问题和内部方法代理问题,成为业界最佳实践。

一句话源码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/**
 * Spring Boot代理配置演进
 */
// ============ Spring Boot 1.x默认配置 ============
// application.properties
spring.aop.proxy-target-class=false  // 默认值,有接口用JDK

// ============ Spring Boot 2.0+默认配置 ============
// application.properties
spring.aop.proxy-target-class=true   // 默认值,强制CGLIB

// ============ Spring Boot源码中的默认配置 ============
@ConfigurationProperties(prefix = "spring.aop")
public class AopProperties {
    
    /**
     * Whether subclass-based (CGLIB) proxies are to be created (true), as
     * opposed to standard Java interface-based proxies (false).
     */
    private boolean proxyTargetClass = true;  // Spring Boot 2.0+默认为true
}

// ============ 推荐配置 ============
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)  // 显式声明,增强可读性
public class AppConfig {
    // 推荐始终使用CGLIB,避免困惑
}

// ============ 特殊情况:局部调整 ============
@Configuration
public class MixedProxyConfig {
    
    @Bean
    @Scope(proxyMode = ScopedProxyMode.INTERFACES)  // 这个Bean强制用JDK
    public RequestContext requestContext() {
        return new RequestContext();
    }
    
    @Bean
    @Scope(proxyMode = ScopedProxyMode.TARGET_CLASS) // 这个Bean强制用CGLIB
    public ShoppingCart shoppingCart() {
        return new ShoppingCart();
    }
}

项目场景:在升级Spring Boot版本时,如果项目中有依赖JDK代理特性的代码(如直接强制类型转换为实现类),可能会出现问题。需要在升级前检查代码,统一使用接口注入或接受CGLIB代理的转换方式。

完整实战指南

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
/**
 * Spring代理选择完整指南
 */
public class ProxySelectionGuide {
    
    /**
     * 1. 决策树
     */
    public class DecisionTree {
        // 1. proxyTargetClass = true ?
        //    ├─ 是 → 使用CGLIB
        //    └─ 否 → 转到2
        
        // 2. 目标类是否有接口?
        //    ├─ 是 → 使用JDK动态代理
        //    └─ 否 → 使用CGLIB
        
        // 3. 是否需要代理非接口方法?
        //    ├─ 是 → 强制CGLIB (proxyTargetClass=true)
        //    └─ 否 → 保持默认
    }
    
    /**
     * 2. 配置方式汇总
     */
    public class ConfigurationWays {
        
        // 方式1:全局配置文件
        // application.yml
        // spring:
        //   aop:
        //     proxy-target-class: true  # 全局强制CGLIB
        
        // 方式2:注解配置
        @EnableAspectJAutoProxy(proxyTargetClass = true)
        @Configuration
        public class AppConfig {}
        
        // 方式3:编程式配置
        @Bean
        public DefaultAdvisorAutoProxyCreator autoProxyCreator() {
            DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
            creator.setProxyTargetClass(true);
            return creator;
        }
        
        // 方式4:局部覆盖
        @Bean
        @Scope(proxyMode = ScopedProxyMode.INTERFACES)  // 覆盖全局配置
        public RequestContext requestContext() {
            return new RequestContext();
        }
    }
    
    /**
     * 3. 验证当前代理类型
     */
    @Component
    public class ProxyTypeMonitor {
        
        private static final Logger log = LoggerFactory.getLogger(ProxyTypeMonitor.class);
        
        @EventListener(ContextRefreshedEvent.class)
        public void onContextRefreshed() {
            log.info("========== 代理类型监控 ==========");
            
            String[] beanNames = context.getBeanDefinitionNames();
            for (String beanName : beanNames) {
                Object bean = context.getBean(beanName);
                
                if (AopUtils.isAopProxy(bean)) {
                    String proxyType = getProxyType(bean);
                    log.info("Bean: {}, 代理类型: {}, 目标类: {}", 
                        beanName, proxyType, bean.getClass().getName());
                }
            }
        }
        
        private String getProxyType(Object bean) {
            if (AopUtils.isJdkDynamicProxy(bean)) {
                return "JDK动态代理";
            } else if (AopUtils.isCglibProxy(bean)) {
                return "CGLIB代理";
            }
            return "未知";
        }
    }
    
    /**
     * 4. 常见问题解决方案
     */
    public class ProblemSolutions {
        
        // 问题1:类型转换异常
        @Service
        public class UserService {}
        
        @Autowired
        private UserService userService;  // ✅ 正确:使用接口注入
        
        // ❌ 错误:强制类型转换
        // UserServiceImpl impl = (UserServiceImpl) userService; 
        // 如果使用CGLIB代理,会抛出ClassCastException
        
        // 解决方案:始终使用接口或依赖注入
        
        // 问题2:内部方法调用不触发代理
        @Service
        public class OrderService {
            
            @Transactional
            public void createOrder() {
                updateStock();  // 不会触发事务!
            }
            
            @Transactional
            public void updateStock() {
                // 事务逻辑
            }
            
            // 解决方案1:通过AopContext获取代理
            public void createOrder_fixed() {
                ((OrderService) AopContext.currentProxy()).updateStock();
            }
            
            // 解决方案2:将方法拆分到另一个Bean
        }
        
        // 问题3:final方法无法代理
        @Service
        public class ProductService {
            public final void cannotProxy() {
                // final方法,不会被代理
            }
        }
        // 解决方案:移除final修饰符
    }
    
    /**
     * 5. 面试必备
     */
    public class InterviewQA {
        
        // Q: Spring Boot 2.0为什么默认用CGLIB?
        // A: 1. 避免因接口缺失导致的代理失败
        //    2. 支持代理非接口方法
        //    3. 减少开发人员的困惑
        
        // Q: 如何判断当前Bean用了哪种代理?
        // A: 使用AopUtils.isJdkDynamicProxy() / isCglibProxy()
        
        // Q: 强制CGLIB有什么风险?
        // A: 1. 需要CGLIB依赖
        //    2. final方法无法代理
        //    3. 类型转换需谨慎
        
        // Q: 什么情况必须用JDK?
        // A: 1. 目标类是接口本身
        //    2. 需要代理多个接口
        //    3. 目标类是final类
    }
}

/**
 * 代理选择总结表
 * 
 * 配置                    目标类有接口              目标类无接口
 * proxyTargetClass=false  JDK动态代理              CGLIB
 * proxyTargetClass=true   CGLIB                    CGLIB
 * 
 * 推荐实践:
 * 1. Spring Boot 2.0+:保持默认CGLIB
 * 2. 统一使用接口注入,避免类型转换
 * 3. 如需JDK特性,局部配置proxyMode
 * 4. 监控代理类型,确保符合预期
 */

// 面试金句
// "Spring选择代理方式就像'出行选择交通工具':
//  JDK动态代理是'公交车'(必须走固定路线,只能到有站的地方),
//  CGLIB是'私家车'(想去哪就去哪,但需要有驾照)。
//  在Spring Boot 2.0后,我们默认都开'私家车'(CGLIB),
//  因为大部分'目的地'(业务类)都没有'公交站'(接口),
//  而且私家车能到'小区里面'(代理非接口方法)。
//  理解这个选择逻辑,就能明白为什么有时候会遇到类型转换异常,
//  以及为什么@Transactional在内部方法调用时不生效"

@Transactional 注解的底层原理是什么?Spring 事务是如何通过 AOP 实现的?

核心原理:AOP代理 + 事务拦截器

一句话原理@Transactional的底层基于Spring AOP实现,通过生成目标类的代理对象,在方法调用时由TransactionInterceptor(事务拦截器)拦截,根据注解属性动态执行事务的开启、提交、回滚,实现声明式事务与业务代码的解耦。

一句话源码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/**
 * 事务拦截器核心源码 - TransactionInterceptor
 */
public class TransactionInterceptor extends TransactionAspectSupport implements MethodInterceptor {
    
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        // 获取事务属性(解析@Transactional注解)
        TransactionAttribute txAttr = getTransactionAttributeSource()
                .getTransactionAttribute(invocation.getMethod(), invocation.getThis().getClass());
        
        // 获取事务管理器(PlatformTransactionManager)
        PlatformTransactionManager tm = getTransactionManager(txAttr, invocation.getMethod().getDeclaringClass());
        
        // 执行事务增强逻辑(父类模板方法)
        return invokeWithinTransaction(invocation.getMethod(), invocation.getThis().getClass(), 
                invocation::proceed, txAttr, tm);
    }
}

// TransactionAspectSupport.invokeWithinTransaction 核心流程
protected Object invokeWithinTransaction(Method method, Class<?> targetClass, 
        InvocationCallback invocation, TransactionAttribute txAttr, PlatformTransactionManager tm) {
    
    TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, methodIdentification); // 1. 开启事务
    
    Object retVal = null;
    try {
        retVal = invocation.proceedWithInvocation(); // 2. 执行目标方法
    } catch (Throwable ex) {
        completeTransactionAfterThrowing(txInfo, ex); // 3. 异常时回滚
        throw ex;
    } finally {
        cleanupTransactionInfo(txInfo);
    }
    commitTransactionAfterReturning(txInfo); // 4. 正常返回时提交
    return retVal;
}

项目场景:在订单系统中,使用@Transactional注解保证创建订单、扣减库存、更新账户余额三个操作的原子性。当中间任何一步抛出运行时异常时,Spring自动回滚所有操作,避免了数据不一致的问题。

事务的AOP实现机制

一句话原理:Spring通过@EnableTransactionManagement导入TransactionManagementConfigurationSelector,向容器注册InfrastructureAdvisorAutoProxyCreator(后置处理器)和BeanFactoryTransactionAttributeSourceAdvisor(事务切面),在Bean初始化后为其创建代理对象,织入事务增强逻辑。

一句话源码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
/**
 * 事务AOP的完整实现流程
 */
@Configuration
@EnableTransactionManagement // 1. 开启事务注解支持
public class AppConfig {
    // 2. 配置事务管理器
    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
}

// @EnableTransactionManagement 源码
@Import(TransactionManagementConfigurationSelector.class)
public @interface EnableTransactionManagement {}

// TransactionManagementConfigurationSelector.selectImports()
public class TransactionManagementConfigurationSelector {
    String[] selectImports(AdviceMode adviceMode) {
        switch(adviceMode) {
            case PROXY:
                // 导入两个核心组件
                return new String[] { 
                    AutoProxyRegistrar.class.getName(),           // 注册后置处理器
                    ProxyTransactionManagementConfiguration.class.getName() // 注册事务切面
                };
            case ASPECTJ:
                // ...
        }
    }
}

// 3. AutoProxyRegistrar:注册后置处理器
public class AutoProxyRegistrar {
    public void registerBeanDefinitions() {
        // 注册 InfrastructureAdvisorAutoProxyCreator
        // 这个BeanPostProcessor会在Bean初始化后为匹配事务切面的Bean创建代理
    }
}

// 4. ProxyTransactionManagementConfiguration:配置事务切面
@Configuration
public class ProxyTransactionManagementConfiguration {
    
    @Bean
    public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor() {
        BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor();
        advisor.setTransactionAttributeSource(transactionAttributeSource()); // 解析@Transactional
        advisor.setAdvice(transactionInterceptor()); // 事务拦截器
        return advisor;
    }
    
    @Bean
    public TransactionInterceptor transactionInterceptor() {
        TransactionInterceptor interceptor = new TransactionInterceptor();
        interceptor.setTransactionAttributeSource(transactionAttributeSource());
        interceptor.setTransactionManagerBeanName("transactionManager");
        return interceptor;
    }
    
    @Bean
    public TransactionAttributeSource transactionAttributeSource() {
        return new AnnotationTransactionAttributeSource(); // 解析@Transactional注解
    }
}

// 5. 代理创建与调用链路
// AbstractAutoProxyCreator.postProcessAfterInitialization() 
// → wrapIfNecessary() → createProxy() → 生成代理对象
    
// JdkDynamicAopProxy.invoke() / CglibAopProxy.intercept()
// → 获取拦截器链(包含TransactionInterceptor)
//  ReflectiveMethodInvocation.proceed() 执行拦截器链

项目场景:在微服务架构中,通过@EnableTransactionManagement开启事务后,Spring自动为所有带有@Transactional的Service创建代理。当服务A调用服务B时,事务管理器确保跨数据源的操作在同一个事务上下文中执行(配合JTA),实现了分布式事务的基础支持。

事务的核心处理流程

一句话原理:事务拦截器按照获取事务属性 → 开启事务 → 执行目标方法 → 异常回滚/正常提交的标准化流程处理每个事务方法,通过TransactionSynchronizationManager将事务资源绑定到当前线程,确保同一事务内的所有数据库操作共享同一个Connection。

一句话源码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
/**
 * 事务核心处理流程源码解析
 */
public abstract class TransactionAspectSupport {
    
    // 事务执行核心方法
    protected Object invokeWithinTransaction(Method method, Class<?> targetClass,
            InvocationCallback invocation, TransactionAttribute txAttr, PlatformTransactionManager tm) {
        
        // 1. 获取或创建事务(根据传播行为)
        TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
        
        Object retVal = null;
        try {
            // 2. 执行目标方法
            retVal = invocation.proceedWithInvocation();
        } catch (Throwable ex) {
            // 3. 异常处理:判断是否需要回滚
            completeTransactionAfterThrowing(txInfo, ex);
            throw ex;
        } finally {
            // 4. 清理事务信息
            cleanupTransactionInfo(txInfo);
        }
        
        // 5. 提交事务
        commitTransactionAfterReturning(txInfo);
        return retVal;
    }
    
    // 创建事务的核心逻辑
    protected TransactionInfo createTransactionIfNecessary(PlatformTransactionManager tm, 
            TransactionAttribute txAttr, String joinpointIdentification) {
        
        // 根据传播行为决定是新建事务、挂起事务还是使用现有事务
        TransactionStatus status = tm.getTransaction(txAttr);
        // 将事务绑定到当前线程(ThreadLocal)
        TransactionSynchronizationManager.setCurrentTransaction(name);
        return new TransactionInfo(txAttr, status);
    }
}

// DataSourceTransactionManager.getTransaction() 处理传播行为
public class DataSourceTransactionManager {
    
    protected void doBegin(Object transaction, TransactionDefinition definition) {
        DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
        Connection con = txObject.getConnectionHolder().getConnection();
        
        // 设置隔离级别
        Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
        txObject.setPreviousIsolationLevel(previousIsolationLevel);
        
        // 关闭自动提交,改为手动提交
        if (con.getAutoCommit()) {
            txObject.setMustRestoreAutoCommit(true);
            con.setAutoCommit(false);
        }
        
        // 将事务资源绑定到ThreadLocal
        TransactionSynchronizationManager.bindResource(getDataSource(), txObject.getConnectionHolder());
    }
    
    protected void doCommit(DefaultTransactionStatus status) {
        DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
        Connection con = txObject.getConnectionHolder().getConnection();
        con.commit(); // 提交事务
    }
    
    protected void doRollback(DefaultTransactionStatus status) {
        DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
        Connection con = txObject.getConnectionHolder().getConnection();
        con.rollback(); // 回滚事务
    }
}

项目场景:在支付回调处理中,通过@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)确保支付记录更新、积分增加、消息发送三个操作在同一事务中。当积分服务异常时,事务回滚保证支付记录状态正确,避免用户支付成功但积分未到账的问题。

完整实战指南

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
/**
 * @Transactional 完整实战指南
 */
public class TransactionalCompleteGuide {
    
    /**
     * 1. 核心配置示例
     */
    @Configuration
    @EnableTransactionManagement(order = 2) // 可以指定切面执行顺序
    public class TransactionConfig {
        
        @Bean
        public PlatformTransactionManager transactionManager(DataSource dataSource) {
            DataSourceTransactionManager manager = new DataSourceTransactionManager();
            manager.setDataSource(dataSource);
            manager.setDefaultTimeout(30); // 默认超时30秒
            manager.setRollbackOnCommitFailure(true); // 提交失败时尝试回滚
            return manager;
        }
        
        // 多个事务管理器时,需要指定qualifier
        @Bean
        @Qualifier("orderTransactionManager")
        public PlatformTransactionManager orderTransactionManager(DataSource orderDataSource) {
            return new DataSourceTransactionManager(orderDataSource);
        }
    }
    
    /**
     * 2. 注解使用示例
     */
    @Service
    public class OrderService {
        
        @Autowired
        private OrderRepository orderRepository;
        @Autowired
        private InventoryService inventoryService;
        
        // 基础用法:传播行为REQUIRED,隔离级别默认,只回滚运行时异常
        @Transactional
        public Order createOrder(Order order) {
            return orderRepository.save(order);
        }
        
        // 完整配置:指定事务管理器、传播行为、隔离级别、超时、回滚规则
        @Transactional(
            transactionManager = "orderTransactionManager",
            propagation = Propagation.REQUIRES_NEW,  // 每次开启新事务
            isolation = Isolation.READ_COMMITTED,    // 读已提交
            timeout = 60,                            // 超时60秒
            readOnly = false,
            rollbackFor = { BusinessException.class, DataAccessException.class },
            noRollbackFor = { NoSuchElementException.class }
        )
        public void processOrder(OrderRequest request) {
            Order order = createOrder(request);
            inventoryService.deductStock(order);
            // 业务逻辑
        }
        
        // 只读事务:优化查询性能
        @Transactional(readOnly = true)
        public Order getOrder(Long id) {
            return orderRepository.findById(id).orElse(null);
        }
    }
    
    /**
     * 3. 事务失效的常见场景及解决方案
     */
    @Service
    public class TransactionalPitfalls {
        
        // 场景1:内部方法调用(this调用不经过代理)
        public void processOuter() {
            // ❌ 这样调用不会开启事务
            processInner(); // this.processInner() 直接调用原始对象
        }
        
        @Transactional
        public void processInner() {
            // 业务逻辑
        }
        
        // ✅ 解决方案1:将事务方法拆分到另一个Bean
        @Autowired
        private TransactionalPitfalls self; // 注入自身
        
        public void processOuterFixed() {
            self.processInner(); // 通过代理调用
        }
        
        // ✅ 解决方案2:使用AopContext获取代理
        public void processOuterAop() {
            ((TransactionalPitfalls) AopContext.currentProxy()).processInner();
        }
        
        // 场景2:私有方法无法被代理
        @Transactional
        private void privateMethod() { // ❌ private方法不会生效
        }
        
        // 场景3:final方法无法被CGLIB代理
        @Transactional
        public final void finalMethod() { // ❌ final方法不会被增强
        }
        
        // 场景4:异常被捕获未抛出
        @Transactional
        public void catchException() {
            try {
                // 数据库操作
            } catch (Exception e) {
                // ❌ 异常被捕获未抛出,不会回滚
                log.error("错误", e);
            }
        }
        
        // 场景5:抛出检查异常(默认不回滚)
        @Transactional
        public void throwChecked() throws Exception {
            throw new Exception("检查异常"); // ❌ 默认不回滚
        }
        
        // ✅ 配置rollbackFor回滚检查异常
        @Transactional(rollbackFor = Exception.class)
        public void throwCheckedFixed() throws Exception {
            throw new Exception("现在会回滚");
        }
        
        // 场景6:多线程事务失效
        @Transactional
        public void multiThread() {
            new Thread(() -> {
                // ❌ 新线程中无法获取原事务的Connection
                orderRepository.save(order);
            }).start();
        }
    }
    
    /**
     * 4. 事务传播行为实战
     */
    @Service
    public class PropagationExample {
        
        // REQUIRED:支持当前事务,没有则新建(默认)
        @Transactional(propagation = Propagation.REQUIRED)
        public void requiredMethod() { }
        
        // REQUIRES_NEW:挂起当前事务,新建事务执行
        @Transactional(propagation = Propagation.REQUIRES_NEW)
        public void requiresNewMethod() { }
        
        // NESTED:嵌套事务(Savepoint)
        @Transactional(propagation = Propagation.NESTED)
        public void nestedMethod() { }
        
        // MANDATORY:必须在已有事务中执行,否则抛异常
        @Transactional(propagation = Propagation.MANDATORY)
        public void mandatoryMethod() { }
        
        // NEVER:不能在事务中执行,否则抛异常
        @Transactional(propagation = Propagation.NEVER)
        public void neverMethod() { }
        
        // 典型场景:记录操作日志(独立事务,不影响主事务)
        @Transactional(propagation = Propagation.REQUIRES_NEW)
        public void saveOperationLog(Log log) {
            // 即使主事务回滚,日志也能保存
        }
    }
    
    /**
     * 5. 监控与调试
     */
    @Component
    public class TransactionMonitor {
        
        private static final Logger log = LoggerFactory.getLogger(TransactionMonitor.class);
        
        @Autowired
        private TransactionManager transactionManager;
        
        // 查看当前事务状态
        public void checkTransactionStatus() {
            boolean inTransaction = TransactionSynchronizationManager.isActualTransactionActive();
            String currentName = TransactionSynchronizationManager.getCurrentTransactionName();
            boolean readOnly = TransactionSynchronizationManager.isCurrentTransactionReadOnly();
            
            log.info("事务状态 - 存在: {}, 名称: {}, 只读: {}", inTransaction, currentName, readOnly);
        }
        
        // 事务同步回调
        @Component
        public class TransactionCallback {
            
            @Transactional
            public void processWithCallback() {
                // 注册事务同步
                TransactionSynchronizationManager.registerSynchronization(
                    new TransactionSynchronization() {
                        @Override
                        public void afterCommit() {
                            log.info("事务提交后执行:发送消息、清理缓存");
                        }
                        
                        @Override
                        public void afterCompletion(int status) {
                            if (status == STATUS_ROLLED_BACK) {
                                log.info("事务回滚后执行:记录失败日志");
                            }
                        }
                    }
                );
            }
        }
    }
}

/**
 * @Transactional 总结表
 * 
 * 核心组件                      作用
 * @EnableTransactionManagement  开启事务注解支持
 * PlatformTransactionManager    事务管理器(DataSource/JPA/JTA)
 * TransactionInterceptor        事务拦截器,增强事务方法
 * TransactionAttributeSource    解析@Transactional注解
 * TransactionSynchronizationManager 线程绑定事务资源
 * 
 * 执行流程:
 * 代理对象 → TransactionInterceptor → 开启事务 → 目标方法 → 提交/回滚 → 清理资源
 * 
 * 常见问题:
 * 1. 内部方法调用失效 → 使用代理调用
 * 2. private/final方法失效 → 改为public
 * 3. 异常被捕获不抛出不回滚 → 抛出符合rollbackFor的异常
 * 4. 多线程事务失效 → 避免在多线程中使用事务
 */

// 面试金句
// "@Transactional的底层就像'智能管家':
//  通过AOP代理为你的方法'站岗'(方法拦截),
//  在方法执行前'铺好桌布'(开启事务),
//  执行中'观察情况'(监控异常),
//  顺利就'收拾桌子'(提交事务),
//  出事就'掀桌子重来'(回滚事务)。
//  在支付系统中,我用@Transactional保证了"支付-减库存-加积分"的原子性,
//  即使积分服务宕机,事务回滚也不会让用户多扣钱。
//  理解它的原理才能避免那些常见的失效陷阱写出可靠的事务代码" 

如果一个类内部方法调用另一个带 @Transactional 的方法,事务会生效吗?为什么?如何解决?

问题本质:代理对象 vs 原始对象

一句话原理:内部方法调用事务不会生效,因为调用发生在目标对象的原始实例上,而非Spring生成的代理对象,导致事务拦截器无法拦截,这是由Spring AOP的代理机制决定的。

一句话源码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
/**
 * 内部调用事务失效问题演示
 */
@Service
public class OrderService {
    
    @Autowired
    private OrderRepository orderRepository;
    
    // 外部方法(没有事务)
    public void processOrder(OrderRequest request) {
        // 业务处理
        saveOrder(request); // 内部调用带事务的方法
    }
    
    @Transactional
    public void saveOrder(OrderRequest request) {
        // 数据库操作
        orderRepository.save(request.toOrder());
    }
    
    // 测试代码
    public static void main(String[] args) {
        // Spring容器中的orderService是代理对象
        OrderService proxy = context.getBean(OrderService.class);
        
        // 1. 直接调用事务方法 - 生效
        proxy.saveOrder(request); // ✅ 事务生效(通过代理)
        
        // 2. 内部调用事务方法 - 失效
        proxy.processOrder(request); 
        // processOrder内部调用的是this.saveOrder()
        // this指向原始对象,不是代理,事务不生效 ❌
    }
}

// Spring生成的代理对象结构(伪代码)
public class OrderService$$EnhancerBySpringCGLIB extends OrderService {
    
    private OrderService target; // 原始对象
    private TransactionInterceptor interceptor; // 事务拦截器
    
    public void saveOrder(OrderRequest request) {
        // 调用事务拦截器
        interceptor.invoke(new MethodInvocation() {
            public Object proceed() {
                // 调用原始对象的方法
                return target.saveOrder(request);
            }
        });
    }
    
    public void processOrder(OrderRequest request) {
        // 没有事务增强,直接调用原始对象
        target.processOrder(request); 
        // 原始对象的processOrder内部调用的是target.saveOrder()
        // 不是代理的saveOrder,所以事务失效
    }
}

项目场景:在订单处理服务中,createOrder方法内部调用带@TransactionalsaveOrder方法,结果数据库操作不在事务中执行,导致当扣减库存失败时,订单仍然被保存,造成数据不一致。这个问题在生产环境中排查了整整半天才找到原因。

为什么内部调用事务会失效

一句话原理:Spring AOP基于代理模式实现,只有通过代理对象调用的方法才会被拦截增强;内部方法调用使用的是this(原始对象),绕过了代理,因此事务等AOP功能无法生效,这是由Java语言的对象调用机制决定的。

一句话源码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
/**
 * 内部调用失效的源码级分析
 */
public class WhyInternalCallFails {
    
    // ============ 1. Spring代理对象的创建 ============
    // AbstractAutoProxyCreator.wrapIfNecessary()
    public Object wrapIfNecessary(Object bean, String beanName) {
        // 如果Bean匹配事务切面,创建代理对象
        Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName);
        if (specificInterceptors != null) {
            // 创建代理对象,包裹原始Bean
            return createProxy(bean.getClass(), beanName, specificInterceptors, bean);
        }
        return bean; // 不匹配,返回原始对象
    }
    
    // ============ 2. 代理对象的调用逻辑 ============
    // CglibAopProxy.DynamicAdvisedInterceptor.intercept()
    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        // 获取事务拦截器链
        List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
        
        if (chain.isEmpty()) {
            // 无事务切面,直接调用原始方法
            return methodProxy.invoke(target, args);
        } else {
            // 有事务切面,创建MethodInvocation执行拦截器链
            MethodInvocation invocation = new CglibMethodInvocation(proxy, target, method, args, chain);
            return invocation.proceed(); // 事务拦截器在这里被调用
        }
    }
    
    // ============ 3. 内部调用的本质 ============
    public class TargetClass {
        
        @Transactional
        public void transactionalMethod() {
            // 数据库操作
        }
        
        public void nonTransactionalMethod() {
            // ❌ 内部调用:this指向原始对象
            this.transactionalMethod(); 
            
            // ✅ 正确方式:通过代理调用
            // ((TargetClass) AopContext.currentProxy()).transactionalMethod();
        }
    }
    
    // ============ 4. 字节码层面的差异 ============
    // 原始类的字节码(反编译)
    public class OrderService {
        public void processOrder() {
            // invokespecial #12  // 调用私有方法 - 直接调用
            this.saveOrder();
        }
        
        public void saveOrder() { }
    }
    
    // 代理类的字节码
    public class OrderService$$EnhancerBySpringCGLIB {
        public void processOrder() {
            // 没有事务增强,直接调用父类方法
            super.processOrder();
        }
        
        public void saveOrder() {
            // 调用拦截器
            interceptor.intercept(...); // 事务增强在这里
        }
    }
}

项目场景:在一次代码审查中,发现团队新成员在Service实现类中,一个非事务方法调用了另一个事务方法,导致事务不生效。这就是典型的"代理对象与原始对象混淆"问题,需要通过工具类或自我注入来解决。

四种解决方案实战

一句话原理:解决内部调用事务失效有四种方案:拆分到不同Bean(最推荐)、自我注入(@Autowired自身)、AopContext获取代理(需配置exposeProxy)、编程式事务(TransactionTemplate),每种方案各有优劣,应根据场景选择。

一句话源码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
/**
 * 四种解决方案完整实战
 */
@Service
public class OrderService {
    
    @Autowired
    private OrderRepository orderRepository;
    
    // ============ 方案1:拆分到不同的Bean(最推荐) ============
    // 将事务方法抽离到单独的Bean中
    @Service
    public static class OrderPersistenceService {
        @Transactional
        public void saveOrder(OrderRequest request) {
            // 数据库操作
        }
    }
    
    @Autowired
    private OrderPersistenceService persistenceService;
    
    public void processOrder_split(OrderRequest request) {
        // 调用另一个Bean的事务方法 ✅ 生效
        persistenceService.saveOrder(request);
    }
    
    // ============ 方案2:自我注入(Spring官方推荐) ============
    @Autowired
    private OrderService self; // 注入自身(代理对象)
    
    public void processOrder_selfInject(OrderRequest request) {
        // 通过代理对象调用事务方法 ✅ 生效
        self.saveOrder(request);
    }
    
    @Transactional
    public void saveOrder(OrderRequest request) {
        orderRepository.save(request.toOrder());
    }
    
    // ============ 方案3:AopContext.currentProxy()(需配置) ============
    public void processOrder_aopContext(OrderRequest request) {
        // 通过AopContext获取当前代理对象
        OrderService proxy = (OrderService) AopContext.currentProxy();
        proxy.saveOrder(request); // ✅ 生效
    }
    
    // 需要在配置类开启exposeProxy
    @Configuration
    @EnableAspectJAutoProxy(exposeProxy = true) // 必须设置为true
    public class AppConfig {
    }
    
    // ============ 方案4:编程式事务(TransactionTemplate) ============
    @Autowired
    private TransactionTemplate transactionTemplate;
    
    public void processOrder_template(OrderRequest request) {
        // 使用编程式事务
        transactionTemplate.execute(status -> {
            orderRepository.save(request.toOrder());
            // 其他操作
            return null;
        });
    }
    
    // ============ 方案5:方法拆分(将逻辑分离) ============
    public void processOrder_refactor(OrderRequest request) {
        // 预处理(非事务)
        validateRequest(request);
        
        // 事务操作
        executeInTransaction(request);
        
        // 后处理(非事务)
        sendNotification(request);
    }
    
    @Transactional
    public void executeInTransaction(OrderRequest request) {
        orderRepository.save(request.toOrder());
        inventoryService.deductStock(request);
    }
}

项目场景:在重构遗留系统时,大量代码存在内部调用事务方法的问题。团队统一采用"方案1+方案2"的组合:核心业务拆分为独立的事务Service,便于单元测试;快速修复场景使用自我注入,最小化代码改动。经过两周的改造,事务失效问题彻底解决。

完整实战指南

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
/**
 * 内部调用事务失效完整指南
 */
public class InternalCallGuide {
    
    /**
     * 1. 解决方案对比表
     */
    public class SolutionComparison {
        // 方案        优点                   缺点                适用场景
        // 拆分Bean    清晰解耦,便于测试      需要设计合理的分层    新功能开发
        // 自我注入    简单直接                可能形成循环依赖      快速修复
        // AopContext  无额外依赖             需要配置,耦合Spring  临时处理
        // 编程式事务   精确控制               代码侵入性强          复杂事务场景
        // 方法重构     根本解决               工作量大             架构优化
    }
    
    /**
     * 2. 验证事务是否生效
     */
    @Component
    public class TransactionVerifier {
        
        @Autowired
        private ApplicationContext context;
        
        public void verifyTransaction() {
            // 获取代理对象
            OrderService proxy = context.getBean(OrderService.class);
            
            // 检查是否是代理对象
            boolean isProxy = AopUtils.isAopProxy(proxy);
            System.out.println("是否是代理对象: " + isProxy);
            
            if (isProxy) {
                // 查看代理类名
                System.out.println("代理类: " + proxy.getClass().getName());
                
                // 如果是CGLIB代理
                if (AopUtils.isCglibProxy(proxy)) {
                    System.out.println("使用CGLIB代理");
                }
                
                // 如果是JDK代理
                if (AopUtils.isJdkDynamicProxy(proxy)) {
                    System.out.println("使用JDK代理");
                }
            }
        }
        
        // 运行时检查是否有事务
        public void checkTransactionActive() {
            boolean inTransaction = TransactionSynchronizationManager.isActualTransactionActive();
            System.out.println("当前是否在事务中: " + inTransaction);
        }
    }
    
    /**
     * 3. 单元测试验证
     */
    @SpringBootTest
    @Transactional // 测试类级别的事务
    public class TransactionTest {
        
        @Autowired
        private OrderService orderService;
        
        @Test
        public void testInternalCall() {
            // 准备测试数据
            OrderRequest request = new OrderRequest();
            
            // 调用非事务方法(内部调用事务方法)
            orderService.processOrder(request);
            
            // 验证数据库操作是否在事务中
            // 如果事务生效,测试结束后数据会自动回滚
            // 如果事务失效,数据会保留在数据库
        }
        
        @Test
        @Rollback
        public void testSelfInject() {
            // 测试自我注入方式
            OrderRequest request = new OrderRequest();
            orderService.processOrder_selfInject(request);
            // 验证数据在事务中
        }
    }
    
    /**
     * 4. 最佳实践总结
     */
    public class BestPractices {
        
        // 1. 设计原则:每个事务方法都是一个独立的操作单元
        // 2. 代码规范:避免在同一个类中相互调用事务方法
        // 3. 命名规范:事务方法用doXxx开头,明确标识
        // 4. 配置规范:@EnableAspectJAutoProxy(exposeProxy = true) 统一配置
        // 5. 测试规范:单元测试验证事务行为
        
        // 推荐的Service结构:
        // @Service
        // public class OrderService {
        //     @Autowired private OrderRepository repository;
        //     @Autowired private OrderTransactionalService txService; // 拆分
        //     @Autowired private OrderService self; // 自我注入
        //     
        //     public void process() {
        //         self.doInTransaction(); // 通过代理调用
        //     }
        //     
        //     @Transactional
        //     public void doInTransaction() {
        //         // 事务操作
        //     }
        // }
    }
    
    /**
     * 5. 面试必备
     */
    public class InterviewQA {
        
        // Q: 为什么内部调用事务会失效?
        // A: 因为this调用的是原始对象,不是代理对象
        
        // Q: 自我注入会不会造成循环依赖?
        // A: 不会,Spring的三级缓存可以解决
        
        // Q: AopContext.currentProxy()的原理?
        // A: 通过ThreadLocal存储当前代理对象
        
        // Q: 编程式事务和声明式事务的选择?
        // A: 声明式用于大多数业务,编程式用于精细控制
        
        // Q: 如何避免这类问题?
        // A: 代码规范 + 单元测试 + 代码审查
    }
}

/**
 * 解决方案总结表
 * 
 * 方案          实现方式                   事务生效   代码侵入  推荐度
 * 拆分Bean      将事务方法移到另一个Service   ✅        低       ⭐⭐⭐⭐⭐
 * 自我注入      @Autowired自身               ✅        中       ⭐⭐⭐⭐
 * AopContext    (OrderService) AopContext.currentProxy() ✅ 中   ⭐⭐⭐
 * 编程式事务     TransactionTemplate          ✅        高       ⭐⭐
 * 
 * 最佳实践:
 * 1. 新代码:使用拆分Bean方案
 * 2. 已有代码:使用自我注入快速修复
 * 3. 临时处理:AopContext方案
 * 4. 复杂场景:编程式事务
 */

// 面试金句
// "内部调用事务失效就像'自己给自己打电话':
//  Spring事务是通过'秘书'(代理对象)来接听的,
//  当你在公司内部(原始对象)直接喊同事(内部调用),
//  '秘书'根本听不到,自然就无法转接事务。
//  解决方案就是让所有通话都经过'秘书':要么请另一个部门的同事帮忙(拆分Bean),
//  要么用对讲机呼叫自己(自我注入),要么装个扩音器(AopContext)。
//  在项目中,我们统一要求所有事务方法都要通过'秘书'调用,
//  并在代码规范中明确禁止在同一个类中调用带@Transactional的方法"