JDK动态代理

代理模式实现方式介绍

什么是代理模式

日常生活中我们经常会碰到代理模式,例如我们找房产中介帮我们找房子,找婚姻中介帮我们介绍对象,找保洁帮我们打理房间等。在无形中运用到了代理模式。

为什么要使用代理?

运用代理可以使我们的生活更加便利,有了代理,我们不需要自己去找房子,不需要自己去找对象,不需要自己去打理房间。当然,你也可以选择一切都自己来干,但是存在前提条件,一是你是否都具备这样的资源和能力来做这些事情,二是你是否愿意花费这么多精力和时间来做这些事情。总之,代理模式使我们各专其事,我们可以将时间消耗在美好的事情上,而不用天天被一些琐事所羁绊。

代理模式有哪些实现?

Java中的代理有静态代理和动态代理,下面分别用一个简单的例子来介绍一下静态代理和动态代理代码实现。

静态代理

代理接口

1
2
3
4
5
6
7
8
9
10
11
12
13
package main.java.com.study.designPatterns.proxy.staticProxy;

/**
* @author: whb
* @description: 代理接口
*/
public interface UserDao {

/**
* 保存
*/
void save();
}

目标对象

1
2
3
4
5
6
7
8
9
10
11
12
13
package main.java.com.study.designPatterns.proxy.staticProxy;

/**
* @author: whb
* @description: 目标对象
*/
public class UserDaoImpl implements UserDao {

@Override
public void save() {
System.out.println("正在保存用户...");
}
}

代理对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package main.java.com.study.designPatterns.proxy.staticProxy;

/**
* @author: whb
* @description: 代理对象
*/
public class TransactionHandler implements UserDao {

/**
* 目标代理对象
*/
private UserDaoImpl target;

/**
* 构造代理对象时传入目标对象
*
* @param target
*/
public TransactionHandler(UserDaoImpl target) {
this.target = target;
}

@Override
public void save() {
//调用目标方法前的处理
System.out.println("开启事务控制...");
//调用目标对象的方法
target.save();
//调用目标方法后的处理
System.out.println("关闭事务控制...");
}
}

测试调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main.java.com.study.designPatterns.proxy.staticProxy;

/**
* @author: whb
* @description: 测试静态代理
*/
public class TestStaticProxy {
public static void main(String[] args) {
//新建目标对象
UserDaoImpl target = new UserDaoImpl();

//创建代理对象, 并使用接口对其进行引用
UserDao userDao = new TransactionHandler(target);

//针对接口进行调用
userDao.save();
}
}

测试结果

1
2
3
开启事务控制...
正在保存用户...
关闭事务控制...

总结

静态代理实现简单也容易理解,但是静态代理不能使一个代理类反复作用于多个目标对象,代理对象直接持有目标对象的引用,这导致代理对象和目标对象类型紧密耦合了在一起。如果UserDao接口下还有另一个实现类也需要进行事务控制,那么就要重新写一个代理类,这样就会产生许多重复的模版代码,不能达到代码复用的目的。而动态代理就可以很好的解决这样的问题。

动态代理

代理接口

1
2
3
4
5
6
7
8
9
10
11
12
13
package main.java.com.study.designPatterns.proxy.JdkProxy;

/**
* @author: whb
* @date: 2019/9/4 9:50
* @description: 代理接口
*/
public interface UserDao {
/**
* 保存用户
*/
void save();
}

目标对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main.java.com.study.designPatterns.proxy.JdkProxy;

/**
* @author: whb
* @date: 2019/9/4 9:51
* @description: 目标对象
*/
public class UserDaoImpl implements UserDao {

@Override
public void save() {
System.out.println("保存用户信息...");
}
}

代理对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package main.java.com.study.designPatterns.proxy.JdkProxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
* @author: whb
* @description: 代理对象
*/
public class TransactionHandler implements InvocationHandler {
/**
* 需要代理的目标对象,这里设计为可以为任意对象添加事务控制, 所以将目标对象声明为Object
*/
private Object target;

/**
* 构造TransactionHandler时传入目标对象
*
* @param target
*/
public TransactionHandler(Object target) {
this.target = target;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//调用目标方法前的处理
System.out.println("开启事务控制...");
//调用目标对象的方法
Object result = method.invoke(target, args);
//调用目标方法后的处理
System.out.println("关闭事务控制...");
//放回方法调用结果
return result;
}
}

测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main.java.com.study.designPatterns.proxy.JdkProxy;

import java.lang.reflect.Proxy;

/**
* @author: whb
* @description: 测试JDK动态代理
*/
public class TestJdkProxy {
public static void main(String[] args) {
//新建目标对象
Object target = new UserDaoImpl();

//创建事务处理器
TransactionHandler handler = new TransactionHandler(target);

//生成代理类并使用接口对其进行引用
UserDao userDao = (UserDao) Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
handler);
//针对接口进行方法调用
userDao.save();
}
}

测试结果

1
2
3
开启事务控制...
保存用户信息...
关闭事务控制...

总结

之前发现静态代理会产生许多重复代码,不能很好的进行代码复用,而动态代理能够很好的解决这个问题,代理类TransactionHandler实现了InvocationHandler接口,并且它持有的目标对象类型是Object,因此事务控制代理类TransactionHandler能够代理任意的对象,为任意的对象添加事务控制的逻辑。因此动态代理才真正的将代码中横向切面的逻辑剥离了出来,起到代码复用的目的。

但是动态代理也有缺点,一是它的实现比静态代理更加复杂也不好理解;二是它存在一定的限制,例如它要求需要代理的对象必须实现了某个接口;三是它不够灵活,动态代理会为接口中的声明的所有方法添加上相同的代理逻辑。

JDK动态代理的底层实现Proxy分析

在动态代理的测试类TestJdkProxy中使用了Proxy类的静态方法newProxyInstance方法去生成一个代理类,这个静态方法接收三个参数,分别是目标类的类加载器,目标类实现的接口集合,InvocationHandler实例,最后返回一个Object类型的代理类。先从该方法开始,看看代理类是怎样一步一步造出来的。

newProxyInstance方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
/**
* 返回指定接口的代理类的实例,该接口将方法调用分发给指定的调用处理程序。这个静态方法有三个参数:
* @param ClassLoader loader,指定当前目标对象使用类加载器
* @param Class<?>[ ] interfaces,目标对象实现的接口的类型,使用泛型方式确认类型
* @param InvocationHandler h,事件处理,执行目标对象的方法时,会触发事件处理器的方法,会把当前执行目标对象的方法作为参数传入
*/
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException {
//验证传入的InvocationHandler不能为空
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);

/*
* 使用指定的调用处理程序调用其构造函数.
*/
try {
//进行一些权限检验
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
//获取参数类型是InvocationHandler.class的代理类构造器
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
//如果代理类是不可访问的, 就使用特权将它的构造器设置为可访问
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
//所有代理类都继承自Proxy, 因此这里会调用Proxy的构造器将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);
}
}

newProxyInstance方法首先是对参数进行一些权限校验,之后通过调用getProxyClass0方法生成了代理类的类对象,然后获取参数类型是InvocationHandler.class的代理类构造器。检验构造器是否可以访问,最后传入InvocationHandler实例的引用去构造出一个代理类实例,InvocationHandler实例的引用其实是Proxy持有着,因为生成的代理类默认继承自Proxy,所以最后会调用Proxy的构造器将引用传入。

getProxyClass0方法

下面看下getProxyClass0这个方法,看看代理类的Class对象是怎样来的。

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 生成一个代理类。必须调用checkProxyAccess方法,在调用这个方法之前需要检查权限。
*/
private static Class<?> getProxyClass0(ClassLoader loader,
Class<?>... interfaces) {
//目标类实现的接口不能大于65535
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
//如果由给定的装载机定义的代理类实现给定的接口存在,这将会返回缓存;否则,它将通过ProxyClassFactory创建代理类。
return proxyClassCache.get(loader, interfaces);
}

getProxyClass0方法内部没有多少内容,首先是检查目标代理类实现的接口不能大于65535这个数,之后是通过类加载器和接口集合去缓存里面获取,如果能找到代理类就直接返回,否则就会调用ProxyClassFactory这个工厂去生成一个代理类。

ProxyClassFactory工厂类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
//代理类生成工厂
private static final class ProxyClassFactory
implements BiFunction<ClassLoader, Class<?>[], 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) {
//这里遍历interfaces数组进行验证, 主要做三件事情
/*
* 1.intf是否可以由指定的类加载进行加载
*/
Class<?> interfaceClass = null;
try {
interfaceClass = Class.forName(intf.getName(), false, loader);
} catch (ClassNotFoundException e) {
}
if (interfaceClass != intf) {
throw new IllegalArgumentException(
intf + " is not visible from class loader");
}
/*
* 2.intf是否是一个接口
*/
if (!interfaceClass.isInterface()) {
throw new IllegalArgumentException(
interfaceClass.getName() + " is not an interface");
}
/*
* 3.intf在数组中是否有重复
*/
if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
throw new IllegalArgumentException(
"repeated interface: " + interfaceClass.getName());
}
}
//生成代理类的包名
String proxyPkg = null;
//生成代理类的访问标志, 默认是public final的
int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
for (Class<?> intf : interfaces) {
//获取接口的访问标志
int flags = intf.getModifiers();
//如果接口的访问标志不是public, 那么生成代理类的包名和接口包名相同
if (!Modifier.isPublic(flags)) {
//生成的代理类的访问标志设置为final
accessFlags = Modifier.FINAL;
//获取接口全限定名, 例如:java.util.Collection
String name = intf.getName();
int n = name.lastIndexOf('.');
//剪裁后得到包名:java.util
String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
//生成的代理类的包名和接口包名是一样的
if (proxyPkg == null) {
proxyPkg = pkg;
} else if (!pkg.equals(proxyPkg)) {
//代理类如果实现不同包的接口, 并且接口都不是public的, 那么就会在这里报错
throw new IllegalArgumentException(
"non-public interfaces from different packages");
}
}
}
//如果接口访问标志都是public的话, 那生成的代理类都放到默认的包下:com.sun.proxy
if (proxyPkg == null) {
proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
}
//生成代理类的序号
long num = nextUniqueNumber.getAndIncrement();
//生成代理类的全限定名, 包名+前缀+序号, 例如:com.sun.proxy.$Proxy0
String proxyName = proxyPkg + proxyClassNamePrefix + num;
//这里是核心, 用ProxyGenerator来生成字节码, 该类放在sun.misc包下
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName,
interfaces, accessFlags);
try {
//根据二进制文件生成相应的Class实例
return defineClass0(loader, proxyName, proxyClassFile,
0, proxyClassFile.length);
} catch (ClassFormatError e) {
throw new IllegalArgumentException(e.toString());
}
}
}

该工厂的apply方法会被调用用来生成代理类的Class对象,由于代码的注释比较详细,只挑关键点进行阐述。

  1. 在代码中可以看到JDK生成的代理类的类名是“$Proxy”+序号。

  2. 如果接口是public的,代理类默认是public final的,并且生成的代理类默认放到com.sun.proxy这个包下。

  3. 如果接口是非public的,那么代理类也是非public的,并且生成的代理类会放在对应接口所在的包下。

  4. 如果接口是非public的,并且这些接口不在同一个包下,那么就会报错。

WeakCache缓存的实现机制

Proxy内部用到了缓存机制,如果根据提供的类加载器和接口数组能在缓存中找到代理类就直接返回该代理类,否则会调用ProxyClassFactory工厂去生成代理类。这里用到的缓存是二级缓存,它的一级缓存key是根据类加载器生成的,二级缓存key是根据接口数组生成的。具体的内部机制直接上代码详细解释。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//Reference引用队列
private final ReferenceQueue<K> refQueue = new ReferenceQueue<>();
//缓存的底层实现, key为一级缓存, value为二级缓存。 为了支持null, map的key类型设置为Object
private final ConcurrentMap<Object, ConcurrentMap<Object, Supplier<V>>>
map = new ConcurrentHashMap<>();
//reverseMap记录了所有代理类生成器是否可用, 这是为了实现缓存的过期机制
private final ConcurrentMap<Supplier<V>, Boolean> reverseMap = new ConcurrentHashMap<>();
//生成二级缓存key的工厂, 这里传入的是KeyFactory
private final BiFunction<K, P, ?> subKeyFactory;
//生成二级缓存value的工厂, 这里传入的是ProxyClassFactory
private final BiFunction<K, P, V> valueFactory;

//构造器, 传入生成二级缓存key的工厂和生成二级缓存value的工厂
public WeakCache(BiFunction<K, P, ?> subKeyFactory, BiFunction<K, P, V> valueFactory) {
this.subKeyFactory = Objects.requireNonNull(subKeyFactory);
this.valueFactory = Objects.requireNonNull(valueFactory);
}

首先看一下WeakCache的成员变量和构造器,WeakCache缓存的内部实现是通过ConcurrentMap来完成的,成员变量map就是二级缓存的底层实现,reverseMap是为了实现缓存的过期机制,subKeyFactory是二级缓存key的生成工厂,通过构造器传入,这里传入的值是Proxy类的KeyFactory,valueFactory是二级缓存value的生成工厂,通过构造器传入,这里传入的是Proxy类的ProxyClassFactory。接下来看一下WeakCache的get方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
public V get(K key, P parameter) {
//这里要求实现的接口不能为空
Objects.requireNonNull(parameter);
//清除过期的缓存
expungeStaleEntries();
//将ClassLoader包装成CacheKey, 作为一级缓存的key
Object cacheKey = CacheKey.valueOf(key, refQueue);
//获取得到二级缓存
ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);
//如果根据ClassLoader没有获取到对应的值
if (valuesMap == null) {
//以CAS方式放入, 如果不存在则放入,否则返回原先的值
ConcurrentMap<Object, Supplier<V>> oldValuesMap = map.putIfAbsent(cacheKey,
valuesMap = new ConcurrentHashMap<>());
//如果oldValuesMap有值, 说明放入失败
if (oldValuesMap != null) {
valuesMap = oldValuesMap;
}
}
//根据代理类实现的接口数组来生成二级缓存key, 分为key0, key1, key2, keyx
Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
//这里通过subKey获取到二级缓存的值
Supplier<V> supplier = valuesMap.get(subKey);
Factory factory = null;
//这个循环提供了轮询机制, 如果条件为假就继续重试直到条件为真为止
while (true) {
//如果通过subKey取出来的值不为空
if (supplier != null) {
//在这里supplier可能是一个Factory也可能会是一个CacheValue
//在这里不作判断, 而是在Supplier实现类的get方法里面进行验证
V value = supplier.get();
if (value != null) {
return value;
}
}
if (factory == null) {
//新建一个Factory实例作为subKey对应的值
factory = new Factory(key, parameter, subKey, valuesMap);
}
if (supplier == null) {
//到这里表明subKey没有对应的值, 就将factory作为subKey的值放入
supplier = valuesMap.putIfAbsent(subKey, factory);
if (supplier == null) {
//到这里表明成功将factory放入缓存
supplier = factory;
}
//否则, 可能期间有其他线程修改了值, 那么就不再继续给subKey赋值, 而是取出来直接用
} else {
//期间可能其他线程修改了值, 那么就将原先的值替换
if (valuesMap.replace(subKey, supplier, factory)) {
//成功将factory替换成新的值
supplier = factory;
} else {
//替换失败, 继续使用原先的值
supplier = valuesMap.get(subKey);
}
}
}
}

WeakCache的get方法并没有用锁进行同步,那它是怎样实现线程安全的呢?

因为它的所有会进行修改的成员变量都使用了ConcurrentMap,这个类是线程安全的。因此它将自身的线程安全委托给了ConcurrentMap, get方法尽可能的将同步代码块缩小,这样可以有效提高WeakCache的性能。

ClassLoader作为了一级缓存的key,这样可以首先根据ClassLoader筛选一遍,因为不同ClassLoader加载的类是不同的。

然后它用接口数组来生成二级缓存的key,这里它进行了一些优化,因为大部分类都是实现了一个或两个接口,所以二级缓存key分为key0,key1,key2,keyX。key0到key2分别表示实现了0到2个接口,keyX表示实现了3个或以上的接口,事实上大部分都只会用到key1和key2。这些key的生成工厂是在Proxy类中,通过WeakCache的构造器将key工厂传入。这里的二级缓存的值是一个Factory实例,最终代理类的值是通过Factory这个工厂来获得的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
private final class Factory implements Supplier<V> {
//一级缓存key, 根据ClassLoader生成
private final K key;
//代理类实现的接口数组
private final P parameter;
//二级缓存key, 根据接口数组生成
private final Object subKey;
//二级缓存
private final ConcurrentMap<Object, Supplier<V>> valuesMap;

Factory(K key, P parameter, Object subKey,
ConcurrentMap<Object, Supplier<V>> valuesMap) {
this.key = key;
this.parameter = parameter;
this.subKey = subKey;
this.valuesMap = valuesMap;
}

@Override
public synchronized V get() {
//这里再一次去二级缓存里面获取Supplier, 用来验证是否是Factory本身
Supplier<V> supplier = valuesMap.get(subKey);
if (supplier != this) {
//在这里验证supplier是否是Factory实例本身, 如果不则返回null让调用者继续轮询重试
//期间supplier可能替换成了CacheValue, 或者由于生成代理类失败被从二级缓存中移除了
return null;
}
V value = null;
try {
//委托valueFactory去生成代理类, 这里会通过传入的ProxyClassFactory去生成代理类
value = Objects.requireNonNull(valueFactory.apply(key, parameter));
} finally {
//如果生成代理类失败, 就将这个二级缓存删除
if (value == null) {
valuesMap.remove(subKey, this);
}
}
//只有value的值不为空才能到达这里
assert value != null;
//使用弱引用包装生成的代理类
CacheValue<V> cacheValue = new CacheValue<>(value);
//将包装后的cacheValue放入二级缓存中, 这个操作必须成功, 否则就报错
if (valuesMap.replace(subKey, this, cacheValue)) {
//将cacheValue成功放入二级缓存后, 再对它进行标记
reverseMap.put(cacheValue, Boolean.TRUE);
} else {
throw new AssertionError("Should not reach here");
}
//最后返回没有被弱引用包装的代理类
return value;
}
}

再看看Factory这个内部工厂类,可以看到它的get方法是使用synchronized关键字进行了同步。进行get方法后首先会去验证subKey对应的suppiler是否是工厂本身,如果不是就返回null,而WeakCache的get方法会继续进行重试。如果确实是工厂本身,那么就会委托ProxyClassFactory生成代理类,ProxyClassFactory是在构造WeakCache的时候传入的。所以这里解释了为什么最后会调用到Proxy的ProxyClassFactory这个内部工厂来生成代理类。生成代理类后使用弱引用进行包装并放入reverseMap中,最后会返回原装的代理类。

ProxyGenerator生成代理类的字节码文件

通过前面的分析,知道了代理类是通过Proxy类的ProxyClassFactory工厂生成的,这个工厂类会去调用ProxyGenerator类的generateProxyClass()方法来生成代理类的字节码。ProxyGenerator这个类存放在sun.misc包下,该类的generateProxyClass()静态方法的核心内容就是去调用generateClassFile()实例方法来生成Class文件。接下来看看generateClassFile()这个方法内部做了些什么。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
private byte[] generateClassFile() {
//第一步, 将所有的方法组装成ProxyMethod对象
//首先为代理类生成toString, hashCode, equals等代理方法
this.addProxyMethod(hashCodeMethod, Object.class);
this.addProxyMethod(equalsMethod, Object.class);
this.addProxyMethod(toStringMethod, Object.class);
//获取接口类
Class[] var1 = this.interfaces;
int var2 = var1.length;

int var3;
Class var4;
//遍历每一个接口的每一个方法, 并且为其生成ProxyMethod对象
for(var3 = 0; var3 < var2; ++var3) {
var4 = var1[var3];
Method[] var5 = var4.getMethods();
int var6 = var5.length;

for(int var7 = 0; var7 < var6; ++var7) {
Method var8 = var5[var7];
this.addProxyMethod(var8, var4);
}
}

//对于具有相同签名的代理方法, 检验方法的返回值是否兼容
Iterator var11 = this.proxyMethods.values().iterator();

List var12;
while(var11.hasNext()) {
var12 = (List)var11.next();
checkReturnTypes(var12);
}
//第二步, 组装要生成的class文件的所有的字段信息和方法信息
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();
//添加代理类的静态字段, 例如:private static Method m1;
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);
}
//验证方法和字段集合不能大于65535
if (this.methods.size() > 65535) {
throw new IllegalArgumentException("method limit exceeded");
} else if (this.fields.size() > 65535) {
throw new IllegalArgumentException("field limit exceeded");
} else {
//第三步, 写入最终的class文件
//验证常量池中存在代理类的全限定名
this.cp.getClass(dotToSlash(this.className));
//验证常量池中存在代理类父类的全限定名, 父类名为:"java/lang/reflect/Proxy"
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();
ByteArrayOutputStream var13 = new ByteArrayOutputStream();
DataOutputStream var14 = new DataOutputStream(var13);

try {
//1.写入魔数
var14.writeInt(-889275714);
//2.写入次版本号
var14.writeShort(0);
//3.写入主版本号
var14.writeShort(49);
//4.写入常量池
this.cp.write(var14);
//5.写入访问修饰符
var14.writeShort(this.accessFlags);
//6.写入类索引
var14.writeShort(this.cp.getClass(dotToSlash(this.className)));
//7.写入父类索引, 生成的代理类都继承自Proxy
var14.writeShort(this.cp.getClass("java/lang/reflect/Proxy"));
//8.写入接口计数值
var14.writeShort(this.interfaces.length);
Class[] var17 = this.interfaces;
int var18 = var17.length;
//9.写入接口集合
for(int var19 = 0; var19 < var18; ++var19) {
Class var22 = var17[var19];
var14.writeShort(this.cp.getClass(dotToSlash(var22.getName())));
}
//10.写入字段计数值
var14.writeShort(this.fields.size());
var15 = this.fields.iterator();
//11.写入字段集合
while(var15.hasNext()) {
ProxyGenerator.FieldInfo var20 = (ProxyGenerator.FieldInfo)var15.next();
var20.write(var14);
}
//12.写入方法计数值
var14.writeShort(this.methods.size());
var15 = this.methods.iterator();
//13.写入方法集合
while(var15.hasNext()) {
ProxyGenerator.MethodInfo var21 = (ProxyGenerator.MethodInfo)var15.next();
var21.write(var14);
}
//14.写入属性计数值, 代理类class文件没有属性所以为0
var14.writeShort(0);
//转换成二进制数组输出
return var13.toByteArray();
} catch (IOException var9) {
throw new InternalError("unexpected I/O Exception", var9);
}
}
}

可以看到generateClassFile()方法是按照Class文件结构进行动态拼接的。那什么是Class文件呢?

我们平时编写的Java文件是以.java结尾的,在编写好了之后通过编译器进行编译会生成.class文件,这个.class文件就是Class文件。Java程序的执行只依赖于Class文件,和Java文件是没有关系的。这个Class文件描述了一个类的信息,当我们需要使用到一个类时,Java虚拟机就会提前去加载这个类的Class文件并进行初始化和相关的检验工作,Java虚拟机能够保证在你使用到这个类之前就会完成这些工作,我们只需要安心的去使用它就好了,而不必关心Java虚拟机是怎样加载它的。当然,Class文件并不一定非得通过编译Java文件而来,你甚至可以直接通过文本编辑器来编写Class文件。在这里,JDK动态代理就是通过程序来动态生成Class文件的。

生成Class文件主要分为三步:

第一步:收集所有要生成的代理方法,将其包装成ProxyMethod对象并注册到Map集合中。

第二步:收集所有要为Class文件生成的字段信息和方法信息。

第三步:完成了上面的工作后,开始组装Class文件。

一个类的核心部分就是它的字段和方法。重点聚焦第二步,看看它为代理类生成了哪些字段和方法。在第二步中,按顺序做了下面四件事。

  1. 为代理类生成一个带参构造器,传入InvocationHandler实例的引用并调用父类的带参构造器。

  2. 遍历代理方法Map集合,为每个代理方法生成对应的Method类型静态域,并将其添加到fields集合中。

  3. 遍历代理方法Map集合,为每个代理方法生成对应的MethodInfo对象,并将其添加到methods集合中。

  4. 为代理类生成静态初始化方法,该静态初始化方法主要是将每个代理方法的引用赋值给对应的静态字段。

通过以上分析,可以大致知道JDK动态代理最终会生成如下结构的代理类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
public class Proxy0 extends Proxy implements UserDao {

//第一步, 生成构造器
protected Proxy0(InvocationHandler h) {
super(h);
}

//第二步, 生成静态域
private static Method m1; //hashCode方法
private static Method m2; //equals方法
private static Method m3; //toString方法
private static Method m4; //...

//第三步, 生成代理方法
@Override
public int hashCode() {
try {
return (int) h.invoke(this, m1, null);
} catch (Throwable e) {
throw new UndeclaredThrowableException(e);
}
}

@Override
public boolean equals(Object obj) {
try {
Object[] args = new Object[] {obj};
return (boolean) h.invoke(this, m2, args);
} catch (Throwable e) {
throw new UndeclaredThrowableException(e);
}
}

@Override
public String toString() {
try {
return (String) h.invoke(this, m3, null);
} catch (Throwable e) {
throw new UndeclaredThrowableException(e);
}
}

@Override
public void save(User user) {
try {
//构造参数数组, 如果有多个参数往后面添加就行了
Object[] args = new Object[] {user};
h.invoke(this, m4, args);
} catch (Throwable e) {
throw new UndeclaredThrowableException(e);
}
}

//第四步, 生成静态初始化方法
static {
try {
Class c1 = Class.forName(Object.class.getName());
Class c2 = Class.forName(UserDao.class.getName());
m1 = c1.getMethod("hashCode", null);
m2 = c1.getMethod("equals", new Class[]{Object.class});
m3 = c1.getMethod("toString", null);
m4 = c2.getMethod("save", new Class[]{User.class});
//...
} catch (Exception e) {
e.printStackTrace();
}
}
}

经过层层分析,深入探究JDK源码,还原动态生成的代理类的本来面目,明白了以下几点:

  1. 代理类默认继承Porxy类,因为Java中只支持单继承,所以JDK动态代理只能去实现接口。

  2. 代理方法都会去调用InvocationHandler的invoke()方法,因此需要重写InvocationHandler的invoke()方法。

  3. 调用invoke()方法时会传入代理实例本身,目标方法和目标方法参数。解释了invoke()方法的参数是怎样来的。

本文标题:JDK动态代理

文章作者:王洪博

发布时间:2018年09月04日 - 09:09

最后更新:2019年09月12日 - 10:09

原始链接:http://whb1990.github.io/posts/dfdc2005.html

▄︻┻═┳一如果你喜欢这篇文章,请点击下方"打赏"按钮请我喝杯 ☕
0%