Java 提供给了我们一个非常方便的动态代理类 Proxy
,让我们今天来研究一下它的实现原理,以及为什么动态代理会存在性能问题。
个人博客:https://blog.N0tExpectErr0r.cn
小专栏:https://xiaozhuanlan.com/N0tExpectErr0r
代理对象的创建
我们往往通过 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
,从而实现用户对接口的代理。