当前位置: 首页 > news >正文

【Spring】静态代理、动态代理

Java中,代理模式是一种设计模式,用于通过代理对象控制对目标对象的访问。代理可以分为静态代理动态代理,其中动态代理又包括JDK动态代理CGLIB动态代理。这些机制在Spring框架中广泛用于AOP(面向切面编程)、事务管理等场景,尤其与Bean的创建和循环依赖解决密切相关。以下是详细说明,包括代码示例、优缺点、与Spring Bean的关联,以及在循环依赖中的表现。


1. 静态代理

  • 定义:静态代理是在编译期手动创建代理类,代理类和目标类实现相同的接口,代理类持有目标对象的引用,并在方法调用前后添加额外逻辑。

  • 特点

    • 代理类和目标类需实现同一接口或继承同一父类。
    • 代理类在代码中硬编码,针对每个目标类需编写特定代理类。
    • 逻辑固定,扩展性较差。
  • 代码示例

    // 公共接口
    public interface UserService {void save();
    }// 目标类
    public class UserServiceImpl implements UserService {@Overridepublic void save() {System.out.println("Saving user...");}
    }// 静态代理类
    public class UserServiceProxy implements UserService {private UserService target;public UserServiceProxy(UserService target) {this.target = target;}@Overridepublic void save() {System.out.println("Before save: Logging...");target.save();System.out.println("After save: Logging...");}
    }// 使用
    public class Main {public static void main(String[] args) {UserService target = new UserServiceImpl();UserService proxy = new UserServiceProxy(target);proxy.save();}
    }
    
    • 输出
      Before save: Logging...
      Saving user...
      After save: Logging...
      
  • 优点

    • 实现简单,逻辑清晰。
    • 适合目标类较少、代理逻辑固定的场景。
  • 缺点

    • 扩展性差:每个目标类需编写一个代理类,代码重复。
    • 维护困难:如果接口方法变更,所有代理类需修改。
  • 适用场景

    • 简单场景,目标类和代理逻辑固定。
    • 学习代理模式或临时实现。
  • 与Spring Bean的关系

    • 静态代理不常用于Spring容器,因为Spring更倾向于动态代理来实现AOP。
    • 如果在Spring中使用静态代理,需手动创建代理Bean并注入目标Bean,可能影响单例Bean的循环依赖解决(需显式管理代理和目标Bean)。

2. 动态代理

动态代理在运行时动态生成代理类,无需为每个目标类手动编写代理类。Java中主要有两种实现:JDK动态代理CGLIB动态代理

2.1 JDK动态代理
  • 定义:基于Java反射API(java.lang.reflect.Proxy),在运行时动态生成代理类,代理类实现目标对象实现的接口。

  • 特点

    • 要求目标类实现至少一个接口,代理类通过接口动态生成。
    • 使用InvocationHandler处理方法调用,代理逻辑集中在invoke方法中。
    • 代理对象是接口的实现,调用接口方法时转发到InvocationHandler
  • 代码示例

    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;// 公共接口
    public interface UserService {void save();
    }// 目标类
    public class UserServiceImpl implements UserService {@Overridepublic void save() {System.out.println("Saving user...");}
    }// InvocationHandler
    public class LoggingInvocationHandler implements InvocationHandler {private final Object target;public LoggingInvocationHandler(Object target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("Before save: Logging...");Object result = method.invoke(target, args);System.out.println("After save: Logging...");return result;}
    }// 使用
    public class Main {public static void main(String[] args) {UserService target = new UserServiceImpl();InvocationHandler handler = new LoggingInvocationHandler(target);UserService proxy = (UserService) Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),handler);proxy.save();}
    }
    
    • 输出
      Before save: Logging...
      Saving user...
      After save: Logging...
      
  • 优点

    • 动态生成代理类,无需手动编写。
    • 代理逻辑集中于InvocationHandler,易于复用。
    • 符合接口规范,适合基于接口的开发。
  • 缺点

    • 必须实现接口:目标类若无接口,无法使用JDK动态代理。
    • 性能略低于CGLIB(因反射调用)。
  • 适用场景

    • 目标类实现了接口(如Spring的AOP默认实现)。
    • 需要动态增强接口方法(如日志、事务)。
  • 与Spring Bean的关系

    • Spring AOP默认使用JDK动态代理(当目标Bean实现接口时)。
    • 单例Bean的代理对象由Spring容器管理,注入时返回代理对象而非原始Bean。
    • 循环依赖:Spring通过三级缓存支持代理对象的循环依赖。例如,BeanABeanB循环依赖,Spring在三级缓存中存储ObjectFactory,在需要时生成代理对象,确保循环依赖正确解析。

2.2 CGLIB动态代理
  • 定义:基于CGLIB(Code Generation Library),通过字节码生成技术在运行时动态创建目标类的子类作为代理类。

  • 特点

    • 不要求目标类实现接口,代理类通过继承目标类实现。
    • 使用MethodInterceptor拦截方法调用,代理逻辑在intercept方法中定义。
    • 代理类是目标类的子类,覆盖父类方法以添加额外逻辑。
  • 代码示例

    import net.sf.cglib.proxy.Enhancer;
    import net.sf.cglib.proxy.MethodInterceptor;
    import net.sf.cglib.proxy.MethodProxy;// 目标类(无接口)
    public class UserService {public void save() {System.out.println("Saving user...");}
    }// MethodInterceptor
    public class LoggingInterceptor implements MethodInterceptor {@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {System.out.println("Before save: Logging...");Object result = proxy.invokeSuper(obj, args);System.out.println("After save: Logging...");return result;}
    }// 使用
    public class Main {public static void main(String[] args) {Enhancer enhancer = new Enhancer();enhancer.setSuperclass(UserService.class);enhancer.setCallback(new LoggingInterceptor());UserService proxy = (UserService) enhancer.create();proxy.save();}
    }
    
    • 输出
      Before save: Logging...
      Saving user...
      After save: Logging...
      
  • 依赖

    • 需要引入CGLIB库(Spring Boot中通过spring-core已包含CGLIB)。
    • 示例依赖(Maven):
      <dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.3.0</version>
      </dependency>
      
  • 优点

    • 不需要接口,适用范围更广(可代理普通类)。
    • 性能略高于JDK动态代理(因字节码生成优于反射)。
    • 支持继承关系,代理类可调用父类的非final方法。
  • 缺点

    • 无法代理final类或final方法(因无法继承或覆盖)。
    • 依赖外部库(CGLIB)。
    • 生成的代理类是子类,可能引发继承相关问题。
  • 适用场景

    • 目标类无接口(如Spring AOP中无接口的Bean)。
    • 需要高性能动态代理。
  • 与Spring Bean的关系

    • Spring AOP在目标Bean无接口时使用CGLIB动态代理。
    • 代理对象作为单例Bean存储在Spring容器中,注入时返回代理对象。
    • 循环依赖:与JDK动态代理类似,CGLIB代理对象通过三级缓存的ObjectFactory生成,支持单例Bean的循环依赖解决。

3. 静态代理 vs JDK动态代理 vs CGLIB动态代理

特性静态代理JDK动态代理CGLIB动态代理
实现方式手动编写代理类基于反射(Proxy基于字节码(子类生成)
目标类要求需实现接口或继承必须实现接口无需接口(但不能是final类)
代理对象类型实现接口或继承目标类实现接口目标类的子类
性能高(无运行时开销)中等(反射调用)较高(字节码生成)
扩展性差(需为每个类写代理)高(动态生成)高(动态生成)
依赖JDK内置CGLIB库
循环依赖支持手动管理,可能复杂通过三级缓存支持通过三级缓存支持
Spring使用场景极少使用默认(有接口时)无接口时或强制指定

4. 代理与Spring Bean循环依赖

  • 单例Bean与代理

    • Spring AOP使用动态代理(JDK或CGLIB)为Bean生成代理对象,增强功能(如事务、日志)。
    • 代理对象存储在Spring的一级缓存(singletonObjects)中,客户端注入的是代理对象而非原始Bean。
    • 三级缓存的singletonFactories在循环依赖时生成代理对象。例如:
      • BeanABeanB循环依赖,BeanA启用了AOP。
      • Spring在三级缓存中存储BeanAObjectFactory,在BeanB需要BeanA时生成代理对象。
      • 确保循环依赖正确解析,注入的BeanA是代理对象。
  • 循环依赖示例

    @Component
    public class BeanA {@Autowiredprivate BeanB beanB;@Transactional // 启用AOP,生成代理public void doSomething() {System.out.println("BeanA doSomething");}
    }@Component
    public class BeanB {@Autowiredprivate BeanA beanA;public void doSomething() {System.out.println("BeanB doSomething");}
    }
    
    • 解决流程
      1. 创建BeanA,实例化后放入三级缓存(ObjectFactory)。
      2. BeanA注入beanB,触发BeanB创建,放入三级缓存。
      3. BeanB需要BeanA,从三级缓存获取BeanA的代理对象(因@Transactional)。
      4. BeanB完成,放入一级缓存;BeanA继续注入beanB,完成并放入一级缓存。
    • 结果BeanA的代理对象和BeanB成功注入,循环依赖解决。
  • 静态代理与循环依赖

    • 静态代理需手动创建代理Bean并注入目标Bean,可能干扰Spring的三级缓存机制。
    • 如果代理Bean和目标Bean都是单例,需确保Spring正确管理依赖关系,否则可能导致循环依赖失败。

5. Spring中代理的选择

  • 默认行为
    • 如果目标Bean实现接口,Spring使用JDK动态代理
    • 如果目标Bean无接口,Spring使用CGLIB动态代理
  • 强制CGLIB
    • @EnableAspectJAutoProxy中设置proxyTargetClass = true
      @EnableAspectJAutoProxy(proxyTargetClass = true)
      @Configuration
      public class AppConfig {}
      
    • 或在application.properties中:
      spring.aop.proxy-target-class=true
      
  • 注意事项
    • JDK动态代理生成接口代理,注入的Bean只能通过接口类型访问。
    • CGLIB生成子类代理,注入的Bean可以通过类类型访问,但需注意final方法不可代理。
    • 循环依赖中,Spring确保代理对象正确生成,客户端无需关心代理类型。

6. 注意事项

  • 性能
    • 静态代理性能最高(无运行时开销)。
    • CGLIB略快于JDK动态代理,但初始化时字节码生成有一定开销。
  • 线程安全
    • 代理对象(如单例Bean)在多线程环境下共享,需确保代理逻辑线程安全。
    • Spring的AOP代理通常无状态,天然线程安全。
  • 循环依赖
    • 动态代理(JDK/CGLIB)通过三级缓存无缝支持单例Bean的循环依赖。
    • 静态代理需手动管理,可能需@Lazy或Setter注入解决循环依赖。
  • AOP与代理
    • Spring AOP依赖动态代理实现切面(如@Transactional@Around)。
    • 三级缓存的ObjectFactory确保AOP代理在循环依赖中正确生成。
  • 限制
    • JDK动态代理要求接口,CGLIB无法代理final类/方法。
    • 静态代理扩展性差,不适合复杂系统。

7. 总结

  • 静态代理
    • 手动编写代理类,简单但扩展性差,适合简单场景。
    • 不常用于Spring,循环依赖需手动管理。
  • JDK动态代理
    • 基于接口,动态生成代理类,Spring AOP默认使用。
    • 支持循环依赖,适合接口驱动开发。
  • CGLIB动态代理
    • 基于子类,适用于无接口的类,性能略优。
    • 支持循环依赖,Spring在无接口时使用。
  • Spring中的应用
    • 动态代理是Spring AOP的核心,结合三级缓存解决单例Bean的循环依赖。
    • 推荐动态代理(JDK或CGLIB)而非静态代理,以支持复杂场景和AOP。

相关文章:

  • 告别 Transformer:Mamba 模型如何实现线性时间序列建模
  • 如何在 Ansys Icepak AEDT 中设置多个流程以加快仿真速度?
  • AGI大模型(12):向量检索之关键字搜索
  • 乐视系列玩机------乐视2 x620红灯 黑砖刷写教程以及新版刷写工具的详细释义
  • GSAP 动画引擎实战:打造丝滑动效交互组件库
  • 百度 Al 智能体心响 App 上线
  • 探秘 SenseGlove Nova 2力反馈手套,解锁 VR 键盘交互新方式
  • 高并发秒杀使用RabbitMQ的优化思路
  • 1.3 本书结构概览:从理论基础到实践案例的系统阐述
  • Python3中使用jupyter notebook
  • 美乐迪电玩大厅加载机制与 RoomList 配置结构分析
  • 给vue-admin-template菜单栏 sidebar-item 添加消息提示
  • WHAT - 静态资源缓存穿透
  • 蓝耘平台介绍:算力赋能AI创新的智算云平台
  • 深入探讨JavaScript性能瓶颈与优化实战指南
  • 【python】如何将文件夹及其子文件夹下的所有word文件汇总导出到一个excel文件里?
  • C++模板学习(进阶)
  • 火山引擎实时语音合成WebSocket V3协议Python实现demo
  • 自动化测试基础知识总结
  • Oracle在ERP市场击败SAP
  • 直播中抢镜“甲亢哥”的翁东华卸任!此前任文和友小龙虾公司董事
  • 科普书单·新书|鸟界戏精观察报告
  • 教育部召开全国中小学幼儿园安全工作视频会议:加强校园安防建设
  • 石中英已任新成立的清华大学教育学院院长
  • 美元指数跌破98关口,人民币对美元即期汇率升值至4月3日来新高
  • 中办、国办印发《农村基层干部廉洁履行职责规定》