Java 中的动态代理与反射

代理, 是一种十分常见的设计模式,即为其他对象提供一个代理从而控制对这个对象的访问。使用代理模式的场景有很多,比如控制对远程对象的访问;对象实例化代价很高,出于性能考虑,需要延迟进行加载;出于安全性的角度,需要屏蔽特定对象的访问等等。

Proxy-Pattern.png

代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理。为了保证行为的一致性,代理类和委托类通常会实现相同的接口,故而在访问者看来并没有区别。然而,通过代理类这一中间层,可以有效地隐藏和保护被代理的真实对象,从而达到了控制访问的目的。

静态代理

通常我们说起代理模式,指的就是静态代理。下面通过懒加载的例子来看一下Java中的代理模式是如何实现的。

所谓的懒加载,就是延迟加载,它的核心思路就是如果当前没有真正地使用某个对象时,就不去创建这个对象,而是使用一个代理去替代它的位置,在真正需要调用的时候才去创建真正的对象。对于很多系统而言,初始化时启动的服务太多,导致用户体验比较糟糕或者系统负载过重,这个时候懒加载就有其用武之地了。

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
//MyInterface.java
interface MyInterface {
void doSomething();
}

//Subject.java
class Subject implements MyInterface {
public Subject() {
//init, may cost long time;
Thread.sleep(1000);
}

@Override
public void doSomething() {
System.out.println("do something!");
}
}

//SubjectProxy.java
class SubjectProxy implements MyInterface {
private Subject real;

public SubjectProxy() {
//init
}

@Override
public void doSomething {
//only create instance of Subject when needed, may be a long time.
if (real == null) {
real = new Subject();
}
real.doSomething();
}
}

//Test.java
class Test {
public static void main(String[] args) {
SubjectProxy proxy = new SubjectProxy(); //using proxy
proxy.doSomething(); //instance of Subject is not created, until really need it.
}
}

动态代理

从静态代理的实现我们可以看到,在被代理的对象比较多,方法也比较多的时候,代理类的实现比较繁琐,需要一一创建相应的代理类。动态代理机制的出现使得动态生成代理类成为可能,从而使得代理模式的实现可以更为优雅。

如何使用动态代理?

Java 提供了java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口来方便实现动态代理。
利用静态方法Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)可以创建一个动态代理的对象,其中传入的三个参数为类加载器,接口列表以及调用处理器。其中调用处理器是InvocationHandler接口的实例,需要实现invoke(Object proxy,Method method,Object[] args)方法,代理对象的所有方法在内部都是通过调用invoke方法来实现的。

还是从代码出发看一下动态代理的使用方法:

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
public interface MyInterface1{
void foo();
}

public interface MyInterface2{
void bar();
}

public Class Subject implements MyInterface1, MyInterface2 {
@Override
public void foo(){
System.out.println("foo!");
}

@Override
public void bar(){
System.out.println("bar!");
}
}

public class InvocationHandlerImpl implements InvocationHandler {
Subject subject = null;

public InvocationHandlerImpl(){
subject = new Subject();
}

@Override
public Object invoke(Object proxy, Method method,
Object[] args) throws Throwable
{

System.out.println("invoke method: " + method.getName());
return method.invoke(subject, args);
}
}

public class Test {
public static void main(String[] args) {
ClassLoader loader = Subject.class.getClassLoader();
Class[] interfaces = Subject.class.getInterfaces();
InvocationHandlerImpl h = new InvocationHandlerImpl();

Object proxy = Proxy.newProxyInstance(loader, interfaces, h);
(MyInterface1)proxy.foo();
(MyInterface2)proxy.bar();
}
}

动态代理是如何创建的?

在上面的例子中,创建一个动态代理的实例是这样进行的:

1
Object proxy = Proxy.newProxyInstance(loader, interfaces, h);

上面这个实现和下面的代码其实是等价的:

1
2
3
4
5
6
//生成动态代理类的类对象
Class proxyClass = Proxy.getProxyClass(loader, interfaces);
//通过反射获取生成的动态代理类的构造方法
Constructor constructor = proxyClass.getConstructor(new Class[] { InvocationHandler.class });
//通过构造函数对象生成动态代理类的实例
Object proxy = constructor.newInstance(h);

从以上的过程我们可以了解到,动态代理的实现起始是依赖于Java 的动态类加载机制。在创建动态代理类对象时,首先根据传入接口列表动态地生成动态代理类的字节码;在将动态生成的字节码加载进JVM后就可以利用反射机制获取构造方法等信息;根据Constructor对象动态地生成动态代理类的实例。动态代理类实现了接口列表中所有接口,方法内部都是通过调用处理器的invoke(Object proxy,Method method,Object[] args)方法进行实现的的。

动态代理的实现关键在于Proxy.getProxyClass(ClassLoader loader, Class<?>... interfaces)方法的实现,该方法负责生成动态代理类的字节码。关于该方法内部实现的细节可以阅读Java 动态代理机制分析及扩展,第 1 部分java设计模式——代理模式。字节码的生成是通过sun.misc.ProxyGenerator.generateProxyClass(String proxyName,class[] interfaces) 底层方法来产生的,可以将生成的字节码保存到本地,反编译后分析生成的代理类的内部细节。

因为Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)方法返回的是Object实例,这里有必要强调一下生成的动态代理类的继承关系:动态代理类继承了Proxy类,并实现了接口列表interfaces中的所有接口。 因而可以安全地转型为其代理的所有接口。

小结

Java 中的动态代理技术的出现简化了代理模式的实现升本,通过动态字节码生成、反射等技术,可以使得开发人员更为简洁优雅地实现代理模式。动态代理在很多技术框架中都有所使用,如Spring中的AOP,一些RPC框架等。除了Java自带的动态代理以外,也可以使用CGLIB等类似的库。

-EOF-

参考
代理模式原理及实例讲解
Java 动态代理机制分析及扩展
Java设计模式——代理模式