设计模式之动态代理
什么是动态代理网上已经讲了很多了,这里我就没必要讲了,只贴一个简单概念出来代理模式的定义:由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。代理模式的主要优点有:
代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;代理对象可以扩展目标对象的功能;代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度,增加了程序的可扩展性其主要缺点是:
代理模式会造成系统设计中类的数量增加在客户端和目标对象之间增加一个代理对象,会造成请求处理速度变慢;增加了系统的复杂度;Jdk动态代理实现的源码分析实现:jdk动态代理的实现为:实现java.lang.reflect.InvocationHandler接口例子:
class ProxyObject implements InvocationHandler { private final Object target; public ProxyObject(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Objects.requireNonNull(target); System.out.println("代理开始前"); Object ret = method.invoke(target, args); System.out.println("代理结束"); return ret; } }然后使用java.lang.reflect.Proxy的newProxyInstance方法
//调用jdk的api生成代理类 public static <T> T getProxy(Class<T> ret, Class<?>[] interfaces, InvocationHandler handler) { Object o = Proxy.newProxyInstance(handler.getClass().getClassLoader(), interfaces, handler); return (T) o; }所以要明白jdk动态代理实现,我们的切入点就是newProxyInstance方法这里贴出该方法中重要的代码段:
//newProxyInstance方法 ... //Look up or generate the designated proxy class. Class<?> cl = getProxyClass0(loader, intfs); .... //获取生成代理类的构造方法 final Constructor<?> cons = cl.getConstructor(constructorParams); ... //反射调用代理类的构造方法实例化 return cons.newInstance(new Object[]{h});从上面的代码可以看出来,我们关心的是getProxyClass0这个方法,怎么生成的代理类,我们继续往下看:
private static Class<?> getProxyClass0(ClassLoader loader, Class<?>... interfaces) { ... //可以看到,代理类是有缓存的,通过缓存的key值是对应的类加载器 return proxyClassCache.get(loader, interfaces); } //缓存的get方法 public V get(K key, P parameter) { ... Object cacheKey = CacheKey.valueOf(key, refQueue); // lazily install the 2nd level valuesMap for the particular cacheKey ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey); if (valuesMap == null) { ConcurrentMap<Object, Supplier<V>> oldValuesMap = map.putIfAbsent(cacheKey, valuesMap = new ConcurrentHashMap<>()); if (oldValuesMap != null) { valuesMap = oldValuesMap; } } // create subKey and retrieve the possible Supplier<V> stored by that // subKey from valuesMap //这里的apply方法生成subKey Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter)); Supplier<V> supplier = valuesMap.get(subKey); Factory factory = null; while (true) { if (supplier != null) { // supplier might be a Factory or a CacheValue<V> instance //get方法获取对应的代理对象 V value = supplier.get(); if (value != null) { return value; } if (factory == null) { factory = new Factory(key, parameter, subKey, valuesMap); } ... //将构建的factor赋值给supplier supplier = factory; } ... } ... } //Factory类的声明 private final class Factory implements Supplier<V> { ... @Override public synchronized V get() { // serialize access // re-check //主要部分 value = Objects.requireNonNull(valueFactory.apply(key, parameter)); ... } ... } //proxyClassCache的声明 /** * a cache of proxy classes */ private static final WeakCache<ClassLoader, Class<?>[], Class<?>> proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());从上面的代码中可以看出,代理类的生成是在ProxyClassFactory的apply方法中
//ProxyClassFactory的apply方法 @Override public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) { ... /* * Generate the specified proxy class. */ //生成代理类 byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces, accessFlags); try { return defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length); } catch (ClassFormatError e) { /* * A ClassFormatError here means that (barring bugs in the * proxy class generation code) there was some other * invalid aspect of the arguments supplied to the proxy * class creation (such as virtual machine limitations * exceeded). */ throw new IllegalArgumentException(e.toString()); } } }但是ProxyGenerator,generateProxyClass方法的源码在sun.misc包下面,没有找到源码。jdk动态代理大概流程但是jdk整个动态代理的大概流程,通过上面的分析,应该已经知晓了整理一下就是1.根据传入的接口生成一个代理类(这个代理类有一个参数为InvocationHandler的构造方法)2.将InvocationHandler的实现类传入到代理类的构造方法中,并实例化代理类数据结构根据这个我们可以大概推敲一下代理类的数据结构,这里贴出我所认为的结构(基于Human接口):
//接口Human interface Human { void say(String args); } class Chinese implements Human { @Override public void say(String word) { System.out.println("哈哈,中国:" + word); } } //我所认为的jdk为human接口生成的代理类 class SimulateProxy implements Human { private final InvocationHandler handler; public SimulateProxy(InvocationHandler handler) { this.handler = handler; } @Override public void say(String word) { Objects.requireNonNull(handler); try { Method say = Human.class.getDeclaredMethod("say", String.class); Object invoke = handler.invoke(this, say, new Object[]{word}); } catch (Throwable e) { e.printStackTrace(); } } } //测试猜测 class ProxyObject implements InvocationHandler { private final Object target; public ProxyObject(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Objects.requireNonNull(target); System.out.println("代理开始前"); Object ret = method.invoke(target, args); System.out.println("代理结束"); return ret; } } public static void main(String[] args) { Chinese chinese = new Chinese(); Class<?>[] classes = new Class<?>[]{Human.class}; InvocationHandler handler = new ProxyObject(chinese); SimulateProxy proxy0 = new SimulateProxy(handler); proxy0.say("推测的结构") } ///////////////////// // 运行结果: // 代理开始前 // 哈哈,中国:推测的结构 // 代理结束好了,通过上面的描述,我们清楚了动态代理生成代理类的结构,所以现在要自己实现类似jdk的动态代理就剩下一个问题,怎么根据接口来生成对应的代理类。这里会用到一个java字节码生成框架javassist自己实现动态代理类似于Jdk的动态代理思路:根据接口生成动态代理的类的思路如下:1.先利用反射获取接口信息,主要是方法签名(方法名称,方法返回类型,方法入参类型)2.利用javassist根据接口信息生成对应的字节码3.返回该字节码
点击查看源码gitee.com/zzlsss/tools/blob/master/ztools-core/src/main/java/github/zhp/core/lang/ref/GenProxy%240.java上面的代理实现了单个接口的动态代理,并且兼容Jdk的InvocationHandler接口,如果需要实现多接口的代理,也简单,只需要将多个接口的方法信息传入上面的生成函数即可。