动态代理
次访问
什么是动态代理?
代理模式是常用的Java 设计模式,它的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。
代理对象和被代理对象一般实现相同的接口,调用者与代理对象进行交互。代理的存在对于调用者来说是透明的,调用者看到的只是接口。代理对象则可以封装一些内部的处理逻辑,如访问控制、远程通信、日志、缓存等。比如一个对象访问代理就可以在普通的访问机制之上添加缓存的支持。
传统的代理模式的实现,需要在源代码中添加一些附加的类。这些类一般是手写或是通过工具来自动生成。此即静态代理模式。
JDK5开始引入动态代理机制,允许开发人员在运行时刻动态的创建出代理类及其对象。在运行时刻,可以动态创建出一个实现了多个接口的代理类。每个代理类的对象都会关联一个表示内部处理逻辑的InvocationHandler接 口的实现。
当使用者调用了代理对象所代理的接口中的方法的时候,这个调用的信息会被传递给InvocationHandler的invoke方法。在 invoke方法的参数中可以获取到代理对象、方法对应的Method对象和调用的实际参数。
invoke方法的返回值被返回给使用者。这种做法实际上相当于对方法调用进行了拦截。
使用代码实例
package study.aop;
//共同接口
public interface Subject {
public void doSomething();
}
package study.aop;
//被代理类
public class RealSubject implements Subject{
public void doSomething() {
System.out.println("call doSomething in RealSubject");
}
}
package study.aop;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
//代理类
public class ProxySubject implements InvocationHandler {
private Object tar;
public Object bind(Object tar) {
//对象绑定,此处的 tar 对象即需要被代理的对象
this.tar = tar;
//获取代理对象
return Proxy.newProxyInstance(tar.getClass().getClassLoader(), tar
.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Object result = null;
//这里可以做一些调用方法之前的工作
System.out.println("------------------before------------------");
result = method.invoke(tar, args);
System.out.println("------------------after------------------");
//这里可以做一些调用方法之后的工作
return result;
}
}
package study.aop;
//测试类
public class TestProxy {
public static void main(String[] args) {
ProxySubject ps = new ProxySubject();
Subject subject = (Subject)ps.bind(new RealSubject());
subject.doSomething();
}
}
运行结果:
------------------before------------------
call doSomething in RealSubject
------------------after------------------
如果需要再doSomething()中添加参数,只需要在原来的接口中声明相应的原型即可。
原理分析
代理对象如何生成
我们注意到在生成对象时用了Proxy的静态方法 newProxyInstance,那么这个方法的是怎么生成代理对象的呢?
可以看一下此方法的源码:
/**
* loader:类加载器
* interfaces:目标对象实现的接口
* h:InvocationHandler的实现类
*/
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 {
// 调用代理对象的构造方法(也就是$Proxy0(InvocationHandler h))
Constructor cons = cl.getConstructor(constructorParams);
// 生成代理类的实例并把MyInvocationHandler的实例传给它的构造方法
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());
}
}
getProxyClass方法代码:
public static Class<?> getProxyClass(ClassLoader loader,
Class<?>... interfaces)
throws IllegalArgumentException
{
// 如果目标类实现的接口数大于65535个则抛出异常(我XX,谁会写这么NB的代码啊?)
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
// 声明代理对象所代表的Class对象(有点拗口)
Class proxyClass = null;
String[] interfaceNames = new String[interfaces.length];
Set interfaceSet = new HashSet(); // for detecting duplicates
// 遍历目标类所实现的接口
for (int i = 0; i < interfaces.length; i++) {
// 拿到目标类实现的接口的名称
String interfaceName = interfaces[i].getName();
Class interfaceClass = null;
try {
// 加载目标类实现的接口到内存中
interfaceClass = Class.forName(interfaceName, false, loader);
} catch (ClassNotFoundException e) {
}
if (interfaceClass != interfaces[i]) {
throw new IllegalArgumentException(
interfaces[i] + " is not visible from class loader");
}
// 中间省略了一些无关紧要的代码 .......
// 把目标类实现的接口代表的Class对象放到Set中
interfaceSet.add(interfaceClass);
interfaceNames[i] = interfaceName;
}
// 把目标类实现的接口名称作为缓存(Map)中的key
Object key = Arrays.asList(interfaceNames);
Map cache;
synchronized (loaderToCache) {
// 从缓存中获取cache
cache = (Map) loaderToCache.get(loader);
if (cache == null) {
// 如果获取不到,则新建地个HashMap实例
cache = new HashMap();
// 把HashMap实例和当前加载器放到缓存中
loaderToCache.put(loader, cache);
}
}
synchronized (cache) {
do {
// 根据接口的名称从缓存中获取对象
Object value = cache.get(key);
if (value instanceof Reference) {
proxyClass = (Class) ((Reference) value).get();
}
if (proxyClass != null) {
// 如果代理对象的Class实例已经存在,则直接返回
return proxyClass;
} else if (value == pendingGenerationMarker) {
try {
cache.wait();
} catch (InterruptedException e) {
}
continue;
} else {
cache.put(key, pendingGenerationMarker);
break;
}
} while (true);
}
try {
// 中间省略了一些代码 .......
// 这里就是动态生成代理对象的最关键的地方
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces);
try {
// 根据代理类的字节码生成代理类的实例
proxyClass = defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
throw new IllegalArgumentException(e.toString());
}
}
// add to set of all generated proxy classes, for isProxyClass
proxyClasses.put(proxyClass, null);
}
// 中间省略了一些代码 .......
return proxyClass;
}
进去ProxyGenerator类的静态方法generateProxyClass,这里是真正生成代理类class字节码的地方。
public static byte[] generateProxyClass(final String name,
Class[] interfaces)
{
ProxyGenerator gen = new ProxyGenerator(name, interfaces);
// 这里动态生成代理类的字节码,由于比较复杂就不进去看了
final byte[] classFile = gen.generateClassFile();
// 如果saveGeneratedFiles的值为true,则会把所生成的代理类的字节码保存到硬盘上
if (saveGeneratedFiles) {
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction<Void>() {
public Void run() {
try {
FileOutputStream file =
new FileOutputStream(dotToSlash(name) + ".class");
file.write(classFile);
file.close();
return null;
} catch (IOException e) {
throw new InternalError(
"I/O exception saving generated file: " + e);
}
}
});
}
// 返回代理类的字节码
return classFile;
}
invoke方法谁来调用
具体字节码较为繁琐,此处略去。简言之就是在testProxy的subject.doSomething()调用时,反编译后,发现调用的方法就是ProxySubject的public Object invoke(Object proxy, Method method, Object[] args)方法。
用处
不允许直接访问某些类;对访问要做特殊处理等。或者,要对原方法进行统一的扩展,例如加入日志记录。
与静态代理的区别
静态代理类:程序员创建或由特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。
动态代理类:在程序运行时,运用反射机制动态创建而成。无需程序员手工编写它的源代码。动态代理类不仅简化了编程工作,而且提高了软件系统的可扩展性,因为Java 反射机制可以生成任意类型的动态代理类。