【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通过三级缓存支持代理对象的循环依赖。例如,BeanA和BeanB循环依赖,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>
 
- 需要引入CGLIB库(Spring Boot中通过
-  优点: - 不需要接口,适用范围更广(可代理普通类)。
- 性能略高于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在循环依赖时生成代理对象。例如:- BeanA和- BeanB循环依赖,- BeanA启用了AOP。
- Spring在三级缓存中存储BeanA的ObjectFactory,在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");} }- 解决流程: - 创建BeanA,实例化后放入三级缓存(ObjectFactory)。
- 为BeanA注入beanB,触发BeanB创建,放入三级缓存。
- BeanB需要- BeanA,从三级缓存获取- BeanA的代理对象(因- @Transactional)。
- 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代理在循环依赖中正确生成。
 
- Spring AOP依赖动态代理实现切面(如
- 限制: - JDK动态代理要求接口,CGLIB无法代理final类/方法。
- 静态代理扩展性差,不适合复杂系统。
 
- JDK动态代理要求接口,CGLIB无法代理
7. 总结
- 静态代理: - 手动编写代理类,简单但扩展性差,适合简单场景。
- 不常用于Spring,循环依赖需手动管理。
 
- JDK动态代理: - 基于接口,动态生成代理类,Spring AOP默认使用。
- 支持循环依赖,适合接口驱动开发。
 
- CGLIB动态代理: - 基于子类,适用于无接口的类,性能略优。
- 支持循环依赖,Spring在无接口时使用。
 
- Spring中的应用: - 动态代理是Spring AOP的核心,结合三级缓存解决单例Bean的循环依赖。
- 推荐动态代理(JDK或CGLIB)而非静态代理,以支持复杂场景和AOP。
 
