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

使用注意

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

BulkBean

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

Java代码  

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

使用注意

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

使用注意

  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. Class clazz = (Class) generator.createClass();
  6. Object obj = generator.create();
  7. PropertyDescriptor[] getters = ReflectUtils.getBeanGetters(obj.getClass());
  8. for (PropertyDescriptor getter : getters) {
  9. Method write = getter.getWriteMethod();
  10. System.out.println(write.getName());
  11. }

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. FastClass clazz = FastClass.create(FastSource.class);
  5. // fast class反射调用
  6. FastSource obj = (FastSource) clazz.newInstance();
  7. clazz.invoke("setValue", new Class[] { int.class }, obj, new Object[] { 1 });
  8. clazz.invoke("setOther", new Class[] { int.class }, obj, new Object[] { 2 });
  9. int value = (Integer) clazz.invoke("getValue", new Class[] {}, obj, new Object[] {});
  10. int other = (Integer) clazz.invoke("getOther", new Class[] {}, obj, new Object[] {});
  11. System.out.println(value + " " + other);
  12. // fastMethod使用
  13. FastMethod setValue = clazz.getMethod("setValue", new Class[] { int.class });
  14. System.out.println("setValue index is : " + setValue.getIndex());
  15. FastMethod getValue = clazz.getMethod("getValue", new Class[] {});
  16. System.out.println("getValue index is : " + getValue.getIndex());
  17. FastMethod setOther = clazz.getMethod("setOther", new Class[] { int.class });
  18. System.out.println("setOther index is : " + setOther.getIndex());
  19. FastMethod getOther = clazz.getMethod("getOther", new Class[] {});
  20. System.out.println("getOther index is : " + getOther.getIndex());
  21. // 其他
  22. System.out.println("getDeclaredMethods : " + clazz.getJavaClass().getDeclaredMethods().length);
  23. System.out.println("getConstructors : " + clazz.getJavaClass().getConstructors().length);
  24. System.out.println("getFields : " + clazz.getJavaClass().getFields().length);
  25. System.out.println("getMaxIndex : " + clazz.getMaxIndex());
  26. }
  27. }
  28. class FastSource {
  29. private int value;
  30. private int other;
  31. }

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. private final synchronized Object CGLIB$LOAD_PRIVATE_3()
      8. {
      9. CGLIB$LAZY_LOADER_3; //保存的属性
      10. if(CGLIB$LAZY_LOADER_3 != null) goto _L2; else goto _L1
      11. _L1:
      12. JVM INSTR pop ;
      13. this;
      14. CGLIB$CALLBACK_3;
      15. if(CGLIB$CALLBACK_3 != null) goto _L4; else goto _L3
      16. _L3:
      17. JVM INSTR pop ;
      18. CGLIB$BIND_CALLBACKS(this);
      19. CGLIB$CALLBACK_3;
      20. _L4:
      21. loadObject();
      22. JVM INSTR dup_x1 ;
      23. CGLIB$LAZY_LOADER_3;
      24. _L2:
      25. return;
      26. }
  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. Enhancer enhancer = new Enhancer();
  7. enhancer.setSuperclass(CalcService.class); //接口类
  8. enhancer.setCallbacks(new Callback[] { logInteceptor, calDispatcher, calcProxyRefDispatcher,lazyLoaderCallback, NoOp.INSTANCE }); // callback数组
  9. enhancer.setCallbackFilter(new CalcCallbackFilter()); // filter
  10. CalcService service = (CalcService) enhancer.create();
  11. int result = service.cal(1, 1);

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

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

transform

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

时间: 2024-10-08 12:57:29

cglib源码分析--转的相关文章

cglib源码分析2 ----- Class name 生成策略

一.如何获取动态生成的class 字节码 结合生成的class文件是一个学习cglib的比较好的方法.在cglib中,生成的class文件默认只存储在内存中,我们可以在代码中加入下面语句来获取class file. System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "F:\\code "); 这里涉及到了一个比较关键的类DebuggingClassWriter,接下来我们简单的分析一下这个类.Debuggi

cglib源码分析(三):Class生成策略

cglib中生成类的工作是由AbstractClassGenerator的create方法使用相应的生成策略完成,具体代码如下: private GeneratorStrategy strategy = DefaultGeneratorStrategy.INSTANCE; byte[] b = strategy.generate(this); GeneratorStrategy是一个接口,它负责调用ClassGenerator 的generateClass方法来生成类.DefaultGenera

cglib源码分析1 ----- 缓存和KEY

cglib是一个java 字节码的生成工具,它是对asm的进一步封装,提供了一系列class generator.研究cglib主要是因为它也提供了动态代理功能,这点和jdk的动态代理类似. 一. Cache的创建 与jdk动态代理一样,cglib也提供了缓存来提高系统的性能,对于已经生成的类,直接使用而不必重复生成.这里不得不提到一个比较重要的抽象类AbstractClassGenerator,它采用了模版方法的设计模式,protected Object create(Object key)

CloudStack核心类ApiServlet、ApiServer、ApiDispatcher、GenericDaoBase源码分析

ApiServlet 首先从整体上看下ApiServlet,Outline视图如下, 一.注意@Inject依赖的是javax.inject.jar,它和spring的@Autowired的区别在于使用它时变量不用生成相应的set方法. 二.CloudStack所有的请求都会被ApiSerlet拦截处理,进入到doGet()或者doPost()方法,然后统一交由processRequest()处理. 三.processRequestInContext()方法: 1.更多的是日志记录和异常信息处理

Android逆向之旅---反编译利器Apktool和Jadx源码分析以及错误纠正

一.前言 在之前的破解过程中可以看到我们唯一离不开的一个神器那就是apktool了,这个工具多强大就不多说了,但是如果没有他我们没法涉及到后面的破解工作了,这个工具是开源的,也是使用Java语言开发的,代码相对简单,我们今天就来分析一下他的大体逻辑,注意是大体逻辑哦,因为如果要一行一行代码分析,首先觉得没必要,其次浪费时间,有了源码,谁看不懂呢.至于为什么要分析这个工具其实原因只有一个,就是我们在之前的反编译过程中会发现,总是有那么几个apk应用不让我们那么容易的反编译,他们就利用apktool

spring4.0源码分析━━━(AOP实现)

AOP的概念 AOP为Aspect Oriented Programming的缩写,意为:面向切面编程(也叫面向方面).这就让一些问题很简单化了,例如:开始我们实现了一些逻辑并上线了,现在客户又来了一个新的需求.要在每次交易之前统计下,或者记录下他们的交易简单资料.而你发现你其他模块可能正好有这部分的功能.那AOP就可以用得上了,使用AOP就可以在不修改源代码的情况下新增这些功能.就是在交易前这个切面,新装你的一些功能.这有点像拦截器和Filter.其实都是一个原理.前面说了解析xml和Bean

Spring IOC 容器源码分析 - 创建单例 bean 的过程

1. 简介 在上一篇文章中,我比较详细的分析了获取 bean 的方法,也就是getBean(String)的实现逻辑.对于已实例化好的单例 bean,getBean(String) 方法并不会再一次去创建,而是从缓存中获取.如果某个 bean 还未实例化,这个时候就无法命中缓存.此时,就要根据 bean 的配置信息去创建这个 bean 了.相较于getBean(String)方法的实现逻辑,创建 bean 的方法createBean(String, RootBeanDefinition, Obj

Spring IOC 容器源码分析 - 创建原始 bean 对象

1. 简介 本篇文章是上一篇文章(创建单例 bean 的过程)的延续.在上一篇文章中,我们从战略层面上领略了doCreateBean方法的全过程.本篇文章,我们就从战术的层面上,详细分析doCreateBean方法中的一个重要的调用,即createBeanInstance方法.在本篇文章中,你将看到三种不同的构造 bean 对象的方式.你也会了解到构造 bean 对象的两种策略.如果你对这些内容感兴趣,那么不妨继续往下读.我会在代码进行大量的注解,相信能帮助你理解代码逻辑.好了,其他的就不多说了

MyBatis 源码分析 - 配置文件解析过程

* 本文速览 由于本篇文章篇幅比较大,所以这里拿出一节对本文进行快速概括.本篇文章对 MyBatis 配置文件中常用配置的解析过程进行了较为详细的介绍和分析,包括但不限于settings,typeAliases和typeHandlers等,本文的篇幅也主要在对这三个配置解析过程的分析上.下面,我们来一起看一下本篇文章的目录结构. 从目录上可以看出,2.3节.2.5节和2.8节的内容比较多.其中2.3节是关于settings配置解析过程的分析,除了对常规的 XML 解析过程分析,本节额外的分析了元