代理模式是一种理论上很是简单,可是各类地方的实现每每却很是复杂。本文将从代理模式的基本概念出发,探讨代理模式在java领域的应用与实现。读完本文你将get到如下几点:java
在生活中咱们一般是去商场购买东西,而不是去工厂。最主要的缘由可能有如下几种:程序员
在面向对象的系统中也有一样的问题,有些对象因为某种缘由,好比对象建立开销很大,或者某些操做须要安全控制等,直接访问会给使用者或者系统结构带来不少麻烦,这时咱们就须要考虑使用代理模式。面试
在应用中咱们可能会用代理模式解决如下问题:spring
代理模式:为其余对象提供一种代理以控制(隔离,使用接口)对这个对象的访问。类图以下:编程
所谓控制,其实使用接口隔离其余对象与这个对象之间的交互;就是为client对象对RealSubject对象的访问一种隔离,本质上就是CLient→RealSuject的关系变成了Client→Subject, Proxy→RealSubject。 须要注意的时,代理类(Proxy)并不必定要求保持接口的完整的一致性(既也能够彻底不需实现Subject接口),只要可以实现间接控制便可。安全
背景:假设已有一个订单系统,能够保存订单信息。cookie
需求:打印保存订单信息消耗时间。ide
/** * 订单服务 * * @author cruder * @date 2019-11-23 15:42 **/ public class OrderService2 { /** * 保存订单接口 */ public void saveOrder(String orderInfo) throws InterruptedException { // 随机休眠,模拟订单保存须要的时间 Thread.sleep(System.currentTimeMillis() & 100); System.out.println("订单:" + orderInfo + " 保存成功"); } }
直接修改源代码,这一般也是最简单和最容易想到的实现。函数
/** * 保存订单接口, 直接修改代码 */ public void saveOrder(String orderInfo) throws InterruptedException { long start = System.currentTimeMillis(); // 随机休眠,模拟订单保存须要的时间 Thread.sleep(System.currentTimeMillis() & 100); System.out.println("订单:" + orderInfo + " 保存成功"); System.out.println("保存订单用时: " + (System.currentTimeMillis() - start) + "ms"); }
面向对象设计原则中的“开闭原则”告诉咱们,开闭原则规定“软件中的对象(类,模块,函数等等)应该对于扩展是开放的,可是对于修改是封闭的”,这意味着一个实体是容许在不改变它的源代码的前提下变动它的行为。工具
/** * 1. 定义接口,为了使代理被代理对象看起来同样。固然这一步彻底能够省略 * * @author cruder * @date 2019-11-23 15:58 **/ public interface IOrderService { /** * 保存订单接口 * @param orderInfo 订单信息 */ void saveOrder(String orderInfo) throws InterruptedException; } /** * 2. 原有订单服务,也实现这个接口。注意 此步骤也彻底能够省略。 * * @author cruder * @date 2019-11-23 15:42 **/ public class OrderService implements IOrderService{ /** * 保存订单接口 */ @Override public void saveOrder(String orderInfo) throws InterruptedException { // 随机休眠,模拟订单保存须要的时间 Thread.sleep(System.currentTimeMillis() & 100); System.out.println("订单:" + orderInfo + " 保存成功"); } } /** * 3. 建立代理类,实现订单服务接口【这才是代理模式的实现】 * * @author cruder * @date 2019-11-23 16:01 **/ public class OrderServiceProxy implements IOrderService{ /** * 内部持有真实的订单服务对象,保存订单工做实际由它来完成 */ private IOrderService orderService; @Override public void saveOrder(String orderInfo) throws InterruptedException { /** * 延迟初始化,也能够建立代理对象时就建立,或者做为构造参数传进来 * 仅做为代码实例,不考虑线程安全问题 */ if (orderService == null) { orderService = new OrderService(); } long start = System.currentTimeMillis(); orderService.saveOrder(orderInfo); System.out.println("保存订单用时: " + (System.currentTimeMillis() - start) + "ms"); } }执行程序
执行程序
优势: 一、职责清晰。 二、高扩展性。 三、智能化。
缺点:
一、因为在客户端和真实主题之间增长了代理对象,所以有些类型的代理模式可能会形成请求的处理速度变慢。 二、实现代理模式须要额外的工做,有些代理模式的实现很是复杂。
在java中代理模式能够按照代理类的建立时机分两类,即静态代理和动态代理,而动态代理又能够分为jdk动态代理和cglib动态代理。每种实现方式都各有千秋,接下来笔者将回针对不一样的实现方式进行演示和剖析。
在上文代理模式代码演进中就使用了静态代理模式。所谓静态代理中的“静”字,无非就是代理类的建立时机不一样罢了。静态代理须要为每一个被代理的对象手动建立一个代理类;而动态代理则时在运行时经过某种机制来动态生成,不须要手动建立代理类。
jdk动态代理模式是利用java中的反射技术,在运行时动态建立代理类。接下来咱们仍借助上文中的订单服务的案例,使用jdk动态代理实现。
基于动态jdk涉及到两个核心的类Proxy类和一个 InvocationHandler接口。
/** * 基于JDK技术 动态代理类技术核心 Proxy类和一个 InvocationHandler 接口 * * @author cruder * @date 2019-11-23 16:40 **/ public class ProxyFactory implements InvocationHandler { /** * 委托对象,既被代理的对象 */ private Object target; public ProxyFactory (Object target) { this.target = target; } /** * 生成代理对象 * 1. Classloader loader: 制定当前被代理对象使用的累加子啊其,获取加载器的方法固定 * 2. Class<?>[] interfaces: 委托类的接口类型,使用泛型方法确认类型 * 3. InvocationHandler handler: 事件处理,执行委托对象的方法时会触发事件处理器方法, * 会把当前执行的委托对象方法做为参数传入 */ public Object getProxyInstance() { Class clazz = target.getClass(); return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { long start = System.currentTimeMillis(); method.invoke(target, args); System.out.println("保存订单用时: " + (System.currentTimeMillis() - start) + "ms"); return null; } } /** * 经过动态代理方式来保存订单 * * @author cruder * @date 2019-11-23 15:49 **/ public class Client { public static void main(String[] args) throws InterruptedException { ProxyFactory proxyFactory= new ProxyFactory (new OrderService()); IOrderService orderService = (IOrderService) proxyFactory.getProxyInstance(); orderService.saveOrder(" cruder 新买的花裤衩 "); } }
以上即是jdk动态代理的所有实现,有种只可意会不可言传的感受,笔者始终感受这种实现看起来很别扭。不过也要强行总结如下,jdk实现动态代理能够分为如下几个步骤:
代理类源码阅读
上文中基于jdk动态代理的代码实现中对于可*的产品经理来讲已经彻底知足了需求,可是对于具备Geek精神的程序员来讲这远远不够,对于这种不知其因此然的东西每每让人感到不安。接下来咱们将经过自定义的一个小工具类将动态生成的代理类保存到本地来一看究竟。
/** * 将生成的代理类保存为.class文件的工具类 * * @author cruder * @date 2019-08-15 0:27 */ public class ProxyUtils { /** * 将代理类保存到指定路径 * * @param path 保存到的路径 * @param proxyClassName 代理类的Class名称 * @param interfaces 代理类接口 * @return */ public static boolean saveProxyClass(String path, String proxyClassName, Class[] interfaces){ if (proxyClassName == null || path == null) { return false; } // 获取文件字节码,而后输出到目标文件中 byte[] classFile = ProxyGenerator.generateProxyClass(proxyClassName, interfaces); try (FileOutputStream out = new FileOutputStream(path)) { out.write(classFile); out.flush(); } catch (IOException e) { e.printStackTrace(); return false; } return true; } }
// 此处是重点, 生成的代理类实现了IOrderService,而且继承了Proxy public final class $Proxy0 extends Proxy implements IOrderService { private static Method m1; private static Method m3; private static Method m2; private static Method m0; public $Proxy0(InvocationHandler var1) throws { super(var1); } public final boolean equals(Object var1) throws { try { return (Boolean)super.h.invoke(this, m1, new Object[]{var1}); } catch (RuntimeException | Error var3) { throw var3; } catch (Throwable var4) { throw new UndeclaredThrowableException(var4); } } public final void saveOrder(Order var1) throws { try { super.h.invoke(this, m3, new Object[]{var1}); } catch (RuntimeException | Error var3) { throw var3; } catch (Throwable var4) { throw new UndeclaredThrowableException(var4); } } public final String toString() throws { try { return (String)super.h.invoke(this, m2, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final int hashCode() throws { try { return (Integer)super.h.invoke(this, m0, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } static { try { // 经过反射获取Method对象 m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object")); m3 = Class.forName("cn.mycookies.test08proxy.IOrderService").getMethod("saveOrder", Class.forName("cn.mycookies.test08proxy.Order")); m2 = Class.forName("java.lang.Object").getMethod("toString"); m0 = Class.forName("java.lang.Object").getMethod("hashCode"); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } } }
ps: 实习转正面试中被问到为何jdk动态代理被代理的类为何要实现接口?
对于cglib我想大多数人应该都很陌生,或者是在学习Spring中AOP(面向切面编程)时据说了它使用jdk和cglib两种方式实现了动态代理。接下来笔者将针对cglib进行简要介绍。
cglib动态代理和jdk动态代理相似,也是采用操做字节码机制,在运行时生成代理类。cglib 动态代理采起的是建立目标类的子类的方式,由于是子类化,咱们能够达到近似使用被调用者自己的效果。
字节码处理机制-指得是ASM来转换字节码并生成新的类
注:spring中有完整的cglib相关的依赖,因此如下代码基于spring官方下载的demo中直接进行编写的
/** * 1. 订单服务-委托类,不须要再实现接口 * * @author cruder * @date 2019-11-23 15:42 **/ public class OrderService { /** * 保存订单接口 */ public void saveOrder(String orderInfo) throws InterruptedException { // 随机休眠,模拟订单保存须要的时间 Thread.sleep(System.currentTimeMillis() & 100); System.out.println("订单:" + orderInfo + " 保存成功"); } } /** * cglib动态代理工厂 * * @author cruder * @date 2019-11-23 18:36 **/ public class ProxyFactory implements MethodInterceptor { /** * 委托对象, 即被代理对象 */ private Object target; public ProxyFactory(Object target) { this.target = target; } /** * 返回一个代理对象 * @return */ public Object getProxyInstance(){ // 1. 建立一个工具类 Enhancer enhancer = new Enhancer(); // 2. 设置父类 enhancer.setSuperclass(target.getClass()); // 3. 设置回调函数 enhancer.setCallback(this); // 4.建立子类对象,即代理对象 return enhancer.create(); } @Override public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { long start = System.currentTimeMillis(); Object result = method.invoke(target, args); System.out.println("cglib代理:保存订单用时: " + (System.currentTimeMillis() - start) + "ms"); return result; } }
/** * 使用cglib代理类来保存订单 * * @author cruder * @date 2019-11-23 15:49 **/ public class Client { public static void main(String[] args) throws InterruptedException { // 1. 建立委托对象 OrderService orderService = new OrderService(); // 2. 获取代理对象 OrderService orderServiceProxy = (OrderService) new ProxyFactory(orderService).getProxyInstance(); String saveFileName = "CglibOrderServiceDynamicProxy.class"; ProxyUtils.saveProxyClass(saveFileName, orderService.getClass().getSimpleName(), new Class[]{IOrderService.class}); orderServiceProxy.saveOrder(" cruder 新买的花裤衩 "); } }
cglib动态代理实现步骤和jdk及其类似,能够分为如下几个步骤:
cglib动态代理生成的代理类和jdk动态代理代码格式上几乎没有什么区别,惟一的区别在于cglib生成的代理类继承了仅仅Proxy类,而jdk动态代理生成的代理类继承了Proxy类的同时也实现了一个接口。代码以下:
// 生成一个Proxy的子类 public final class OrderService extends Proxy { private static Method m1; private static Method m2; private static Method m0; public OrderService(InvocationHandler var1) throws { super(var1); } public final boolean equals(Object var1) throws { try { return (Boolean)super.h.invoke(this, m1, new Object[]{var1}); } catch (RuntimeException | Error var3) { throw var3; } catch (Throwable var4) { throw new UndeclaredThrowableException(var4); } } public final String toString() throws { try { return (String)super.h.invoke(this, m2, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final int hashCode() throws { try { return (Integer)super.h.invoke(this, m0, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } static { try { m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object")); m2 = Class.forName("java.lang.Object").getMethod("toString"); m0 = Class.forName("java.lang.Object").getMethod("hashCode"); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } } }
JDK Proxy 的优点:
cglib 优点: