案例分析数组
package com.demo; public class UserService { public void addUser(){ System.out.println("添加用户"); } }
简单介绍下UserService
模拟数据层操做,TxHelper
做为一个cglib加强的回调.缓存
package com.demo; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; public class TxHelper implements MethodInterceptor { public Object intercept(final Object obj, final Method method, final Object[] args, final MethodProxy proxy) throws Throwable { System.out.println("开启事务"); Object res=proxy.invokeSuper(obj,args); // 这里调用invoke方法就会致使死循环从而栈溢出 // Object res=proxy.invoke(obj,args); System.out.println("关闭事务"); return res; } /*这个方法根据clazz使用空参构造器获取clazz的cglib子类*/ public Object getInstance(Class clazz){ Enhancer enhancer=new Enhancer(); enhancer.setSuperclass(clazz); enhancer.setCallback(this); return enhancer.create(); } public Object getInstance2(Class clazz,Class params[],Object[] args){ Enhancer enhancer=new Enhancer(); enhancer.setSuperclass(clazz); enhancer.setCallback(this); return enhancer.create(params,args); } }
TxHelper
中为了节省代码量,将获取Cglib生成的子类写在TxHelper
中,即getInstance(class)
和getInstance2(clazz,clazz[],Object[])
方法,都是调用的Enhancer.create
来获取Cglib子类.安全
Cglib依赖添加工具
<dependencies> <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>2.2</version> </dependency> <dependency> <groupId>asm</groupId> <artifactId>asm-util</artifactId> <version>3.1</version> </dependency> </dependencies>
依赖说明:cglib2.2版本只依赖于asm3.1,asm-util是asm的相关工具包,这里引入是另有目的.性能
测试方法测试
package com.demo; import net.sf.cglib.core.DebuggingClassWriter; public class UserServiceTests { public static void main(String[] args) { //设置cglib.debugLocation属性指定将动态代理的类指定生成在哪里 // 如下方式等价于 -Dcglib.debugLocation=E:\\data\\blog System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"E:\\data\\blog"); UserService userService= (UserService) new TxHelper().getInstance(UserService.class); userService.addUser(); } }
测试方法说明: System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY)
是用于设置 cglib动态代理的子类生成的位置,等价于启动参数 -Dcglib.debugLocation=E:\\data\\blog
,这样就能够将代理子类生成到咱们指定目录。 上面额外引入的依赖 asm-util
则是会将动态生成的子类的字节码展现出来。ui
public static final String DEBUG_LOCATION_PROPERTY = "cglib.debugLocation";
测试效果this
能够看到methodProxy.invokeSuper
方法会调用父类的方法 成功添加父类,至于另外的状况最后再分析.线程
进入以前设置的cglib.debugLocation
指定的目录,查看cglib生成的子类class文件.
在该目录下的本身的包名文件夹com/demo
下会有咱们的UserService
的cglib子类,额外的还有net/sf/cglib/core
以及net/sf/cglib/proxy
这两个cglib额外生成的层级目录,由于上面引入了 asm-util ,因此伴随着class文件,还会有一些特殊的 asm 文件,asm 文件使用notepad++等工具就能够查看了,这些asm文件记录着每个类生成过程当中字节码.
查看cglib生成的 class文件
能够看到咱们的UserService
类就已经生成了三个class文件, 其中UserService$$EnhancerByCGLIB$$268385a2
能够理解为是 UserService
的真实子类,而 UserService$$FastClassByCGLIB$$417ebd8c
和UserService$$EnhancerByCGLIB$$268385a2$$FastClassByCGLIB$$c6a21d27
则是计算动态代理类调用方法走父类仍是自己的方法,这里后面也会发现CGLIB比反射效率高(个人理解,直接调用会比反射效率高).
查看 asm 文件
补充一点:class version 46.0表明 JDK1.2版本编译的class文件,高版本编译环境可编译低版本编译过来的Class文件,可是低版本的JDK1.7就不能编译JDK1.8的class文件了,这时候尝试编译会报错:==Unsupported major version==
CGLIB子类一探究竟
上面测试效果、CGLIB子类咱们都获取到了,甭管怎么生成的CGLIB子类,咱们先来看看,CGLIB子类究竟长啥样?
一般会用 jd-gui 工具查看class文件,可是cglib生成的子类用 jd-gui 查看效果不是很好,不利于阅读,查看方式:直接将class文件拖到 IDEA 中,就能够查看反编译后的代码.
我不太习惯看 反编译后的 class 文件,因而就将 class文件的内容复制到 com/demo
目录下,而且修改文件名为 .java结尾,可是class文件内容直接复制到java 文件中,会有好多处红叉报错:一一解决下. 这里改为java文件只是为了方便本身阅读,不能用来调试.
第一处报错: final类型变量可能没初始化,致使编译不过;其实cglib是初始化了,可是为啥IDEA不认呢?
先说解决方法: 将final 关键字都去掉
其实cglib关于 final变量都是初始化了的,一个静态代码块调用 CGLIB$STATICHOOK1
,在静态方法CGLIB$STATICHOOK1
中进行了初始化,可是为啥IDEA不认呢?
static { CGLIB$STATICHOOK1(); } static void CGLIB$STATICHOOK1() { CGLIB$THREAD_CALLBACKS = new ThreadLocal(); CGLIB$emptyArgs = new Object[0]; Class var0 = Class.forName("com.demo.UserService$$EnhancerByCGLIB$$268385a2"); Class var1; Method[] var10000 = ReflectUtils.findMethods(new String[]{"addUser", "()V", "getUser", "()V"}, (var1 = Class.forName("com.demo.UserService")).getDeclaredMethods()); CGLIB$addUser$0$Method = var10000[0]; CGLIB$addUser$0$Proxy = MethodProxy.create(var1, var0, "()V", "addUser", "CGLIB$addUser$0"); CGLIB$getUser$1$Method = var10000[1]; CGLIB$getUser$1$Proxy = MethodProxy.create(var1, var0, "()V", "getUser", "CGLIB$getUser$1"); var10000 = ReflectUtils.findMethods(new String[]{"finalize", "()V", "equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;"}, (var1 = Class.forName("java.lang.Object")).getDeclaredMethods()); CGLIB$finalize$2$Method = var10000[0]; CGLIB$finalize$2$Proxy = MethodProxy.create(var1, var0, "()V", "finalize", "CGLIB$finalize$2"); CGLIB$equals$3$Method = var10000[1]; CGLIB$equals$3$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$3"); CGLIB$toString$4$Method = var10000[2]; CGLIB$toString$4$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/String;", "toString", "CGLIB$toString$4"); CGLIB$hashCode$5$Method = var10000[3]; CGLIB$hashCode$5$Proxy = MethodProxy.create(var1, var0, "()I", "hashCode", "CGLIB$hashCode$5"); CGLIB$clone$6$Method = var10000[4]; CGLIB$clone$6$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/Object;", "clone", "CGLIB$clone$6"); }
第二处报错:
MethodInterceptor.intercept
方法异常没有捕获解决方案: 手动Try catch异常吧.
报错三:
new
实例化对象缺乏括号,下面多了字节码<init>()
解决方案:上面添上括号,
<init>()
这行注释掉
另外两个 FastClass 类也是相似的操做,细心地发现:只有UserService$$EnhancerByCGLIB$$743464da
是继承了咱们的UserService
类的而且实现了Factory
接口 ,另外两个类都是继承自FastClass
.
上一步已经获得了cglib生成的动态代理类,动态代理类确定要初始化一个实例对象,实例化的流程呢:AbstractClassGenerator.create
--->Enhancer.firstInstance
--->Enhancer.createUsingReflection
private Object createUsingReflection(Class type) { setThreadCallbacks(type, callbacks); try{ if (argumentTypes != null) { return ReflectUtils.newInstance(type, argumentTypes, arguments); } else { return ReflectUtils.newInstance(type); } }finally{ // clear thread callbacks to allow them to be gc'd setThreadCallbacks(type, null); } }
Enhancer.createUsingReflection
首先setThreadCallbacks(type,callbacks)
----->ReflectUtils.newInstance
type就是当前这个动态代理子类的class UserService$$EnhancerByCGLIB$$743464da
,callbacks 就是咱们本身设置到enhancer
中的.
setThreadCallbacks
就是调用动态代理的类的静态CGLIB$SET_THREAD_CALLBACKS
方法,将callbacks
设置进入,查看动态代理类的静态CGLIB$SET_THREAD_CALLBACKS
方法:
public static void CGLIB$SET_THREAD_CALLBACKS(Callback[] var0) { CGLIB$THREAD_CALLBACKS.set(var0); } private static ThreadLocal CGLIB$THREAD_CALLBACKS;
其中 CGLIB$THREAD_CALLBACKS
原本是final static 变量,不过被咱们强行改成了static,这里能够看到 动态代理的类有个静态量ThreadLocal,持有回调数组callback
,至于ThreadLocal 的初始化在 static 代码块中完成了.
ReflectUtils.newInstance
方法会根据动态代理类可用的构造方法调用 反射来实例化 动态代理子类UserService$$EnhancerByCGLIB$$743464da
. 而实例化完成以后,仍然调用CGLIB$THREAD_CALLBACKS
将ThreadLocal变量中的callback
清除.
UserService$$EnhancerByCGLIB$$743464da
的实例化
public UserService$$EnhancerByCGLIB$$743464da() { CGLIB$BIND_CALLBACKS(this); } private static final void CGLIB$BIND_CALLBACKS(Object var0) { UserService$$EnhancerByCGLIB$$743464da var1 = (UserService$$EnhancerByCGLIB$$743464da)var0; if (!var1.CGLIB$BOUND) { var1.CGLIB$BOUND = true; Object var10000 = CGLIB$THREAD_CALLBACKS.get(); if (var10000 == null) { var10000 = CGLIB$STATIC_CALLBACKS; if (CGLIB$STATIC_CALLBACKS == null) { return; } } var1.CGLIB$CALLBACK_0 = (MethodInterceptor)((Callback[])var10000)[0]; } }
能够看到 动态代理类初始化调用了 CGLIB$BIND_CALLBACKS(this)
,先判断CGLIB$BOUND标志位,第一次绑定以后就为true了,绑定过程就是 从 ThreadLocal 中提取出来callback
,并赋给动态代理对象的CGLIB$CALLBACK_0
属性,回调属性都是以CGLIB$CALLBACK_
开头,假如回调数组元素为多个,那就有多少个属性,分别是CGLIB$CALLBACK_0
、CGLIB$CALLBACK_1
.
总结下来:回调数组callback赋值给了动态代理类的每个属性,有几个回调元素,就有几个回调属性;赋值过程是在实例化动态代理类时候完成的,为了防止线程不安全,用的是ThreadLocal来保存callback
如今UserService$$EnhancerByCGLIB$$743464da
实例已经有了,调用addUser
方法,会先进入子类的方法.
public final void addUser() { MethodInterceptor var10000 = this.CGLIB$CALLBACK_0; if (this.CGLIB$CALLBACK_0 == null) { CGLIB$BIND_CALLBACKS(this); var10000 = this.CGLIB$CALLBACK_0; } if (var10000 != null) { try { var10000.intercept(this, CGLIB$addUser$0$Method, CGLIB$emptyArgs, CGLIB$addUser$0$Proxy); } catch (Throwable throwable) { throwable.printStackTrace(); } } else { super.addUser(); } }
尝试获取CGLIB$CALLBACK_0
属性,为何说尝试呢?若是CGLIB$CALLBACK_0
为空,还回去从 ThreadLocal中取,当获取到CGLIB$CALLBACK_0
不为空,调用CGLIB$CALLBACK_0.intercept
方法.
四个入参:
CGLIB$addUser$0$Method
静态代码块中初始化,获取的是父类void UserService.addUser()
方法,也就是父类方法的Method.private final static Method CGLIB$addUser$0$Method; static{ .......... Class var1=null; try { CGLIB$addUser$0$Method = ReflectUtils.findMethods(new String[]{"addUser", "()V"}, (var1 = Class.forName("com.demo.UserService")).getDeclaredMethods())[0]; } catch (ClassNotFoundException e) { e.printStackTrace(); } ............ }
CGLIB$emptyArgs
一个大小为0的Object数组
CGLIB$addUser$0$Proxy
经过MethodProxy.create(superClass,cglibSonClass,methodDescriptor,superMethodName,sonMethodName)
来获取MethodProxy对象.
private static final MethodProxy CGLIB$addUser$0$Proxy; static{ Class var0 = null; try { var0 = Class.forName("com.demo.UserService$$EnhancerByCGLIB$$743464da"); } catch (ClassNotFoundException e) { e.printStackTrace(); } Class var1=null; try { CGLIB$addUser$0$Method = ReflectUtils.findMethods(new String[]{"addUser", "()V"}, (var1 = Class.forName("com.demo.UserService")).getDeclaredMethods())[0]; } catch (ClassNotFoundException e) { e.printStackTrace(); } CGLIB$addUser$0$Proxy = MethodProxy.create(var1, var0, "()V", "addUser", "CGLIB$addUser$0"); }
MethodProxy.create过程
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; }
解释说明: c1是父类class,c2是cglib子类class,desc就是方法修饰符,好比()V表明无如参void类型,(Ljava/lang/String;)I表明String入参int返回值的方法,对象类型都是 L全限定名; 这种,基本数据类型int对应 I 这种,数组对应 [
name1是父类中方法的名称,name2是子类中方法的名称 ,好比addUser
如今cglib子类想要直接调用父类的方法,不须要加强,那我调用CGLIB$addUser$0
方法就等价于 直接调用父类的方法,调用方式以下:
意味着:cglib子类中的addUser方法就是加强的方法,而CGLIB$addUser$0
就是未加强的方法,方法名的生成规则后续再记录.
public static void main(String[] args) throws Exception { //设置cglib.debugLocation属性指定将动态代理的类指定生成在哪里 // 如下方式等价于 -Dcglib.debugLocation=E:\\data\\blog System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"E:\\data\\blog"); UserService userService= (UserService) new TxHelper().getInstance(UserService.class); // userService.addUser(); for (final Method declaredMethod : userService.getClass().getDeclaredMethods()) { System.out.println(declaredMethod.getName()); } Method m = userService.getClass().getDeclaredMethod("CGLIB$addUser$0", null); m.invoke(userService,null); }
回到MethodProxy.create
中,生成了一个MethodProxy对象,sig一、sig2都是Signature
类型的,表明方法签名,sig1是父类方法的签名,sig2是子类方法的签名;而 createInfo
属性最主要的是持有 c1(父类class)、c2(cglib子类class).
至此咱们已经分析完毕MethodProxy.create
的逻辑.
MethodInterceptor.intercept
调用逻辑
MethodInterceptor 怎么来的、有什么属性咱们已经分析完毕,intercept
方法就进入了咱们自定义的回调类中,离咱们分析的目标更近了.
咱们自定义回调逻辑中,知道proxy.invokeSuper
方法才是正确的,而proxy.invoke
会致使栈溢出. 如今咱们已经知道这个intercept方法的几个入参: obj 就是当前cglib子类实例,method就是父类UserService.adUser()
的Method,args就是方法的入参,proxy就是上面建立的MethodProxy
对象。 cglib子类中每个继承父类的方法都会生成一个MethodProxy对应,额外还有Object类的toString
、hashCode
、equals
、finalize
、clone
方法
public Object intercept(final Object obj, final Method method, final Object[] args, final MethodProxy proxy) throws Throwable { System.out.println("开启事务"); Object res=proxy.invokeSuper(obj,args); // 这里invoke方法就会致使死循环从而栈溢出 // Object res=proxy.invoke(obj,args); System.out.println("关闭事务"); return res; }
MethodProxy.invokeSuper
MethodProxy的 invokeSuper方法以下.
说明文档上介绍:调用加强类(cglib子类)的父类的方法,obj对象就是加强的cglib子类.
init()
方法private void init() { if (fastClassInfo == null) { synchronized (initLock) { if (fastClassInfo == null) { CreateInfo ci = createInfo; FastClassInfo fci = new FastClassInfo(); fci.f1 = helper(ci, ci.c1); fci.f2 = helper(ci, ci.c2); fci.i1 = fci.f1.getIndex(sig1); fci.i2 = fci.f2.getIndex(sig2); fastClassInfo = fci; } } } }
双重检查锁机制,一直觉得是安全的,可是cglib注释提到 这段代码在JDK1.5以前可能会致使fastClassInfo
实例化多个?因此fastClassInfo
对象用了volatile
关键字来修饰,咱也没用过volatile
关键字,这里就暂留做疑问吧.
createInfo
对象以前提到了,是在实例化MethodProxy
中实例化的,持有两个重要属性父类c1、cglib子类c2,helper
方法另外生成了两个FastClass
的实例,f1就是UserService
这种被UserService$$FastClassByCGLIB$$xxxx
,f2就是UserService$$EnhancerByCGLIB$$xxx$$FastClassByCGLIB$$xxx
,生成过程采用ASM生成字节码较为复杂就忽略,若是须要查看字节码,引入asm-util
而且指定cglib.debugLocation
参数便可生成asm文件.这里的fastClassInfo
是单例的,又有点缓存的意思,不少初始化逻辑在第一次实例化过程当中完成,后续进入就不须要init
.
init()
方法中volatile
修饰fastClassInfo
的做用是啥?
参考上面获取cglib java类,咱们能够看到指定的cglib.debugLocation
指定包目录下另外两个class
就是这里的FastClass
以UserService
的FastClass来看,主要实现了三类方法getIndex
、invoke
、newInstance
,先来看第一类方法getIndex
Signature是cglib包中的类,表明一个方法的签名. 根据Signature来获取索引,索引表明要执行哪一个方法. 首先判断 方法名 + 方法返回值 的hash值,hash值一致的状况下,还须要再用 equals 方法再次比较。 由于两个不一样的字符串是有可能hash值相等的. 这样确保获取到的索引正确,索引用呢是在invoke
方法中. 至于这个索引则是在 生成字节码中判断的.
上面获取的方法索引在这里就能够体现用处了,就是var1参数。 switch中的序号和上面 getIndex
必定是一一对应的.
同理,cglib子类的getIndex
、invoke
方法都是相似的,只不过cglib子类的 FastClassgetIndex以及 invoke的选项会多一倍,由于一个是 继承自父类,也就是加强的方法,还一个是重命名的父类方法,单纯调用父类方法,好比CGLIB$toString$3()
#### init方法继续
if (fastClassInfo == null) { synchronized (initLock) { if (fastClassInfo == null) { CreateInfo ci = createInfo; FastClassInfo fci = new FastClassInfo(); fci.f1 = helper(ci, ci.c1); fci.f2 = helper(ci, ci.c2); fci.i1 = fci.f1.getIndex(sig1); fci.i2 = fci.f2.getIndex(sig2); fastClassInfo = fci; } } }
FastClassInfo
经过ASM生成了两个FastClass子类以后,f1就是 UserService$$FastClassByCGLIB
的实例,f2就是UserService$$EnhancerByCGLIB$$FastClass..
的实例,sig1表明被加强方法的签名,addUser()V
这种,sig2表明别加强方法在cglib子类中的签名,如CGLIB$addUser$0
.
public Object invokeSuper(Object obj, Object[] args) throws Throwable { try { init(); FastClassInfo fci = fastClassInfo; return fci.f2.invoke(fci.i2, obj, args); } catch (InvocationTargetException e) { throw e.getTargetException(); } }
fci.i2就是CGLIB$addUser$0
在其中的索引,好比我这里是15. obj对象是cglib的子类,invokeSuper就是调用这个cglib加强类的索引为15的方法,而绕回来这个索引又是经过 getIndex
获取CGLIB$addUser$0
获取的,等于直接调用了 cglib加强子类的CGLIB$addUser$0
方法,就是调用了父类的addUser方法. 这样invokeSuper就实现了动态调用父类的方法,好比UserService
有不少种方法,我如今是A
方法,那里面的MethodProxy
对象又是一个全新的关联了A
以及CGLIB$A$...
方法,而咱们只须要使用proxy.invokeSuper
就能自动调用父类方法,这里绕开了反射,相应的在动态代理类、每一个方法第一次调用会带来必定的性能损耗,可是后续使用起来与普通调用无差异,会优于后续反射来调用方法.
final void CGLIB$addUser$0() { super.addUser(); }
相信这里咱们就能大概明白,为啥invoke
会带来方法死循环.
public Object invoke(Object obj, Object[] args) throws Throwable { try { init(); FastClassInfo fci = fastClassInfo; return fci.f1.invoke(fci.i1, obj, args); } catch (InvocationTargetException e) { throw e.getTargetException(); } catch (IllegalArgumentException e) { if (fastClassInfo.i1 < 0) throw new IllegalArgumentException("Protected method: " + sig1); throw e; } }
这里与invokeSuper
的区别大概是,f2换成了f1,索引也换成了对应的i1,咱们不饶一圈,就是直接调用了动态代理类的addUser
方法. 嗯? 咱们都加强了addUser
方法,你还调用加强的方法,那不是又饶了一圈嘛,难怪会死循环,从而栈溢出.
我经常在想假如我就不想加强某个方法,咋调用呢? 想了个笨的方法,反射找到那个直接调用父类的方法,反射执行行不?
public static void main(String[] args) throws Exception { //设置cglib.debugLocation属性指定将动态代理的类指定生成在哪里 // 如下方式等价于 -Dcglib.debugLocation=E:\\data\\blog System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"E:\\data\\blog"); UserService userService= (UserService) new TxHelper().getInstance(UserService.class); // userService.addUser(); for (final Method declaredMethod : userService.getClass().getDeclaredMethods()) { System.out.println(declaredMethod.getName()); } Method m = userService.getClass().getDeclaredMethod("CGLIB$addUser$0", null); m.setAccessible(true); m.invoke(userService,null); }
查看输出.......
想了想,知识有限,咱们查看他全部的方法,还好对于CGLIB有一点点了解,找那个方法名相似CGLIB$XXX$数字
的方法,反射调用试试呗, 虽然这种方法确定存在不少弊端,奈何能耐有限呢?