CGLib用法详解

statement:本篇内容只是建立在我目前经验的基础之上,必然有不完善甚至是不正确的地方,请谨慎阅读,如果能指出错误与不足之处,更是不甚感激
附:本篇内容旨在基本探究一下CGLib提供了哪些功能,并提供基础示例和使用方法,本文主要内容来自于Rafael Winterhalter的文章《CGLib: The Missing Manual》,并在此基础上进行一些添加与修正。本篇内容将可能会被多次更新(如果有更深的理解,或者是需要添加的说明之处)


一、前言

1.1 CGLib介绍

CGLib是一个基于ASM字节码操纵框架之上的类生成工具,相比于底层的ASM,CGLib不需要你理解那么多必需的和字节码相关底层知识,你可以很轻松的就在运行时动态生成一个类或者一个接口。CGLib广泛应用于AOP方面和代理方面,比如Spring、Hibernate,甚至也可以用于Mock测试方面。

1.2 CGLib使用需知

CGLib有些地方需要提前引起注意,至于原因为何,我将会在最后一节说明。

  • 切记不要使用非静态内部类(除外部类)的实例作为CGLib某些方法的参数,尤其不要乱使用匿名内部类
  • 切记在CGLib某些方法中如果需要一个接口作为参数,那么请尽量保证这个接口的访问权限是公有的,而不是包私有的。
  • CGLib使用切记不可太过频繁

二、CGLib核心用法

2.1 CGLib核心用法介绍及示例
  • 介绍
    CGLib最核心的用法就是在程序运行时动态生成一个类,要做到这一点,你只需要弄清楚三个类(接口)就可以了,分别是EnhancerCallbackCallbackFilter。其中,Enhancer就相当于JDK代理中的Proxy,作为CGLib执行类创建动作的一个客户端,Callback相当于回调器,某些方法的调用将会被拦截,并转而调用该回调器,进行自定义操作,CallbackFilter相当于回调过滤器,回调过滤器会决定每个方法对应的回调器是哪一个。

  • 基础示例

    • SuperClass.java
    public class SuperClass {
      public void superClassFuncPublicVoid() {
          System.out.println("superClassfuncPublicVoid");
      }
    }
    

    新生成类的父类

    • SuperInterface.java
    public interface SuperInterface {
      void superInterfaceFuncPublicVoid();
    }
    

    新生成类需要实现的接口

    • MyMethodInterceptor.java
    public class MyMethodInterceptor implements MethodInterceptor {
      @Override
      public Object intercept(Object proxy, Method method, Object[] params, MethodProxy methodProxy) throws Throwable {
          if("superInterfaceFuncPublicVoid".equals(method.getName())) {
              System.out.println("superInterfaceFuncPublicVoid");
              return null;
          }
          return methodProxy.invokeSuper(proxy, params);
      }
    }
    

    MethodInterceptor是Callback之一,应该是比较常用回调器之一了,能够提供对被拦截方法的比较完全的控制

    • MyCallbackFilter .java
    public class MyCallbackFilter implements CallbackFilter {
      @Override
      public int accept(Method method) {
          return 0;
      }
    }
    

    直接对CallbckFilter接口进行实现,返回的序号代表了Callback数组中的索引

    • Test.java
    public class Test {
      public static void main(String[] args) {
          Callback myMehotdInterceptor = new MyMethodInterceptor();
          Callback[] callbacks = new Callback[] {myMehotdInterceptor};
          CallbackFilter myCallbackFilter = new MyCallbackFilter();
          SuperClass newlyClass = (SuperClass)Enhancer.create(SuperClass.class, new Class[] {SuperInterface.class}, myCallbackFilter , callbacks);
          newlyClass.superClassFuncPublicVoid();
          SuperInterface newlyInterf = (SuperInterface)newlyClass;
          newlyInterf.superInterfaceFuncPublicVoid();
      }
    }
    

    结果:
    superClassfuncPublicVoid
    superInterfaceFuncPublicVoid

2.2 CGLib核心用法中三大组件
  • Enhancer
    • Enhancer#create
    public Object create();
    public Object create(Class[] argumentTypes, Object[] arguments);
    public static Object create(Class superclass, Callback callback);
    public static Object create(Class superclass, Class interfaces[], Callback callback);
    public static Object create(Class superclass, Class[] interfaces, CallbackFilter filter, Callback[] callbacks);
    

    create方法用于执行创建你设定的新类,后三个静态方法默认子类调用父类存在的无参构造器进行父类的实例化,使用第二个带参的实例化方法你可以指定需要调用的父类构造器

    • Enhancer#getMethods
    public static void getMethods(Class superclass, Class[] interfaces, List methods);
    

    该方法会返回一些方法(Method类型)的并将这些方法添加进第三个参数中的集合里去,这些方法就是会被CGLib拦截的方法,对,不是所有方法都会被CGLib拦截,比如private、static、final的方法,特殊一点的有finalize方法,当然还有net.sf.cglib.proxy.Factory接口的所有方法(该接口会默认被新类继承)

    • Enhancer#isEnhanced
    public static boolean isEnhanced(Class type);
    

    用于判断某个类是否是由Enhancer生成的

    • Enhancer#createClass
    public Class createClass();
    

    返回生成类的Class类型

    • Enhancer#setCallback
    • Enhancer#setCallbacks
    • Enhancer#setCallbackFilter
    • Enhancer#setSuperClass
    • Enhancer#setInterfaces

    上述几个方法看名字都知道干嘛的,不说了

  • Callback
    CGLib中实现了Callback接口的接口(类)常用有七种(你自己就不要实现Callback接口啦,显然不能那么用,Callback是一个空方法接口,它的作用仅仅就相当于一个标识,就像Cloneable接口一样,其实按照惯例不建议使用空方法接口作为一个标识,因为那会让人产生误解,以为实现了这个接口就可以实现某些功能了),这六种回调器各有各的功能,接下来将会逐一介绍并演示
    1. MethodInterceptor
    • 介绍
      MethodInterceptor回调器提供了对拦截方法比较完全的控制能力,是最为常用的回调器之一,但要注意的是,由于需要生成MethodProxy参数的缘故,因此效率也是最低下的
    • 需要实现的方法签名
    public Object intercept(Object proxy, Method method, Object[] params, MethodProxy methodProxy) throws Throwable
    

    第一个参数proxy就是新生成类的实例本身,第二个参数method是被拦截的方法,第三个参数是调用方法时的参数列表,第四个参数可以理解为被拦截方法的代理,你可以通过这个参数调用被拦截这个方法本身(会导致无限递归),或者是调用父类同名方法(实际上调用的是一个存在于新类内的自动生成的方法,该方法才真正负责调用父类方法)

    • 演示
      • MyMethodInterceptor .java
      public class MyMethodInterceptor implements MethodInterceptor {
          @Override
          public Object intercept(Object proxy, Method method, Object[] params, MethodProxy methodProxy) throws Throwable {
                System.out.println("MyMethodInterceptor");
                return methodProxy.invokeSuper(proxy, params);
          }
      }
      
      • Test.java
      public class Test {
          public static void main(String[] args) {
              Callback myMehotdInterceptor = new MyMethodInterceptor();
              Callback[] callbacks = new Callback[] {myMehotdInterceptor};
              SuperClass newlyClass = (SuperClass)Enhancer.create(SuperClass.class, null, null , callbacks);
              newlyClass.superClassFuncPublicVoid();
          }
      }
      

      结果:
      MyMethodInterceptor
      superClassfuncPublicVoid

    1. InvocationHandler
    • 介绍
      InvocationHandler需要实现的方法与MethodInterceptor的功能类似,知识少了最后一个方法代理的参数,因而失去了调用对应父类方法的能力,对了该接口是指CGLib里面的,而不是Java反射里的那个
    • 方法签名
    public Object invoke(Object proxy, Method method, Object[] params) throws Throwable
    
    • 演示
      • MyInvocationHandler.java
      public class MyInvocationHandler implements InvocationHandler {
          @Override
          public Object invoke(Object proxy, Method method, Object[] params) throws Throwable {
              return null;
          }
      }
      
      • Test.java
        public class Test {
          public static void main(String[] args) {
              Callback myInvocationHandler = new MyInvocationHandler();
              Callback[] callbacks = new Callback[] {myInvocationHandler};
              SuperClass newlyClass = (SuperClass)Enhancer.create(SuperClass.class, null, null , callbacks);
              newlyClass.superClassFuncPublicVoid();
          }
      }
      

      结果
      MyInvocationHandler

    1. NoOp
    • 介绍
      NoOp正如其名,代表不做任何操作,直接访问父类同名方法,你无需直接实现NoOp接口,使用NoOp.INSTANCE就可以达到你的目的
    • 演示
      • Test.java
      public class Test {
          public static void main(String[] args) {
              Callback[] callbacks = new Callback[] {NoOp.INSTANCE};
              SuperClass newlyClass = (SuperClass)Enhancer.create(SuperClass.class, null, null , callbacks);
              newlyClass.superClassFuncPublicVoid();
          }
      }
      

      结果
      superClassfuncPublicVoid

    1. LazyLoader
    • 介绍
      接下来三个回调器需要实现的方法一模一样,但是功能目标却大相径庭,LazyLoaderloadObject方法希望你能返回一个对象,该对象是被继承类的实例(也就是本篇例子中的SuperClass的实例),CGLib对被拦截方法的调用实际上就等于对这个返回实例上对应方法的调用,需要注意的是,LazyLoader只会执行一次(不是对每个拦截方法只执行一次),因为CGLib会将第一次返回的实例进行缓存,对了,在使用下面三种回调器类型之前,请务必小心类型转换异常的问题,毕竟返回的类型都是Object并没有任何可以提示你小心类型问题的地方
    • 方法签名
    public Object loadObject() throws Exception
    
    • 演示
      • MyLazyLoader.java
      public class MyLazyLoader implements LazyLoader {
          @Override
          public Object loadObject() throws Exception {
              System.out.println("MyLazyLoader");
              SuperClass superClass = new SuperClass();
              return superClass;
          }
      }
      
      • Test.java
      public class Test {
          public static void main(String[] args) {
              Callback myLazyLoader = new MyLazyLoader();
              Callback[] callbacks = new Callback[] {myLazyLoader};
              SuperClass newlyClass = (SuperClass)Enhancer.create(SuperClass.class, null, null , callbacks);
              newlyClass.superClassFuncPublicVoid();
              newlyClass.superClassFuncPublicVoid();
          }
      }
      

      结果
      MyLazyLoader
      superClassfuncPublicVoid
      superClassfuncPublicVoid

    1. Dispatcher
    • 介绍
      相比于LazyLoaderDispatcher会参与到每次回调过程中,而不是只运行一次
    • 演示
      • MyDispatcher.java
      public class MyDispatcher implements Dispatcher {
          @Override
          public Object loadObject() throws Exception {
              System.out.println("MyDispatcher");
              SuperClass superClass = new SuperClass();
              return superClass;
          }
      }
      
      • Test.java
      public class Test {
          public static void main(String[] args) {
              Callback myDispatcher = new MyDispatcher();
              Callback[] callbacks = new Callback[] {myDispatcher};
              SuperClass newlyClass = (SuperClass)Enhancer.create(SuperClass.class, null, null , callbacks);
              newlyClass.superClassFuncPublicVoid();
              newlyClass.superClassFuncPublicVoid();
          }
      }
      

      结果
      MyDispatcher
      superClassfuncPublicVoid
      MyDispatcher
      superClassfuncPublicVoid

    1. FixedValue
    • 介绍
      FixedValue的目标与上面两个就不一样了,它旨在返回一个对象,该对象就是被拦截方法的返回值,说白了就是对每个方法都返回同一个对象
    • 演示
      • MyFixedValue.java
      public class MyFixedValue implements FixedValue {
          @Override
          public Object loadObject() throws Exception {
              return "MyFixedValue";
          }
      }
      
      • Test.java
      public class Test {
          public static void main(String[] args) {
              Callback myFixedValue = new MyFixedValue();
              Callback[] callbacks = new Callback[] {myFixedValue};
              SuperClass2 newlyClass = (SuperClass2)Enhancer.create(SuperClass2.class, null, null , callbacks);
              System.out.println(newlyClass.getString());
          }
      }
      
      class SuperClass2 {
          public String getString() {
              return "SuperClass2";
          }
      }
      

      结果
      MyFixedValue

    1. ProxyRefDispatcher
    • 介绍
      就是比Dispatcher需要实现的方法多一个代理对象的引用的参数
    • 方法签名
    public Object loadObject(Object proxy) throws Exception
    
  • CallbackFilter
    CallbackFilter如前所述用于控制方法对应的回调器是哪个,除了可以直接实现CallbackFilter接口来创建一个回调过滤器,你还可以通过继承CallbackHelper类来更加优雅的控制回调器(如果你有实际用过以实现CallbackFilter接口来进行回调过滤的话,你就会发现这种方法的弊端)
    • CallbackHelper
      CallbackHelper是一个抽象类,实现了CallbackFilter接口。因为CallbackHelper没有提供无参构造函数的缘故,所以你在继承CallbackHelper之后需要在构造器中显式调用父类构造器
    public class MyCallbackHelper extends CallbackHelper {
      public MyCallbackHelper(Class superClass, Class[] interfaces) {
          super(superClass, interfaces);
      }
    
      @Override
      protected Object getCallback(Method method) {
          Callback myMethodInterceptor = new MyMethodInterceptor();
          return myMethodInterceptor;
      }
    }
    

    构造器中的superClass指的是需要继承的类,interfaces表示需要实现的接口们,getCallback是需要我们自己实现的,该方法旨在根据不同方法(method)返回一个对应的Callback类型,为什么说CallbackHelper更加优雅呢?请看下面的使用例子

    public class Test {
      public static void main(String[] args) {
          CallbackHelper myCallbackHelper = new MyCallbackHelper(SuperClass.class, null);
          SuperClass newlyClass = (SuperClass)Enhancer.create(SuperClass.class, null, myCallbackHelper , myCallbackHelper.getCallbacks());
          newlyClass.superClassFuncPublicVoid();
      }
    }
    

    结果
    MyMethodInterceptor
    superClassfuncPublicVoid


三、CGLib Bean工具

3.1 CGLib Bean工具介绍
  • 介绍
    CGLib还提供了对Java Bean进行操作的工具类,包括生成Bean、复制Bean、转换Bean字属性Map、不可变Bean等等操作,这些工具类都可以在net.sf.cglib.beans包下找到,需要注意的是这些Bean工具都是通过判断公有的Setter和Getter方法来判定Bean中是否具有某个属性,也即你Bean中就算一个字段都没有,但是有诸如setNamegetName这样的方法,则这些Bean工具类认为你这个Bean中具有Name这个属性。
3.2 CGLib Bean工具用法详述
  • BeanGenerator
    • 介绍
      Bean生成工具,用于在运行时生成一个Bean类,你可以自由的添加属性,这些属性最后都会以“前缀+属性名”的方式存在于新类的实例域中,并且还会生成相应的Setter和Getter方法
    • 用法
      • Test.java
      public class Test {
          public static void main(String[] args) throws Exception{
              BeanGenerator beanGenerator = new BeanGenerator();
              beanGenerator.addProperty("name", String.class);
              Object obj = beanGenerator.create();
              Method setNameFunc = obj.getClass().getMethod("setName", String.class);
              setNameFunc.invoke(obj, "mutsuki");
              Method getNameFunc = obj.getClass().getMethod("getName");       
              System.out.println(getNameFunc.invoke(obj));
          }
      }
      

      使用BeanGenerator#addProperty添加属性,使用BeanGenerator#create创建新Bean类的实例
      ---------------------
      执行结果:
      mutsuki

  • ImmutableBean
    • 介绍
      该工具用于创建一个指定Bean实例的不可变代理Bean,源Bean实例会被绑定在代理Bean实例的finl实例域中,所以企图更换代理Bean的绑定对象来使得代理Bean呈现可变状态将是不可行的,对代理Bean调用Setter方法将会直接报出异常
    • 用法
      • SampleBean.java
      public class SampleBean {
          private String name;
      
          public String getName() {
              return name;
          }
      
          public void setName(String name) {
              this.name = name;
          }
      }
      
      • Test.java
      public class Test {
          public static void main(String[] args){
              SampleBean sampleBean = new SampleBean();
              sampleBean.setName("mtk");
              SampleBean immutableBean = (SampleBean)ImmutableBean.create(sampleBean);
              System.out.println(immutableBean.getName());
              sampleBean.setName("lalal");
              System.out.println(immutableBean.getName());
              immutableBean.setName("lalal");
          }
      }
      

      使用ImmutableBean#create方法来创建代理Bean实例
      -------------------
      结果:
      mtk
      lalal
      Exception in thread "main" java.lang.IllegalStateException: Bean is immutable
      ...

  • BeanMap
    • 介绍
      BeanMap用于抽取Bean中的属性来生成一个属性名 ==> 值的映射,由于也是绑定关系,所以对该映射进行操作就是对源Bean操作,反过来也一样,该类还提供了一个newInstance方法,该方法允许你更换绑定Bean实例,但是必须保证和第一个Bean实例是同类型的,否则将会抛出类型异常
    • 用法
    public class Test {
      public static void main(String[] args){
          SampleBean sampleBean = new SampleBean();       
          BeanMap beanMap = BeanMap.create(sampleBean);
          sampleBean.setName("mtk");
          System.out.println(beanMap.get("name"));
          beanMap.put("name", "mtk!!!");
          System.out.println(sampleBean.getName());
          SampleBean sampleBean2 = new SampleBean();
          sampleBean2.setName("test name");
          beanMap = beanMap.newInstance(sampleBean2);
          System.out.println(beanMap.get("name"));
      }
    }  
    

    使用BeanMap#create创建指定Bean实例的的BeanMap
    ---------------
    结果:
    mtk
    mtk!!!
    test name

  • BulkBean
    • 介绍
      创建了了该代理类实例后,你就可以通过该代理类实例,使用一个包含你要设置的值的数组快速去设置被代理的Bean的属性,也可以通过一个数组快速获取被代理Bean中的属性,需要注意的是其create方法的参数,第一个参数是被绑定Bean实例,第二个参数是要绑定到Bean实例中的Getter方法名数组,第三个是Setter方法名数组,第四个是属性类型数组,没有被设置进去的属性不会被快速赋值和获取
    • 用法
    public class Test {
      public static void main(String[] args){
          BulkBean bulkBean = BulkBean.create(SampleBean.class, new String[] {"getName"}, new String[] {"setName"}, new Class[] {String.class});
          SampleBean sampleBean = new SampleBean();
          bulkBean.setPropertyValues(sampleBean, new Object[] {"mutsuki"});
          System.out.println(sampleBean.getName());
          System.out.println(Arrays.toString(bulkBean.getPropertyValues(sampleBean)));
          System.out.println(Arrays.toString(bulkBean.getPropertyTypes()));
      }
    }
    

    结果:
    mutsuki
    [mutsuki]
    [class java.lang.String]

  • BeanCopier
    • 介绍
      BeanCopier用于将源Bean实例中的某些属性(与目标Bean同名同类型的属性)的属性值赋值给目标Bean,对于源Bean和目标Bena,其实并不要求他们的类型一样
    • 用法
      • OtherSampleBean.java
      public class OtherSampleBean {
          private String name;
          private Integer age;
      
          public String getName() {
              return name;
          }
          public void setName(String name) {
              this.name = name;
          }
          public Integer getAge() {
              return age;
          }
          public void setAge(Integer age) {
              this.age = age;
          }
      }
      
      • Test.java
      public class Test {
          public static void main(String[] args){
              BeanCopier beanCopier = BeanCopier.create(OtherSampleBean.class, SampleBean.class, false);
              OtherSampleBean otherSampleBean = new OtherSampleBean();
              otherSampleBean.setName("mutsuki");
              SampleBean sampleBean = new SampleBean();
              beanCopier.copy(otherSampleBean, sampleBean, null);
              System.out.println(sampleBean.getName());
          }
      }
      

      create第三个参数设置为false后,copy方法第三个参数就可以设置为null
      ------------------------
      结果:
      mutsuki

    • 附加:Conveter接口的含义Object convert(Object value, Class target, Object context);
      三个参数的意义分别是:value复制源某个字段的值,target复制源对应字段的类型,context调用的set方法全名。
      返回值的含义是:要输出给目标字段的值。

四、CGLib其他用法

4.1 方法、构造器代理
  • 说明
    CGLib还提供了对方法和构造器的代理,有了他们之后,就可以使你代码的可见性进一步的缩小,原本为了让下一个方法去执行某个对象的一个特定方法,你需要传递整个对象给下一个方法,现在你只需要传递一个该方法的代理过去,下一个方法能够见到的东西就被大幅度缩小了,提高了代码的安全性能。但是其实说到底,不过是创建了一个新类然后绑定了源对象而已,所以你必须注意不能滥用该功能,否则你就等着出现内存问题吧
  • MethodDelegate
    • 介绍
      MethodDelegate用于代理一个方法,要实现这个功能 ,你需要先创建一个接口,该接口只能包含一个方法,该方法与你需要代理的方法参数和返回值应该一样(返回值其实可以是被代理方法的返回值的父类),方法名字尽量能够描述被代理方法的功能
    • 用法
      • MethodProxySetName.java
      public interface MethodProxySetName {
        void setName(String name);
      }
      
      • Test.java
      public class Test {
          public static void main(String[] args){
              SampleBean sampleBean = new SampleBean();
              MethodProxySetName methodProxySetName = (MethodProxySetName)MethodDelegate.create(sampleBean , "setName", MethodProxySetName.class);
              methodProxySetName.setName("snmutsuki");
              System.out.println(sampleBean.getName());
          }
      }
      

      create方法的第一个参数是被绑定源对象,第二个参数是需要绑定的方法名,第三个参数是你创建的单方法接口,第二个参数和第三个参数将共同决定方法匹配的过程
      -----------------------------
      结果:
      snmutsuki

  • MulticastDelegate
    • 介绍
      MulticastDelegate相当于MethodDelegate的一对多版,调用MulticastDelegate的代理方法将会对其绑定的所有对象的相应方法进行调用,但是该类需要的前置条件比较苛刻,所以用起来也颇为尴尬,除开都要创建的单方法接口外,它还需要它所有被绑定的对象都实现这个单方法接口,否则将会在创建时抛出异常,再者,该单方法接口中的方法必须和被代理方法同名(对,和MethodDelegate那个不一样,后者可以自定义方法名)。在用法上建议用于对于无返回值方法进行代理以实现调用一次相当于调用多次的效果,对有返回值的方法进行代理会导致在调用时丢失除最后一次调用外的所有返回值
    • 用法
    //已经修改SampleBean和OtherSampleBean实现了MethodProxySetName接口
    public class Test {
      public static void main(String[] args){
          SampleBean sampleBean = new SampleBean();
          OtherSampleBean otherSampleBean = new OtherSampleBean();
          MulticastDelegate multicastDelegate = MulticastDelegate.create(MethodProxySetName.class);   
          multicastDelegate = multicastDelegate.add(sampleBean).add(otherSampleBean);
          MethodProxySetName methodProxySetName = (MethodProxySetName)multicastDelegate;
          methodProxySetName.setName("snmutsuki");
          System.out.println(sampleBean.getName());
          System.out.println(otherSampleBean.getName());
      }
    }
    

    需要小心add方法会返回一个添加添加了绑定对象后的MulticastDelegate引用,直接使用的话会绑定不了对象
    -------------------------------
    结果:
    snmutsuki
    snmutsuki

  • ConstructorDelegate
    • 介绍
      ConstructorDelegate就是构造器代理了,实现这个功能同样也需要创建一个单方法接口,该方法必须名为newInstance,且返回值必须为Object及其子类,对了,妄图代理非公开的构造器,你会收到一个错误IllegalAccessError
    • 用法
      • ConstructorProxyNoParams.java
      public interface ConstructorProxyNoParams {
          Object newInstance();
      }
      
      • Test.java
      public class Test {
          public static void main(String[] args){
              ConstructorProxyNoParams constructorProxyNoParams = (ConstructorProxyNoParams) ConstructorDelegate.create(SampleBean.class, ConstructorProxyNoParams.class);
              System.out.println(constructorProxyNoParams.newInstance().getClass());
          }
      }
      

      结果:
      class cglib_test.SampleBean

4.2 Fast反射系列
  • 说明
    Fast反射系列可以类比于Java标准库中的ClassFieldMethodConstructor,由于使用字节码技术的原因,Fast反射系列比Java标准库对应的那些类效率更高,但是,现代较新的JVM都在这个方面进行了优化,如果某个反射用得过于频繁,JVM将对其进行优化,优化后的效率其实不弱于Fast反射系列,所以这个系列的类用处并不如想象的大,列出来仅供参考,对了,你可以通过设置虚拟机参数sun.reflect.inflationThreshold来设置优化的阈值
  • FastClass
  • FastMember
  • FastConstructor
  • FastMethod
4.3 Mixin
  • 说明
    Mixin用于逻辑上实现将多个不同的类进行合并,由于在Bean上的应用过于优秀,所以Mixin还专门提供了createBean方法来专门进行多个Bean的合并,其与create方法产生的效果有些不同,需要注意。但是,相同的是,Mixin会在新类中新增一个CGLIB$DELEGATES实例域用于存储被合并那些实例
  • Mixin#create用法
public class Test {
    public static void main(String[] args){
        ClassA classA = new ClassA();
        ClassB classB = new ClassB();
        Mixin mixin = Mixin.create(new Object[] {classA , classB});
        InterfaceA interA = (InterfaceA)mixin;
        System.out.println(interA.funcA());
        classA.setStr("changed");
        System.out.println(interA.funcA());
        InterfaceB interB = (InterfaceB)mixin;
        interB.funcB();
    }
}

interface InterfaceA {
    String funcA();
}

interface InterfaceB {
    void funcB();
}

class ClassA implements InterfaceA {
    private String str = "origin";
    
    @Override
    public String funcA() {
        return str; 
    }
    
    public void setStr(String str) {
        this.str = str;
    }
}

class ClassB implements InterfaceB{
    @Override
    public void funcB() {
        System.out.println("funcB");
    }
    
}

Mixin#create会抽取出被绑定的对象们的接口,然后在新类中实现,也就是说CGLib不会添加那些不在接口中定义的方法,CGLib也不会把被绑定对象们的字段添加到新类之中,所以只能称之为逻辑上的合并
--------------------------------
结果:
origin
changed
funcB

  • Mixin#createBean用法
public class Test {
    public static void main(String[] args) throws Exception{
        SampleBean sampleBean = new SampleBean();
        sampleBean.setName("snmutsuki");
        OtherSampleBean otherSampleBean = new OtherSampleBean();
        otherSampleBean.setName("yoom");
        otherSampleBean.setAge(16);
        Mixin mixin = Mixin.createBean(new Object[] {sampleBean , otherSampleBean});
        System.out.println(mixin.getClass().getDeclaredMethod("getName").invoke(mixin));
        BeanMap beanMap = BeanMap.create(mixin);
        System.out.println(beanMap);
    }
}

Mixin#createBean会抽取被绑定Bean的Setter和Getter方法,遇到同名的Setter和Getter方法则忽略当前这个Setter和Getter方法以上一个为准,同样地,该新类也不会合并属性字段
----------------------------------
结果:
snmutsuki
{name=snmutsuki, age=16}

4.4 其他
  • KeyFactory
    • 介绍
      KeyFactory主要用于把你想用于生成key的属性集转换成一个对象,然后你就可以使用这个对象作为key,只有两个对象代表的属性集一样时,两个对象才相等。在使用这个功能之前,你需要创建一个单方法接口,该方法描述了用于生成对象的属性参数
    • 用法
      • MyKeyInterface.java
      public interface MyKeyInterface {
          Object newInstance(String firstStr , int secondInt , int thirdInt);
      }
      
      • Test.java
      public class Test {
        public static void main(String[] args) {
            MyKeyInterface keyFactory = (MyKeyInterface) KeyFactory.create(MyKeyInterface.class);
            Map<Object,String> map = new HashMap<Object,String>();
            map.put(keyFactory.newInstance("key", 20, 20), "snmutsuki");
            System.out.println(map.get(keyFactory.newInstance("key", 20, 19)));
            System.out.println(map.get(keyFactory.newInstance("key", 20, 20)));
        }
      }
      

      结果:
      null
      snmutsuki

  • InterfaceMaker
    • 介绍
      该类用于动态生成一个新的接口
    • 用法
    public class Test {
      public static void main(String[] args){
          Signature signature = new Signature("test", Type.VOID_TYPE, new Type[0]);//第三个参数(方法参数类型)不可为null
          InterfaceMaker interfaceMaker = new InterfaceMaker();
          interfaceMaker.add(signature, null);//也支持Method
          for(Method m : interfaceMaker.create().getDeclaredMethods()) {
              System.out.println(m.getName());
          }
      }
    }
    

    Signature构造方法的第一个参数是方法名,第二个是返回类型,第三个是参数类型,第三个参数不可以为null,使用InterfaceMaker#add添加方法
    ----------------------
    结果:
    test


五、注意事项及说明

  • 关于使用需知中“不要使用非静态内部类(除外部类)的实例作为CGLib某些方法的参数,尤其不要乱使用匿名内部类”一项的说明
    A:因为CGLib会在新类中添加一个的静态域,用于存储回调器的引用,如果你使用了非静态内部类,那么会导致这个内部类所属的外部类对象同样无法被gc回收,在CGLib这种比较容易出现内存问题的工具中,你要格外小心任何一点内存泄漏的问题。
  • 关于使用需知中“请尽量保证这个接口的访问权限是公有的,而不是包私有的” 一项的说明
    A:因为某些CGLib某些方法的操作中,可能涉及到通过该接口反射调用其方法的情况,且CGLib并没有设置权限处理相关的操作,因此包私有的接口将会出现AccessError的异常
  • CGLib整个系统都是围绕动态生成类这一功能来设计的,而类这种东西是存储在JVM的方法区(也可以称为永久代),这一块区域,GC甚少光顾,所以生成太多的类将会造成内存问题。

参考文档:
[1] CGLib: The Missing Manual
[2] https://github.com/cglib/cglib/wiki/How-To

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容