定义:为某个对象提供一个代理,以达到对这个对象的访问控制,代理类和委托类有共同的父类或者父接口,这样可以在使用委托类的地方都可以使用代理对象来替换(这符合程序设计中的“里氏替换原则”),代理类负责请求的预处理、过滤等初步处理之后,再将请求分派给委托类进行处理,代理类当然也可以在委托类执行完毕之后做一些其它工作;
代理模式根据代理类的生成时间不同可以静态代理和动态代理。
静态代理:是由程序员创建或工具生成代理类的源码,在编译期就已经确定了委托类和代理类,在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就已经确定了。
动态代理:动态代理类的源码是在程序运行期间由JVM根据反射等机制动态生成的 ,所以不存在代理类的字节码文件,代理类和委托类的关系在程序运行时动态确定。
说了这么多那么代理有什么优点呢?
说起了一大堆,总的来说主要有两大点:1、可以对客户端隐藏委托类的实现;2、可以实现客户端和委托类之间的解耦,在不修改委托类的情况下做一些其他的处理,当然核心业务还得调用委托类的方法处理;
日常生活中代理的场景很常见,比如说我们有一套房子需要出售,但是我们没有时间自己天天带看房,那么我们就可以把这套房子挂委托给房产中介,中介就可以帮我们筛选潜在客户,带客户看房,确定买家之后,中介就联系我们和买家签订合同,买家付款、双方完成过户、房屋物业水电燃气交接事宜等,在这个过程中卖家就是委托类,而中介就是代理类;
其实在Java中也有很多场景需要使用代理,如RPC的远程调用,我们就是通过代理类去实现的,还有Spring中的AOP切面也是为切面生成了代理类;
下面我们先讲讲静态代理的实现:
1、定义接口和接口的实现(委托类);
2、定义代理类(定义接口的代理对象);
3、将接口的实例注入到代理对象中,然后通过代理对象去调用委托类的实现;
静态代理的示例代码如下:
package cn.com.pep.model.proxy; /** * * @version: 1.0 * @Description: 声明委托类的行为,卖房 * @author: wwh * @date: 2022年9月4日-下午4:17:12 */ public interface Sell { public void sellHouse(); }
package cn.com.pep.model.proxy; /** * * @version: 1.0 * @Description: 委托类 * @author: wwh * @date: 2022年9月4日-下午4:16:45 */ public class Seller implements Sell{ public void sellHouse() { System.err.println("我是房主,我要卖房"); } }
package cn.com.pep.model.proxy; /** * * @version: 1.0 * @Description: 代理类,和委托类实现共同的接口, * 并通过组合的方式持有一个委托类的引用,核心方法内部还是调用委托类来实现, * 这样就可以对客户端隐藏委托类的实现,同时也达到了委托类和客户端解耦的目标; * @author: wwh * @date: 2022年9月4日-下午3:40:16 */ public class SellerProxy implements Sell{ private Sell seller; public SellerProxy(Sell seller) { this.seller = seller; } private void lookBuyer() { System.err.println("寻找买家,带买家看房..."); } public void sellHouse() { lookBuyer(); seller.sellHouse(); getCharge(); } private void getCharge() { System.err.println("收取中介费"); } }
package cn.com.pep.model.proxy; /** * * @version: 1.0 * @Description: 测试类 * @author: wwh * @date: 2022年9月4日-下午5:17:45 */ public class StaticProxyPatternDemo { public static void main(String[] args) { Sell sell = new SellerProxy(new Seller()); sell.sellHouse(); } }
通过以上代码我们也不难发现静态代理的缺点也很明显:假设系统中有N个委托类需要代理,那么可能就需要N个代理类,这就容易造成系统的类爆炸,再者假如委托类中的方法很多,那么也可能在代理类中存在大量的重复代码,所以我们可以看出静态代理的可复用性不高。那么我们如何解决上面这个问题呢?答案就是动态代理。
动态代理分为两种一种是基于接口的jdk的动态代理,一种是基于继承的cglib的动态代理,我们先来说说jdk的动态代理。
一个JAVA类在JVM中的生命周期分为这几个过程:加载-》验证->准备-》解析-》初始化-》使用-》卸载,而其中的加载阶段主要完成以下三件事情:
1、通过一个类的全限定名来获取定义此类的二进制流;
2、将这个字节流所代表的静态数据结构转换为方法区的运行时数据结构;
3、在内存中生成一个代表这个类的java.lang.Class对象,作为方法区中这个类的各种数据的访问入口;
而我们要说的动态代理,主要就发生在第一个阶段, 这个阶段类的二进制字节流的来源可以有很多, 比如 zip 包、网络、运行时计算生成、其它文件生成 (JSP)、数据库获取。其中运行时计算生成就是我们所说的动态代理技术,在 Proxy 类中, 就是运用了 ProxyGenerator.generateProxyClass 来为特定接口生成形式为 *$Proxy
的代理类的二进制字节流。所谓的动态代理就是想办法根据接口或者目标对象计算出代理类的字节码然后加载进 JVM 中。实际计算的情况会很复杂,我们借助一些诸如 JDK 动态代理实现;
jdk的动态代理是在程序运行时,根据一组接口定义,使用Proxy、InvocationHandler等工具类生成代理对象的实例;
下面是jdk动态代理测试的demo:
package cn.com.pep.model.proxy.jdkproxy; /** * * @version: 1.0 * @Description: 委托类接口 * @author: wwh * @date: 2022年9月4日-下午7:01:48 */ public interface IService { public void doSomething(); public void doOtherthing(); }
package cn.com.pep.model.proxy.jdkproxy; /** * * @version: 1.0 * @Description: 委托类,执行核心的方法 * @author: wwh * @date: 2022年9月4日-下午7:02:57 */ public class ServiceImpl implements IService{ public void doSomething() { // TODO Auto-generated method stub System.err.println("执行doSomething方法"); } public void doOtherthing() { // TODO Auto-generated method stub System.err.println("执行doOtherthing方法"); } }
package cn.com.pep.model.proxy.jdkproxy; import java.util.Date; /** * * @version: 1.0 * @Description: * @author: wwh * @date: 2022年9月4日-下午7:05:51 */ public class ServiceUtil { public static void recoredBegin() { System.err.println("方法执行的开始时间是:" + new Date()); } }
package cn.com.pep.model.proxy.jdkproxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; /** * * @version: 1.0 * @Description: 中间类,通过组合持有一个委托类的引用,在invoke()方法中调用了委托类的中的目标方法 * @author: wwh * @date: 2022年9月4日-下午7:07:21 */ public class MyInvocationHandler implements InvocationHandler { /** * 委托类的引用 */ private Object target; public MyInvocationHandler(Object target) { // TODO Auto-generated constructor stub this.target = target; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //记录方法的执行开始时间 ServiceUtil.recoredBegin(); System.err.println("开始调用invoke()方法啦"); System.err.println("真正执行的方法是:" + method.getName()); //真正开始调用被代理对象的方法 Object obj = method.invoke(target, args); return obj; } }
package cn.com.pep.model.proxy.jdkproxy; import java.lang.reflect.Proxy; /** * * @version: 1.0 * @Description: 测试类 * @author: wwh * @date: 2022年9月4日-下午7:20:07 */ public class JDKProxyDemo { public static void main(String[] args) { IService impl = new ServiceImpl(); IService proxy = (IService) Proxy.newProxyInstance(impl.getClass().getClassLoader(), impl.getClass().getInterfaces(), new MyInvocationHandler(impl)); proxy.doSomething(); } }
在以上的测试代码中,我们调用Proxy类的newProxyInstance()方法来获取一个代理对象的实例,这个实例实现了我们指定的接口,并且会把方法调用分发到我们指定的调用处理器MyInvocationHandler中,调用invoke()方法,我们在invoke()方法中调用委托类的对应方法,并添加上自己的处理逻辑;
jdk的动态代理最大的特点是代理类和委托类实现共同的接口,jdk的动态代理内部其实是通过反射机制来实现的,已知一个对象,在运行的时候动态调用它的方法,并且在调用的时候还可以家上一些自己的逻辑在里面;
那假如没有结构我们该如何实现动态代理呢?这时候我们的cglib动态代理就横空出世啦。
cglib的动态代理是通过一个第三方框架来实现的,所以我们在使用的时候应该引入对应的jar包,例如:
其原理大致是:对指定的委托类生成一个子类并重写其中的业务方法来实现的;
cglib的测试代码如下:
package cn.com.pep.model.proxy.cglib; /** * * @version: 1.0 * @Description: 委托类 * @author: wwh * @date: 2022年9月4日-下午8:52:20 */ public class SayHello { public void sayHello() { System.err.println("目标方法执行了"); } }
package cn.com.pep.model.proxy.cglib; import java.lang.reflect.Method; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; /** * * @version: 1.0 * @Description: 创建一个拦截器,cglib动态代理,代理类的方法都会被分发到此类中的intercept()方法中 * @author: wwh * @date: 2022年9月4日-下午9:16:07 */ public class CglibInterceptor implements MethodInterceptor{ /** * obj,表示要增强的对象 * method,表示拦截的方法 * args,数组表示参数列表,基本数据类型需要传入其包装类型 * methodProxy,表示要触发父类的方法对象 */ public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { before(method.getName()); /** * 在方法的内部主要调用的methodProxy.invokeSuper,执行的原始类的方法。如果调用invoke方法否会出现死循环。 */ Object result = methodProxy.invokeSuper(obj, args); after(method.getName()); return result; } /** * 调用invoke方法之前执行 */ private void before(String methodName) { System.out.println("调用方法" + methodName +"之【前】的日志处理"); } /** * 调用invoke方法之后执行 */ private void after(String methodName) { System.out.println("调用方法" + methodName +"之【后】的日志处理"); } }
package cn.com.pep.model.proxy.cglib; import net.sf.cglib.proxy.Enhancer; /** * * @version: 1.0 * @Description: 测试类 * @author: wwh * @date: 2022年9月4日-下午9:29:22 */ public class CglibDemo { public static void main(String[] args) { //创建一个类增强器 Enhancer enhancer = new Enhancer(); //设置类加载器 enhancer.setClassLoader(enhancer.getClassLoader()); //设置被代理对象 enhancer.setSuperclass(SayHello.class); //设置方法访问拦截器 enhancer.setCallback(new CglibInterceptor()); //创建代理对象 SayHello proxy = (SayHello) enhancer.create();
//通过代理对象进行方法调用 proxy.sayHello(); } }
cglib动态代理的创建过程可以总结为以下几个步骤:
1、查找目标类定义的所有非final修饰的public类型的方法;
2、将符合条件的方法定义转换成字节码;
3、将字节码转换成相应的代理的class对象;
4、实现MethodInterceptor接口,用来处理对所有代理方法的拦截;
jdk动态代理和cglib动态代理的比较:
jdk的动态代理:基于反射来实现,委托类必须实现了接口才能创建代理类,代码实现简单,简化了开发和维护,jdk原生支持,反射速度较慢;
cglib的动态代理:基于ASM机制,通过字节码技术,通过生成委托类的子类,采用方法拦截的技术来拦截所有弗雷方法的调用,织入横切逻辑,完成代理,并且无需实现接口,达到代理类的无侵入,只关心业务类即可,并且是直接操作字节码生成的,速度上有一定的优势,但是无法对final修饰的方法进行代理,spring全家桶中的很多功能是通过这种方式来实现的;
注意事项:
1、和适配器模式相比,适配器模式重点在于改变所考虑对象的接口,而代理不能改变所代理对象的接口;
2、和装饰设计模式相比,装饰设计模式重点在于强调类功能的增强,而代理模式的重点在于对象的访问控制;
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:设计模式之(8)——代理模式 - Python技术站