静态代理与动态代理,Spring AOP底层精髓全解析

只会用Spring AOP写@Transactional、@Async?

其实Spring AOP的底层,全是代理模式的身影。90%的高级开发面试,都会被问:"静态代理和动态代理的区别?JDK代理和CGLIB代理底层实现有何不同?"

代理模式是Java高级开发的"基本功",更是Spring AOP、事务控制、权限拦截、日志监控的核心底层。不懂代理,就看不懂Spring的核心设计,更无法应对复杂业务的扩展与优化。

一、先搞懂:代理模式到底解决什么问题?

在Java开发中,经常会遇到这样的需求:在不修改原有业务代码的前提下,给方法添加额外逻辑------比如日志打印、权限校验、事务控制、性能监控。

如果直接修改业务代码,会违反"开闭原则"(对扩展开放、对修改关闭),而且会让业务代码变得臃肿,日志、权限等非业务逻辑与核心业务耦合在一起,后续维护成本极高。

代理模式的核心价值,就是「解耦」:通过一个"代理对象",包装原始的"目标对象",在不修改目标对象源码的前提下,对目标方法进行增强、拦截和扩展

核心角色

  • 目标对象(Target):被增强的原始业务类,负责执行核心业务逻辑;

  • 代理对象(Proxy):包装目标对象,负责添加额外逻辑(增强),并调用目标对象的核心方法;

  • 公共接口(Interface):目标对象和代理对象共同实现的接口(静态代理、JDK动态代理必需),保证代理对象和目标对象对外的行为一致,客户端无需感知代理的存在。

举个通俗的例子:你去房产中介买房,你(客户端)不需要直接和房东(目标对象)打交道,中介(代理对象)会帮你筛选房源、谈价格、办手续(增强逻辑),最终还是房东把房子卖给你(核心业务)------中介就是"代理",帮你做了额外的事情,却没有改变"买房"这个核心业务本身。

二、静态代理:手写代理类,简单但笨重

静态代理,顾名思义,就是「提前手写好代理类」,代理类和目标类一一对应,在程序编译时就已经确定好代理关系,无法动态修改。

1. 静态代理完整实战(以"用户管理"为例)

需求:给用户新增、删除的业务方法,添加"日志打印"和"权限校验"的增强逻辑,不修改原有业务代码。

步骤1:定义公共业务接口(规范目标对象和代理对象的行为)
java 复制代码
/**
 * 公共接口:用户服务
 * 目标对象和代理对象都必须实现此接口
 */
public interface UserService {
    // 新增用户
    void addUser(String username);
    // 删除用户
    void deleteUser(Long userId);
}
步骤2:实现目标对象(核心业务逻辑)
java 复制代码
/**
 * 目标对象:用户服务实现类(核心业务)
 * 只关注业务逻辑,不掺杂任何非业务代码(日志、权限等)
 */
public class UserServiceImpl implements UserService {
    @Override
    public void addUser(String username) {
        // 核心业务:新增用户
        System.out.println("核心业务:新增用户【" + username + "】");
    }

    @Override
    public void deleteUser(Long userId) {
        // 核心业务:删除用户
        System.out.println("核心业务:删除用户【" + userId + "】");
    }
}
步骤3:手写静态代理类(实现接口,组合目标对象)
java 复制代码
/**
 * 静态代理类:用户服务代理
 * 实现和目标对象相同的接口,组合目标对象,添加增强逻辑
 */
public class UserStaticProxy implements UserService {

    // 组合目标对象(核心:代理对象不直接实现业务,而是调用目标对象的业务方法)
    private final UserService target;

    // 构造方法注入目标对象
    public UserStaticProxy(UserService target) {
        this.target = target;
    }

    @Override
    public void addUser(String username) {
        // 增强逻辑1:权限校验(前置增强)
        System.out.println("静态代理:权限校验通过,允许执行新增用户操作");
        // 增强逻辑2:日志打印(前置增强)
        System.out.println("静态代理:开始执行新增用户方法,参数:" + username);
        
        // 调用目标对象的核心业务方法
        target.addUser(username);
        
        // 增强逻辑3:日志打印(后置增强)
        System.out.println("静态代理:新增用户方法执行完成");
    }

    @Override
    public void deleteUser(Long userId) {
        // 前置增强:权限校验+日志
        System.out.println("静态代理:权限校验通过,允许执行删除用户操作");
        System.out.println("静态代理:开始执行删除用户方法,参数:" + userId);
        
        // 执行核心业务
        target.deleteUser(userId);
        
        // 后置增强:日志
        System.out.println("静态代理:删除用户方法执行完成");
    }
}
步骤4:客户端调用(无感知代理)
java 复制代码
/**
 * 测试类:客户端调用
 * 客户端只和代理对象交互,不知道目标对象的存在
 */
public class StaticProxyTest {
    public static void main(String[] args) {
        // 1. 创建目标对象(核心业务类)
        UserService target = new UserServiceImpl();
        // 2. 创建代理对象,注入目标对象
        UserService proxy = new UserStaticProxy(target);
        // 3. 调用代理对象的方法(间接调用目标对象的方法,同时执行增强逻辑)
        proxy.addUser("Java高级开发");
        System.out.println("------------------------");
        proxy.deleteUser(1L);
    }
}
运行结果
text 复制代码
静态代理:权限校验通过,允许执行新增用户操作
静态代理:开始执行新增用户方法,参数:Java高级开发
核心业务:新增用户【Java高级开发】
静态代理:新增用户方法执行完成
------------------------
静态代理:权限校验通过,允许执行删除用户操作
静态代理:开始执行删除用户方法,参数:1
核心业务:删除用户【1】
静态代理:删除用户方法执行完成

2. 静态代理的优缺点

✅ 优点
  • 实现简单、逻辑清晰,无需依赖任何框架;

  • 运行效率高,编译时就确定代理关系,无反射、无动态生成开销;

  • 增强逻辑明确,可精准控制每个方法的增强细节。

❌ 缺点(致命缺陷,限制其使用场景)
  • 类爆炸问题:一个目标类对应一个代理类,如果有100个业务类,就需要写100个代理类,维护成本极高;

  • 硬编码冗余:多个代理类的增强逻辑(如日志、权限)重复,无法复用;

  • 扩展性极差:如果接口新增一个方法,所有实现该接口的目标类和代理类都必须同步修改,违反开闭原则。

正因为这些缺陷,静态代理只适用于「简单场景、少量类」的增强,在实际企业开发中(尤其是微服务场景),几乎不会使用静态代理------此时,动态代理应运而生。

三、动态代理:运行时自动生成代理类,优雅且灵活

动态代理的核心的是:不需要手写代理类,程序运行期间,通过反射、字节码生成等技术,动态创建代理对象

也就是说,代理类不是提前写好的,而是程序运行时"临时生成"的,一个代理处理器可以适配所有目标类,彻底解决静态代理的类爆炸、冗余问题。

Java 中主流的两种动态代理方式:

  1. JDK 动态代理:基于「接口」实现,JDK 原生支持,无需引入额外依赖;

  2. CGLIB 动态代理:基于「子类继承」实现,需要引入第三方依赖,可代理无接口的普通类。

四、JDK 动态代理(Spring 默认首选)

JDK 动态代理是 Java 原生提供的代理方式,底层依赖java\.lang\.reflect\.Proxy 类和 InvocationHandler接口,核心要求:目标类必须实现接口

核心原理
  1. 客户端调用代理对象的方法时,会被InvocationHandlerinvoke方法拦截;

  2. invoke 方法中,可以添加增强逻辑(前置、后置);

  3. 通过 Method\.invoke\(target, args\) 调用目标对象的核心方法;

  4. 程序运行时,JDK 会自动生成一个实现了目标接口的代理类(字节码),无需手写。

1. JDK 动态代理完整实战
步骤1:复用之前的 UserService 接口和 UserServiceImpl 目标类
步骤2:实现 InvocationHandler(代理处理器,统一处理增强逻辑)
java 复制代码
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
 * JDK动态代理处理器
 * 所有的增强逻辑,都在这里统一实现,可适配所有实现接口的目标类
 */
public class JdkProxyHandler implements InvocationHandler {

    // 目标对象(通用,可接收任意实现接口的目标类)
    private final Object target;

    // 构造方法注入目标对象
    public JdkProxyHandler(Object target) {
        this.target = target;
    }

    /**
     * 代理方法拦截:所有代理对象的方法调用,都会走到这里
     * @param proxy 代理对象(自身)
     * @param method 被调用的目标方法
     * @param args 方法参数
     * @return 目标方法的返回值
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 前置增强:权限校验 + 日志打印(统一增强,所有方法都生效)
        System.out.println("JDK动态代理:权限校验通过");
        System.out.println("JDK动态代理:开始执行方法【" + method.getName() + "】,参数:" + args[0]);
        
        // 调用目标对象的核心业务方法
        Object result = method.invoke(target, args);
        
        // 后置增强:日志打印
        System.out.println("JDK动态代理:方法【" + method.getName() + "】执行完成");
        
        return result;
    }
}
步骤3:生成代理对象 + 客户端调用
java 复制代码
import java.lang.reflect.Proxy;

/**
 * JDK动态代理测试
 */
public class JdkProxyTest {
    public static void main(String[] args) {
        // 1. 创建目标对象
        UserService target = new UserServiceImpl();
        
        // 2. 创建代理处理器,注入目标对象
        JdkProxyHandler handler = new JdkProxyHandler(target);
        
        // 3. 动态生成代理对象(核心:JDK自动生成代理类)
        UserService proxy = (UserService) Proxy.newProxyInstance(
                target.getClass().getClassLoader(), // 目标类的类加载器
                target.getClass().getInterfaces(),  // 目标类实现的所有接口
                handler                             // 代理处理器
        );
        
        // 4. 调用代理对象的方法
        proxy.addUser("JDK动态代理");
        System.out.println("------------------------");
        proxy.deleteUser(2L);
    }
}
2. JDK 动态代理的优缺点
✅ 优点
  • 无需手写代理类,一个处理器适配所有实现接口的目标类,解决类爆炸问题;

  • JDK 原生支持,无需引入第三方依赖,轻量化;

  • 增强逻辑统一管理,可复用、可扩展。

❌ 缺点
  • 强制依赖接口:目标类必须实现接口,否则无法使用 JDK 动态代理;

  • 只能代理接口中的方法,无法代理目标类中的非接口方法(如私有方法、默认方法)。

五、CGLIB 动态代理(无接口场景首选)

CGLIB(Code Generation Library)是一个第三方字节码生成库,它的核心原理是:通过字节码技术,动态生成目标类的子类,子类就是代理类

正因为是基于继承实现的,所以 CGLIB 不需要目标类实现接口,可直接代理普通类------这也是它弥补 JDK 动态代理缺陷的核心优势。

注意:Spring 中已经集成了 CGLIB,无需额外引入依赖;如果是纯 Java 项目,需要手动引入 CGLIB 依赖。

核心原理
  1. 通过 Enhancer 类(CGLIB 核心类)设置目标类为父类;

  2. 实现 MethodInterceptor 接口,重写 intercept 方法,添加增强逻辑;

  3. 程序运行时,CGLIB 动态生成目标类的子类(代理类),并重写父类的方法;

  4. 客户端调用代理类的方法时,会被 intercept 方法拦截,执行增强逻辑后,调用父类(目标类)的方法。

1. CGLIB 动态代理完整实战
步骤1:定义无接口的目标类(测试 CGLIB 无接口代理能力)
java 复制代码
/**
 * 无接口的目标类:普通业务类
 * CGLIB 可直接代理此类,无需实现任何接口
 */
public class OrderService {
    // 核心业务:创建订单
    public void createOrder(Long orderId) {
        System.out.println("核心业务:创建订单【" + orderId + "】");
    }

    // 核心业务:取消订单
    public void cancelOrder(Long orderId) {
        System.out.println("核心业务:取消订单【" + orderId + "】");
    }
}
步骤2:实现 MethodInterceptor(CGLIB 代理处理器)
java 复制代码
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;

/**
 * CGLIB动态代理处理器
 * 基于子类继承,无需接口,可代理任意普通类
 */
public class CglibProxyHandler implements MethodInterceptor {

    /**
     * 生成代理对象
     * @param targetClass 目标类的Class对象
     * @return 代理对象(目标类的子类)
     */
    public <T> T getProxy(Class<T> targetClass) {
        // 1. 创建Enhancer对象(CGLIB核心类,用于生成代理类)
        Enhancer enhancer = new Enhancer();
        // 2. 设置父类(目标类)
        enhancer.setSuperclass(targetClass);
        // 3. 设置代理处理器(当前类)
        enhancer.setCallback(this);
        // 4. 动态生成代理类,并返回代理对象
        return (T) enhancer.create();
    }

    /**
     * 拦截代理对象的方法调用
     * @param obj 代理对象(目标类的子类)
     * @param method 被调用的目标方法(父类的方法)
     * @param args 方法参数
     * @param proxy 方法代理对象(用于调用父类方法)
     * @return 目标方法的返回值
     */
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        // 前置增强:日志打印
        System.out.println("CGLIB动态代理:开始执行方法【" + method.getName() + "】,参数:" + args[0]);
        
        // 调用目标类(父类)的核心方法(注意:用proxy.invokeSuper,不是method.invoke)
        Object result = proxy.invokeSuper(obj, args);
        
        // 后置增强:日志打印
        System.out.println("CGLIB动态代理:方法【" + method.getName() + "】执行完成");
        
        return result;
    }
}
步骤3:生成代理对象 + 客户端调用
java 复制代码
/**
 * CGLIB动态代理测试
 */
public class CglibProxyTest {
    public static void main(String[] args) {
        // 1. 创建CGLIB代理处理器
        CglibProxyHandler handler = new CglibProxyHandler();
        // 2. 动态生成代理对象(目标类的子类)
        OrderService proxy = handler.getProxy(OrderService.class);
        // 3. 调用代理对象的方法
        proxy.createOrder(1001L);
        System.out.println("------------------------");
        proxy.cancelOrder(1001L);
    }
}
2. CGLIB 动态代理的优缺点
✅ 优点
  • 无需目标类实现接口,可代理任意普通类,适用范围更广;

  • 可代理目标类中的所有方法(除了final方法),包括私有方法、默认方法;

  • 运行效率略高于 JDK 动态代理(无反射调用,直接调用父类方法)。

❌ 缺点
  • 需要引入第三方依赖(Spring 已集成,无需额外处理);

  • 基于继承实现,无法代理 final 类和 final 方法(final 类不能被继承,final 方法不能被重写);

  • 动态生成子类的字节码,初始化代理对象的开销略大于 JDK 动态代理。

六、三大代理方式核心对比

代理类型 实现方式 依赖条件 核心优点 核心缺点 适用场景
静态代理 手写代理类,组合目标对象 目标类和代理类实现同一接口 实现简单、运行效率高 类爆炸、维护成本高、扩展性差 简单场景、少量类的增强
JDK 动态代理 Proxy + InvocationHandler,反射机制 目标类必须实现接口 无类爆炸、原生支持、轻量化 无法代理无接口的类 有接口的业务类增强(Spring 默认)
CGLIB 动态代理 Enhancer + MethodInterceptor,字节码生成子类 无接口要求,不能是final类/方法 适用范围广,可代理普通类 依赖第三方,无法代理final 无接口的业务类增强

七、Spring 中的代理模式落地

日常用的 Spring AOP、@Transactional、@Async、统一日志拦截,底层全是动态代理------Spring 会根据目标类是否有接口,自动选择代理方式。

1. Spring 代理选择规则(核心)

  • 如果目标类实现了接口 :Spring 默认使用 JDK 动态代理

  • 如果目标类没有实现接口 :Spring 自动切换为 CGLIB 动态代理

  • 可通过配置强制开启 CGLIB 代理(无论是否有接口):
    spring: aop: proxy\-target\-class: true \# 强制使用CGLIB代理

2. Spring AOP 代理的常见坑(生产实战必避)

坑1:内部方法调用,AOP 增强失效

场景:在目标类中,一个方法调用自身的另一个被 AOP 增强的方法(如 @Transactional 方法),增强逻辑不生效。

java 复制代码
@Service
public class UserServiceImpl implements UserService {
    @Override
    public void addUser(String username) {
        // 内部调用:this.deleteUser(1L),不会走代理,@Transactional失效
        this.deleteUser(1L); 
        System.out.println("新增用户:" + username);
    }

    @Transactional
    @Override
    public void deleteUser(Long userId) {
        System.out.println("删除用户:" + userId);
    }
}

原因:this 指向的是目标对象本身,不是代理对象,无法触发 AOP 拦截。

解决方案:通过 AopContext\.currentProxy\(\) 获取代理对象,再调用方法:

java 复制代码
// 替换 this.deleteUser(1L) 为以下代码
((UserService) AopContext.currentProxy()).deleteUser(1L);
坑2:final 方法无法被 AOP 增强

如果目标方法被 final 修饰,无论用 JDK 还是 CGLIB 代理,增强逻辑都不会生效------CGLIB 无法重写 final 方法,JDK 代理也无法拦截 final 方法。

八、总结:如何用好代理模式?

  1. 简单场景、少量类增强:用静态代理(虽然笨重,但高效、易懂);

  2. 企业级开发、大量类增强:用动态代理(优先 JDK 代理,无接口用 CGLIB);

  3. Spring 项目:无需手动选择代理方式,Spring 自动适配,重点关注 AOP 内部调用失效的坑;

  4. 重点:记住三大代理的实现方式、优缺点、依赖条件,以及 Spring 代理选择规则。

相关推荐
希望永不加班2 小时前
SpringBoot 敏感数据脱敏(序列化层)
java·spring boot·后端·spring
希望永不加班2 小时前
SpringBoot 数据库索引优化:慢查询分析
java·数据库·spring boot·后端·spring
我登哥MVP5 小时前
【SpringMVC笔记】 - 11 - SpringMVC 执行流程
java·spring boot·笔记·spring·tomcat·intellij-idea
zhenxin01227 小时前
GitSubmodule避坑指南:从入门到精通
spring boot·后端·spring
椰猫子7 小时前
Spring Framework(Bean)
java·前端·spring
用户860821135659 小时前
Springboot按jar包方式启动,究竟发生了什么
spring
所愿ღ11 小时前
SSM框架-Spring1
java·开发语言·笔记·spring
Flittly11 小时前
【SpringSecurity新手村系列】(5)RBAC角色权限与账户状态校验
java·spring boot·笔记·安全·spring·ai
所愿ღ11 小时前
SSM框架-Spring2
java·开发语言·笔记·spring