动态代理原理
博客专区 > markGao 的博客 > 博客详情
动态代理原理
markGao 发表于4年前
动态代理原理
  • 发表于 4年前
  • 阅读 281
  • 收藏 10
  • 点赞 0
  • 评论 0

腾讯云 十分钟定制你的第一个小程序>>>   

摘要: 本节将结合动态代理的源代码讲解其实现原理。动态代理的核心其实就是代理对象的生成,即Proxy.newProxyInstance(classLoader, proxyInterface, handler)。让我们进入newProxyInstance方法观摩下,核心代码其实就三行。

生成代理类

//获取代理类
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次数。

共有 人打赏支持
粉丝 16
博文 151
码字总数 91352
×
markGao
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额: