Spring AOP 原理:JDK 动态代理与 CGLIB

面向切面编程(Aspect-Oriented Programming,AOP)是 Spring 框架的核心特性之一。它允许开发者将横切关注点(如日志记录、事务管理、权限控制等)从业务逻辑中分离出来,实现代码的模块化和可复用。本文将深入剖析 Spring AOP 的底层实现原理,重点讲解 JDK 动态代理和 CGLIB 两种代理机制的工作方式、差异以及适用场景。

一、AOP 核心概念回顾

在深入源码之前,我们先快速回顾一下 AOP 的几个核心概念:

概念说明
Aspect(切面)横切关注点的模块化,包含通知和切点
Join Point(连接点)程序执行过程中的某个特定点,如方法调用
Pointcut(切点)匹配连接点的表达式,定义通知何时触发
Advice(通知)切面的具体行为,如何增强目标方法
Target(目标对象)被代理的原始对象
Proxy(代理对象)AOP 框架生成的增强后的对象

二、Spring AOP 的两种代理方式

Spring AOP 默认采用代理模式实现,提供了两种代理机制:

2.1 JDK 动态代理

JDK 动态代理是 Java 原生支持的代理机制,要求目标类必须实现至少一个接口

工作原理

// 定义业务接口
public interface UserService {
    void addUser(String username);
    void deleteUser(String username);
}

// 实现类
public class UserServiceImpl implements UserService {
    @Override
    public void addUser(String username) {
        System.out.println("添加用户: " + username);
    }
    
    @Override
    public void deleteUser(String username) {
        System.out.println("删除用户: " + username);
    }
}
// 使用 JDK 动态代理创建代理对象
public class JdkProxyFactory {
    
    public static <T> T createProxy(T target) {
        return (T) 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代理] 方法调用前: " + method.getName());
                    
                    // 执行目标方法
                    Object result = method.invoke(target, args);
                    
                    System.out.println("[JDK代理] 方法调用后: " + method.getName());
                    return result;
                }
            }
        );
    }
}

// 使用
UserService userService = new UserServiceImpl();
UserService proxy = JdkProxyFactory.createProxy(userService);
proxy.addUser("张三");  // 通过代理对象调用

底层机制

JDK 动态代理的核心是 java.lang.reflect.Proxy 类和 InvocationHandler 接口:

  1. Proxy.newProxyInstance():在运行时动态生成代理类字节码
  2. 生成的代理类:继承自 Proxy,实现了目标接口
  3. InvocationHandler:所有接口方法调用都会转发到其 invoke() 方法

生成的代理类大致结构如下:

public final class $Proxy0 extends Proxy implements UserService {
    private InvocationHandler h;
    
    public void addUser(String username) {
        h.invoke(this, m3, new Object[]{username});
    }
    // ...
}

2.2 CGLIB 代理

CGLIB(Code Generation Library)是一个基于 ASM 字节码操作库,可以代理没有实现接口的类

工作原理

public class OrderService {
    public void createOrder(String orderId) {
        System.out.println("创建订单: " + orderId);
    }
    
    public void cancelOrder(String orderId) {
        System.out.println("取消订单: " + orderId);
    }
}
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

public class CglibProxyFactory {
    
    public static <T> T createProxy(Class<T> targetClass) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(targetClass);  // 设置目标类为父类
        enhancer.setCallback(new MethodInterceptor() {
            @Override
            public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
                System.out.println("[CGLIB代理] 方法调用前: " + method.getName());
                
                // 调用父类(目标类)的方法
                Object result = proxy.invokeSuper(obj, args);
                
                System.out.println("[CGLIB代理] 方法调用后: " + method.getName());
                return result;
            }
        });
        
        return (T) enhancer.create();
    }
}

// 使用
OrderService proxy = CglibProxyFactory.createProxy(OrderService.class);
proxy.createOrder("ORD-001");

底层机制

CGLIB 通过生成目标类的子类来实现代理:

  1. Enhancer:核心类,用于配置和创建代理对象
  2. MethodInterceptor:方法拦截器,拦截所有非 final 方法的调用
  3. 字节码生成:在运行时生成目标类的子类,重写父类方法

生成的代理类大致结构:

public class OrderService$$EnhancerByCGLIB extends OrderService {
    private MethodInterceptor callback;
    
    public void createOrder(String orderId) {
        callback.intercept(this, method, new Object[]{orderId}, proxy);
    }
}

三、Spring AOP 如何选择代理方式

Spring 框架通过 DefaultAopProxyFactory 决定使用哪种代理:

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() || Proxy.isProxyClass(targetClass)) {
                return new JdkDynamicAopProxy(config);
            }
            // 否则使用 CGLIB
            return new ObjenesisCglibAopProxy(config);
        }
        else {
            // 默认使用 JDK 动态代理
            return new JdkDynamicAopProxy(config);
        }
    }
}

3.1 选择规则总结

条件代理方式
目标类实现了接口(默认配置)JDK 动态代理
目标类没有实现接口CGLIB 代理
设置 proxyTargetClass=true强制使用 CGLIB
目标类是接口JDK 动态代理

3.2 配置方式

// 方式1:注解配置
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AppConfig {}

// 方式2:XML 配置
<aop:aspectj-autoproxy proxy-target-class="true"/>

// 方式3:Spring Boot 配置
spring.aop.proxy-target-class=true

四、两种代理方式对比

特性JDK 动态代理CGLIB 代理
实现方式实现接口继承目标类
要求目标类必须实现接口目标类不能是 final
方法限制只能代理接口方法不能代理 final/static 方法
性能反射调用,稍慢使用 MethodProxy,较快
启动速度较快需要生成字节码,较慢
依赖JDK 内置需要 CGLIB 库

五、实际应用场景

5.1 日志记录切面

@Aspect
@Component
public class LoggingAspect {
    
    @Pointcut("execution(* com.example.service.*.*(..))")
    public void serviceMethods() {}
    
    @Around("serviceMethods()")
    public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.currentTimeMillis();
        String methodName = joinPoint.getSignature().getName();
        
        log.info("[开始] 方法: {}, 参数: {}", methodName, Arrays.toString(joinPoint.getArgs()));
        
        try {
            Object result = joinPoint.proceed();
            long duration = System.currentTimeMillis() - start;
            log.info("[结束] 方法: {}, 耗时: {}ms, 返回: {}", methodName, duration, result);
            return result;
        } catch (Exception e) {
            log.error("[异常] 方法: {}, 错误: {}", methodName, e.getMessage());
            throw e;
        }
    }
}

5.2 事务管理

@Transactional(rollbackFor = Exception.class)
public void transferMoney(String fromAccount, String toAccount, BigDecimal amount) {
    //  Spring 使用 AOP 代理实现事务的开启、提交和回滚
    debit(fromAccount, amount);
    credit(toAccount, amount);
}

5.3 性能监控

@Aspect
@Component
public class PerformanceAspect {
    
    @Around("@annotation(PerformanceLog)")
    public Object measurePerformance(ProceedingJoinPoint joinPoint) throws Throwable {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        
        Object result = joinPoint.proceed();
        
        stopWatch.stop();
        
        // 记录到 Micrometer/Prometheus
        Metrics.timer("method.execution", 
            "class", joinPoint.getTarget().getClass().getSimpleName(),
            "method", joinPoint.getSignature().getName())
            .record(stopWatch.getTotalTimeMillis(), TimeUnit.MILLISECONDS);
        
        return result;
    }
}

六、常见问题与陷阱

6.1 同类方法调用问题

@Service
public class UserService {
    
    public void methodA() {
        // 这里调用 methodB 不会经过代理!
        methodB();  // 直接调用,AOP 失效
    }
    
    @Transactional
    public void methodB() {
        // 事务不会生效
    }
}

解决方案:

@Service
public class UserService {
    
    @Autowired
    private ApplicationContext context;
    
    public void methodA() {
        // 通过代理对象调用
        UserService proxy = context.getBean(UserService.class);
        proxy.methodB();
    }
    
    @Transactional
    public void methodB() {
        // 事务生效
    }
}

6.2 final 方法无法被代理

@Service
public class OrderService {
    
    // CGLIB 无法代理 final 方法
    public final void processOrder() {
        // 通知不会生效
    }
}

6.3 循环依赖问题

AOP 代理对象的创建可能引发循环依赖,Spring 通过三级缓存机制解决,但在构造器注入时仍可能出现问题。

七、最佳实践

  1. 接口优先:设计时优先定义接口,既符合面向接口编程原则,又能利用 JDK 动态代理的高效性
  2. 合理选择代理方式:需要代理具体类时开启 proxyTargetClass,但要注意 final 方法的限制
  3. 避免循环依赖:尽量使用 Setter 注入而非构造器注入,减少代理相关的循环依赖问题
  4. 自调用问题:同类方法间调用如果需要 AOP 增强,应通过代理对象调用
  5. 性能考量:对于高频调用的方法,考虑代理带来的性能开销

八、总结

Spring AOP 通过 JDK 动态代理和 CGLIB 两种方式为开发者提供了强大的横切关注点处理能力。理解这两种代理机制的原理和差异,有助于我们:

  • 更好地设计系统架构
  • 合理选择代理方式
  • 快速定位和解决 AOP 相关的问题
  • 写出更高效、更健壮的代码

JDK 动态代理基于接口实现,符合面向接口编程的设计理念;CGLIB 通过继承实现,为没有接口的类提供了 AOP 支持。在实际项目中,应根据具体需求和约束条件选择合适的代理方式。


本文深入剖析了 Spring AOP 的底层实现原理,希望能帮助你更好地理解和使用这一强大的特性。如有疑问或建议,欢迎在评论区留言讨论。