java动态代理

java动态代理可以对任意方法做类似外观模式封装,从而增强接口功能,常见场景统计接口耗时,日志埋点等。

前言

代理的目的是为了让原始类和使用类之间解耦,也就是增加一个中间类。
而根据加载代理类的时机可以分为静态代理和动态代理,静态代理在编译的时候就知道代理类是谁,而动态代理只有到了调用的时候才知道。因此动态代理可以更灵活的控制接口的实现逻辑,代码的抽象程度也更高。
动态代理的实现方式

  • 基于cglib
  • 基于javassist
  • 基于jdk的Proxy
  • 基于ASM

几种动态代理的对比

基于JDK

以下分析基于JDK8。详细流程参考Proxy类的核心方法newProxyInstance

1
2
3
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)

核心思路是基于java的WeakCache缓存,用classloader作为第一层key,用需要代理的interfaces作为第二层key,value为基于ProxyGenerator类动态生成java字节码,并通过底层jni接口生成类,这个类也就是实现所要代理接口的类并继承了Proxy。最后再通过java反射实例化代理类,如果接口是public,那么构建出的代理类名称是com.sun.proxy.$Proxy0,如果接口是protected那么会在接口所在包下构建出$Proxy0类,这也是为了适配java类的访问机制,可以通过配置System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");将生成的.class字节码保存下来,从而查看实际生成的类。

基于CGLIB

基于JDK的动态代理只能代理接口,如果想要代理一个类,那么基于JDK的方式就做不到了。CGLIB是一个基于ASM的可以动态生成字节码的库,功能相比JDK的更为强大。

CGLIB通过Enhancer的配置,包括filter,callback,序列化id等参数,生成一个key,用于set或者hashmap中的唯一健。然后将代理类用weakHashMap缓存在内存中,注意缓存有两层,第一层为classLoader,不同的classLoader有不同key,第二层才是Enhancer的key,默认情况下支持缓存,也就是说不用重复生成代理类的字节码,最后通过java的反射生成代理类实例,详细参考Enhancer.generateClass()方法.
在调用的过程中,CGLIB也没有采用反射的方式调用方法,也是通过生成字节码的方式,并且通过缓存来提高调用效率。

cglib的github地址

参考

美团技术博客

ulysses wechat
订阅+