以前已经用了5篇文章完整解释了java动态代理的原理,本文将会为这个系列补上最后一块拼图,展现java动态代理的使用方式和应用场景html
主要分为如下4个部分java
1.为何要使用java动态代理redis
2.如何使用java动态代理spring
3.框架中java动态代理的应用数据库
4.java动态代理的基本原理设计模式
在设计模式中有一个很是经常使用的模式:代理模式。学术一些来说,就是为某些对象的某种行为提供一个代理对象,并由代理对象彻底控制该行为的实际执行。数组
通俗来讲,就是我想点份外卖,可是手机没电了,因而我让同窗用他手机帮我点外卖。在这个过程当中,其实就是我同窗(代理对象)帮我(被代理的对象)代理了点外卖(被代理的行为),在这个过程当中,同窗能够彻底控制点外卖的店铺、使用的APP,甚至把外卖直接吃了都行(对行为的彻底控制)。缓存
所以总结一下代理的4个要素:网络
从实际编码的角度来讲,咱们假设遇到了这样一个需求,须要记录下一些方法的执行时间,因而最简单的方式固然就是在方法的开头记录一个时间戳,在return以前记录一个时间戳。但若是方法的流程很复杂,例如:框架
public class Executor { public void execute(int x, int y) { log.info("start:{}", System.nanoTime()); if (x == 3) { log.info("end:{}", System.nanoTime()); return; } for (int i = 0; i < 100; i++) { if (y == 5) { log.info("end:{}", System.nanoTime()); return; } } log.info("end:{}", System.nanoTime()); return; } }
咱们须要在每个return前都增长一行记录时间戳的代码,很麻烦。因而咱们想到能够由方法的调用者来记录时间,例如:
public class Invoker { private Executor executor = new Executor(); public void invoke() { log.info("start:{}", System.nanoTime()); executor.execute(1, 2); log.info("end:{}", System.nanoTime()); } }
咱们又遇到一个问题,若是该方法在不少地方调用,或者须要记录的方法有多个,那么依然会面临重复手动写log代码的问题。
因而,咱们就能够考虑建立一个代理对象,让它负责帮咱们统一记录时间戳,例如:
public class Proxy { Executor executor = new Executor(); public void execute(int x, int y) { log.info("start:{}", System.nanoTime()); executor.execute(x, y); log.info("start:{}", System.nanoTime()); } }
而在Invoker中,则由直接调用Executor中的方法改成调用Proxy的方法,固然方法的名字和签名是彻底相同的。当其余地方须要调用execute方法时,只须要调用Proxy中的execute方法,就会自动记录下时间戳,而对于使用者来讲是感知不到区别的。以下示例:
public class Invoker { private Proxy executor; public void invoke() { executor.execute(1, 2); } }
上面展现的代理,就是一个典型的静态代理,“静态”体如今代理方法是咱们直接编码在类中的。
接着咱们就遇到了下一个问题,若是Executor新增了一个方法,一样要记录时间,那咱们就不得不修改Proxy的代码。而且若是其余类也有一样的需求,那就须要新建不一样的Proxy类才能较好的实现该功能,一样很是麻烦。
那么咱们就须要将静态代理升级成为动态代理了,而“动态”正是为了优化前面提到的2个静态代理遇到的问题。
建立java动态代理须要使用以下类
java.lang.reflect.Proxy
调用其newProxyInstance方法,例如咱们须要为Map建立一个代理:
Map mapProxy = (Map) Proxy.newProxyInstance( HashMap.class.getClassLoader(), new Class[]{Map.class}, new InvocationHandler(){...} );
咱们接着就来分析这个方法。先查看其签名:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
ClassLoader类型的loader:被代理的类的加载器,能够认为对应4要素中的被代理的对象。
Class数组的interfaces:被代理的接口,这里其实对应的就是4要素中的被代理的行为,能够注意到,这里须要传入的是接口而不是某个具体的类,所以表示行为。
InvocationHandler接口的h:代理的具体行为,对应的是4要素中的行为的彻底控制,固然也是java动态代理的核心。
最后返回的对象Object对应的是4要素中的代理对象。
接着咱们来示例用java动态代理来完成记录方法执行时间戳的需求:
首先定义被代理的行为,即接口:
public interface ExecutorInterface { void execute(int x, int y); }
接着定义被代理的对象,即实现了接口的类:
public class Executor implements ExecutorInterface { public void execute(int x, int y) { if (x == 3) { return; } for (int i = 0; i < 100; i++) { if (y == 5) { return; } } return; } }
接着是代理的核心,即行为的控制,须要一个实现了InvocationHandler接口的类:
public class TimeLogHandler implements InvocationHandler { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return null; } }
这个接口中的方法并不复杂,咱们仍是先分析其签名
Object类型的proxy:最终生成的代理对象
Method类型的method:被代理的方法。这里实际上是2个要素的复合,即被代理的对象是如何执行被代理的行为的。由于虽然咱们说要对行为彻底控制,但大部分时候,咱们只是对行为增添一些额外的功能,所以依然是要利用被代理对象原先的执行过程的。
Object数组的args:方法执行的参数
由于咱们的目的是要记录方法的执行的时间戳,而且原方法自己仍是依然要执行的,因此在TimeLogHandler的构造函数中,将一个原始对象传入,method在调用invoke方法时便可使用。
定义代理的行为以下:
public class TimeLogHandler implements InvocationHandler { private Object target; public TimeLogHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { log.info("start:{}", System.nanoTime()); Object result = method.invoke(target, args); log.info("end:{}", System.nanoTime()); return result; } }
接着咱们来看Invoker如何使用代理,这里为了方便演示咱们是在构造函数中实例化代理对象,在实际使用时能够采用依赖注入或者单例等方式来实例化:
public class Invoker { private ExecutorInterface executor; public Invoker() { executor = (ExecutorInterface) Proxy.newProxyInstance( Executor.class.getClassLoader(), new Class[]{ExecutorInterface.class}, new TimeLogHandler(new Executor()) ); } public void invoke() { executor.execute(1, 2); } }
此时若是Exector新增了任何方法,那么Invoker和TimeLogHandler将不须要任何改动就能够支持新增方法的的时间戳记录,有兴趣的同窗能够本身尝试一下。
另外若是有其余类也须要用到时间戳的记录,那么只须要和Executor同样,经过Proxy.newProxyInstance方法建立便可,而不须要其余的改动了。
接着咱们看一下java动态代理在如今的一些经常使用框架中的实际应用
spring aop是咱们spring项目中很是经常使用的功能。
例如咱们在获取某个数据的时候须要先去redis中查询是否已经有缓存了,若是没有缓存再去读取数据库。咱们就能够定义以下的一个切面和行为,而后在须要该功能的方法上增长相应注解便可,而再也不须要每一个方法单独写逻辑了。以下示例:
@Aspect @Component public class TestAspect { /** * 表示全部有cn.tera.aop.RedisPoint注解的方法 * 都会执行先读取Redis的行为 */ @Pointcut("@annotation(cn.tera.aop.RedisPoint)") public void pointCut() { } /** * 实际获取数的流程 */ @Around("pointCut()") public Object advise(ProceedingJoinPoint joinPoint) { try { /** * 先去查询redis */ Object data = RedisUtility.get(some_key); if (data == null) { /** * joinPoint.proceed()表示执行原方法 * 若是redis中没有缓存,那么就去执行原方法获取数据 * 而后塞入redis中,下次就能直接获取到缓存了 */ data = joinPoint.proceed(); RedisUtility.put(some_key, data); } return data; } catch (Throwable r) { return null; } } }
而其背后的原理使用的就是java动态代理。固然这里要求被注解的方法所在的类必须是实现了接口的(回想下Proxy.newProxyInstance方法的签名),不然就须要使用另一个GCLib的库了,不过这就是另一个故事了,这里就不展开了。
Spring AOP中大部分状况下都是给原执行逻辑添加一些东西。
在一些rpc框架中,客户端只须要关注接口的的调用,而具体的远程请求则由框架内部实现,例如咱们模拟一个简单的rpc 请求,接口以下:
public interface OrderInterface { /** * 生成一张新订单 */ void addOrder(); }
rpc框架能够生成接口的代理对象,例如:
public class SimpleRpcFrame { /** * 建立一个远程请求代理对象 */ public static <T> T getRemoteProxy(Class<T> service) { return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[]{service}, new RpcHandler(service)); } /** * 处理具体远程调用的类 */ static class RpcHandler implements InvocationHandler { private Class service; public RpcHandler(Class service) { this.service = service; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { /** * 根据接口名和方法名,发起一次特定的网络请求,获取数据 */ Object result = RemoteCallUtility.request(service.getName(), method.getName(), args); return result; } } }
而客户端调用的时候不须要接口的具体实现,只须要经过rpc框架获取接口的代理便可,此时到底是采用http协议或者直接经过socket请求数据都交由框架负责了,例如:
public class RpcInvoker { public void invoke() { OrderInterface order = SimpleRpcFrame.getRemoteProxy(OrderInterface.class); order.addOrder(); } }
RPC中的代理则是彻底不须要原执行逻辑,而是彻底地控制了行为的执行过程
那么框架使用java动态代理的示例就介绍到此。
以前我已经经过5篇文章完整介绍了java动态代理的实现原理,不过由于实在有些晦涩,因此这里我抛弃细节代码的解析,使得你们尽可能从直觉的角度来理解其基本原理。
假设咱们仍是实现一开始的添加时间戳的功能,此时,咱们须要以下代码获取其代理:
ExecutorInterface executor = (ExecutorInterface) Proxy.newProxyInstance( Executor.class.getClassLoader(), new Class[]{ExecutorInterface.class}, new TimeLogHandler() ); executor.execute(1, 2);
此时,咱们打印一下executor的实际类名、所实现的接口和父类的名称,获得结果以下:
类名:com.sun.proxy.$Proxy11 父类:java.lang.reflect.Proxy 实现接口:ExecutorInterface
所以,生成的代理类有以下3个特色:
1.继承了Proxy类
2.实现了咱们传入的接口
3.以$Proxy+随机数字的命名
接着咱们仍是须要略微查看一下newProxyInstance方法的源码,只须要关心下面几行核心代码,以下:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException { ... /** * 根据咱们传进来的接口建立一个类 */ Class<?> cl = getProxyClass0(loader, intfs); ... /** * 找到类的构造方法,该构造方法获取一个InvocationHandler类型的参数 */ final Constructor<?> cons = cl.getConstructor(constructorParams); ... /** * 经过构造方法生成代理类的实例 */ return cons.newInstance(new Object[]{h}); }
所以总结一下动态代理对象建立的过程
1.根据咱们传入的接口动态地建立一个Class
2.获取类的构造函数
3.将InvocationHandler做为参数传入构造函数,实例化代理对象的实例,并将其返回
固然,这里最核心的方法天然是类的建立,简而言之,就是在运行时,一个字节一个字节地构造一个字节数组,而这个字节数组正是一个.class字节码,而后经过一个native方法,将其转化为咱们运行时的Class类。
再通俗一些来讲:平时咱们使用的类都是预先编译好的.class文件,而动态代理则是直接在运行时经过组装一个byte数组的方式建立一个.class文件,这样应该就是比较好理解了吧。
若是对这个byte数组是如何构建的有兴趣,那么欢迎看一下我以前写的5篇文章,里面不只介绍了动态代理的源码,还能深刻了解一下class字节码更细节的结构
1.https://www.cnblogs.com/tera/p/13267630.html
2.https://www.cnblogs.com/tera/p/13280547.html
3.https://www.cnblogs.com/tera/p/13336627.html
4.https://www.cnblogs.com/tera/p/13419025.html
5.https://www.cnblogs.com/tera/p/13442018.html
最后,咱们来看一下这个生成出来的代理类究竟长啥样,正符合咱们以前总结出的代理对象的3个特色(在以前的文章中也有展现如何看到该内容)。特别注意的是由于全部的类都是继承自Object,所以除了咱们本身接口中定义的方法,还会有Object类的种的方法:
public final class $Proxy11 extends Proxy implements ExecutorInterface { private static Method m1; private static Method m2; private static Method m0; private static Method m3; public $Proxy11(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); } } public final void execute(int var1, int var2) throws { try { super.h.invoke(this, m3, new Object[]{var1, var2}); } catch (RuntimeException | Error var4) { throw var4; } catch (Throwable var5) { throw new UndeclaredThrowableException(var5); } } 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"); m3 = Class.forName("cn.tera.aopproxy.proxyuse.ExecutorInterface").getMethod("execute", Integer.TYPE, Integer.TYPE); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } } }
到此,java动态代理的基本介绍就结束了
最后咱们总结一下java动态代理的思想和原理
1.代理的4要素:代理对象、被代理的行为、被代理的对象、行为的彻底控制
2.代理的应用:方便地为某些行为添加一些共同的逻辑(Spring AOP)或者是将行为的执行彻底交由代理控制(RPC)
3.java动态代理的原理:在运行时构建一个class字节码数组,并将其转换成一个运行时的Class对象,而后构造其实例