Java动态代理

代理模式,目前应用广泛的一种设计模式之一,最熟悉的莫过于在Spring的AOP了,如此重要的设计模式,在Java中如何使用呢?又是怎么实现的呢?接下来将围绕着这两个问题来进行实验解析。

首先是如何使用?在使用它的动态代理之前,先实现一个简单的静态代理。创建两个类,分别是PersonPersonProxy,并让这两个类都实现Walkable接口,然后我们通过PersonProxy来代替Person走路。

定义如下:

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
package top.devonte.stream.proxy;

public interface Walkable {
void walk();
}

package top.devonte.stream.proxy;

public class People implements Walkable {

@Override
public void walk() {
System.out.println("people is walking...");
}
}

package top.devonte.stream.proxy;

public class WalkableProxy implements Walkable {

private Walkable walkable;

public WalkableProxy(Walkable walkable) {
this.walkable = walkable;
}

@Override
public void walk() {
System.out.println("proxy make it walk");
walkable.walk();
System.out.println("proxy make it walk");
}
}

package top.devonte.stream.proxy;

public class ProxyTest {
public static void main(String[] args) {
Walkable walkable = new WalkableProxy(new People());
walkable.walk();
}
}

上面这段代码的输出结果为:

proxy make it walk
people is walking…
proxy make it walk

可以看到,静态代理实际上就是对被代理对象进行了一个包装。而代理类在程序运行时创建的代理方式被称为动态代理,在Java中,为我们提供了Proxy类,我们可以通过Proxy类来创建对应的代理对象。下面来看看在这个动态代理的实验中,能有什么样的收获吧。

首先定义一个Lion类,同样实现了Walkable接口,然后我们定义一个JdkProxyHandler类,实现InvocationHanlder接口。

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
package top.devonte.stream.proxy;

public class Lion implements Walkable {
@Override
public void walk() {
System.out.println("lion is walking...");
}
}

package top.devonte.stream.proxy;

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

public class JdkProxyHandler implements InvocationHandler {

private Walkable walkable;

public JdkProxyHandler(Walkable walkable) {
this.walkable = walkable;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("current invocation is: " + method.getName());
System.out.println("invocationHandler start work...");
Object result = method.invoke(walkable, args);
System.out.println("invocationHandler work completed...");
return result;
}
}

package top.devonte.stream.proxy;

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

public class ProxyTest {

public static void main(String[] args) {
Lion lion = new Lion();
InvocationHandler handler = new JdkProxyHandler(lion);
Walkable walkable = (Walkable) Proxy.newProxyInstance(Lion.class.getClassLoader(),
new Class[]{Walkable.class}, handler);
System.out.println("======== walkable.equals(lion) ========");
System.out.println(walkable.equals(lion));
System.out.println("======== walkableProxyClass.getClassLoader() ========");
Class<? extends Walkable> walkableProxyClass = walkable.getClass();
ClassLoader classLoader = walkableProxyClass.getClassLoader();
System.out.println(classLoader);
System.out.println("======== walkableProxyClass.getClassLoader() ========");
Method[] methods = walkableProxyClass.getMethods();
for (Method method : methods) {
System.out.println(method.getName());
}
System.out.println("======== walkable.walk() ========");
walkable.walk();
}

}

上述代码的输出结果为:

======== walkable.equals(lion) ========
current invocation is: equals
invocationHandler start work…
invocationHandler work completed…
true
======== walkableProxyClass.getClassLoader() ========
sun.misc.Launcher$AppClassLoader@18b4aac2
======== walkableProxyClass.getMethods() ========
equals
toString
hashCode
walk
isProxyClass
getInvocationHandler
getProxyClass
newProxyInstance
wait
wait
wait
getClass
notify
notifyAll
======== walkable.walk() ========
current invocation is: walk
invocationHandler start work…
lion is walking…
invocationHandler work completed…

将一系列的类属性打印出来后,我们可以得出以下几点:

  • 调用对象的equals方法时,实际上调用的是JdkProxyHandlerinvoke方法
  • 使用的类加载器为sun.misc.Launcher$AppClassLoader
  • 包含的方法相对于普通的对象多了isProxyClassgetInvocationHandlergetProxyClassnewProxyInstance

当我们把创建代理对象的代码改成如下形式时,会产生java.lang.IllegalArgumentException: top.devonte.stream.proxy.Lion is not an interface异常:

1
Lion walkable = (Lion) Proxy.newProxyInstance(Lion.class.getClassLoader(),new Class[]{Lion.class}, handler);

Java的动态代理需要通过接口来实现,如果没有实现相应的接口是没办法创建代理对象的。接着我们通过代理对象来获取代理对象的Class信息,探究代理对象到底是一个怎么样的类。

1
2
System.out.println("======== walkable.getClass() ========");
System.out.println(walkable.getClass());

得到的输出如下:

======== walkable.getClass() ========
class com.sun.proxy.$Proxy0

Java给我们动态生成了一个Proxy类,这个类对象是如何创建出来的呢?即Java是如何实现动态代理的呢?我们从获取类对象的方法开始探究。

执行newProxyInstance时,会将我们传入的interfaces进行克隆,接着通过类加载器获取代理类对象,如果可以进行代理,则通过反射获取到这个类的构造器,使用newInstance创建出代理类。

1
2
3
4
5
final Class<?>[] intfs = interfaces.clone();
...省略一部分代码...
Class<?> cl = getProxyClass0(loader, intfs);
...省略一部分代码...
return cons.newInstance(new Object[]{h});

分析到这里,我们对Java的动态代理应该有了一个比较深刻的认识,Java通过InvocationHandler来实现了具体的代理功能,通过反射动态生成一个存在于内存中的Proxy类,最终的调用都是通过这个InvocationHandler来实现方法的调用。如果想探究生成的代理类到底是一个怎么样的东西,可以通过反射将代理类输出到文件中,然后通过反编译来查看。下面给出将代理类输出到文件中的方法:

1
2
3
4
5
6
7
8
9
byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0", Lion.class.getInterfaces());
String path = "LionProxy.class";
try(FileOutputStream fos = new FileOutputStream(path)) {
fos.write(classFile);
fos.flush();
System.out.println("代理类class文件写入成功");
} catch (Exception e) {
System.out.println("写文件错误");
}

参考资料:

java动态代理实现与原理详细分析(友好版)

JAVA动态代理(详细)