本文有以下三个主题:
Proxy的newProxyInstance原理
动态代理字节码技术分析
反编译动态代理生成的类的代码
前置声明:
下面列出的JDK源码中,隐藏了与分析无关的代码,并不影响整体流程的理解。 如需查看全部源码,请翻阅JDK源码
正文
今天手写了一个动态代理的代码生成,感觉不够过瘾,于是就对Proxy进行更深一层的探究。关于动态代理代码生成,我将会在另一篇文章中详细讲解,这里主要来探究一下Proxy的原理和JDK的字节码技术。
说到动态代理,不得不提到JDK中的Proxy类,它首先将业务类和InvocationHandler组装,然后生成一个实现了业务类接口的代理对象。这个代理对象对业务类的方法进行拦截,可以在业务方法前、后、异常等位置插入拦截代码,也可以对业务方法的参数、返回值进行修改,从而实现非常强大的代理功能。 Proxy的newProxyInstance原理 Proxy的核心方法就是newProxyInstance,具体源码分析如下
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
。。。。
//对接口数组进行复制
final Class<?>[] intfs = interfaces.clone();
。。。。
//核心中的核心,使用intfs创建一个代理对象,并使用loader进行加载,加载出来的类就是cl
Class<?> cl = getProxyClass0(loader, intfs);
try {
。。。
//获取一个参数为constructorParams的构造器,其中constructorParams定义为:
//private static final Class<?>[] constructorParams = { InvocationHandler.class };
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
。。。。。。
//利用上面的有参构造器和传入的参数h,创建一个实例对象
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
//异常处理的代码
}
}
在简化的源码,可以看到 Proxy 生成一个代理类的主要步骤如下: 利用getProxyClass0创建一个代理类 获取代理类的有参构造器,其中参数类型是:InvocationHandler.class 利用传入的参数和有参构造器创建一个对象并返回,完成整个代理类的创建
再继续探究下getProxyClass0是怎么创建一个代理类的。
private static Class<?> getProxyClass0(ClassLoader loader,
Class<?>... interfaces) {
//如果接口的数量大于65535个,就会有异常
//一般业务中,不会有这么多个接口,可以放心的使用
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
//大致意思:如果之前有缓存的Class就直接获取,如果没有,就使用ProxyClassFactory创建一个
// 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。
private static final class ProxyClassFactory
implements BiFunction<ClassLoader, Class<?>[], Class<?>>
{
//代理类的名称由两部分组成 proxyClassNamePrefix+nextUniqueNumber,比如:$Proxy0
//代理类的前缀名
private static final String proxyClassNamePrefix = "$Proxy";
//代理类里的编号
private static final AtomicLong nextUniqueNumber = new AtomicLong();
// 创建代理类的部分
@Override
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
//第一步 验证接口
for (Class<?> intf : interfaces) {
//第1步 验证所有的接口都是由loader加载的
//这里由疑问的同学,去了解一些类加载的机制和ClassLoader的命名空间
//我简单讲一下,同一个类被不同的ClassLoader加载出来的多个Class对象,在JVM中会被认为是不同的类,无法进行类型强转
//比如说 类CA 实现了接口IA,巧合情况下,类CA是由ClassLoaderA加载,接口IA由ClassLoaderB加载,如果ClassLoaderA和ClassLoaderB之间没有委托关系,那么CA是无法强转成IA的
//因为CA和IA是属于不同的ClassLoader,归属于不同的命名空间,相互之间不可见
//这里的验证就是为了避免这个问题。
//一般这种类加载的验证的流程都如下:
//1.使用给定的ClassLoader再次加载出一个临时的Class对象
//2.和之前加载的Class对象比较
//3.如果两个Class对象相等,就说明两个类的ClassLoader是同一个,验证通过
//4.否则就会抛出IllegalArgumentException异常
//有兴趣的同学可以去分析一下JDBC的类加载流程,也有类似的类加载验证
Class<?> interfaceClass = null;
try {
//使用给定的loader再次加载一次
interfaceClass = Class.forName(intf.getName(), false, loader);
} catch (ClassNotFoundException e) {
}
//和传入的接口类对比
if (interfaceClass != intf) {
throw new IllegalArgumentException(
intf + " is not visible from class loader");
}
//第2步
//验证是不是真正的接口
if (!interfaceClass.isInterface()) {
throw new IllegalArgumentException(
interfaceClass.getName() + " is not an interface");
}
//第3步
//验证是不是重复的接口,不重复就把接口放到interfaceSet中
if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
throw new IllegalArgumentException(
"repeated interface: " + interfaceClass.getName());
}
}
//第二步 确定代理类的包名
//如果接口 全是public 的话,报名就是ReflectUtil.PROXY_PACKAGE,即com.sun.proxy
//如果接口 有内部接口 的话,就用内部接口的外部类的包名
//如果内部接口来自不同类的话,就不能生成代理类,直接抛出异常返回
String proxyPkg = null; // package to define proxy class in
int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
//验证所有接口是不是全部public 或者 都来自同一个包中
for (Class<?> intf : interfaces) {
int flags = intf.getModifiers();
if (!Modifier.isPublic(flags)) {
accessFlags = Modifier.FINAL;
String name = intf.getName();
int n = name.lastIndexOf('.');
String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
if (proxyPkg == null) {
proxyPkg = pkg;
} else if (!pkg.equals(proxyPkg)) {
throw new IllegalArgumentException(
"non-public interfaces from different packages");
}
}
}
if (proxyPkg == null) {
//使用默认的com.sun.proxy作为包名
proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
}
//第三步 确定代理类的类名
//使用上面的包名和生成的类名组成一个全限定名
long num = nextUniqueNumber.getAndIncrement();
String proxyName = proxyPkg + proxyClassNamePrefix + num;
//第四步 生成代理类的字节码
//这里生成代理类使用了不开源的ProxyGenerator实现,下面会进行分析
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
//第五步 利用字节码创建一个Class对象
//这里使用了本地方法defineClass0 来定义一个类,里面的原理不再进行分析,有兴趣的同学,可以
//搜索相关的资料
try {
return defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
throw new IllegalArgumentException(e.toString());
}
}
}
动态代理字节码技术分析
终于到了最激动人心的地方,JDK动态代理技术的核心,字节码生成技术! 虽然这里是不开源的,但是通过反编译的代码还是可以大致的学习一下。
public static byte[] generateProxyClass(final String proxyName, Class<?>[] interfaces, int accessFlags) {
//创建一个ProxyGenerator
ProxyGenerator proxyGenerator = new ProxyGenerator(proxyName, interfaces, accessFlags);
//生成字节码 ,字节码技术核心方法
final byte[] bytes = proxyGenerator.generateClassFile();
//是否存储生成的字节码到class文件中,这块不进行分析了,有兴趣的同学自行分析下
if (saveGeneratedFiles) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
try {
int interfaces = proxyName.lastIndexOf(46);//46就是 ASCII中的 .
Path accessFlags;
if (interfaces > 0) {
Path path = Paths.get(proxyName.substring(0, interfaces).replace('.', File.separatorChar));
Files.createDirectories(path);
accessFlags = path.resolve(proxyName.substring(interfaces + 1, proxyName.length()) + ".class");
} else {
accessFlags = Paths.get(proxyName + ".class");
}
Files.write(accessFlags, bytes, new OpenOption[0]);
return null;
} catch (IOException e) {
throw new InternalError("I/O exception saving generated file: " + e);
}
}
});
}
return bytes;
}
JDK动态代理字节码技术核心类 ProxyGenerator
注意,如果对于JVM规范中Class文件结构不了解的同学,请不要深究下面的代码分析。可以先去了解一下JVM中的Class文件规范 Class文件格式参考地址: docs.oracle.com/javase/spec…
反编译的代码,基本可以看懂整个流程
private byte[] generateClassFile() {
//生成3个基本方法-和代理方法的字节码
this.addProxyMethod(hashCodeMethod, Object.class);
this.addProxyMethod(equalsMethod, Object.class);
this.addProxyMethod(toStringMethod, Object.class);
//获取所有接口中所有的方法,生成方法字节码
Class[] interfaces = this.interfaces;
int len = interfaces.length;
int i;
Class clazz;
for (i = 0; i < len; ++i) {
clazz = interfaces[i];
Method[] methods = clazz.getMethods();
int methodCount = methods.length;
for (int j = 0; j < methodCount; ++j) {
Method m = methods[j];
this.addProxyMethod(m, clazz);
}
}
Iterator var11 = this.proxyMethods.values().iterator();
//检查返回值,实际上是检查方法的重载,是不是符合java规范
//两个同名方法如果参数类型一致,返回值不同被认为是不合法的
List var12;
while (var11.hasNext()) {
var12 = (List) var11.next();
checkReturnTypes(var12);
}
Iterator var15;
try {
//添加构造函数的字节码
this.methods.add(this.generateConstructor());
var11 = this.proxyMethods.values().iterator();
while (var11.hasNext()) {
var12 = (List) var11.next();
var15 = var12.iterator();
while (var15.hasNext()) {
ProxyGenerator.ProxyMethod var16 = (ProxyGenerator.ProxyMethod) var15.next();
this.fields.add(new ProxyGenerator.FieldInfo(var16.methodFieldName, "Ljava/lang/reflect/Method;", 10));
this.methods.add(var16.generateMethod());
}
}
//生成静态代码块字节码
this.methods.add(this.generateStaticInitializer());
} catch (IOException var10) {
throw new InternalError("unexpected I/O Exception", var10);
}
if (this.methods.size() > 65535) {
throw new IllegalArgumentException("method limit exceeded");
} else if (this.fields.size() > 65535) {
throw new IllegalArgumentException("field limit exceeded");
} else {
//常量池添加元素
this.cp.getClass(dotToSlash(this.className));
this.cp.getClass("java/lang/reflect/Proxy");
var1 = this.interfaces;
var2 = var1.length;
for (var3 = 0; var3 < var2; ++var3) {
var4 = var1[var3];
this.cp.getClass(dotToSlash(var4.getName()));
}
this.cp.setReadOnly();
//准备拼凑Class文件
ByteArrayOutputStream var13 = new ByteArrayOutputStream();
DataOutputStream var14 = new DataOutputStream(var13);
try {
//写入魔术CAFEBABE
var14.writeInt(-889275714);
//写入版本号
var14.writeShort(0);
var14.writeShort(49);
//写入常量池
this.cp.write(var14);
//写入类访问标志
var14.writeShort(this.accessFlags);
//写入本类类名
var14.writeShort(this.cp.getClass(dotToSlash(this.className)));
//写入父类类名
var14.writeShort(this.cp.getClass("java/lang/reflect/Proxy"));
//写入接口的数量
var14.writeShort(this.interfaces.length);
//写入接口的字节码
Class[] var17 = this.interfaces;
int var18 = var17.length;
for (int var19 = 0; var19 < var18; ++var19) {
Class var22 = var17[var19];
var14.writeShort(this.cp.getClass(dotToSlash(var22.getName())));
}
//写入属性字段的个数
var14.writeShort(this.fields.size());
var15 = this.fields.iterator();
//写入所有的属性字段
while (var15.hasNext()) {
ProxyGenerator.FieldInfo var20 = (ProxyGenerator.FieldInfo) var15.next();
var20.write(var14);
}
//写入方法个数
var14.writeShort(this.methods.size());
var15 = this.methods.iterator();
//写入全部方法
while (var15.hasNext()) {
ProxyGenerator.MethodInfo var21 = (ProxyGenerator.MethodInfo) var15.next();
var21.write(var14);
}
//写入其他附加属性个数,这里没有附加属性
var14.writeShort(0);
//将生成的Class字节码转成byte[] 返回
return var13.toByteArray();
} catch (IOException var9) {
throw new InternalError("unexpected I/O Exception", var9);
}
}
}
看到这里,相信很多人还是一脸懵,不过JDK中提供了工具,可以将生成的字节码输出成Class文件,我们在利用反编译工具,就可以查看动态生成的代码到底是什么样子的。
反编译动态代理生成的类的代码
工具类代码
/**
* @author wangpp
*/
public class ProxyUtils {
/**
* 获取代理类对象的文件
*
* @param className
* @param fileName
* @param interfaces
* @return
*/
public static File getProxyClassFile(String className, String fileName, Class<?>[] interfaces) {
byte[] bytes = ProxyGenerator.generateProxyClass(className, interfaces);
File file = new File(fileName);
try {
FileOutputStream fileOutputStream = new FileOutputStream(file);
fileOutputStream.write(bytes);
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
return file;
}
public static void main(String[] args) {
File h = getProxyClassFile("h", "h.class", new Class[]{IA.class});
System.out.println(h);
}
}
接口类IA代码
/**
* @author wangpp
*/
public interface IA {
String m1(String a, String b);
void m2(String a, String b) throws Exception;
}
编译出的Class文件:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
import com.edfeff.proxy.jdk.demo.IA;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class h extends Proxy implements IA {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m4;
private static Method m0;
public h(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 String m1(String var1, String var2) throws {
try {
return (String)super.h.invoke(this, m3, new Object[]{var1, var2});
} catch (RuntimeException | Error var4) {
throw var4;
} catch (Throwable var5) {
throw new UndeclaredThrowableException(var5);
}
}
public final void m2(String var1, String var2) throws Exception {
try {
super.h.invoke(this, m4, new Object[]{var1, var2});
} catch (Exception | Error var4) {
throw var4;
} catch (Throwable var5) {
throw new UndeclaredThrowableException(var5);
}
}
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");
m3 = Class.forName("com.edfeff.proxy.jdk.demo.IA").getMethod("m1", Class.forName("java.lang.String"), Class.forName("java.lang.String"));
m4 = Class.forName("com.edfeff.proxy.jdk.demo.IA").getMethod("m2", Class.forName("java.lang.String"), Class.forName("java.lang.String"));
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
可以看到生成的代理类,帮我们代理了类的所有方法,并且方法的执行模式都是一致的,分析如下:
try {
//调用invocationHandler的invoke方法,将代理对象(自己),拦截的方法、方法参数,传入
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
看到这里,相信大家已经明白了动态代理的原理了。其中核心的类就是Proxy、InvocationHandler和ProxyGenerator。 Proxy供外部访问、管理校验、流程管理、生成代理类的加载、代理类缓存、代理对象的实例化 InvocationHandler 负责融合大家的业务代码 ProxyGenerator 负责组装Class字节码 关于ProxyGenerator中字节码的生成部分,有机会给大家继续解析。
Tips
- 为什么接口的数量不大于65535?
Class文件中规定了存储Interface数量的大小为2个字节,即16位
- 追问:那么为什么数量只有2个字节?
第一、这个数字已经足够大了,足以满足任意种业务情况,几乎没有任何一个正常程序的接口数量会达到这个值 第二、这个要问下高司令了(狗头)