你好,我是 N0tExpectErr0r,一名热爱技术的 Android 开发
我的个人博客:blog.N0tExpectErr0r.cn
Java 提供给了我们一个非常方便的动态代理类 Proxy,让我们今天来研究一下它的实现原理,以及为什么动态代理会存在性能问题。
代理对象的创建
我们往往通过 newProxyInstance(ClassLoader, Class<?>[], InvocationHandler) 方法进行代理对象的创建,它传递了三个参数:loader、interfaces 以及 h:
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException {
Objects.requireNonNull(h);
final Class<?>[] intfs = interfaces.clone();
// 通过 SercurityManager 进行对即将代理的类的 PackageAccess 的权限检测
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
// 尝试获取或创建代理类
Class<?> cl = getProxyClass0(loader, intfs);
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
// 对非public类通过AccessController将Accessible设置为true
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
// 以 InvocationHandler 作为参数调用代理类的构造函数
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
}
这里主要是如下的几步:
- 通过
SecurityManager对即将代理的类的 package access 进行检测。 - 通过
getProxyClass0尝试获取代理Class类。 - 对非 public 的类,通过
AccessController将Accessible设置为 true - 以
InvokeHandler作为参数调用代理类的构造函数构造对象。
显然,动态代理的关键想必就是在 getProxyClass0 这个方法中了,可以大胆猜测一下这个代理类中的任何方法的调用都会通过传递进去的 InvokeHandler 进行代理。
我们看到 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);
}
很奇怪,这里竟然只有一个从 proxyClassCache.get 的调用来获取缓存中的代理 Class,刚开始看到这里的时候以为 proxyClassCache 是个 Map 导致非常困惑。之后看到了上面的注释:
如果代理类已经被给定的
ClassLoader实现过了,则从缓存中直接 copy 一份拿出,否则它会通过代理类的工厂ProxyClassFactory进行创建
然后再仔细一看,原来 proxyClassCache 是一个 WeakCache 对象😂,那看来对代理 Class 对象的创建就在它的 get 方法中实现了。
WeakCache 缓存
数据结构
首先我们来了解一下 WeakCache 究竟是用来干什么的,我们先看到它内部的数据结构:
final class WeakCache<K, P, V> {
private final ReferenceQueue<K> refQueue
= new ReferenceQueue<>();
// the key type is Object for supporting null key
private final ConcurrentMap<Object, ConcurrentMap<Object, Supplier<V>>> map
= new ConcurrentHashMap<>();
private final ConcurrentMap<Supplier<V>, Boolean> reverseMap
= new ConcurrentHashMap<>();
private final BiFunction<K, P, ?> subKeyFactory;
private final BiFunction<K, P, V> valueFactory;
}
可以看到,这里有如下的几个成员变量:
refQueue:引用队列。map:一个ConcurrentHashMap,里面的value也是ConcurrentHashMap,它们的key均为Object,用来支持为 null 的key,而value则是Supplier类。reverseMap:一个key为Supplier,value为Boolean的ConcurrentHashMap。subKeyFactory:用于生成subKey的工厂,也就是map中的 Map 的key。valueFactory:用于生成value对象的工厂,既然我们的Class为value,这里想必就是ProxyClassFactory了。
果然,看到 proxyClassCache 的声明处:
private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
可以看到,它的 subKeyFactory 为 KeyFactory 类,而 valueFactory 为 ProxyClassFactory。
get
我们接着看看它的 get 方法究竟做了什么:
public V get(K key, P parameter) {
Objects.requireNonNull(parameter);
expungeStaleEntries();
// 通过 CacheKey.valueOf 将 key 转换为了存在缓存中的 CacheKey
Object cacheKey = CacheKey.valueOf(key, refQueue);
// 对 map 中的 value Map 进行获取,采用了懒创建的思路,这里还对并发问题进行了考虑
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;
}
}
// 通过 subKeyFactory 创建 subKey 并获取对应的 Supplier
Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
Supplier<V> supplier = valuesMap.get(subKey);
Factory factory = null;
while (true) {
// 通过 supplier.get 获取对应的 value
if (supplier != null) {
// supplier might be a Factory or a CacheValue<V> instance
V value = supplier.get();
if (value != null) {
return value;
}
}
// 如果缓存中没有对应的 supplier,可能是被 CacheValue 清理了,或者 Factory 还未 install
if (factory == null) {
factory = new Factory(key, parameter, subKey, valuesMap);
}
if (supplier == null) {
// 将 factory 放入 valuesMap 中,并将其设置为 supplier
supplier = valuesMap.putIfAbsent(subKey, factory);
if (supplier == null) {
// successfully installed Factory
supplier = factory;
}
// 如果此时已经有 supplier,则用它继续重试
} else {
if (valuesMap.replace(subKey, supplier, factory)) {
// Factory 成功 install,则将其设置为 supplier 并重试
supplier = factory;
} else {
// retry with current supplier
supplier = valuesMap.get(subKey);
}
}
}
}
这里看上去代码比较长,其实结构是比较清晰的:
- 首先通过
CacheKey将key转变为在map中的key,这里的key实际上是我们的ClassLoader,也就是这里先用ClassLoader进行了一遍筛选,是第一级缓存。 - 对
map中的valuesMap进行获取,若还没有则对其进行创建,这里生成的就是我们的第二级缓存 Map。 - 通过
subKeyFactory创建subKey并获取对应的Supplier,通过自定义的subKeyFactory进行创建,这个subKey用于通过第二级缓存获取真正的数据。 - 不断循环,对
Supplier进行创建并调用其get方法获取需要的value。这里创建Supplier的具体逻辑感兴趣的可以看看上面的代码及注释,最终创造出来的Supplier是一个Factory对象。
上面的代码中可以发现考虑到了非常多的并发安全性问题,用到了很多 CAS 操作进行值的 put,值得我们学习。
并且可以发现,WeakCache 中通过以 ClassLoader 为 key 的第一级缓存,以及以自定义的 subKeyFactory 生成的 subKey 为 key 的第二级缓存,极大地加快了 Map 的查找效率,也是很值得我们学习的一个操作。
那么既然存在 valuesMap 中的 Supplier 是 Factory 对象,让我们看看 Factory.get 方法:
@Override
public synchronized V get() { // serialize access
// 再次检查当前的 Supplier 是不是自己
Supplier<V> supplier = valuesMap.get(subKey);
if (supplier != this) {
return null;
}
// 通过 valueFactory.apply 方法创建需要的对象
V value = null;
try {
value = Objects.requireNonNull(valueFactory.apply(key, parameter));
} finally {
if (value == null) { // remove us on failure
valuesMap.remove(subKey, this);
}
}
// ... 省略
return value;
}
可以看到,它实际上就是通过 valueFactory 进行了 value 的创建,并将其返回。
那么我们对 WeakCache 类进行一下总结:
-
WeakCache中只有get方法,当缓存中没有存在对应的value时,它会通过创建时传入的valueFactory对value进行创建。 -
它通过以
ClassLoader为key的第一级缓存以及以自定义的subKeyFactory生成的subKey为key的第二级缓存,这样的二级缓存机制极大的加快了我们对Map的查找效率。 -
WeakCache并没有通过加锁来保证线程安全,而是将它的线程安全交给了ConcurrentHashMap来保证,并且通过大量的 CAS 操作来保证对Map操作的安全性。
代理 Class 的创建
ProxyClassFactory
接着我们看看 ProxyClassFactory 是如何生成我们的代理类 Class 对象的:
// 代理类名的前缀
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) {
// 遍历接口列表通过 Class.forName 加载 Class 对象
Class<?> interfaceClass = null;
try {
interfaceClass = Class.forName(intf.getName(), false, loader);
} catch (ClassNotFoundException e) {
}
// ... 异常检查
}
String proxyPkg = null; // package to define proxy class in
int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
// 如果存在非 public 的接口,记录非 public 接口的包名,后面会将它们生成的代理类采用同样的 package access(这些接口必须来自同一个包)
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");
}
}
}
// 没有非 public 的接口,用 sun.proxy 作为其包名
if (proxyPkg == null) {
proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
}
// 构造代理类的名字
long num = nextUniqueNumber.getAndIncrement();
String proxyName = proxyPkg + proxyClassNamePrefix + num;
// 通过 ProxyGenerator.generateProxyClass 生成代理类 Class
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
try {
// 通过 defineClass0 声明 Class 并返回
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());
}
}
可以看到,ProxyClassFactory 中主要是对代理类名的确定,它主要经历了下面的步骤:
- 遍历接口列表,通过
Class.forName加载接口对应的Class对象。 - 若存在非 public 的接口,用它们的包名作为所有代理类的包名,并且它们的包访问权限也会变成非 public。
- 若没有非 public 的接口,使用
sun.proxy作为包名。 - 以
proxyPkg + proxyClassNamePrefix + num的格式构建代理类名 - 调用
ProxyGenerator.generateProxyClass创建代理Class的 Class 文件。 - 通过
defineClass0定义Class并返回。
ProxyGenerator
显然,Class 文件生成的关键位于ProxyGenerator.generateProxyClass 中,我们首先看到 ProxyGeneratory.generateProxyClass。
我的 IDEA 只能查看到 Class 文件,如果想看 Java 文件的读者可以到这里查看:ProxyGenerator.java:
public static byte[] generateProxyClass(final String name,
Class<?>[] interfaces,
int accessFlags) {
ProxyGenerator gen = new ProxyGenerator(name, interfaces, accessFlags);
final byte[] classFile = gen.generateClassFile();
if (saveGeneratedFiles) {
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction<Void>() {
public Void run() {
try {
int i = name.lastIndexOf('.');
Path path;
if (i > 0) {
Path dir = Paths.get(name.substring(0, i).replace('.', File.separatorChar));
Files.createDirectories(dir);
path = dir.resolve(name.substring(i+1, name.length()) + ".class");
} else {
path = Paths.get(name + ".class");
}
Files.write(path, classFile);
return null;
} catch (IOException e) {
throw new InternalError(
"I/O exception saving generated file: " + e);
}
}
});
}
return classFile;
}
可以看到,它实际上是构建了一个对应参数的 ProxyGenerator 对象,并调用了其 generateClassFile 方法:
private byte[] generateClassFile() {
// 第一步,将类中的所有法组装为 ProxyMethod 对象
// 对于 hashCode equals toString 方法,首先调用 addProxyMethod 从而将原本的方法用 Object 的实现进行覆盖
addProxyMethod(hashCodeMethod, Object.class);
addProxyMethod(equalsMethod, Object.class);
addProxyMethod(toStringMethod, Object.class);
// 对接口中其余的方法调用 addProxyMethod 添加代理方法
for (Class<?> intf : interfaces) {
for (Method m : intf.getMethods()) {
addProxyMethod(m, intf);
}
}
// 遍历所有生成的代理方法,确保它们的返回值是兼容的
for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
checkReturnTypes(sigmethods);
}
// 第二步,组装生成的Class文件的FieldInfo和MethodInfo (每个方法都会生成一个static的Method对象和对应的代理方法)
try {
methods.add(generateConstructor());
for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
for (ProxyMethod pm : sigmethods) {
// 添加代理类的 static 字段
fields.add(new FieldInfo(pm.methodFieldName,
"Ljava/lang/reflect/Method;",
ACC_PRIVATE | ACC_STATIC));
// 添加代理方法
methods.add(pm.generateMethod());
}
}
methods.add(generateStaticInitializer());
} catch (IOException e) {
throw new InternalError("unexpected I/O Exception", e);
}
// 确保不会超过 65535 的限制
if (methods.size() > 65535) {
throw new IllegalArgumentException("method limit exceeded");
}
if (fields.size() > 65535) {
throw new IllegalArgumentException("field limit exceeded");
}
// 第三步,写入 Class 文件
// 对常量池进行验证
cp.getClass(dotToSlash(className));
cp.getClass(superclassName);
for (Class<?> intf: interfaces) {
cp.getClass(dotToSlash(intf.getName()));
}
// 常量池设置为已读
cp.setReadOnly();
ByteArrayOutputStream bout = new ByteArrayOutputStream();
DataOutputStream dout = new DataOutputStream(bout);
try {
// 写入MagicNumber
dout.writeInt(0xCAFEBABE);
// 写入minor版本号
dout.writeShort(CLASSFILE_MINOR_VERSION);
// 写入major版本号
dout.writeShort(CLASSFILE_MAJOR_VERSION);
// 写入常量池
cp.write(dout);
// 写入 accessFlags
dout.writeShort(accessFlags);
// 写入当前class索引
dout.writeShort(cp.getClass(dotToSlash(className)));
// 写入父类/接口索引
dout.writeShort(cp.getClass(superclassName));
// 写入接口个数
dout.writeShort(interfaces.length);
// 写入接口
for (Class<?> intf : interfaces) {
dout.writeShort(cp.getClass(
dotToSlash(intf.getName())));
}
// 写入字段个数
dout.writeShort(fields.size());
// 写入字段
for (FieldInfo f : fields) {
f.write(dout);
}
// 写入方法个数
dout.writeShort(methods.size());
// 写入方法
for (MethodInfo m : methods) {
m.write(dout);
}
// 写入属性表,代理类不需要属性表(包含了代码)
dout.writeShort(0);
} catch (IOException e) {
throw new InternalError("unexpected I/O Exception", e);
}
// 输出为byte数组
return bout.toByteArray();
}
这里的代码非常非常非常长,但是结构其实非常清晰,并且官方几乎对每个操作都加上了注释,非常易于理解:
- 第一步,将类中的所有法组装为
ProxyMethod对象- 对于
hashCode、equals、toString方法,首先调用addProxyMethod从而将原本的方法用Object的实现进行覆盖 - 对接口中其余的方法调用
addProxyMethod添加代理方法。 - 遍历所有生成的代理方法,确保它们的返回值是兼容的。
- 对于
- 第二步,组装生成的
Class文件的FieldInfo和MethodInfo(每个方法都会生成一个static的Method成员变量和对应的代理方法)- 遍历所有
ProxyMethod,添加代理类的static Method对象字段,并添加对应的代理方法。 - 确保方法数、字段数不会超过 65535 的限制。
- 遍历所有
- 第三步,将所有内容写入
Class文件(之前写ClassDecoder的时候写了很多类似的代码,很有感触哈哈,又复习了一遍Class文件的结构)- 写入
MagicNumber - 写入
minor版本号 - 写入
major版本号 - 写入常量池
- 写入
accessFlags - 写入当前类的索引
- 写入父类/接口的索引
- 写入接口个数
- 写入接口
- 写入字段个数
- 写入字段
- 写入方法个数
- 写入方法
- 写入属性表,由于代理类不需要属性表,因此写入的是 0
- 输出为
byte数组。
- 写入
关于 ProxyGenerator 的 addProxyMethod 等具体实现我们就不再关注了,实际上就是通过反射解析方法的参数等信息然后创建一个对应的方法。最后生成的代理类中的每个方法都会调用到 InvokeHandler.invoke 方法。
而最后的 defineClass0 方法是一个 native 方法,估计是调用到了 JVM 底层的方法,这里就不去关注了。
总结
JDK 中的动态代理是一套基于反射获取接口的信息,通过对 Class 文件按字节写入生成新的反射类文件并加载进 JVM 的动态代理框架。它有个很致命的特点就是只支持对接口的代理
一个代理对象的生成主要分两步:
-
通过
WeakCache获取缓存的Class对象。其中WeakCache存在如下的特点:-
WeakCache中只有get方法,当缓存中没有存在对应的value时,它会通过创建时传入的valueFactory对value进行创建。 -
它通过以
ClassLoader为key的第一级缓存以及以自定义的subKeyFactory生成的subKey为key的第二级缓存,这样的二级缓存机制极大的加快了我们对Map的查找效率。 -
WeakCache并没有通过加锁来保证线程安全,而是将它的线程安全交给了ConcurrentHashMap来保证,并且通过大量的 CAS 操作来保证对Map操作的安全性。
-
-
创建接口对应的
Class代理类对象。- 通过
ProxyClassFactory先解析接口的信息并生成对应的代理类名。 - 通过
ProxyGenerator对类文件的信息进行组装并输出对应的Class文件,主要分为以下三步:- 第一步,将类中的所有法组装为
ProxyMethod对象。 - 第二步,组装生成的
Class文件的FieldInfo和MethodInfo。 - 第三步,将所有内容写入
Class文件
- 第一步,将类中的所有法组装为
- 通过
最终,代理类中所有的方法的调用都会调用到 InvocationHandler.invoke,从而实现用户对接口的代理。