前言
Spring的分享也要接近尾声了,由于这两周都比较忙,抽不出时间来准备,关于AOP的分享暂时就准备了JDK和CGLIB的原理,并结合应用分析其应用场景,时间仓促,若有不足,还望指正!
关于动态代理与其在应用中的实现主要参考了楼江航<以前诚信通的前辈,可惜组织找不到了>的文章。
有关ASM字节码的处理没有研究,本文主要针对以下内容做出总结:
1、动态代理的背景
2、动态代理的实现方式
3、JDK和CGLIB的实现方式
4、JDK和CGLIB的优劣对比
5、应用中的使用场景
代理
代理的概念不言而喻,它可以实现过滤请求、插入横切逻辑等功能,应用场景丰富多彩。
代理的方式分为静态代理和动态代理两种。
1、静态代理
程序运行前代理类的字节码文件依然存在,需要程序员编写源文件。
缺点:要针对于每一个类撰写代理类;对于单个被代理的类,如果需要被代理的方法很多,又加大了工作量。
优点:直观,可读性较强。
2、动态代理
程序运行时动态生成代理类的字节码文件,不需要程序员编写代理类java文件。
缺点:由于是运行时动态生成的,因此可读性不是很强;而且受限于被代理类自身的属性(jdk需要提供接口,cglib需要是非私有类)。
优点:代码更加简洁,解放了无谓的编码工作。
实现方式
让你来实现一个代理类,需要哪些上下文,有哪些解决方案~正是JDK和CGLIB两种解决方案的映射。
要生产一个类A的代理类,唯一需要了解的就是生成一个什么类,因此就有了基于该类的接口构造一个“A”,或者继承A生产一个“B”。(一开始刚接触jdk动态代理的时候我也很不解为什么要提供接口)。
至于如何生成一个class文件,在既定规则下你当然可以先生产java文件,再编译成字节码文件。而最好的做法是直接操作字节码文件,jdk和cglib生成字节码文件分别用的sun的ProxyGenerator和开源项目ASM字节码框架。
环境准备
1、引cglib和asm的jar包
2、target类
接口:
public interface BookFacade { public void addBook(); }实现:
public class BookFacadeImpl implements BookFacade, Serializable { private static final long serialVersionUID = 1L; @Override public void addBook() { System.out.println("增加图书方法。。。"); } }
3、代理类
JDK代理类
public class BookFacadeProxy implements InvocationHandler { private Object target; /** * 绑定委托对象并返回一个代理类 * @param target * @return */ public Object bind(Object target) { this.target = target; //取得代理对象 return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this); //要绑定接口(这是一个缺陷,cglib弥补了这一缺陷) } @Override /** * 调用方法 */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object result=null; System.out.println("事物开始"); //执行方法 result=method.invoke(target, args); System.out.println("事物结束"); return result; } }CGLIB代理类
public class BookFacadeProxyCglib implements MethodInterceptor { private Object target; public Object getInstance(Object target) { this.target = target; Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(this.target.getClass()); // 回调方法 // enhancer.setCallbackType(this.getClass()); enhancer.setCallback(this); // 创建代理对象 return enhancer.create(); } @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("before run!"); proxy.invokeSuper(obj, args); System.out.println("after run!"); return null; } }4、client类
public class TestProxy { private static String outputFile = "/Users/apple/Downloads/out"; //控制cglib生成的class文件持久化到本地硬盘 static { System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, outputFile); } public static void main(String[] args) throws IOException { TestProxy testProxy = new TestProxy(); BookFacadeImpl bookProxy = testProxy.cglibProxyClient(); bookProxy.addBook(); // jdkToFile(bookProxy); // testProxy.testSerail(); } public void testSerail() throws IOException { BookFacadeImpl obj = new BookFacadeImpl(); toFile(obj); } public void jdkProxyClient() { BookFacadeProxy proxy = new BookFacadeProxy(); BookFacade bookProxy = (BookFacade) proxy.bind(new BookFacadeImpl()); bookProxy.addBook(); } public BookFacadeImpl cglibProxyClient() { BookFacadeProxyCglib proxy = new BookFacadeProxyCglib(); BookFacadeImpl bookProxy = (BookFacadeImpl) proxy.getInstance(new BookFacadeImpl()); return bookProxy; } //JDK动态代理生成的字节码文件持久化 public static void jdkToFile(BookFacadeImpl obj) throws IOException { Class clazz = obj.getClass(); String className = clazz.getName(); byte[] classFile = ProxyGenerator.generateProxyClass(className, BookFacadeImpl.class.getInterfaces()); FileOutputStream fos = new FileOutputStream(outputFile); // ClassReader cr = new ClassReader(className); // byte[] bits = cr.b; fos.write(classFile); } //另一种JDK字节码文件持久化方法 public static void toFile(BookFacadeImpl obj) throws IOException { Class clazz = obj.getClass(); String className = clazz.getName(); FileOutputStream fos = new FileOutputStream(outputFile); ClassReader cr = new ClassReader(className); byte[] bits = cr.b; fos.write(bits); } }
JDK动态代理实现原理
1、反编译后的源文件
public final class $Proxy0 extends Proxy implements BookFacade, Serializable { private static Method m1; private static Method m3; private static Method m0; private static Method m2; public $Proxy0(InvocationHandler invocationhandler) { super(invocationhandler); } public final boolean equals(Object obj) { try { return ((Boolean)super.h.invoke(this, m1, new Object[] { obj })).booleanValue(); } catch (Error ) { } catch (Throwable throwable) { throw new UndeclaredThrowableException(throwable); } } public final void addBook() { try { super.h.invoke(this, m3, null); return; } catch (Error ) { } catch (Throwable throwable) { throw new UndeclaredThrowableException(throwable); } } public final int hashCode() { try { return ((Integer)super.h.invoke(this, m0, null)).intValue(); } catch (Error ) { } catch (Throwable throwable) { throw new UndeclaredThrowableException(throwable); } } public final String toString() { try { return (String)super.h.invoke(this, m2, null); } catch (Error ) { } catch (Throwable throwable) { throw new UndeclaredThrowableException(throwable); } } static { try { m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") }); m3 = Class.forName("proxy.BookFacade").getMethod("addBook", new Class[0]); m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]); m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]); } catch (NoSuchMethodException nosuchmethodexception) { throw new NoSuchMethodError(nosuchmethodexception.getMessage()); } catch (ClassNotFoundException classnotfoundexception) { throw new NoClassDefFoundError(classnotfoundexception.getMessage()); } } }
2、代理类生成原理
(1)Proxy的newProxyInstance
生成代理类,然后把横切逻辑作为构造函数的入参去实例化该代理类。
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException { if (h == null) { throw new NullPointerException(); } /* * Look up or generate the designated proxy class. */ Class cl = getProxyClass(loader, interfaces); /* * Invoke its constructor with the designated invocation handler. */ try { Constructor cons = cl.getConstructor(constructorParams); return (Object) cons.newInstance(new Object[] { h }); } catch (NoSuchMethodException e) { throw new InternalError(e.toString()); } catch (IllegalAccessException e) { throw new InternalError(e.toString()); } catch (InstantiationException e) { throw new InternalError(e.toString()); } catch (InvocationTargetException e) { throw new InternalError(e.toString()); } }
(2)Proxy的getProxyClass
相当于CGLIB的Enhance.create,主要做的是key和缓存的管理。
public static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces) throws IllegalArgumentException { // 如果目标类实现的接口数大于65535个则抛出异常(我XX,谁会写这么NB的代码啊?) if (interfaces.length > 65535) { throw new IllegalArgumentException("interface limit exceeded"); } //遍历target类的接口 Class proxyClass = null; String[] interfaceNames = new String[interfaces.length]; Set interfaceSet = new HashSet(); // for detecting duplicates for (int i = 0; i < interfaces.length; i++) { String interfaceName = interfaces[i].getName(); Class interfaceClass = null; try { interfaceClass = Class.forName(interfaceName, false, loader); } catch (ClassNotFoundException e) { } if (interfaceClass != interfaces[i]) { throw new IllegalArgumentException( interfaces[i] + " is not visible from class loader"); } ....... } // 把目标类实现的接口名称作为缓存(Map)中的key,相当于CGLIB的KeyFactory Object key = Arrays.asList(interfaceNames); Map cache; synchronized (loaderToCache) { cache = (Map) loaderToCache.get(loader); if (cache == null) { cache = new HashMap(); loaderToCache.put(loader, cache); } } synchronized (cache) { do { // 根据接口的名称从缓存中获取对象 Object value = cache.get(key); if (value instanceof Reference) { proxyClass = (Class) ((Reference) value).get(); } if (proxyClass != null) { // 如果代理对象的Class实例已经存在,则直接返回 return proxyClass; } else if (value == pendingGenerationMarker) { try { cache.wait(); } catch (InterruptedException e) { } continue; } else { cache.put(key, pendingGenerationMarker); break; } } while (true); } try { ....... // 动态生成代理对象 byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces); try { // 根据代理类的字节码生成代理类的实例 proxyClass = defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length); } catch (ClassFormatError e) { throw new IllegalArgumentException(e.toString()); } } // add to set of all generated proxy classes, for isProxyClass proxyClasses.put(proxyClass, null); } ....... return proxyClass; }(3) ProxyGenerator的generateProxyClass
其实这里才是核心,但是关于字节码的处理就不做深究了,倒不如去研究asm框架,性能差距可不小。
public static byte[] generateProxyClass(final String name, Class[] interfaces) { ProxyGenerator gen = new ProxyGenerator(name, interfaces); // 动态生成代理类的字节码 final byte[] classFile = gen.generateClassFile(); // 持久化字节码,client中的持久化也就是抄的这里 if (saveGeneratedFiles) { java.security.AccessController.doPrivileged( new java.security.PrivilegedAction<Void>() { public Void run() { try { FileOutputStream file = new FileOutputStream(dotToSlash(name) + ".class"); file.write(classFile); file.close(); return null; } catch (IOException e) { throw new InternalError( "I/O exception saving generated file: " + e); } } }); } return classFile; }
3、横切逻辑的invoke逻辑
JDK的动态代理生成比较简单。细心的话你会发现构建代理类的时候入参只有接口和classloader,因此JDK的动态代理类功能也比较单一。最后看下横切逻辑是何时执行的。
下面代码摘自于代理类中反编译后的addBook方法:
"h"即横切逻辑类,返回h中invoke实现,会发现依赖于源target实例,通过反射来调用target中相应的方法。而cglib在此也做了较大的优化。
public final void addBook() { try { super.h.invoke(this, m3, null); return; } catch (Error ) { } catch (Throwable throwable) { throw new UndeclaredThrowableException(throwable); } }
CGLIB动态代理实现原理
1、反编译后的代理类
2、代理类简介
有个初步的认识,简要介绍这9个标注的地方:
①回调函数,由于是static类型,可以在class生成之后实例化前注入,适用于相同的class不同的回调函数的应用场景,在有些screen中回调函数是带有属性的,因此不能生成class的时候就织入。
②用户定义的回调函数,织入class中,在诸多回调函数中拥有最高的优先级。
③持有代理类和target类;④持有代理类和target类中相应的方法,每个方法均含有两个该组类;⑤⑥代理类和target中对应方法的具体实现。③~⑥都是用来替换反射调用的解决方法中的关键点,下面有详述。
⑦非织入的回调函数处理逻辑,结合上面的描述可以总结,回调函数的优先级为:织入的回调函数>ThreadLocal回调函数>static回调函数,结合织入的回调函数的filter机制可以构建出更加强大的处理逻辑~
⑧横切逻辑的执行,由此可以看出,默认采用MethodProxy来实行“反射”调用,空间换时间,加速了代理类的执行速度。
⑨ThreadLocal或Static回调函数的注入,留给外界的接口,本文的案例也利用了该点功能。
3、代理类的生成流程
ClassGenerator类控制生成的流程,具体实现在Enhancer中,并且Enhancer也提供了自定义的key。
4、KeyFactory
相比较上面JDK的key,CGLIB中Key的生成方式比较独特,而生成Key的生成策略也是由Enhancer来决定,所以最终动态生成的类增加了一个EnhancerKeyFactory。
private static final EnhancerKey KEY_FACTORY = (EnhancerKey)KeyFactory.create(EnhancerKey.class);
5、ClassGenerator
抽象类,控制class生成的流程,不提供具体的实现。如下流程可以看到,具体的实现都留给了子类Enhancer实现。
protected Object create(Object key) { try { Class gen = null; synchronized (source) { ClassLoader loader = getClassLoader(); Map cache2 = null; cache2 = (Map)source.cache.get(loader); if (cache2 == null) { cache2 = new HashMap(); cache2.put(NAME_KEY, new HashSet()); source.cache.put(loader, cache2); } else if (useCache) { Reference ref = (Reference)cache2.get(key); gen = (Class) (( ref == null ) ? null : ref.get()); } if (gen == null) { Object save = CURRENT.get(); CURRENT.set(this); try { this.key = key; if (attemptLoad) { try { gen = loader.loadClass(getClassName()); } catch (ClassNotFoundException e) { // ignore } } if (gen == null) { byte[] b = strategy.generate(this); String className = ClassNameReader.getClassName(new ClassReader(b)); getClassNameCache(loader).add(className); gen = ReflectUtils.defineClass(className, b, loader); } if (useCache) { cache2.put(key, new WeakReference(gen)); } return firstInstance(gen); } finally { CURRENT.set(save); } } } return firstInstance(gen); } catch (RuntimeException e) { throw e; } catch (Error e) { throw e; } catch (Exception e) { throw new CodeGenerationException(e); } }
6、GeneratorStrategy
正如其名,生成策略,提供给开发者扩展使用。
如下可以看到,类很简洁,默认使用DebuggingClassWriter的字节码处理策略,transform空实现,留做拓展。
public class DefaultGeneratorStrategy implements GeneratorStrategy { public static final DefaultGeneratorStrategy INSTANCE = new DefaultGeneratorStrategy(); public byte[] generate(ClassGenerator cg) throws Exception { ClassWriter cw = getClassWriter(); transform(cg).generateClass(cw); return transform(cw.toByteArray()); } protected ClassWriter getClassWriter() throws Exception { return new DebuggingClassWriter(ClassWriter.COMPUTE_MAXS); } protected byte[] transform(byte[] b) throws Exception { return b; } protected ClassGenerator transform(ClassGenerator cg) throws Exception { return cg; } }
7、CallBackFilter
当类中每个方法所需要的拦截器都不尽相同的时候,CallBackFilter就派上用场了。
int index = filter.accept(actualMethod); if (index >= callbackTypes.length) { throw new IllegalArgumentException("Callback filter returned an index that is too large: " + index); } originalModifiers.put(method, new Integer((actualMethod != null) ? actualMethod.getModifiers() : method.getModifiers())); indexes.put(method, new Integer(index)); List group = (List)groups.get(generators[index]); if (group == null) { groups.put(generators[index], group = new ArrayList(methods.size())); } group.add(method);
根据上面字节码处理逻辑可以总结出:
(1)CallBackTypes可知,仅仅过滤织入class的那些回调函数
(2)根据CallBackFilter的Accept函数来确定代理方法所需要执行的callBack下标。
(3)一个方法只能织入一个CallBack
8、MultiCallBack
CallBack函数有多个子类,前面介绍的MethodInterCeptor只是其中一个,再介绍一个延迟加载的拦截器,应用面也是比较广的。
LazyLoader意为用到再加载,Bean的部分属性设置为延迟加载在某些应用场景会有出乎意料的效果(案例)
LazyLoader的原理可以从反编译的代码中总结出来:
①分别是回调函数和缓存的结果
②每个方法的执行都要被代理到回调方法中
③如果缓存中没有,则执行LoadObject方法,并缓存结果。
9、FastClass
(1)前言
如果说CGLIB优于JDK的一点在于ASM框架的优势,那么另一个优势就是替换了原有了反射调用的局限性,对比下面JDK和CGLIB在invoke的异同:
CGLIB->
JDK->
分析上面两段代码可以看到,CGLIB采用了Method的invokerSuper方法,走的并不是反射调用这一条道路。
(2)FastClass实现原理
大致流程如下:
①执行invoke的时候cglib为代理类和taret类分别生成一份FastClass,主要用于Method名称和方法执行的映射
②代理类中每个方法都有MethodProxy,此类封装了代理类方法的上下文信息,用于和FastClass适配而已。
③代理类中的MethodProxy在执行invokerSuper时会依据方法名调用到代理类的FastClass对应的方法去执行。
(3)FastClass的初始化
下面可以看到,初始化的时候会生成代理类和target类的FastClass
private void init() { /* * Using a volatile invariant allows us to initialize the FastClass and * method index pairs atomically. * * Double-checked locking is safe with volatile in Java 5. Before 1.5 this * code could allow fastClassInfo to be instantiated more than once, which * appears to be benign. */ 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; createInfo = null; } } } }(4)反编译后的FastClass
public class BookFacadeImpl$$EnhancerByCGLIB$$1adc12da$$FastClassByCGLIB$$e7355050 extends FastClass { public int getIndex(Signature signature) { String s = signature.toString(); s; s.hashCode(); JVM INSTR lookupswitch 27: default 527 // -2055565910: 236 // -1725733088: 247 // -1696758078: 258 // -1457535688: 269 // -1411812934: 280 // -1026001249: 291 // -894172689: 302 // -623122092: 312 // -419626537: 323 // 243996900: 334 // 374345669: 345 // 560567118: 356 // 811063227: 367 // 946854621: 377 // 973717575: 388 // 1116248544: 399 // 1221173700: 410 // 1230699260: 420 // 1365077639: 431 // 1517819849: 442 // 1584330438: 453 // 1826985398: 464 // 1902039948: 474 // 1913648695: 485 // 1972855819: 495 // 1984935277: 506 // 2011844968: 516; goto _L1 _L2 _L3 _L4 _L5 _L6 _L7 _L8 _L9 _L10 _L11 _L12 _L13 _L14 _L15 _L16 _L17 _L18 _L19 _L20 _L21 _L22 _L23 _L24 _L25 _L26 _L27 _L28 //根据MethodProxy中的上下文信息适配合适的方法 public Object invoke(int i, Object obj, Object aobj[]) throws InvocationTargetException { //拦截器中传入的代理类实例,依次为载体执行相应的方法,如果执行的是target类FastClass的话便会内存泄露 (BookFacadeImpl$$EnhancerByCGLIB$$1adc12da)obj; i; JVM INSTR tableswitch 0 26: default 392 // 0 128 // 1 143 // 2 147 // 3 159 // 4 169 // 5 191 // 6 201 // 7 206 // 8 226 // 9 237 // 10 248 // 11 259 // 12 272 // 13 276 // 14 286 // 15 291 // 16 296 // 17 301 // 18 316 // 19 320 // 20 332 // 21 336 // 22 341 // 23 355 // 24 378 // 25 382 // 26 387; goto _L1 _L2 _L3 _L4 _L5 _L6 _L7 _L8 _L9 _L10 _L11 _L12 _L13 _L14 _L15 _L16 _L17 _L18 _L19 _L2: "CGLIB$SET_THREAD_CALLBACKS([Lnet/sf/cglib/proxy/Callback;)V"; equals(); JVM INSTR ifeq 528; goto _L29 _L30
(5) invoker vs invokerSuper
Method中执行的是invokerSuper方法,因此会调用代理类的FastClass中相应方法,进而回调代理类中的方法而进行target中原方法的调用,还记得下面那段代码么,上文提到过~,FastClass正是回调的该方法从而调用了target中的addBook实现。
final void CGLIB$addBook$0() { super.addBook(); }
有了上面的基础,来看看 invoker方法,该方法调用的是target类FastClass中相应的方法,试想如果你真的写成了invoker方法,最后回调的可不是CGLIB$addBook$0这个方法了,而是代理类中的addBook方法了,那么循环调用就开始了,内存会发生泄露。
你或许你已经发现了,CGLIB中很多field都是代理类和Target类双份的,其实target类感觉至此没有用,还会有不可预料到的风险。
10、CGLIB小结
(1)动态生成了KEY、FASTCLASS、PROXYCLASS三种类型的CLASS
(2)拦截器的优先级:可织入>ThreadLocal>static
(3)ASM框架处理字节码+无反射调用改善了代理的效率
(4)丰富的扩展点,延伸出来的功能点很多,包括BeanMap、BeanCopy、延迟加载等相关功能。
(5)Enhancer暴露出来的功能点有:
①设置属性
②生成CLASS或者实例
③对于已经生成的CLASS反射调用注入相应的拦截器
案例分析
1、背景
在学习业务的时候发现了应用中关于异步加载的代码,因此停下来研究了下,作者也沉淀了相关文档:http://agapple.iteye.com/blog/918898。
2、异步并行加载
(1)业务环境
(2)异步加载模板
线程池无阻塞处理异步调用的逻辑
(3)CGLIB代理类的生成逻辑
①模板是单例,因此缓存每个class,避免多余的开销
②局部变量不存在线程安全的问题,其实没必要设置在ThreadLocal回调函数中,但是CGLIB只提供了两种后处理方法,这也是没有办法的事情。
③延迟加载拦截器,带返回值的task,超时三秒。
不得不说,线程+CGLIB+延迟加载=异步并行加载,非常巧妙!