Skip to content

Spring AOP动态代理

要理解Spring AOP的动态代理,我们需要从「AOP的核心问题」出发——如何在不修改目标对象代码的情况下,对其方法进行增强(比如日志、事务、权限校验)?动态代理正是解决这个问题的底层技术。

一、AOP的核心概念

在讲动态代理前,先明确AOP的几个关键术语(避免后续混淆):

  • 目标对象(Target):需要被增强的原始对象(比如业务层的UserService)。
  • 代理对象(Proxy):动态生成的、包裹目标对象的对象,实际对外提供服务的是代理对象。
  • 连接点(JoinPoint):目标对象中可以被增强的点(比如所有方法的执行)。
  • 切点(Pointcut):具体选择哪些连接点(比如只增强UserService中以save开头的方法)。
  • 通知(Advice):增强的逻辑(比如前置日志、后置事务提交)。
  • 切面(Aspect):切点 + 通知的组合(比如「对save*方法添加日志」就是一个切面)。

二、动态代理的本质

动态代理的核心是在运行时生成一个「代理对象」,这个代理对象会:

  1. 实现与目标对象相同的接口(JDK代理)或继承目标对象(CGLIB代理);
  2. 拦截目标对象的方法调用;
  3. 在方法执行前后插入通知逻辑(比如日志、事务);
  4. 最终调用目标对象的原始方法。

Spring AOP支持两种动态代理实现:JDK动态代理(JDK原生)和CGLIB动态代理(第三方库,Spring已集成)。

三、JDK动态代理(基于接口)

JDK动态代理是Java原生支持的代理方式,要求目标对象必须实现接口

1. 核心类/接口

  • java.lang.reflect.Proxy:用于生成代理对象的工具类,核心方法是newProxyInstance()
  • java.lang.reflect.InvocationHandler:代理逻辑的回调接口,需要实现invoke()方法(所有代理对象的方法调用都会走到这里)。

2. 工作原理

  1. 目标类实现一个接口(比如UserService接口);
  2. 创建InvocationHandler实现类,在invoke()方法中编写增强逻辑;
  3. 通过Proxy.newProxyInstance()生成代理对象(代理对象实现了目标接口);
  4. 调用代理对象的方法时,会触发InvocationHandler.invoke(),执行增强逻辑后调用目标对象的原始方法。

3. 代码示例

假设我们有一个「用户服务」,需要在保存用户时添加日志:

步骤1:定义接口和目标类

java
// 1. 目标接口
public interface UserService {
    void saveUser(String username);
}

// 2. 目标类(实现接口)
public class UserServiceImpl implements UserService {
    @Override
    public void saveUser(String username) {
        System.out.println("目标方法执行:保存用户 [" + username + "]");
    }
}

步骤2:实现InvocationHandler(代理逻辑)

java
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

// 代理逻辑处理器:负责插入增强代码
public class LogInvocationHandler implements InvocationHandler {
    // 目标对象(被代理的原始对象)
    private Object target;

    public LogInvocationHandler(Object target) {
        this.target = target;
    }

    /**
     * 所有代理对象的方法调用都会触发这个方法
     * @param proxy 代理对象本身(很少用)
     * @param method 目标方法(被调用的方法)
     * @param args 目标方法的参数
     * @return 目标方法的返回值
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 1. 前置增强(日志)
        System.out.println("前置通知:准备调用 [" + method.getName() + "] 方法,参数:" + args[0]);
        
        // 2. 调用目标对象的原始方法
        Object result = method.invoke(target, args);
        
        // 3. 后置增强(日志)
        System.out.println("后置通知:[" + method.getName() + "] 方法执行完成");
        
        return result;
    }
}

步骤3:生成代理对象并测试

java
import java.lang.reflect.Proxy;

public class JdkProxyTest {
    public static void main(String[] args) {
        // 1. 创建目标对象
        UserService target = new UserServiceImpl();
        
        // 2. 创建InvocationHandler(传入目标对象)
        InvocationHandler handler = new LogInvocationHandler(target);
        
        // 3. 生成代理对象(三个参数:类加载器、目标接口数组、InvocationHandler)
        UserService proxy = (UserService) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),  // 类加载器(与目标对象一致)
                target.getClass().getInterfaces(),   // 目标对象实现的接口(代理对象会实现这些接口)
                handler                               // 代理逻辑处理器
        );
        
        // 4. 调用代理对象的方法(会触发invoke())
        proxy.saveUser("张三");
    }
}

输出结果

前置通知:准备调用 [saveUser] 方法,参数:张三
目标方法执行:保存用户 [张三]
后置通知:[saveUser] 方法执行完成

四、CGLIB动态代理(基于继承)

如果目标类没有实现接口,JDK动态代理就无法使用,这时需要用CGLIB(Code Generation Library)。CGLIB是一个第三方字节码生成库,通过继承目标类生成代理类(代理类是目标类的子类)。

1. 核心类/接口

  • org.springframework.cglib.proxy.Enhancer:CGLIB的核心类,用于生成代理对象。
  • org.springframework.cglib.proxy.MethodInterceptor:方法拦截器接口,类似JDK的InvocationHandler,需要实现intercept()方法。

2. 工作原理

  1. 目标类不需要实现接口(比如OrderService);
  2. 创建MethodInterceptor实现类,在intercept()方法中编写增强逻辑;
  3. 通过Enhancer设置父类(目标类)和回调(MethodInterceptor),生成代理对象(代理类是目标类的子类);
  4. 调用代理对象的方法时,会触发MethodInterceptor.intercept(),执行增强逻辑后调用目标对象的原始方法。

3. 代码示例

假设我们有一个「订单服务」,没有实现接口,需要添加日志:

步骤1:定义目标类(无接口)

java
// 目标类(无接口)
public class OrderService {
    public void createOrder(String orderId) {
        System.out.println("目标方法执行:创建订单 [" + orderId + "]");
    }
}

步骤2:实现MethodInterceptor(代理逻辑)

java
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;

// 方法拦截器:负责插入增强代码
public class LogMethodInterceptor implements MethodInterceptor {
    // 目标对象(被代理的原始对象)
    private Object target;

    public LogMethodInterceptor(Object target) {
        this.target = target;
    }

    /**
     * 所有代理对象的方法调用都会触发这个方法
     * @param proxy 代理对象本身
     * @param method 目标方法(被调用的方法)
     * @param args 目标方法的参数
     * @param methodProxy 方法代理(用于调用目标方法,比反射更快)
     * @return 目标方法的返回值
     */
    @Override
    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        // 1. 前置增强(日志)
        System.out.println("前置通知:准备调用 [" + method.getName() + "] 方法,参数:" + args[0]);
        
        // 2. 调用目标对象的原始方法(两种方式:反射或methodProxy)
        // 方式1:反射(与JDK类似)
        // Object result = method.invoke(target, args);
        // 方式2:methodProxy(CGLIB推荐,性能更好)
        Object result = methodProxy.invoke(target, args);
        
        // 3. 后置增强(日志)
        System.out.println("后置通知:[" + method.getName() + "] 方法执行完成");
        
        return result;
    }
}

步骤3:生成代理对象并测试

java
import org.springframework.cglib.proxy.Enhancer;

public class CglibProxyTest {
    public static void main(String[] args) {
        // 1. 创建目标对象
        OrderService target = new OrderService();
        
        // 2. 创建MethodInterceptor(传入目标对象)
        LogMethodInterceptor interceptor = new LogMethodInterceptor(target);
        
        // 3. 生成代理对象(Enhancer是CGLIB的核心类)
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(target.getClass());  // 设置父类(目标类)
        enhancer.setCallback(interceptor);          // 设置回调(代理逻辑)
        OrderService proxy = (OrderService) enhancer.create();  // 生成代理对象
        
        // 4. 调用代理对象的方法(会触发intercept())
        proxy.createOrder("OD123456");
    }
}

输出结果

前置通知:准备调用 [createOrder] 方法,参数:OD123456
目标方法执行:创建订单 [OD123456]
后置通知:[createOrder] 方法执行完成

五、JDK vs CGLIB:关键区别

维度JDK动态代理CGLIB动态代理
依赖JDK原生(无需额外依赖)需要CGLIB库(Spring已集成)
目标类要求必须实现接口无需实现接口,但不能是final类(无法继承)、方法不能是final(无法重写)
代理方式实现目标接口(代理类是接口的实现)继承目标类(代理类是目标类的子类)
性能JDK 8+优化后,性能优于CGLIB生成代理类时比JDK慢(字节码生成),但运行时性能与JDK接近
Spring默认选择目标类实现接口时,优先用JDK目标类无接口时,用CGLIB

六、Spring AOP如何使用动态代理?

Spring AOP自动选择代理方式:

  • 如果目标类实现了接口:默认用JDK动态代理;
  • 如果目标类没有实现接口:用CGLIB;
  • 可以通过配置强制使用CGLIB(比如spring.aop.proxy-target-class=true,或在@EnableAspectJAutoProxy(proxyTargetClass = true)中设置)。

Spring AOP的简化开发

开发者不需要手动写ProxyEnhancer的代码,只需定义切面@Aspect)和通知@Before/@After等),Spring会自动生成代理对象。

示例:Spring AOP的日志切面

java
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

// 1. 标记为切面(@Aspect)+ 组件(@Component,让Spring扫描到)
@Aspect
@Component
public class LogAspect {
    // 2. 定义切点:匹配com.example.service包下所有类的所有方法
    @Pointcut("execution(* com.example.service.*.*(..))")
    public void servicePointcut() {}

    // 3. 前置通知:切点执行前触发
    @Before("servicePointcut()")
    public void beforeAdvice(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        System.out.println("前置通知:方法[" + methodName + "],参数:" + args[0]);
    }

    // 4. 后置通知:切点执行后触发(无论是否异常)
    @After("servicePointcut()")
    public void afterAdvice(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("后置通知:方法[" + methodName + "]执行完成");
    }
}

业务类(无需修改)

java
import org.springframework.stereotype.Service;

@Service
public class UserService {  // 即使没有实现接口,Spring也会用CGLIB代理
    public void saveUser(String username) {
        System.out.println("保存用户:" + username);
    }
}

测试类

java
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@EnableAspectJAutoProxy  // 开启AOP自动代理
@ComponentScan("com.example")  // 扫描组件
public class SpringAopTest {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(SpringAopTest.class);
        UserService userService = context.getBean(UserService.class);
        userService.saveUser("李四");  // 调用代理对象的方法,触发切面
    }
}

输出结果

前置通知:方法[saveUser],参数:李四
保存用户:李四
后置通知:方法[saveUser]执行完成

七、注意事项

  1. this调用的问题
    如果目标类内部用this调用自己的方法(比如UserServicesaveUser()调用this.updateUser()),this是目标对象本身,不会经过代理对象,所以切面通知不会生效。
    解决方法:用AopContext.currentProxy()获取代理对象,再调用方法:

    java
    // 错误方式(this是目标对象)
    // this.updateUser(username);
    // 正确方式(获取代理对象)
    ((UserService) AopContext.currentProxy()).updateUser(username);

    需要开启exposeProxy = true(在@EnableAspectJAutoProxy(exposeProxy = true)中设置)。

  2. final类/方法的问题
    CGLIB无法代理final类(因为无法继承),也无法代理final方法(因为无法重写)。如果目标类是final,Spring会抛出异常。

  3. 代理对象的类型
    JDK代理的对象是接口类型(比如UserService),不能强制转换为目标类(UserServiceImpl);CGLIB代理的对象是目标类的子类,可以强制转换为目标类。

八、总结

  • Spring AOP的底层是动态代理,通过运行时生成代理对象,实现对目标方法的增强。
  • JDK动态代理基于接口,CGLIB基于继承,Spring自动选择。
  • 开发者只需关注切面通知的定义,无需手动处理代理逻辑。

动态代理是Spring AOP的「基石」,理解它能帮你更深刻地掌握AOP的工作原理,解决实际开发中的代理相关问题(比如this调用不生效、final方法无法增强等)。