尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

设计模式结构型——代理模式

设计模式结构型——代理模式 目录什么是代理模式代理模式的用途代理模式的实现代理模式的角色代理模式的类图代理模式的分类静态代理JDK动态代理CGLIB动态代理代理模式的特点优点缺点使用场景注意事项与其他模式比较什么是代理模式代理模式Proxy Pattern是一种结构型设计模式它允许通过创建一个代理对象来间接访问原始对象。代理模式的核心思想是将对目标对象的访问进行控制并在访问前后执行一些额外操作以增强或限制原始对象的功能。在代理模式中代理对象和原始对象都实现相同的接口使得客户端无需知道实际的对象只需要通过代理去访问目标对象。代理对象可以拦截对目标对象的访问然后决定是否允许、何时以及如何访问目标对象。总之代理模式通过引入代理对象实现了对目标对象的间接访问和控制提供了更加灵活和安全的对象访问方式。代理模式的用途代理模式的常见用途包括远程代理通过代理对象在不同的地址空间中访问远程对象隐藏了网络通信细节。虚拟代理延迟创建开销较大的对象直到真正需要使用时才进行创建以提升性能。安全代理控制对敏感对象的访问限制非授权用户的操作权限。智能代理在访问对象前后执行额外的逻辑如缓存结果、记录日志、实现懒加载等。代理模式的实现代理模式的角色抽象主题角色Subject定义了真实主题Real Subject和代理Proxy之间的共同接口代理类和被代理类都要实现该接口这样代理对象可以替代真实主题进行操作。真实主题角色Real Subject定义了代理所代表的真实对象是最终执行业务逻辑的对象供代理角色调用。代理角色Proxy实现了抽象主题接口并维护一个指向真实主题对象的引用是真实角色的代理, 需要持有真实角色的引用。代理对象可以在执行真实主题操作前后进行一些额外处理。通过代理模式客户端可以通过与代理对象进行交互来完成任务代理对象在必要时会调用真实对象来执行具体的业务逻辑。这种方式可以增加额外的功能同时也可以隐藏真实对象的细节实现了客户端与真实对象之间的解耦。代理模式的类图代理模式的分类代理模式分为静态代理JDK动态代理CGLIB动态代理三类。静态代理、JDK动态代理和CGLIB动态代理都是常见的代理模式实现方式它们在实现上有一些区别静态代理静态代理需要手动编写代理类代理类和真实类实现相同的接口或继承相同的父类。在编译期间代理类的代码就已经确定无法在运行时动态修改代理行为。需要为每个真实类编写一个对应的代理类导致代码冗余。JDK动态代理JDK动态代理利用Java反射机制生成代理类无需手动编写代理类。基于接口的代理代理对象必须实现一个或多个接口。代理类是在运行时动态生成的可以通过InvocationHandler接口在代理类的方法前后插入额外逻辑。JDK动态代理只能代理实现了接口的类对于没有实现接口的类不能进行代理。CGLIB动态代理CGLIB是针对类进行代理的方式通过继承来实现代理。CGLIB动态代理不需要目标类实现接口因此可以代理没有实现接口的类。代理类是通过字节码技术在运行时动态生成的生成的代理类是目标类的子类。CGLIB代理相比JDK动态代理的效率略低。综上所述静态代理在编译期间就确定代理类需要为每个真实类编写代理类JDK动态代理通过反射在运行时动态生成代理类代理对象必须实现接口而CGLIB动态代理是针对类进行代理不需要实现接口通过继承在运行时生成代理类。根据具体需求和场景选择适合的代理方式。下面将依次展现三种代理模式的实现静态代理其代码实现如下抽象主题角色代码package com.common.demo.pattern.proxy; /** * author Evan Walker 昂焱数据: https://www.ayshuju.com * version 1.0 * desc 抽象主题角色Subject 代理人 * date 2023/07/23 10:57:42 */ public interface Agency { void renting(); }真实主题角色代码package com.common.demo.pattern.proxy; /** * author Evan Walker 昂焱数据: https://www.ayshuju.com * version 1.0 * desc 真实主题角色Real Subject Evan * date 2023/07/23 10:59:29 */ public class Evan implements Agency{ Override public void renting() { System.out.println(Evan 有一百套房子要出租 ); } }代理角色代码package com.common.demo.pattern.proxy; /** * author Evan Walker 昂焱数据: https://www.ayshuju.com * version 1.0 * desc 代理角色Proxy 房屋代理人 * date 2023/07/23 11:00:31 */ public class ProxyAgency implements Agency { private Agency agency; public ProxyAgency(Agency agency){ this.agency agency; } Override public void renting() { System.out.println(向房客出租房屋); this.agency.renting(); System.out.println(完成售后服务); } }测试代码package com.common.demo.pattern.proxy; /** * author Evan Walker 昂焱数据: https://www.ayshuju.com * version 1.0 * desc 客户类 * date 2023/07/23 11:03:21 */ public class Client { public static void main(String[] args) { Agency evan new Evan(); Agency agency new ProxyAgency(evan); agency.renting(); } }测试截图JDK动态代理编写一个jdk代理实例的基本步骤如下编写业务接口因为jdk代理是基于接口的因此只能将业务方法定义成接口但它可以一次生成多个接口的代理对象编写调用处理器即编写一个java.lang.reflect.InvocationHandler接口的实现类代理对象的业务逻辑就写成该接口的invoke方法中生成代理对象有了业务接口和调用处理器后将二者作为参数通过Proxy.newProxyInstance方法便可以生成这个或这些接口的代理对象。比如上述示例代码中的businessProxy对象它拥有greeting()这个方法调用该方法时实际执行的就是invoke方法。其代码实现如下抽象主题角色代码package com.common.demo.pattern.proxyJdk; /** * author JdkEvan Walker 昂焱数据: https://www.ayshuju.com * version 1.0 * desc 抽象主题角色Subject 代理人 * date 2023/07/23 10:57:42 */ public interface JdkAgency { void renting(); }真实主题角色代码package com.common.demo.pattern.proxyJdk; /** * author JdkEvan Walker 昂焱数据: https://www.ayshuju.com * version 1.0 * desc 真实主题角色Real Subject JdkEvan * date 2023/07/23 10:59:29 */ public class JdkEvan implements JdkAgency { Override public void renting() { System.out.println(JdkEvan 有一百套房子要出租 ); } }代理角色代码package com.common.demo.pattern.proxyJdk; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * author JdkEvan Walker 昂焱数据: https://www.ayshuju.com * version 1.0 * desc 代理角色Proxy 房屋代理人 * date 2023/07/23 11:00:31 */ public class JdkProxyAgency implements InvocationHandler { //真实对象 private Object target; /** * 建立代理对象和真实对象的代理关系方法并返回代理对象 * * param target 真实对象 * return 代理对象 */ public Object bing(Object target) { this.target target; return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this); } 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; } }测试代码package com.common.demo.pattern.proxyJdk; /** * author JdkEvan Walker 昂焱数据: https://www.ayshuju.com * version 1.0 * desc 客户类 * date 2023/07/23 11:03:21 */ public class JdkClient { public static void main(String[] args) { JdkProxyAgency jdkProxyAgency new JdkProxyAgency(); //绑定关系因为挂在JdkAgency接口下所以声明代理对象 jdkProxyAgency JdkAgency proxy (JdkAgency) jdkProxyAgency.bing(new JdkEvan()); //注意 此时JdkEvan对象已经是一个代理对象它会进入代理的逻辑方法invoke proxy.renting(); } }测试截图CGLIB动态代理编写一个cglib代理实例的基本步骤如下添加依赖首先需要在项目中添加 CGLIB 的相关依赖。可以通过 Maven 或其他构建工具将 CGLIB 加入到项目中。创建目标类定义一个目标类被代理类它不需要实现任何接口。创建拦截器类编写一个拦截器类实现MethodInterceptor接口并重写intercept方法。该方法在代理对象的方法调用前后执行额外的逻辑。创建代理对象使用 CGLIB 的Enhancer类来生成代理对象。设置目标类为父类设置拦截器为回调方法。调用代理对象通过代理对象调用目标类的方法这时会先调用拦截器中的逻辑再调用目标类的方法。抽象主题角色代码(可不需要)真实主题角色代码package com.common.demo.pattern.proxyCglib; /** * author CglibEvan Walker 昂焱数据: https://www.ayshuju.com * version 1.0 * desc 真实主题角色Real Subject CglibEvan * date 2023/07/23 10:59:29 */ public class CglibEvan{ public void renting() { System.out.println(CglibEvan 有一百套房子要出租 ); } }代理角色代码package com.common.demo.pattern.proxyCglib; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; /** * author CglibEvan Walker 昂焱数据: https://www.ayshuju.com * version 1.0 * desc 代理角色Proxy 房屋代理人 * date 2023/07/23 11:00:31 */ public class CglibProxyAgency implements MethodInterceptor { Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println(带领房客看房...签租房协议); // 动态的回调父类当中的方法 methodProxy.invokeSuper(o, objects); System.out.println(售后服务); return o; } }测试代码package com.common.demo.pattern.proxyCglib; import net.sf.cglib.proxy.Enhancer; /** * author CglibEvan Walker 昂焱数据: https://www.ayshuju.com * version 1.0 * desc 客户类 * date 2023/07/23 11:03:21 */ public class CglibClient { public static void main(String[] args) { //生成空的字节码对象 Enhancer enhancer new Enhancer(); // 设置这个字节码对象的父类目标对象 enhancer.setSuperclass(CglibEvan.class); //设置被增强的方法 enhancer.setCallback(new CglibProxyAgency()); //得到代理对象 CglibEvan factory (CglibEvan) enhancer.create(); //执行被代理的方法 factory.renting(); } }测试截图代理模式的特点优点隐藏对象的具体实现代理模式通过代理对象与客户端进行交互客户端无需知道实际的对象是如何实现的从而将对象的具体实现细节隐藏起来。控制对对象的访问代理对象可以控制对目标对象的访问并在访问前后执行一些额外操作。例如在访问一个敏感对象时代理可以验证用户的权限在访问一个远程对象时代理可以处理网络通信等。扩展原始对象的功能代理模式可以通过在代理对象中添加一些额外的功能来扩展原始对象的功能。这样可以避免直接修改原始对象的代码符合开闭原则。分离客户端和目标对象代理模式使得客户端只需要与代理对象进行交互无需直接与目标对象交互。这样可以降低系统的耦合度提高系统的灵活性和可维护性。懒加载在使用虚拟代理时可以延迟创建开销较大的对象直到真正需要使用时才进行创建。这样可以提升系统的性能并减少资源的占用。透明性代理模式可以实现透明的访问即客户端无需感知自己正在使用的是代理对象还是目标对象可以将代理视为目标对象的透明扩展。缺点引入代理对象会增加系统的复杂性涉及到多个类之间的交互增加了开发和维护的成本。由于代理模式需要额外的对象来进行封装和管理可能会导致系统的运行效率降低。代理模式在某些情况下可能会造成请求的处理速度变慢特别是远程代理的情况下会涉及到网络通信的延迟。如果代理对象的实现不当可能会导致系统出现更多的问题如资源泄漏、并发访问的问题等。使用场景远程代理Remote Proxy在客户端和远程对象之间建立代理使得客户端能够通过代理访问远程对象隐藏了网络通信的细节。虚拟代理Virtual Proxy当创建一个对象的成本很高时可以先使用代理对象来替代真实对象延迟真实对象的创建。例如图片加载时可以使用虚拟代理在需要显示图片时再真正加载。安全代理Protection Proxy控制对真实对象的访问权限。代理对象可以检查调用者是否具有执行特定操作的权限从而保护真实对象的安全性。缓存代理Caching Proxy为开销较大的计算结果提供缓存当再次请求相同的数据时直接返回缓存结果避免重复计算。日志记录代理Logging Proxy在调用真实对象的方法前后添加日志记录功能用于记录方法的调用信息、参数等方便调试和跟踪。延迟加载代理Lazy Loading Proxy延迟加载即只在真正需要时才创建真实对象代理对象会在第一次访问时进行初始化。这种方式可以提高系统启动速度和内存占用。AOP 切面编程Aspect-Oriented Programming代理模式常被应用于 AOP 中通过代理将横切逻辑如事务管理、日志记录与业务逻辑分离。注意事项接口设计要合理在定义抽象主题Subject时应该仔细考虑需要暴露给代理对象的方法避免过于冗杂或不必要的接口方法。选择适当的代理类型代理模式有静态代理和动态代理两种实现方式。静态代理需要手动编写代理类而动态代理则可以在运行时生成代理对象。根据具体需求选择合适的代理类型。理解代理对象与真实对象的关系代理对象作为真实对象的代表应该能够处理与真实对象相关的事务并在必要时将请求转发给真实对象。同时代理对象还可以在调用前后执行额外的操作如权限验证、性能监控等。考虑代理对象的生命周期代理对象的生命周期可能与真实对象不同。需要确保代理对象的创建、销毁等操作符合实际需求避免产生过多的代理对象或无效的代理对象。避免滥用代理模式代理模式适用于在访问真实对象之前或之后添加额外逻辑的场景。但过度使用代理模式可能会导致代码复杂性增加降低系统性能。应用场景考虑代理模式适用于很多场景比如远程代理、安全代理、延迟加载等。在应用代理模式时要明确自己的需求选择合适的代理实现。与其他模式比较代理模式和装饰者模式的不同装饰器模式强调的是增强自身增强后你还是你只不过能力更强了而已。代理模式强调要让别人代理类帮你去做一些本身与你业务没有太多关系的职责记录日志、设置缓存代理模式和外观模式的不同代理模式是为了实现对象的控制因为被代理的对象往往难以直接获得或者是其内部不想暴露出来。外观模式定义了一个高层接口为多个子系统中的接口提供一个一致的界面不对目标功能进行增强。
返回列表