1. 代理模式基础概念解析
代理模式是面向对象编程中一种常见的设计模式,它为其他对象提供一种代理以控制对这个对象的访问。在实际开发中,我们经常会遇到需要控制对象访问的场景,比如:
- 远程对象访问(RPC调用)
- 创建开销大的对象(延迟加载)
- 需要安全控制的对象
- 需要额外功能的对象(日志、监控等)
代理模式的核心在于通过一个代理对象来间接访问目标对象,这样可以在不修改原始对象的基础上,增加额外的功能或控制逻辑。根据代理类的创建时机和方式,我们可以将代理分为静态代理和动态代理两大类。
提示:代理模式与装饰器模式在实现上有些相似,但它们的关注点不同。装饰器模式侧重于动态添加功能,而代理模式侧重于控制访问。
2. 静态代理详解与实现
2.1 静态代理的实现原理
静态代理是最基础的代理实现方式,它的特点是在编译期就已经确定了代理关系。具体实现需要:
- 定义业务接口
- 创建真实对象实现业务接口
- 创建代理类同样实现业务接口
- 在代理类中持有真实对象的引用
- 在代理类方法中调用真实对象方法,并添加额外逻辑
// 业务接口 public interface UserService { void saveUser(String user); } // 真实对象 public class UserServiceImpl implements UserService { @Override public void saveUser(String user) { System.out.println("保存用户:" + user); } } // 代理类 public class UserServiceProxy implements UserService { private UserService target; public UserServiceProxy(UserService target) { this.target = target; } @Override public void saveUser(String user) { System.out.println("开始事务..."); target.saveUser(user); System.out.println("提交事务..."); } }2.2 静态代理的优缺点分析
优点:
- 实现简单,易于理解
- 编译期就能确定代理关系,运行效率高
- 可以在不修改目标对象的前提下扩展功能
缺点:
- 每个真实对象都需要创建一个代理类,当接口变化时,真实对象和代理类都需要修改
- 如果系统中有大量需要代理的类,会导致代理类数量膨胀
- 代理类和真实对象的关系在编译期就已经确定,不够灵活
2.3 静态代理的适用场景
静态代理适合以下场景:
- 代理类数量较少且稳定的系统
- 需要高性能的场景(因为静态代理没有运行时开销)
- 需要对少量特定方法进行增强的场景
注意:在实际项目中,如果发现需要为大量类创建几乎相同的代理逻辑时,就应该考虑使用动态代理了。
3. 动态代理深入解析
3.1 动态代理的核心机制
动态代理与静态代理最大的区别在于代理类的创建时机。动态代理的代理类是在程序运行时动态生成的,主要分为两类实现方式:
- 基于接口的动态代理:JDK原生支持,使用
java.lang.reflect.Proxy类 - 基于类的动态代理:第三方库如CGLib实现
动态代理的核心在于:
- 不需要手动编写代理类
- 通过反射机制在运行时生成代理类
- 通过
InvocationHandler接口统一处理代理逻辑
// 动态代理示例 public class DynamicProxyHandler implements InvocationHandler { private Object target; public DynamicProxyHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("动态代理前置处理"); Object result = method.invoke(target, args); System.out.println("动态代理后置处理"); return result; } } // 使用动态代理 UserService userService = new UserServiceImpl(); UserService proxy = (UserService) Proxy.newProxyInstance( userService.getClass().getClassLoader(), userService.getClass().getInterfaces(), new DynamicProxyHandler(userService) ); proxy.saveUser("张三");3.2 JDK动态代理实现细节
JDK动态代理的实现依赖于以下几个核心类:
java.lang.reflect.Proxy:负责创建代理对象java.lang.reflect.InvocationHandler:代理逻辑处理器
实现步骤:
- 创建实现
InvocationHandler接口的处理器类 - 通过
Proxy.newProxyInstance()方法创建代理对象 - 代理对象调用方法时,会自动转发到
InvocationHandler.invoke()方法
重要限制:
- 只能代理接口,不能代理类
- 目标对象必须实现至少一个接口
- 性能相比静态代理稍差(因为涉及反射调用)
3.3 CGLib动态代理实现
当目标对象没有实现接口时,可以使用CGLib实现基于类的动态代理。CGLib通过继承目标类并在子类中重写方法来实现代理。
// CGLib代理示例 public class CglibProxy implements MethodInterceptor { private Object target; public Object getInstance(Object target) { this.target = target; Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(target.getClass()); enhancer.setCallback(this); return enhancer.create(); } @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("CGLib前置处理"); Object result = proxy.invokeSuper(obj, args); System.out.println("CGLib后置处理"); return result; } } // 使用CGLib代理 UserServiceImpl userService = (UserServiceImpl) new CglibProxy().getInstance(new UserServiceImpl()); userService.saveUser("李四");CGLib特点:
- 可以代理普通类,不要求实现接口
- 通过生成子类的方式实现代理
- 性能比JDK动态代理更好(在较新JVM上)
- 需要引入第三方库
4. 静态代理与动态代理的对比分析
4.1 实现方式对比
| 对比维度 | 静态代理 | 动态代理 |
|---|---|---|
| 创建时机 | 编译期 | 运行期 |
| 代理类来源 | 手动编写 | 自动生成 |
| 代码量 | 多(每个代理类都需要单独编写) | 少(统一处理逻辑) |
| 维护成本 | 高(接口变化需要修改所有代理类) | 低(只需修改处理器逻辑) |
4.2 性能对比
在性能方面,静态代理通常最优,因为:
- 没有运行时生成类的开销
- 方法调用是直接的,没有反射开销
动态代理中:
- JDK动态代理由于使用反射调用,性能稍差
- CGLib代理在生成代理类后,方法调用通过方法索引直接定位,性能接近静态代理
实际测试数据(百万次调用耗时):
- 静态代理:约50ms
- CGLib代理:约80ms
- JDK动态代理:约120ms
4.3 适用场景对比
选择静态代理当:
- 代理类数量少且稳定
- 需要极致性能
- 代理逻辑简单且固定
选择动态代理当:
- 需要代理大量类
- 代理逻辑复杂或可能变化
- 目标对象可能动态变化
- 需要AOP编程(如Spring AOP)
5. 实际应用中的经验与技巧
5.1 代理模式的最佳实践
接口设计原则:
- 为需要代理的类设计清晰的接口
- 接口方法粒度要适中,不宜过大或过小
- 考虑将代理相关的方法集中定义
性能优化技巧:
- 对于高频调用的方法,考虑使用静态代理
- 在动态代理中缓存Method对象避免重复查找
- 对于不需要代理的方法,可以在InvocationHandler中做短路处理
线程安全考虑:
- 代理对象通常是多线程共享的
- 确保InvocationHandler的实现是线程安全的
- 避免在代理逻辑中使用共享可变状态
5.2 常见问题与解决方案
问题1:动态代理导致栈溢出
- 现象:在invoke方法中又调用了代理对象的方法,形成递归调用
- 解决:确保在invoke方法中调用的是原始对象的方法,而非代理对象的方法
问题2:CGLib代理final方法失效
- 现象:final方法无法被代理,因为CGLib通过继承实现代理
- 解决:避免为final方法添加代理逻辑,或改用其他方式实现功能增强
问题3:JDK动态代理接口变化
- 现象:接口新增方法后,代理类没有实现新方法
- 解决:重新生成代理对象,或使用更灵活的代理方案
5.3 Spring框架中的代理实现
Spring框架广泛使用了代理模式,特别是其AOP功能。Spring会根据目标对象的情况自动选择代理方式:
- JDK动态代理:当目标对象实现了接口时使用
- CGLib代理:当目标对象没有实现接口时使用
Spring代理配置技巧:
- 使用
proxy-target-class=true强制使用CGLib - 通过
@Scope(proxyMode=...)指定代理方式 - 注意
final方法和private方法无法被代理
// Spring AOP代理示例 @Configuration @EnableAspectJAutoProxy(proxyTargetClass = true) public class AppConfig { // 配置类 } @Aspect @Component public class LoggingAspect { @Before("execution(* com.example.service.*.*(..))") public void logBefore(JoinPoint joinPoint) { System.out.println("准备执行: " + joinPoint.getSignature()); } }在实际项目开发中,理解代理模式的实现原理和适用场景,能够帮助我们更好地设计和实现系统功能,特别是在需要横切关注点(如日志、事务、安全等)处理的场景下,代理模式提供了一种优雅的解决方案。