Cglib动态代理
-
最底层的是字节码ByteCode,字节码是Java为了保证“一次编译,到处运行”而产生的一种虚拟的指令格式。
-
-
位于ASM之上的是CGLIB、Groovy、BeanShell,后两种并不是Java体系中的内容而是脚本语言,它们通过ASM框架生成字节码变相执行Java代码,这说明在JVM中执行程序并不一定非要写Java代码,只要你能生成Java字节码,JVM并不关心字节码的来源,当然通过Java代码生成的JVM字节码是通过编译器直接生成的,算是最“正统”的JVM字节码。
-
位于CGLIB、Groovy、BeanShell之上的就是Hibernate、Spring AOP这些框架了。
-
最上层的是Applications,即具体应用,一般都是一个Web项目或者本地跑一个程序。
从一个案列引入Cglib
真实类
public class HelloService { public HelloService() { System.out.println("HelloService Constructor..."); } /** * 该方法不能被子类覆盖,Cglib是无法代理final修饰的方法的 */ final public String sayOthers(String name) { System.out.println("HelloService...sayOthers..." + name); return null; } public void sayHello() { System.out.println("HelloService...sayHello..."); } }
cglib方法拦截器类
public class MyMethodInterceptor implements MethodInterceptor { /** * cglib * * @param o cglib生成的代理对象 * @param method 被代理对象的方法 * @param objects 方法入参 * @param methodProxy 代理方法 * @return * @throws Throwable */ @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println("before advice..."); Object object = methodProxy.invokeSuper(o, objects); System.out.println("after advice..."); return object; } }
测试类
public class Main { public static void main(String[] args) { //代理类class文件存入本地磁盘方便我们反编译查看源码 System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "F:\\workspace\\demo"); //通过CGLIB动态代理获取代理对象的过程 Enhancer enhancer = new Enhancer(); //设置enhancer对象的父类 enhancer.setSuperclass(HelloService.class); //设置enhancer的回调对象 enhancer.setCallback(new MyMethodInterceptor()); //创建代理对象 HelloService proxy = (HelloService) enhancer.create(); //通过代理对象调用目标方法 proxy.sayHello(); } }
测试结果
HelloService Constructor...
before advice...
HelloService...sayHello...
after advice...
从案例代码可以了解到,如果你了解过jdk的动态代理,那么与之比较,jdk动态代理需要实现代理类的接口,编写调用处理器,且要实现InvocationHandler。而cglib采用的是继承的方式,最终生成的代理类,是继承了真实类。编写自定义的方法拦截器,都要实现MethodInterceptor,这是使用cglib代理必须要实现的接口,然后通过其intercept方法实现具体的逻辑。而代理类是怎样和真实类产生联系的呢?看上述的测试代码,通过Enhancer对象创建代理类,并设置了其父类就是真实类,这样生成的代理类就和真实类有了继承关系。那么这个Enhancer是什么呢?它是一个字节码增强器,用来为无接口的类创建代理的,这里先不过多介绍。
一个小小的案例,让我们知道了如何使用cglib,以及最后的结果。但是如果你是一个愿意刨根问底的,那么一定会有一些疑问。cglib到底是怎样完成这样的代理的?是如果实现方法拦截的?所以接下来呢,我们还是要带着问题,一起深入了解一下。
Cglib原理分析
我们先从生成的字节码入手,看看cglib如何利用它们来完成代理的。HelloService$$EnhancerByCGLIB$$750c1a60.class。前面说了,cglib是采用继承的方式,所以结果也不难看出,生成的字节码类继承了真实类,这样是可以重写其父类的方法,并且还可以添加一些额外的逻辑。
public class HelloService$$EnhancerByCGLIB$$750c1a60 extends HelloService implements Factory { private boolean CGLIB$BOUND; public static Object CGLIB$FACTORY_DATA; private static final ThreadLocal CGLIB$THREAD_CALLBACKS; private static final Callback[] CGLIB$STATIC_CALLBACKS; private MethodInterceptor CGLIB$CALLBACK_0; private static Object CGLIB$CALLBACK_FILTER; //真实对象的方法 private static final Method CGLIB$sayHello$0$Method; //代理对象的代理方法(都与真实对象的方法一一对应) private static final MethodProxy CGLIB$sayHello$0$Proxy; private static final Object[] CGLIB$emptyArgs; private static final Method CGLIB$equals$1$Method; private static final MethodProxy CGLIB$equals$1$Proxy; private static final Method CGLIB$toString$2$Method; private static final MethodProxy CGLIB$toString$2$Proxy; private static final Method CGLIB$hashCode$3$Method; private static final MethodProxy CGLIB$hashCode$3$Proxy; private static final Method CGLIB$clone$4$Method; private static final MethodProxy CGLIB$clone$4$Proxy; //静态代码块,会优先加载 static void CGLIB$STATICHOOK1() { CGLIB$THREAD_CALLBACKS = new ThreadLocal(); CGLIB$emptyArgs = new Object[0]; //利用Class.forName创建对象,后面的全限定名就是代理对象 Class var0 = Class.forName("com.alibaba.proxy.cglib.HelloService$$EnhancerByCGLIB$$750c1a60"); //这里的var1会在下面进行创建,这里只是声明 Class var1; Method[] var10000 = ReflectUtils.findMethods(new String[]{"equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;"}, (var1 = Class.forName("java.lang.Object")).getDeclaredMethods()); CGLIB$equals$1$Method = var10000[0]; CGLIB$equals$1$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$1"); CGLIB$toString$2$Method = var10000[1]; CGLIB$toString$2$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/String;", "toString", "CGLIB$toString$2"); CGLIB$hashCode$3$Method = var10000[2]; CGLIB$hashCode$3$Proxy = MethodProxy.create(var1, var0, "()I", "hashCode", "CGLIB$hashCode$3"); CGLIB$clone$4$Method = var10000[3]; CGLIB$clone$4$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/Object;", "clone", "CGLIB$clone$4"); //上面的var1声明,这里进行创建对象并赋值。同样是通过Class.forName创建,后面是真实对象的全限定名 CGLIB$sayHello$0$Method = ReflectUtils.findMethods(new String[]{"sayHello", "()V"}, (var1 = Class.forName("com.alibaba.proxy.cglib.HelloService")).getDeclaredMethods())[0]; //创建一个MethodProxy对象,将代理类、真实类、代理方法、真实方法都作为入参 CGLIB$sayHello$0$Proxy = MethodProxy.create(var1, var0, "()V", "sayHello", "CGLIB$sayHello$0"); }
CGLIB$sayHello$0()方法,是代理方法,methodProxy.invokeSuper会调用。下面的sayHello是真实方法,methodProxy.invoke会调用。这里解释了为什么在拦截器中调用methodProxy.invoke会死循环,因为下面的代码中一直在调用intercept。
final void CGLIB$sayHello$0() { super.sayHello(); } public final void sayHello() { MethodInterceptor var10000 = this.CGLIB$CALLBACK_0; if (var10000 == null) { CGLIB$BIND_CALLBACKS(this); var10000 = this.CGLIB$CALLBACK_0; } if (var10000 != null) { //调用intercept方法,也就是在自定义的Intercepter类中实现的方法。 var10000.intercept(this, CGLIB$sayHello$0$Method, CGLIB$emptyArgs, CGLIB$sayHello$0$Proxy); } else { super.sayHello(); } }
一直到上面,简单说一下流程:代理对象调用sayHello,然后调用拦截器intercept,然后执行拦截器中的前面代码,然后执行invokeSuper,会执行CGLIB$sayHello$0方法,然后调用父类sayHello,然后执行拦截器后面代码。
那么从invokeSuper方法中为什么就能调用到CGLIB$sayHello$0()方法呢?
-
MethodProxy.create:在代理类中create一个MethodProxy,将被代理对象与方法传入Signature1,将代理对象与方法传入Signature2,将代理类对象与被代理类对象传入MethodProxy.CreateInfo,Signature1与MethodProxy.CreateInfo可以认为是实体类存储信息的。
-
调用methodProxy.invokeSuper了,调用invokeSuper时又会首先调用init方法。
-
init方法就是给在create方法中没有赋值的fastclassInfo赋值,将createInfo与Signature1与Signature2中的信息取出,转成fastclass,如果fastclass在缓存中有就从缓存中取,没有的话就生成新的fastclass。
-
通过Signature1与Signature2获得方法的索引,存入i1与i2。
-
通过fci.f2.invoke(fci.i2, obj, args),fastclass反射调用代理对象的代理方法的索引,直接定位到代理方法。
-
代理方法中调用super.sayHello()
public class MethodProxy { private Signature sig1; private Signature sig2; private CreateInfo createInfo; private final Object initLock = new Object(); private volatile FastClassInfo fastClassInfo; /** * For internal use by {@link Enhancer} only; see the {@link org.springframework.cglib.reflect.FastMethod} class * for similar functionality. */ public static MethodProxy create(Class c1, Class c2, String desc, String name1, String name2) { MethodProxy proxy = new MethodProxy(); proxy.sig1 = new Signature(name1, desc); proxy.sig2 = new Signature(name2, desc); proxy.createInfo = new CreateInfo(c1, c2); return proxy; } public Object invokeSuper(Object obj, Object[] args) throws Throwable { try { init(); FastClassInfo fci = fastClassInfo; //fastCLass.invoke(代理方法索引,代理对象,方法参数) return fci.f2.invoke(fci.i2, obj, args); } catch (InvocationTargetException e) { throw e.getTargetException(); } }
MethodProxy调用流程
-
首先main方法中调用getInstance调用Enhancer创建代理类class文件,并将代理类引用返回。
-
然后调用代理对象的sayHello()
-
然后通过反编译代理类,查看sayHello方法,发现在sayHello中会调用拦截器方法。
-
然后在拦截器方法中会通过MethodProxy调用invokeSuper方法。
FastClass机制
Cglib动态代理执行代理方法效率之所以比JDK高是因为Cglib采用了FastClass机制,它为代理类和被代理类各生成了一个class,这个class会为代理类与被代理类的方法分类index。这个index作为方法参数,FastClass可以直接定位到要调用的方法进行调用,这样省去了反射调用,所以效率比JDK动态代理快。FastClass不是与代理类一起生成的,而是在第一次执行MethodProxy invoke/invokeSuper时生成的并放入缓存。
public class HelloService$$FastClassByCGLIB$$8656ab6f extends FastClass { public HelloService$$FastClassByCGLIB$$8656ab6f(Class var1) { super(var1); } public int getIndex(Signature var1) { String var10000 = var1.toString(); switch(var10000.hashCode()) { case -1488716497: if (var10000.equals("sayOthers(Ljava/lang/String;)Ljava/lang/String;")) { return 1; } break; } return -1; } public Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException { HelloService var10000 = (HelloService)var2; int var10001 = var1; try { switch(var10001) { case 0: var10000.sayHello(); return null; case 1: return var10000.sayOthers((String)var3[0]); case 2: return new Boolean(var10000.equals(var3[0])); case 3: return var10000.toString(); case 4: return new Integer(var10000.hashCode()); } } catch (Throwable var4) { throw new InvocationTargetException(var4); } } }
因为生成的FastClass代理类较长,这里只截取其中一部分,只为了说明问题。这里面包含了getIndex方法和invoke方法。getIndex方法会为代理类和真实类的方法分类index,这样每个index会对应到具体方法,那么在上面那个invokeSuper那里,实际上就是用fastClass对象调用的invoke方法,传入的参数就包含这个index,那么在invoke方法中,就直接根据index定位到具体方法执行了。
jdk动态代理和cglib动态代理的区别
1、jdk动态代理是实现了被代理对象的接口,cglib是继承了被代理对象。
2、jdk和cglib都是在运行期生成字节码,jdk是直接写class字节码,cglib使用ASM框架写class字节码,cglib代理实现更复杂,生成代理类比jdk效率低。