署理形式能够说是使用最为广泛的规划形式之一,同时也是其他一些规划形式的根底或组成部分。
在上篇文章 浅显易懂 Retrofit 中,便是经过 动态署理 来完成详细的网络恳求逻辑。本着刨根究底的原则,这篇文章来探究一下动态署理的技能原理。
静态署理
在这之前,先来看一下根底的 静态署理 。
接口类 ICount
和完成类 Counter
:
public interface ICount {
void test1();
void test2();
}
public class Counter implements ICount {
@Override
public void test1() {
System.out.println("test1");
}
@Override
public void test2() {
System.out.println("test2");
}
}
现在要加一个需求,计算 Counter
类中每个办法的耗时。本着开闭原则,对扩展敞开,对修改封闭,最好不要去动 Counter 本来的逻辑,能够供给一个同样完成 ICount
的署理类 CounterProxy
:
public class CounterProxy implements ICount {
private Counter counter;
public CounterProxy(Counter counter) {
this.counter = counter;
}
@Override
public void test1() {
long start = System.currentTimeMillis();
counter.test1();
long end = System.currentTimeMillis();
System.out.println("test1" + " consume: " + (end - start));
}
@Override
public void test2() {
long start = System.currentTimeMillis();
counter.test2();
long end = System.currentTimeMillis();
System.out.println("test2" + " consume: " + (end - start));
}
}
这便是 静态署理 的基本运用。
看起来很简单,但是使用场景也很明显。在需要对接口办法做一致处理的场景下,典型的如 Retrofit,静态署理便是个灾难,你得为每一个接口办法都供给完成。
让静态署理动起来
有没有办法把这个进程自动化呢?这便是 动态署理 的功能:自动生成署理类。
自动生成 Class 文件的方案很多,生写硬怼,Javassist,ASM 都能够。那么,是不是照着静态署理类的结构,在运行时自动生成就能够了呢?
这样规划显然是不合理的,为每个接口办法动态生成完成逻辑,那每个办法单独的处理逻辑从哪来呢?
合理的做法应该把一切接口办法桥接收拢,一致署理给一个接口办法 invoke()
,将 办法称号和参数 作为 invoke()
办法的参数,这样动态署理的运用者就能够针对 methodName 进行对应处理。
现在你能够猜测一下动态生成的署理类的详细结构:
-
署理类不依赖详细的完成类,所以它也应该完成被署理的接口
-
署理类需要把一切接口办法的完成桥接给同一个接口,假定叫做
InvocationHandler.invoke()
用伪代码表示:
public class DynamicProxy implements ICount { private Method method1 = Class.forName("proxy.ICount").getMethod("test1"); private Method method2 = Class.forName("proxy.ICount").getMethod("test2"); private InvocationHandler handler; public DynamicProxy(InvocationHandler handler) { this.handler = handler; } @Override public void test1() { handler.invoke(this, method1, (Object[])null) } @Override public void test2() { handler.invoke(this, method2, (Object[])null) } }
除了一些代码细节,这其实已经很接近真正生成的动态署理类了。
咱们再来看 JDK 中动态署理的运用办法:
Counter counter = new Counter(); ICount proxyCount = (ICount) Proxy.newProxyInstance(counter.getClass().getClassLoader(), counter.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { long start = System.currentTimeMillis(); Object result = method.invoke(counter, args); long end = System.currentTimeMillis(); System.out.println(method.getName() + " consume: " + (end - start)); return result; } }); proxyCount.test1(); proxyCount.test2();
中心点在于经过
Proxy.newProxyInstance()
动态生成署理类。它有三个参数:-
ClassLoader loader
: 类加载器 -
Class<?>[] interfaces
:要署理的接口 -
InvocationHandler h
: 一切接口办法会被桥接到 InvocationHandler 的invoke()
办法。
invoke()
办法有三个参数。proxy
是署理类,method
是署理的办法,args
是署理办法的参数。既满足了对署理办法的一致处理,也能够针对 method 做单独处理。完全符合咱们之前的伪代码。
经过下面的 JDK 的参数配置,能够在当前目录直接生成动态署理类的 class 文件。
System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");
你能够在 这儿 检查生成的
com.sun.proxy.$Proxy0.class
文件。源码解析
动态署理的原理很简单,运行时动态生成并加载署理类 。咱们跟进源码,再加深一下印象。
PS:以下代码基于 OpenJdk 15 。
从
Proxy.newProxyInstance()
开端。public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) { Objects.requireNonNull(h); final Class<?> caller = System.getSecurityManager() == null ? null : Reflection.getCallerClass(); // 1. 获取署理类的 Constructor Constructor<?> cons = getProxyConstructor(caller, loader, interfaces); // 2. 创立署理类实例 return newProxyInstance(caller, cons, h); }
先看代码 2 处的
newProxyInstance()
:private static Object newProxyInstance(Class<?> caller, // null if no SecurityManager Constructor<?> cons, InvocationHandler h) { try { if (caller != null) { // 1. 权限检查 checkNewProxyPermission(caller, cons.getDeclaringClass()); } // 2. 创立署理类实例 return cons.newInstance(new Object[]{h}); } catch (IllegalAccessException | InstantiationException e) { throw new InternalError(e.toString(), e); } catch (InvocationTargetException e) { ...... } }
直接调用
Constructor.newInstance()
办法创立署理类实例,留意结构器参数是传入的InvocationHandler
目标。要点在于怎么获取署理类的结构器?再回到代码 1 处的
getProxyConstructor()
。private static Constructor<?> getProxyConstructor(Class<?> caller, ClassLoader loader, Class<?>... interfaces) { // 单接口的优化处理 // 为了方便,咱们直接看这儿 if (interfaces.length == 1) { Class<?> intf = interfaces[0]; if (caller != null) { checkProxyAccess(caller, loader, intf); } // 从 proxyCache 中获取,或者经过 ProxyBuilder 新建 return proxyCache.sub(intf).computeIfAbsent( loader, (ld, clv) -> new ProxyBuilder(ld, clv.key()).build() ); } else { ...... } }
这儿供给了 Constructor 的缓存类
proxyCache
防止重复生成。假如缓存中没有,就经过ProxyBuilder
获取结构器,传入的参数是 Classloader 和 Interface 。直接看
ProxyBuilder.build()
办法。Constructor<?> build() { // 1. 获取署理类的 Class 目标 Class<?> proxyClass = defineProxyClass(module, interfaces); final Constructor<?> cons; try { // 2. 依据署理类的 Class 目标获取结构器 cons = proxyClass.getConstructor(constructorParams); } catch (NoSuchMethodException e) { throw new InternalError(e.toString(), e); } ...... return cons; }
中心在于获取署理类 Class 目标的
defineProxyClass()
办法。private static Class<?> defineProxyClass(Module m, List<Class<?>> interfaces) { String proxyPkg = null; // 署理类是 public final 的 int accessFlags = Modifier.PUBLIC | Modifier.FINAL; ...... if (proxyPkg == null) { // PROXY_PACKAGE_PREFIX 值为 com.sun.proxy proxyPkg = m.isNamed() ? PROXY_PACKAGE_PREFIX + "." + m.getName() : PROXY_PACKAGE_PREFIX; } else if (proxyPkg.isEmpty() && m.isNamed()) { throw new IllegalArgumentException( "Unnamed package cannot be added to " + m); } ...... /* * proxyClassNamePrefix 值为 $Proxy * 生成的署理类名 com.sun.proxy.$Proxy0 * 结尾数字从 0 开端递加 */ long num = nextUniqueNumber.getAndIncrement(); String proxyName = proxyPkg.isEmpty() ? proxyClassNamePrefix + num : proxyPkg + "." + proxyClassNamePrefix + num; ...... /* * 生成署理类字节码 */ byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces.toArray(EMPTY_CLASS_ARRAY), accessFlags); try { Class<?> pc = UNSAFE.defineClass(proxyName, proxyClassFile, 0, proxyClassFile.length, loader, null); reverseProxyCache.sub(pc).putIfAbsent(loader, Boolean.TRUE); return pc; } catch (ClassFormatError e) { throw new IllegalArgumentException(e.toString()); } }
中心在于生成署理类字节码的
ProxyGenerator.generateProxyClass()
办法,有三个参数:-
name
: 署理类称号,$Proxy0 -
interfaces
: 署理接口,数组 -
accessFlags
:Modifier.PUBLIC | Modifier.FINAL
static byte[] generateProxyClass(ClassLoader loader, final String name, List<Class<?>> interfaces, int accessFlags) { ProxyGenerator gen = new ProxyGenerator(loader, name, interfaces, accessFlags); // 生成字节码文件 final byte[] classFile = gen.generateClassFile(); if (saveGeneratedFiles) { /* * 能够经过配置 jdk.proxy.ProxyGenerator.saveGeneratedFiles 参数 * 将字节码文件输出到本地文件 */ } return classFile; }
再跟进
ProxyGenerator.generateClassFile()
。private byte[] generateClassFile() { this.visit(58, this.accessFlags, dotToSlash(this.className), (String)null, "java/lang/reflect/Proxy", typeNames(this.interfaces)); // 增加 hashCode 办法 this.addProxyMethod(hashCodeMethod); // 增加 equals 办法 this.addProxyMethod(equalsMethod); // 增加 toString 办法 this.addProxyMethod(toStringMethod); Iterator var1 = this.interfaces.iterator(); while(var1.hasNext()) { Class<?> intf = (Class)var1.next(); Method[] var3 = intf.getMethods(); int var4 = var3.length; for(int var5 = 0; var5 < var4; ++var5) { Method m = var3[var5]; if (!Modifier.isStatic(m.getModifiers())) { // 增加署理接口办法 this.addProxyMethod(m, intf); } } } var1 = this.proxyMethods.values().iterator(); List sigmethods; while(var1.hasNext()) { sigmethods = (List)var1.next(); checkReturnTypes(sigmethods); } // 生成结构器 this.generateConstructor(); var1 = this.proxyMethods.values().iterator(); while(var1.hasNext()) { sigmethods = (List)var1.next(); Iterator var8 = sigmethods.iterator(); while(var8.hasNext()) { ProxyMethod pm = (ProxyMethod)var8.next(); this.visitField(10, pm.methodFieldName, "Ljava/lang/reflect/Method;", (String)null, (Object)null); pm.generateMethod(this, this.className); } } // 生成静态代码块 this.generateStaticInitializer(); return this.toByteArray(); }
ProxyGenerator
承继自ClassWriter
,经过 ASM 生成了字节码。这块就不细看了,详细 API 我也不是很清楚。挑一个生成结构器的generateConstructor()
办法看一下:private void generateConstructor() { MethodVisitor ctor = this.visitMethod(1, "<init>", "(Ljava/lang/reflect/InvocationHandler;)V", (String)null, (String[])null); ctor.visitParameter((String)null, 0); ctor.visitCode(); ctor.visitVarInsn(25, 0); ctor.visitVarInsn(25, 1); ctor.visitMethodInsn(183, "java/lang/reflect/Proxy", "<init>", "(Ljava/lang/reflect/InvocationHandler;)V", false); ctor.visitInsn(177); ctor.visitMaxs(-1, -1); ctor.visitEnd(); }
能够看到确实是将
InvocationHandler
作为了结构器参数,但并不是直接给署理类生成的,而是Proxy
类。署理类是承继自Proxy
类,并引证父类的结构器。生成署理类字节码文件之后,经过
UnSafe.defineClass()
注册到 VM 。public Class<?> defineClass(String name, byte[] b, int off, int len, ClassLoader loader, ProtectionDomain protectionDomain) { if (b == null) { throw new NullPointerException(); } if (len < 0) { throw new ArrayIndexOutOfBoundsException(); } return defineClass0(name, b, off, len, loader, protectionDomain); } public native Class<?> defineClass0(String name, byte[] b, int off, int len, ClassLoader loader, ProtectionDomain protectionDomain);
发问环节
到这,动态署理的流程就跟完了。
经典发问环节:动态署理只能署理接口吗?假如是,为什么?
在谈论区,留下你的答案吧!
-