• cglib源码分析--转


    原文地址:http://www.iteye.com/topic/799827

    背景

        前段时间在工作中,包括一些代码阅读过程中,spring aop经常性的会看到cglib中的相关内容,包括BeanCopier,BulkBean,Enancher等内容,以前虽大致知道一些内容,原理是通过bytecode,但没具体深入代码研究,只知其所用不知其所以然,所以就特地花了半天多的工作时间研究了CGLIB的相关源码,同时结合看了下 spring Aop中对CGLIB的使用。

        本文主要通过对cglib有原理的分析,反编译查看源码,例子等方式做一个介绍。

    cglib基本信息

    1. cglib的官方网站: http://cglib.sourceforge.net/
    2. cglib目前的最新版本应该是2.2,公司普遍使用的版本也是这个
    3. 官网的samples : http://cglib.sourceforge.net/xref/samples/

    cglib代码包结构

    • core (核心代码)
      • EmitUtils
      • ReflectUtils
      • KeyFactory
      • ClassEmitter/CodeEmitter
      • NamingPolicy/DefaultNamingPolicy
      • GeneratorStrategy/DefaultGeneratorStrategy
      • DebuggingClassWriter
      • ClassGenerator/AbstractClassGenerator
    • beans (bean操作类)
      • BeanCopier
      • BulkBean
      • BeanMap
      • ImmutableBean
      • BeanGenerator
    • reflect
      • FastClass
    • proxy
      • Enhancer
      • CallbackGenerator
      • Callback
        • MethodInterceptor , Dispatcher, LazyLoader , ProxyRefDispatcher , NoOp , FixedValue , InvocationHandler(提供和jdk proxy的功能)
      • CallbackFilter
    • util
      • StringSwitcher 
      • ParallelSorter 
    • transform 

    core核心代码部分

    EmitUtils

    重要的工具类,主要封装了一些操作bytecode的基本函数,比如生成一个null_constructor,添加类属性add_property等

    ReflectUtils

    处理jdk reflect的工具类,比如获取一个类所有的Method,获取构造函数信息等。

    ClassEmitter/CodeEmitter

    对asm的classAdapter和MethodAdapter的实现,贯穿于cglib代码的处理

    KeyFactory

    类库中重要的唯一标识生成器,用于cglib做cache时做map key,比较底层的基础类。
    例子:

    interface BulkBeanKey {
    public Object newInstance(String target, String[] getters, String[] setters, String[] types);
    }
    (BulkBeanKey)KeyFactory.create(BulkBeanKey.class).newInstance(targetClassName, getters, setters, typeClassNames);

    说明:

    • 每个Key接口,都必须提供newInstance方法,但具体的参数可以随意定义,通过newInstance返回的为一个唯一标示,只有当传入的所有参数的equals都返回true时,生成的key才是相同的,这就相当于多key的概念。

    NamingPolicy

    默认的实现类:DefaultNamingPolicy, 具体cglib动态生成类的命名控制。
    一般的命名规则:

    • 被代理class name + "$$" + 使用cglib处理的class name + "ByCGLIB" + "$$" + key的hashcode
    • 示例:FastSource$$FastClassByCGLIB$$e1a36bab.class

    GeneratorStrategy

    默认的实现类: DefaultGeneratorStrategy
    控制ClassGenerator生成class的byte数据,中间可插入自己的处理。注意这里依赖了:DebuggingClassWriter进行class generator的处理

    DebuggingClassWriter

    cglib封装asm的处理类,用于生成class的byte流,通过GeneratorStrategy回调ClassGenerator.generateClass(DebuggingClassWriter),将自定义的class byte处理回调给具体的cglib上层操作类,比如由具体的BeanCopier去控制bytecode的生成。

    ClassGenerator

    其中一个抽象实现:AbstractClassGenerator。cglib代码中核心的Class bytecode操作主体,包含了一些cache,调用NamingPolicy,GeneratorStrategy进行处理,可以说是一个最核心的调度者。

    对应的类图:

     

    1. 外部的BeanCopier都包含了一Generator,继承自AbstractClassGenerator,实现了generateClass(ClassVisitor v),Object firstInstance(Class type)方法。
    2. AbstractClassGenerator自身会根据Source进行cache,所以针对已经生成过的class,这里KeyFactory对应的值要相等,则会直接返回cache中的结果。所以BeanCopier每次create慢只是每次都需要new两个对象,一个是KeyFactory.newInstance,另一个是firstInstance方法调用生成一个对象。

    反编译tips

    大家都知道cglib是进行bytecode操作,会动态生成class,最快最直接的学习就是结合他生成的class,对照代码进行学习,效果会好很多。

    Java代码  收藏代码
    1. system.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "指定输出目录");   

     可参见 cores/DebuggingClassWriter代码。说明:这样cglib会将动态生成的每个class都输出到文件中,然后我们可以通过decomp进行反编译查看源码。

    beans (相关操作类)

    BeanCopier

    简单的示例代码就不做介绍,相信大家都指导怎么用,这里主要介绍下Convert的使用。

    • 许多网友都做过BeanCopier,BeanUtils的测试,基本BeanCopier的性能是BeanUtils的10倍以上。,出了反射这一性能差异外,BeanUtils默认是开启Converter功能,允许同名,不同类型的属性进行拷贝,比如Date对象到String属性。
    • 有兴趣的同学可以去比较下PropertyUtils,默认不开启Converter功能,发现性能是BeanUtils的2倍多。

    初始化例子:BeanCopier copier = BeanCopier.create(Source.class, Target.class, true); 
    第三个参数useConverter,是否开启Convert,默认BeanCopier只会做同名,同类型属性的copier,否则就会报错。

    Converter使用例子代码  收藏代码
    1. public class BeanCopierTest {  
    2.   
    3.     public static void main(String args[]) {  
    4.         System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/tmp/1");  
    5.         BeanCopier copier = BeanCopier.create(Source.class, Target.class, true);  
    6.         Source from = new Source();  
    7.         from.setValue(1);  
    8.   
    9.         Target to = new Target();  
    10.         Converter converter = new BigIntConverter();  
    11.         copier.copy(from, to, converter); //使用converter类  
    12.   
    13.         System.out.println(to.getValue());  
    14.     }  
    15. }  
    16.   
    17. class BigIntConverter implements net.sf.cglib.core.Converter {  
    18.   
    19.     @Override  
    20.     public Object convert(Object value, Class target, Object context) {  
    21.         System.out.println(value.getClass() + " " + value); // from类中的value对象  
    22.         System.out.println(target); // to类中的定义的参数对象  
    23.         System.out.println(context.getClass() + " " + context); // String对象,具体的方法名  
    24.         if (target.isAssignableFrom(BigInteger.class)) {  
    25.             return new BigInteger(value.toString());  
    26.         } else {  
    27.             return value;  
    28.         }  
    29.     }  
    30.   
    31. }  
    32. ----  
    33. 反编译后看的代码:  
    34. public class Target$$BeanCopierByCGLIB$$e1c34377 extends BeanCopier  
    35. {  
    36.     public void copy(Object obj, Object obj1, Converter converter)  
    37.     {  
    38.         Target target = (Target)obj1;  
    39.         Source source = (Source)obj;  
    40.         // 注意是直接调用,没有通过reflect  
    41.         target.setValue((BigInteger)converter.convert(new Integer(source.getValue()), CGLIB$load_class$java$2Emath$2EBigInteger, "setValue"));   
    42.     }  
    43. }  

    使用注意

    1. 避免每次进行BeanCopier.create创建对象,一般建议是通过static BeanCopier copier = BeanCopier.create()
    2. 合理使用converter。
    3. 应用场景:两个对象之间同名同属性的数据拷贝, 不能单独针对其中的几个属性单独拷贝

    BulkBean

         相比于BeanCopier,BulkBean将整个Copy的动作拆分为getPropertyValues,setPropertyValues的两个方法,允许自定义处理的属性。

    Java代码  收藏代码
    1. public class BulkBeanTest {  
    2.   
    3.     public static void main(String args[]) {  
    4.         System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/home/ljh/cglib");  
    5.         String[] getter = new String[] { "getValue" };  
    6.         String[] setter = new String[] { "setValue" };  
    7.         Class[] clazzs = new Class[] { int.class };  
    8.   
    9.         BulkBean bean = BulkBean.create(BulkSource.class, getter, setter, clazzs);  
    10.         BulkSource obj = new BulkSource();  
    11.         obj.setValue(1);  
    12.   
    13.         Object[] objs = bean.getPropertyValues(obj);  
    14.         for (Object tmp : objs) {  
    15.             System.out.println(tmp);  
    16.         }  
    17.     }  
    18. }  
    19. class BulkSource {  
    20.     private int value;  
    21.     .....  
    22. }  
    23.   
    24. // 反编译后的代码:   
    25.  public void getPropertyValues(Object obj, Object aobj[])  
    26.     {  
    27.         BulkSource bulksource = (BulkSource)obj;  
    28.         aobj[0] = new Integer(bulksource.getValue());  
    29.     }  

    使用注意

    1. 避免每次进行BulkBean.create创建对象,一般建议是通过static BulkBean.create copier = BulkBean.create
    2. 应用场景:针对特定属性的get,set操作,一般适用通过xml配置注入和注出的属性,运行时才确定处理的Source,Target类,只需关注属性名即可。

    BeanMap

    相比于BeanCopier,BulkBean,都是针对两个Pojo Bean进行处理,那如果对象一个是Pojo Bean和Map对象之间,那就得看看BeanMap,将一个java bean允许通过map的api进行调用。
    几个支持的操作接口:

    • Object get(Object key)
    • Object put(Object key, Object value)
    • void putAll(Map t)
    • Set entrySet()
    • Collection values()
    • boolean containsKey(Object key)
    • ....
    Java代码  收藏代码
    1. public class BeanMapTest {  
    2.   
    3.     public static void main(String args[]) {  
    4.         // 初始化  
    5.         BeanMap map = BeanMap.create(new Pojo());  
    6.         // 构造  
    7.         Pojo pojo = new Pojo();  
    8.         pojo.setIntValue(1);  
    9.         pojo.setBigInteger(new BigInteger("2"));  
    10.         // 赋值  
    11.         map.setBean(pojo);  
    12.         // 验证  
    13.         System.out.println(map.get("intValue"));  
    14.         System.out.println(map.keySet());  
    15.         System.out.println(map.values());  
    16.     }  
    17. }  
    18.   
    19. class Pojo {  
    20.   
    21.     private int        intValue;  
    22.     private BigInteger bigInteger;  
    23.     ....  
    24. }  
    25.   
    26. //反编译代码查看:  
    27. //首先保存了所有的属性到一个set中  
    28. private static FixedKeySet keys = new FixedKeySet(new String[] {  
    29.         "bigInteger", "intValue"  
    30.     });  
    31. public Object get(Object obj, Object obj1)  
    32.     {  
    33.         (Pojo)obj;  
    34.         String s = (String)obj1;  
    35.         s;  
    36.         s.hashCode();  
    37.         JVM INSTR lookupswitch 2: default 72  
    38.     //                   -139068386: 40  
    39.     //                   556050114: 52;  
    40.            goto _L1 _L2 _L3  
    41. _L2:  
    42.         "bigInteger";  
    43.  //属性判断是否相等  
    44.         equals();  
    45.         JVM INSTR ifeq 73;  
    46.            goto _L4 _L5  
    47. _L5:  
    48.         break MISSING_BLOCK_LABEL_73;  
    49. _L4:  
    50.         getBigInteger();  
    51.         return;  
    52. _L3:  
    53.   
    54. ....  
    55.   
    56. }  

    使用注意

    1. 避免每次进行BeanMap map = BeanMap.create();创建对象,不同于BeanCopier对象,BeanMap主要针对对象实例进行处理,所以一般建议是map.setBean(pojo);进行动态替换持有的对象实例。
    2. 应用场景:针对put,putAll操作会直接修改pojo对象里的属性,所以可以通过beanMap.putAll(map)进行map<->pojo属性的拷贝。

    BeanGenerator

       暂时没有想到合适的使用场景,不过BeanGenerator使用概念是很简单的,就是将一个Map<String,Class>properties的属性定义,动态生成一个pojo bean类。

    Java代码  收藏代码
    1. BeanGenerator generator = new BeanGenerator();  
    2. generator.addProperty("intValue", int.class);  
    3. generator.addProperty("integer", Integer.class);  
    4. generator.addProperty("properties", Properties.class);  
    5.          
    6. Class clazz = (Class) generator.createClass();  
    7. Object obj = generator.create();  
    8.   
    9. PropertyDescriptor[] getters = ReflectUtils.getBeanGetters(obj.getClass());  
    10. for (PropertyDescriptor getter : getters) {  
    11.     Method write = getter.getWriteMethod();  
    12.     System.out.println(write.getName());  
    13. }  

    ImmutableBean

    bean Immutable模式的一种动态class实现,Immutable模式主要应用于服务设计上,返回的pojo bean对象,不运行进行write方法调用。

    说明

    个人是不太建议使用cglib动态class的方式来实现bean Immutable的模式,Immutable模式应该是一种服务接口上的显示声明,而不是如此隐晦,而且pojo bean尽量做到是轻量级,简答的set/get方法,如果要做充血的领域模型那就另当别论了。

    reflect (class,method处理)

    FastClass

    顾明思义,FastClass就是对Class对象进行特定的处理,比如通过数组保存method引用,因此FastClass引出了一个index下标的新概念,比如getIndex(String name, Class[] parameterTypes)就是以前的获取method的方法。
    通过数组存储method,constructor等class信息,从而将原先的反射调用,转化为class.index的直接调用,从而体现所谓的FastClass。

    Java代码  收藏代码
    1. public class FastClassTest {  
    2.     public static void main(String args[]) throws Exception {  
    3.         System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/home/ljh/cglib");  
    4.   
    5.         FastClass clazz = FastClass.create(FastSource.class);  
    6.         // fast class反射调用  
    7.         FastSource obj = (FastSource) clazz.newInstance();  
    8.         clazz.invoke("setValue", new Class[] { int.class }, obj, new Object[] { 1 });  
    9.         clazz.invoke("setOther", new Class[] { int.class }, obj, new Object[] { 2 });  
    10.   
    11.         int value = (Integer) clazz.invoke("getValue", new Class[] {}, obj, new Object[] {});  
    12.         int other = (Integer) clazz.invoke("getOther", new Class[] {}, obj, new Object[] {});  
    13.         System.out.println(value + " " + other);  
    14.         // fastMethod使用  
    15.         FastMethod setValue = clazz.getMethod("setValue", new Class[] { int.class });  
    16.         System.out.println("setValue index is : " + setValue.getIndex());  
    17.   
    18.         FastMethod getValue = clazz.getMethod("getValue", new Class[] {});  
    19.         System.out.println("getValue index is : " + getValue.getIndex());  
    20.   
    21.         FastMethod setOther = clazz.getMethod("setOther", new Class[] { int.class });  
    22.         System.out.println("setOther index is : " + setOther.getIndex());  
    23.   
    24.         FastMethod getOther = clazz.getMethod("getOther", new Class[] {});  
    25.         System.out.println("getOther index is : " + getOther.getIndex());  
    26.         // 其他  
    27.         System.out.println("getDeclaredMethods : " + clazz.getJavaClass().getDeclaredMethods().length);  
    28.         System.out.println("getConstructors : " + clazz.getJavaClass().getConstructors().length);  
    29.         System.out.println("getFields : " + clazz.getJavaClass().getFields().length);  
    30.         System.out.println("getMaxIndex : " + clazz.getMaxIndex());  
    31.     }  
    32. }  
    33.   
    34. class FastSource {  
    35.     private int value;  
    36.     private int other;  
    37.   
    38. }  

    proxy (spring aop相关)

    总体类结构图:

    Callback & CallbackGenerator

    1. MethodInterceptor
      • 类似于spring aop的around Advise的功能,大家都知道,不多做介绍。唯一需要注意的就是proxy.invokeSuper和proxy.invoke的区别。invokeSuper是退出当前interceptor的处理,进入下一个callback处理,invoke则会继续回调该方法,如果传递给invoke的obj参数出错容易造成递归调用
    2. Dispatcher, ProxyRefDispatcher
      • 类似于delegate的模式,直接将请求分发给具体的Dispatcher调用,是否有着接口+实现分离的味道,将接口的方法调用通过Dispatcher转到实现target上。ProxyRefDispatcher与Dispatcher想比,loadObject()多了个当前代理对象的引用。
      • 反编译的部分代码代码  收藏代码
        1. //反编译的部分代码  
        2. public final int cal(int i, int j)  
        3. {  
        4.         CGLIB$CALLBACK_1;  
        5.         if(CGLIB$CALLBACK_1 != null) goto _L2; else goto _L1  
        6. _L1:  
        7.         JVM INSTR pop ;  
        8.         CGLIB$BIND_CALLBACKS(this);  
        9.         CGLIB$CALLBACK_1;  
        10. _L2:  
        11.         loadObject(); //每次都进行调用  
        12.         (DefaultCalcService);  
        13.         i;  
        14.         j;  
        15.         cal(); //调用实现类的方法  
        16.         return;  
        17.     }   
    3. LazyLoader
      • 相比于Dispatcher,lazyLoader在第一次获取了loadObject后,会进行缓存,后续的请求调用都会直接调用该缓存的属性.
      • 反编译部分代码代码  收藏代码
        1. //反编译部分代码  
        2. public final int cal(int i, int j)  
        3. {  
        4.     this;  
        5.     return ((DefaultCalcService)CGLIB$LOAD_PRIVATE_3()).cal(i, j);  
        6. }  
        7.   
        8. private final synchronized Object CGLIB$LOAD_PRIVATE_3()  
        9. {  
        10.         CGLIB$LAZY_LOADER_3; //保存的属性  
        11.         if(CGLIB$LAZY_LOADER_3 != null) goto _L2; else goto _L1  
        12. _L1:  
        13.         JVM INSTR pop ;  
        14.         this;  
        15.         CGLIB$CALLBACK_3;  
        16.         if(CGLIB$CALLBACK_3 != null) goto _L4; else goto _L3  
        17. _L3:  
        18.         JVM INSTR pop ;  
        19.         CGLIB$BIND_CALLBACKS(this);  
        20.         CGLIB$CALLBACK_3;  
        21. _L4:  
        22.         loadObject();  
        23.         JVM INSTR dup_x1 ;  
        24.         CGLIB$LAZY_LOADER_3;  
        25. _L2:  
        26.         return;  
        27.     }  
    4. NoOp
      • 不做任何处理,结合Filter针对不需要做代理方法直接返回,调用其原始方法
    5. FixedValue
      • 强制方法返回固定值,可结合Filter进行控制
    6. InvocationHandler(提供和jdk proxy的功能),不常用

    CallbackFilter

    主要的作用就是callback调度,主要的一个方法:int accept(Method method); 
    返回的int在int值,代表对应method需要插入的callback,会静态生成到class的代码中,这样是cglib proxy区别于jdk proxy的方式,一个是静态的代码调用,一个是动态的reflect。
    可以查看: Enhancer类中的emitMethods方法,line:883。在构造class method字节吗之前就已经确定需要运行的callback。

    Enhancer

    Java代码  收藏代码
    1. System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/home/ljh/cglib");  
    2. LogInteceptor logInteceptor = new LogInteceptor();  
    3. CalDispatcher calDispatcher = new CalDispatcher();  
    4. CalcProxyRefDispatcher calcProxyRefDispatcher = new CalcProxyRefDispatcher();  
    5. LazyLoaderCallback lazyLoaderCallback = new LazyLoaderCallback();  
    6.   
    7. Enhancer enhancer = new Enhancer();  
    8. enhancer.setSuperclass(CalcService.class); //接口类  
    9. enhancer.setCallbacks(new Callback[] { logInteceptor, calDispatcher, calcProxyRefDispatcher,lazyLoaderCallback, NoOp.INSTANCE }); // callback数组  
    10. enhancer.setCallbackFilter(new CalcCallbackFilter()); // filter  
    11. CalcService service = (CalcService) enhancer.create();  
    12.   
    13. int result = service.cal(1, 1);  

    Util  (工具类,感觉有点鸡肋)

    • StringSwitcher 提供string和int的map映射查询,给定一个string字符串,返回同个下标数组的int值,感觉很鸡肋,用Map不是可以很快速的实现功能
    • ParallelSorter 看了具体的代码,没啥意思,就是提供了一个二分的快速排序和多路归并排序。没有所谓的并行排序,原本以为会涉及多线程处理,可惜没有

    transform

         暂时没仔细研究,更多的是对asm的封装,等下次看了asm代码后再回来研究下。

  • 相关阅读:
    2018-2019-2 20175120 实验四《Android程序设计》实验报告
    mini dc(选做)
    20175120彭宇辰 《Java程序设计》第十一周学习总结
    20175120彭宇辰 《Java程序设计》第十周学习总结
    实验三 《敏捷开发与XP实践》实验报告
    20175120彭宇辰 《Java程序设计》第九周学习总结
    20175120彭宇辰 《Java程序设计》第八周学习总结
    20175120彭宇辰-结对编程-四则运算(二)
    实验二《面向对象程序设计》实验报告
    20175120彭宇辰 《Java程序设计》第七周学习总结
  • 原文地址:https://www.cnblogs.com/davidwang456/p/5654097.html
Copyright © 2020-2023  润新知