生成代理类
//获取代理类
Class cl = getProxyClass(loader, interfaces);
//获取带有InvocationHandler参数的构造方法
Constructor cons = cl.getConstructor(constructorParams);
//把handler传入构造方法生成实例
return (Object) cons.newInstance(new Object[] { h });
其中getProxyClass(loader, interfaces)方法用于获取代理类,它主要做了三件事情:在当前类加载器的缓存里搜索是否有代理类,没有则生成代理类并缓存在本地JVM里。清单三:查找代理类。
// 缓存的key使用接口名称生成的List
Object key = Arrays.asList(interfaceNames);
synchronized (cache) {
do {
Object value = cache.get(key);
// 缓存里保存了代理类的引用
if (value instanceof Reference) {
proxyClass = (Class) ((Reference) value).get();
}
if (proxyClass != null) {
// 代理类已经存在则返回
return proxyClass;
} else if (value == pendingGenerationMarker) {
// 如果代理类正在产生,则等待
try {
cache.wait();
} catch (InterruptedException e) {
}
continue;
} else {
//没有代理类,则标记代理准备生成
cache.put(key, pendingGenerationMarker);
break;
}
} while (true);
}
代理类的生成主要是以下这两行代码。 清单四:生成并加载代理类
//生成代理类的字节码文件并保存到硬盘中(默认不保存到硬盘)
proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces);
//使用类加载器将字节码加载到内存中
proxyClass = defineClass0(loader, proxyName,proxyClassFile, 0, proxyClassFile.length);
ProxyGenerator.generateProxyClass()方法属于sun.misc包下,Oracle并没有提供源代码,但是我们可以使用JD-GUI这样的反编译软件打开jre\lib\rt.jar来一探究竟,以下是其核心代码的分析。
清单五:代理类的生成过程
//添加接口中定义的方法,此时方法体为空
for (int i = 0; i < this.interfaces.length; i++) {
localObject1 = this.interfaces[i].getMethods();
for (int k = 0; k < localObject1.length; k++) {
addProxyMethod(localObject1[k], this.interfaces[i]);
}
}
//添加一个带有InvocationHandler的构造方法
MethodInfo localMethodInfo = new MethodInfo("<init>", "(Ljava/lang/reflect/InvocationHandler;)V", 1);
//循环生成方法体代码(省略)
//方法体里生成调用InvocationHandler的invoke方法代码。(此处有所省略)
this.cp.getInterfaceMethodRef("InvocationHandler", "invoke", "Object; Method; Object;")
//将生成的字节码,写入硬盘,前面有个if判断,默认情况下不保存到硬盘。
localFileOutputStream = new FileOutputStream(ProxyGenerator.access$000(this.val$name) + ".class");
localFileOutputStream.write(this.val$classFile);
那么通过以上分析,我们可以推出动态代理为我们生成了一个这样的代理类。把方法doSomeThing的方法体修改为调用LogInvocationHandler的invoke方法。
清单六:生成的代理类源码
package sample.proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; public class ProxyBusiness{ private LogInvocationHandler h; public void doBusiness2() { try { Method m = (h.target).getClass().getMethod("doBusiness2", null); h.invoke(this, m, null); } catch (Throwable e) { // 异常处理(略) } } public boolean doBusiness1() { try { Method m = (h.target).getClass().getMethod("doBusiness1", null); return (Boolean) h.invoke(this, m, null); } catch (Throwable e) { // 异常处理(略) } return false; } public ProxyBusiness(LogInvocationHandler h) { this.h = h; } /** * 打印日志的切面 */ public static class LogInvocationHandler implements InvocationHandler { private Object target; // 目标对象 LogInvocationHandler(Object target) { this.target = target; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 执行原有逻辑 Object rev = method.invoke(target, args); // 执行织入的日志,你可以控制哪些方法执行切入逻辑 if (method.getName().equals("doBusiness2")) { System.out.println("记录日志"); } return rev; } } // 测试用 public static void main(String[] args) { // 构建AOP的Advice LogInvocationHandler handler = new LogInvocationHandler(new Business()); new ProxyBusiness(handler).doBusiness1(); new ProxyBusiness(handler).doBusiness2(); } }
小结
从前两节的分析我们可以看出,动态代理在运行期通过接口动态生成代理类,这为其带来了一定的灵活性,但这个灵活性却带来了两个问题,第一代理类必须实现一 个接口,如果没实现接口会抛出一个异常。第二性能影响,因为动态代理使用反射的机制实现的,首先反射肯定比直接调用要慢,经过测试大概每个代理类比静态代 理多出10几毫秒的消耗。其次使用反射大量生成类文件可能引起Full GC造成性能影响,因为字节码文件加载后会存放在JVM运行时区的方法区(或者叫持久代)中,当方法区满的时候,会引起Full GC,所以当你大量使用动态代理时,可以将持久代设置大一些,减少Full GC次数。