【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。