资讯动态

代理模式详解:原理、应用与性能优化

发布时间:2026/10/26 11:54:18 来源:尧图企业网站定制
1. 代理模式为什么我们需要中间人想象一下这个场景你是一家跨国公司的CEO每天要处理上百封邮件。如果每封邮件都直接发到你手里恐怕你连喝咖啡的时间都没有。这时候秘书的作用就体现出来了——她帮你过滤垃圾邮件、整理重要信息、安排优先级最后只把真正需要你处理的内容递到你面前。这就是代理模式在现实生活中的完美体现。在软件工程中代理模式Proxy Pattern是一种结构型设计模式它为其他对象提供一种代理以控制对这个对象的访问。简单来说就是用一个替身对象来控制对本体对象的访问在访问本体对象之前或之后执行一些额外操作。提示代理模式与装饰器模式经常被混淆但它们的意图不同。装饰器模式关注的是动态添加功能而代理模式关注的是控制访问。2. 代理模式的四种典型应用场景2.1 虚拟代理延迟加载的智慧虚拟代理Virtual Proxy是最常见的代理类型之一它推迟了昂贵对象的创建直到真正需要时才进行实例化。这在处理大型对象如图片、视频时特别有用。// 图片接口 public interface Image { void display(); } // 真实图片类 public class RealImage implements Image { private String fileName; public RealImage(String fileName) { this.fileName fileName; loadFromDisk(); } private void loadFromDisk() { System.out.println(Loading fileName); } public void display() { System.out.println(Displaying fileName); } } // 图片代理类 public class ProxyImage implements Image { private RealImage realImage; private String fileName; public ProxyImage(String fileName) { this.fileName fileName; } public void display() { if (realImage null) { realImage new RealImage(fileName); } realImage.display(); } }在这个例子中ProxyImage充当了RealImage的代理只有在调用display()方法时才会真正加载图片。这种延迟加载技术可以显著提高应用程序的启动速度。2.2 保护代理访问控制的守卫者保护代理Protection Proxy控制对原始对象的访问权限。这在需要不同级别的访问控制时非常有用比如某些操作只允许管理员执行。class SensitiveData: def get_data(self): return This is sensitive data class DataProxy: def __init__(self, user_role): self._real_data SensitiveData() self._user_role user_role def get_data(self): if self._user_role admin: return self._real_data.get_data() else: return Access denied2.3 远程代理跨越网络的桥梁远程代理Remote Proxy为位于不同地址空间的对象提供本地代表。它隐藏了对象存在于不同地址空间的事实使得客户端可以像调用本地对象一样调用远程对象。RPC远程过程调用框架如gRPC、Dubbo等都大量使用了这种模式。2.4 智能引用代理对象管理的智能管家智能引用代理Smart Reference Proxy在访问对象时执行额外的操作比如引用计数当对象不再被引用时自动释放懒加载第一次引用时才加载锁定确保其他对象不能修改它日志记录记录方法调用3. 代理模式的UML结构与实现要点3.1 标准UML类图解析---------------- ---------------- ---------------- | Subject | | Proxy | | RealSubject | ---------------- ---------------- ---------------- | request():void|----| -realSubject: |------| request():void| ---------------- | RealSubject | ---------------- ---------------- | request():void| ----------------Subject定义RealSubject和Proxy的公共接口RealSubject定义代理所代表的真实对象Proxy维护一个引用使得代理可以访问真实对象控制对真实对象的访问可能负责创建和删除它3.2 实现时的五个关键决策点接口设计代理和被代理对象必须实现相同的接口这是代理模式的核心原则之一。如果被代理类没有接口可以考虑使用继承但这会增加耦合度。创建时机静态代理编译时确定代理关系动态代理运行时动态创建如Java的Proxy类、CGLib职责划分代理类应该只负责访问控制不应该包含业务逻辑如果发现代理类中出现了业务逻辑可能需要考虑重构性能考量代理会引入额外的间接层可能影响性能对于频繁调用的简单方法代理可能不是最佳选择线程安全如果多个线程可能同时访问代理需要考虑同步问题但要注意避免过度同步导致的性能问题4. 动态代理运行时编织的魔法静态代理的一个主要缺点是每个代理类只能为一个接口服务这样程序中会产生过多的代理类。动态代理可以解决这个问题它在运行时动态创建代理类。4.1 Java动态代理实现import java.lang.reflect.*; interface IService { void serve(); } class RealService implements IService { public void serve() { System.out.println(RealService is serving); } } class LoggingHandler implements InvocationHandler { private Object target; public LoggingHandler(Object target) { this.target target; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println(Before method: method.getName()); Object result method.invoke(target, args); System.out.println(After method: method.getName()); return result; } } public class DynamicProxyDemo { public static void main(String[] args) { IService realService new RealService(); IService proxy (IService) Proxy.newProxyInstance( IService.class.getClassLoader(), new Class?[] { IService.class }, new LoggingHandler(realService) ); proxy.serve(); } }4.2 CGLib动态代理与JDK动态代理不同CGLib可以代理没有实现接口的类。它通过生成被代理类的子类来实现代理。import net.sf.cglib.proxy.*; class ConcreteService { public void serve() { System.out.println(ConcreteService is serving); } } class MethodInterceptorImpl implements MethodInterceptor { public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println(Before method: method.getName()); Object result proxy.invokeSuper(obj, args); System.out.println(After method: method.getName()); return result; } } public class CglibProxyDemo { public static void main(String[] args) { Enhancer enhancer new Enhancer(); enhancer.setSuperclass(ConcreteService.class); enhancer.setCallback(new MethodInterceptorImpl()); ConcreteService proxy (ConcreteService) enhancer.create(); proxy.serve(); } }注意CGLib通过继承实现代理所以final类或final方法无法被代理。此外CGLib创建代理的速度较慢但运行效率更高适合单例对象或不需要频繁创建代理的场景。5. 代理模式的实战应用与性能优化5.1 Spring AOP中的代理模式Spring框架广泛使用代理模式来实现面向切面编程AOP。根据目标对象是否实现接口Spring会选择使用JDK动态代理或CGLib代理。Service public class UserServiceImpl implements UserService { Override Transactional public void addUser(User user) { // 业务逻辑 } }在这个例子中Transactional注解就是通过代理实现的。Spring会创建一个代理对象在调用addUser方法前后管理事务。5.2 MyBatis中的延迟加载MyBatis使用代理模式实现关联对象的延迟加载。当查询主对象时关联对象不会立即加载只有在真正访问关联对象时才会执行额外的SQL查询。resultMap idblogResultMap typeBlog id propertyid columnid/ result propertytitle columntitle/ association propertyauthor columnauthor_id javaTypeAuthor selectselectAuthor/ /resultMap5.3 RPC框架中的远程代理在分布式系统中远程服务调用通常通过代理模式实现。客户端调用本地代理对象的方法代理负责将调用转发到远程服务。FeignClient(name user-service) public interface UserServiceProxy { GetMapping(/users/{id}) User getUser(PathVariable(id) Long id); }5.4 性能优化技巧代理对象缓存对于创建成本高的代理对象应该考虑缓存和重用。减少代理层级避免多层代理嵌套这会显著增加方法调用的开销。选择性代理不是所有方法都需要代理可以为特定方法配置代理逻辑。异步代理对于耗时操作可以考虑异步代理不阻塞调用线程。代理工厂模式使用工厂模式统一管理代理对象的创建提高代码的可维护性。6. 代理模式的局限性与替代方案6.1 何时不该使用代理模式简单对象如果对象创建和访问的开销很小引入代理可能得不偿失。性能敏感场景代理会引入额外的间接调用在性能关键路径上可能不合适。过度设计如果访问控制需求很简单直接在被代理类中实现可能更清晰。6.2 常见替代方案装饰器模式当主要目的是添加功能而非控制访问时。适配器模式当需要转换接口而非控制访问时。外观模式当需要为复杂子系统提供简化接口时。6.3 代理模式与其他模式的组合代理工厂通过工厂方法创建代理对象隐藏创建细节。代理单例确保代理对象的唯一性。代理观察者代理可以作为主题通知观察者访问事件。7. 从源码看代理模式JDK动态代理的实现原理理解JDK动态代理的底层实现有助于我们更好地使用它。让我们深入Proxy类的源码public class Proxy implements java.io.Serializable { protected InvocationHandler h; protected Proxy(InvocationHandler h) { this.h h; } public static Object newProxyInstance(ClassLoader loader, Class?[] interfaces, InvocationHandler h) { // 1. 验证接口数组 final Class?[] intfs interfaces.clone(); // 2. 查找或生成代理类 Class? cl getProxyClass0(loader, intfs); // 3. 获取构造器 final Constructor? cons cl.getConstructor(constructorParams); // 4. 创建代理实例 return cons.newInstance(new Object[]{h}); } }关键点代理类是在运行时动态生成的字节码所有代理方法都会转发到InvocationHandler的invoke方法生成的代理类会继承Proxy类这也是为什么JDK动态代理只能基于接口生成代理类的过程大致如下生成类名通常为$ProxyN的形式生成方法代码将所有方法调用转发给InvocationHandler将字节码加载到JVM在实际项目中我遇到过动态代理导致栈溢出的情况。这是因为在invoke方法中又调用了代理对象的其他方法形成了递归调用。解决方法是在invoke方法中直接调用目标对象的方法而不是通过代理。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑