代理模式使我们可以做到很多不同的事情,比如封装真实对象,使其对客户端透明(安全性);跨域访问(远程代理)等等。代理可以被分为静态代理和动态代理,我们可以使用动态代理来延迟创建我们所需的对象(很多程序往往并不是一开始就需要所有的对象,特别是对于某些耗费很多资源的对象)。
在谈论动态代理之前,我们先来回顾一下代理模式设计到的角色及其相互关系。
java中的代理涉及到四个不同的角色:代理的协议接口Subject(暴露给Client),协议接口的具体实现 RealSubject , 代理对象SubjectProxy,Client。相互关系如图1所示。
图1 代理模式UML图
在静态代理中,SubjectProxy一般是由我们人工创建的,但是使用java的动态代理,我们可以让机器帮我们创建这个对象。那么java中究竟是如何做到这一点的呢?
java动态代理除了上述代理模式中的Client、Subject、RealSubject之外,还涉及到另外两个对象:InvocationHandler 和 Proxy (均位于java反射包中),其中Proxy负责创建代理对象,即上文提到的SubjectProxy,所有对SubjectProxy 方法的访问都会映射到InvocationHandler 实现类的invoke方法上面。
我们先来看一个动态代理的例子:
ProxyTestSuite.java
import java.io.PrintWriter;
/**
*
* @author H.John
*/
public class ProxyTestSuite {
public static void main(String[] args) {
Subject subject = (Subject) ProxyHandler.createProxy(
new RealSubject(),new PrintWriter(System.out,true));
subject.request();
}
}
/**
*
* 代理协议接口
*/
interface Subject{
void request();
}
/**
*
* 真实对象
*
*/
class RealSubject implements Subject{
public void request(){
System.out.println("i am a real subject");
}
}
ProxyHandler.java
/**
*
* @author H.John
* 代理实例调用处理程序。
* 对代理实例调用方法时,将对方法调用进行编码并将其指派到它的调用处理程序的 invoke方法。
*/
public class ProxyHandler implements InvocationHandler {
//真实被代理对象引用
private Object target_;
private PrintWriter writer_;
public ProxyHandler(Object target, PrintWriter writer) {
target_ = target;
writer_ = writer;
}
public static Subject createProxy(Object target,PrintWriter writer) {
return (Subject) Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new ProxyHandler(target, writer));
}
/**
* 对代理实例调用方法时触发的方法,通过此方法,以反射的方式去调用代理实例的实现方法。
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result;
try {
writer_.println(method.getName());
result = method.invoke(target_, args);
} catch (Exception e) {
writer_.println(method.getName() + " throws " + e.getCause());
throw e.getCause();
}
writer_.println(method.getName() + " returns");
return result;
}
}
那么上述几个角色在java动态代理中是怎样被联系起来的呢?下面我们就通过阅读源码来找寻我们需要的答案。
一切的奥秘均在Proxy类中:
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
if (h == null) {
throw new NullPointerException();
}
/*
* Look up or generate the designated proxy class.
*/
Class cl = getProxyClass(loader, interfaces);
/*
* Invoke its constructor with the designated invocation handler.
*/
try {
Constructor cons = cl.getConstructor(constructorParams);
return (Object) cons.newInstance(new Object[] { h });
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString());
} catch (IllegalAccessException e) {
throw new InternalError(e.toString());
} catch (InstantiationException e) {
throw new InternalError(e.toString());
} catch (InvocationTargetException e) {
throw new InternalError(e.toString());
}
}
我们可以用上述方法来创建所需的代理对象,传入三个参数,类加载器,被代理对象subject实现的接口,handler实现类(使得代理对象和被代理对象联系起来的类,该类持有被代理对象RealSubject)。
在newProxyInstance方法中,有2个关键步骤:
(1) 获取getProxyClass方法获取代理类的Class。
(2) 通过Class获取参数为InvocationHandler的构造方法,并创建代理对象。
那么我们想知道getProxyClass方法中究竟干了什么事情?
{
/*
* Choose a name for the proxy class to generate.
*/
long num;
synchronized (nextUniqueNumberLock) {
num = nextUniqueNumber++;
}
String proxyName = proxyPkg + proxyClassNamePrefix + num;
/*
* Verify that the class loader hasn't already
* defined a class with the chosen name.
*/
/*
* Generate the specified proxy class.
*/
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces);
try {
proxyClass = 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());
}
}
我们可以看到在getProxyClass方法中,程序通过ProxyGenerator.generateProxyClass(...)方法帮我们做了我们平时都在做的编码工作,只不过是没有生成源代码,直接根据class文件的格式生成了一个具有InvocationHandler实例对象的class二进制文件。再通过native方法defineClass0去构造我们平时都在用的Class对象。
ok,到现在为止,我们对java动态反射的运用以及jdk的内部实现都有了一个比较初步的了解。