基本流程
主方法
在该方法中,我们呈现了在日常使用 JDK 动态代理机制的方法。
public class VehicleDynamicProxy {
/**
* 被代理对象
*/
public Vehicle targetVehicle;
public VehicleDynamicProxy(Vehicle targetVehicle) {
this.targetVehicle = targetVehicle;
}
public Vehicle getProxy() {
//获取类的加载器
ClassLoader classLoader = targetVehicle.getClass().getClassLoader();
//获取类的接口数组
Class<?>[] interfaces = targetVehicle.getClass().getInterfaces();
//调用函数,用来的调用方法
InvocationHandler invocationHandler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
try {
System.out.println("前置通知 before");
//调用被代理对象的方法
result = method.invoke(targetVehicle, args);
System.out.println("返回通知 afterReturning");
} catch (Exception e) {
//异常通知 AfterThrowing throw new RuntimeException(e);
} finally {
//最终通知 After }
return result;
}
};
//此时的proxy对象是实现了Vehicle接口,继承了Proxy类的代理对象
Vehicle proxy = (Vehicle) Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
return proxy;
}
}
解读:
- 我们应该特别注意到
Proxy.newProxyInstance()
方法,该方法就是我们关注的生成代理对象的方法; - 接下来,我们进入到该方法的内部去观察;
Proxy.newProxyInstance ()
为突出方法重点,我们只保留了核心逻辑,省略了异常捕获和其他校验的代码。
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
Objects.requireNonNull(h);
final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
Class<?> cl = getProxyClass0(loader, intfs);
final Constructor<?> cons = cl.getConstructor(constructorParams);
return cons.newInstance(new Object[]{h});
}
解读:
- 我们先校验了相关权限,然后通过
getProxyClass0(loader, intfs);
生成代理类的 Class 对象(此时,我们可以通过这个对象生成代理类实例),最后我们通过构造器的方式生成代理类实例; - 毫无疑问,
getProxyClass0
方法是最重要的。这也是我们探索的主要阵地;
getProxyClass0 ()
private static Class<?> getProxyClass0(ClassLoader loader,
Class<?>... interfaces) {
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
// If the proxy class defined by the given loader implementing
// the given interfaces exists, this will simply return the cached copy; // otherwise, it will create the proxy class via the ProxyClassFactory
return proxyClassCache.get(loader, interfaces);
}
解读:
- 从源码注释我们就可以知道,倘若类加载器和接口数组都已经存在,那么就会通过 ProxyClassFactory 创建一个代理类的 Class 对象;
- 很明显,我们应该看 ProxyClassFactory 类;
ProxyClassFactory
该类是 Proxy 类的一个静态内部工厂类。如下图所示:
我们还可以发现该类只有一个方法apply()
,所以只有这个方法才可以生成我们的代理类:
接下来是对于ProxyClassFactory.apply()
的解读,同样,我们会精简出核心的逻辑:
@Override
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
// package to define proxy class in
String proxyPkg = null;
int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
if (proxyPkg == null) {
// if no non-public proxy interfaces, use com.sun.proxy package
proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
}
long num = nextUniqueNumber.getAndIncrement();
String proxyName = proxyPkg + proxyClassNamePrefix + num;
/*
* Generate the specified proxy class. */
*/
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
return defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
}
}
解读:
-
首先我们先生成一个代理类的名字
proxyName
,proxyClassNamePrefix
的值就为我们喜闻乐见的:
同时也为我们指示了包名:
-
然后,我们通过
generateProxyClass
方法生成了代理类的字节码,可以看到这里特别点名要使用到我们提供的接口数组; -
最后,调用本地方法
defineClass0
生成 Class 对象: -
显然,我们应该继续追
generateProxyClass
方法的源码;
ProxyGenerator. generateProxyClass ()
参数描述:
- var0:代理类的名字;
- var1:接口数组;
- var2:权限校验参数(不用管);
public static byte[] generateProxyClass(final String var0, Class<?>[] var1, int var2) {
ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2);
final byte[] var4 = var3.generateClassFile();
if (saveGeneratedFiles) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
try {
int var1 = var0.lastIndexOf(46);
Path var2;
if (var1 > 0) {
Path var3 = Paths.get(var0.substring(0, var1).replace('.', File.separatorChar));
Files.createDirectories(var3);
var2 = var3.resolve(var0.substring(var1 + 1, var0.length()) + ".class");
} else {
var2 = Paths.get(var0 + ".class");
}
Files.write(var2, var4, new OpenOption[0]);
return null; } catch (IOException var4x) {
throw new InternalError("I/O exception saving generated file: " + var4x);
}
}
});
}
return var4;
}
解读:
- 其实该方法只用看前两行。通过
var3.generateClassFile()
生成一个 byte 数组var4
,该数组就是代理类对应的 Class 对象的字节表示,该数组会通过本地方法defineClass0
生成 Class 对象; saveGeneratedFiles
属性主要是决定程序是否将生成的动态代理对象保存到磁盘上,这个属性将在我们剖析生成的动态代理类结构发挥极大的作用;
总结
通过上述对于源码的解析,我们可以发现,动态代理对象的创建涉及到底层本地方法,也就是说,动态代理对象是通过我们提供的类信息由 JVM 虚拟机自动创建的。这就是动态代理区别于其他代理方式的根本不同。
动态代理方式提供了更加的灵活的选择。
不过,我们还没有解决为什么在生成代理对象的时候要给出接口的问题。
我们将通过解析运行阶段生成的代理对象来分析该问题。
代理对象详解
调试理解
我们先通过调试来看看这个对象究竟是什么:
解读:
- 我们的动态代理类原来叫做
$Proxy0
,我们接下来直接通过一些手段拿到该动态代理类的字节码,然后进行反编译;
拿到动态代理对象
注意到,ProxyGenerator.generateProxyClass()
方式中的 saveGeneratedFiles
参数,我们通过这个参数来让动态代理对象写入到磁盘中。
有两种方法将该参数调整为 true。
设计 vm 参数:
-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true
在生成代理对象前调用如下代码:
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
于是,我们就可以在项目主目录下,拿到 JVM 自动生成的动态代理类:
代理对象剖析
反编译后如下,我们在该类中省略了无关的方法实现,例如 hashcode
、toString
,只保留了被代理对象实现的方法:
package com.sun.proxy;
import com.yelanyanyu.dynamicProxy.Vehicle;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class $Proxy0 extends Proxy implements Vehicle {
private static Method m3;
private static Method m4;
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
public final void run() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final String fly(int var1) throws {
try {
return (String)super.h.invoke(this, m4, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
static {
try {
m3 = Class.forName("com.yelanyanyu.dynamicProxy.Vehicle").getMethod("run");
m4 = Class.forName("com.yelanyanyu.dynamicProxy.Vehicle").getMethod("fly", Integer.TYPE);
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
解读:
- 从方法的描述
$Proxy0 extends Proxy implements Vehicle
可以看出,该代理类的类名就是我们之前分析的$Proxy0
; - 该类继承原来的 Proxy 类,实现我们提供的接口;
- 特别注意:
return (String)super.h.invoke(this, m4, new Object[]{var1});
,我们可以再次印证我在前一篇文章中得出的结论,代理对象的方法调用是通过转发到InvocationHandler
的 invoke 来实现的;
为什么一定是接口
现在我们就可以来解释解释这个问题了。原因主要有:
- 代理对象是动态创建的,也就是说,程序员刚开始并不知道其类名,也不能在编译阶段使用这个代理对象。所以,我们只能使用 OOP 原则中的向下转型,用
$Proxy0
的父类或者其实现的接口来接收,但是$Proxy0
的父类已经是 Proxy 类了(Proxy 类中没有对应的方法);由于 Java 是单继承的,所以只能用实现接口的方式,来让接口接受 JVM 创建的动态代理对象,如下:
Vehicle proxy = (Vehicle) Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
解读:
-
我们不可能写成
$Proxy0 proxy = Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
-
或者
Proxy proxy = (Proxy) Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
-
另一个原因则是默认了对象采用了合适的模板设计模式:类的绝大部分方法都在接口中有实现,代理对象可以使用到被代理对象的绝大部分方法。