JAVA 中的代码生成包 CGLIB (Code Generation Library)

JAVA 中的代码生成包 CGLIB (Code Generation Library)

CGLIB 是一个功能强大,高性能的代码生成包。它为没有实现接口的类提供代理,为 JDK 的动态代理提供了很好的补充。通常可以使用Java的动态代理创建代理,但当要代理的类没有实现接口或者为了更好的性能,CGLIB 是一个好的选择。

CGLIB 作为一个开源项目,其代码托管在 Github,地址为:https://github.com/cglib/cglib

1. CGLIB 原理

名称解释
CGLIB 原理动态生成一个要代理类的子类,子类重写要代理的类的所有不是final的方法。在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。它比使用java反射的JDK动态代理要快。
CGLIB 底层使用字节码处理框架ASM,来转换字节码并生成新的类。不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉。
CGLIB 缺点对于final方法,无法进行代理。

广泛的被许多 AOP 的框架使用,例如 Spring AOP 和 dynaop 。Hibernate 使用 CGLIB 来代理单端single-ended(多对一和一对一)关联。

2. 为什么使用 CGLIB?

CGLIB 代理主要通过对字节码的操作,为对象引入间接级别,以控制对象的访问。我们知道 Java 中有一个动态代理也是做这个事情的,那我们为什么不直接使用 Java 动态代理,而要使用 CGLIB 呢?

答案是 CGLIB 相比于 JDK动态代理 更加强大,JDK动态代理 虽然简单易用,但是其有一个致命缺陷是,只能对接口进行代理。如果要代理的类为一个普通类、没有接口,那么 Java动态代理 就没法使用了。

2.1 JAVA 动态代理分析

Java动态代理机制 的出现,使得 Java开发人员 不用手工编写代理类,只要简单地制定一组接口及委托类对象,便能动态地获得代理类。代理类会负责将所有的方法调用分配到委托对象上反射执行,配置执行过程中,开发人员还可以进行修改。

2.1.1 代理设计模式

代理是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问。代理类负责为委托类预处理消息、过滤消息并转发消息,以及进行消息被委托类执行后的后续处理。

  1. 为了保持行为的一致性,代理类和委托类通常会实现相同的接口
  2. 引入代理能够控制对委托对象的直接访问,可以很好的隐藏和保护委托对象,也更加具有灵活性
2.1.2 相关的类和接口

要了解 Java 动态代理的机制,首先需要了解以下相关的类或接口:

  1. java.lang.reflect.Proxy :这是 Java 动态代理机制的主类,它提供了一组静态方法来为一组接口动态地生成代理类及其对象
  2. java.lang.reflect.InvocationHandler :这是调用处理器接口,它自定义了一个 invoke 方法,用于几种处理在动态代理类对象上的方法调用。通常在该方法中实现对委托类的代理访问。
  3. java.lang.ClassLoader :Proxy 静态方法生成动态代理类同样需要通过类装载器来进行装载才能使用,它与普通类的唯一区别就是其字节码是由 JVM 在运行时动态生成的而非预存在于任何一个 .class 文件中。
2.1.3 代理机制及其特点

首先让我们来了解一下如何使用 Java 动态代理。具体有如下四步骤:

  1. 通过实现 InvocationHandler 接口创建自己的调用处理器;
  2. 通过为 Proxy 类指定 ClassLoader 对象和一组 interface 来创建动态代理类;
  3. 通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型;
  4. 通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入。
// InvocationHandlerImpl 实现了 InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发
// 其内部通常包含指向委托类实例的引用,用于真正执行分派转发过来的方法调用
InvocationHandler handler = new InvocationHandlerImpl(..); 

// 通过 Proxy 为包括 Interface 接口在内的一组接口动态创建代理类的类对象
Class clazz = Proxy.getProxyClass(classLoader, new Class[] { Interface.class, ... }); 

// 通过反射从生成的类对象获得构造函数对象
Constructor constructor = clazz.getConstructor(new Class[] { InvocationHandler.class }); 

// 通过构造函数对象创建动态代理类实例
Interface Proxy = (Interface)constructor.newInstance(new Object[] { handler });

实际使用过程更加简单,因为 Proxy 的静态方法 newProxyInstance 已经为我们封装了步骤 2 到步骤 4 的过程,所以简化后的过程如下:

// InvocationHandlerImpl 实现了 InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发
InvocationHandler handler = new InvocationHandlerImpl(..); 

// 通过 Proxy 直接创建动态代理类实例
Interface proxy = (Interface)Proxy.newProxyInstance( classLoader, 
     new Class[] { Interface.class }, 
     handler );

动态生成的代理类本身的一些特点

  1. 包:如果所代理的接口都是 public 的,那么它将被定义在顶层包(即包路径为空),如果所代理的接口中有 非 public 的接口(因为接口不能被定义为 protectprivate ,所以除 public 之外就是默认的 package 访问级别,那么它将被定义在该接口所在包,这样设计的目的是为了最大程度的保证动态代理类不会因为包管理的问题而无法被成功定义并访问;
  2. 类修饰符:该代理类具有 finalpublic 修饰符,意味着它可以被所有的类访问,但是不能被再度继承;
  3. 类名:格式是 $ProxyN ,其中 N 是一个逐一递增的阿拉伯数字,代表 Proxy 类 第 N 次 生成的动态代理类,值得注意的一点是,并不是每次调用 Proxy 的静态方法创建动态代理类都会使得 N 值增加,原因是如果对同一组接口(包括接口排列的顺序相同)试图重复创建动态代理类,它会很聪明地返回先前已经创建好的代理类的类对象,而不会再尝试去创建一个全新的代理类,这样可以节省不必要的代码重复生成,提高了代理类的创建效率。
  4. 类继承关系:Proxy 类是它的父类,这个规则适用于所有由 Proxy 创建的动态代理类。而且该类还实现了其所代理的一组接口。

在这里插入图片描述

代理类实例的一些特点:

  1. 每个实例都会关联一个 InvocationHandler (调用处理器对象),在代理类实例上调用其代理接口中声明的方法时,最终都会由 InvocationHandlerinvoke方法 执行;
  2. java.lang.Object 中有三个方法也同样会被分派到调用处理器的 invoke 方法 执行,它们是 hashCodeequalstoString

被代理接口的一组特点

  1. 要注意不能有重复的接口
  2. 接口对于类装载器必须可见,否则类装载器将无法链接它们
  3. 被代理的所有非 public 的接口必须在同一个包中,接口的数目不能超过65535
2.1.4 美中不足

Proxy 只能对 interface 进行代理,无法实现对 class 的动态代理。观察动态生成的代理继承关系图可知原因,他们已经有一个固定的父类叫做 Proxy ,Java语法 限定其不能再继承其他的父类。

2.1.5 代码示例

最后以一个简单的动态代理例子结束

public class DynamicProxy {
    interface IHello{
        void sayHello();
    }

    static class Hello implements IHello{
        public void sayHello() {
            System.out.println("hello world");
        }
    }

    static class DynamicProxyTest implements InvocationHandler{
        Object originalObj;
        Object bind(Object originalObj){
            this.originalObj = originalObj;
            return Proxy.newProxyInstance(originalObj.getClass().getClassLoader(),
                    originalObj.getClass().getInterfaces(), this);
        }
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("Welcome");
            return method.invoke(originalObj, args);
        }
    }

    public static void main(String[] args){
        //设置这个值,在程序运行完成后,可以生成代理类
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
        IHello hello = (IHello) new DynamicProxyTest().bind(new Hello());
        hello.sayHello();
    }
}

程序输出为:

Welcome
hello world

3. CGLIB 组成结构

在这里插入图片描述

CGLIB 底层使用了ASM(一个短小精悍的字节码操作框架)来操作字节码生成新的类。除了 CGLIB库 外,脚本语言(如 Groovy 和 BeanShell )也使用 ASM 生成字节码。ASM 使用类似 SAX 的解析器来实现高性能。

4. CGLIB 的 API

4.1 Jar包

  • cglib-nodep-2.2.jar:使用 nodep 包不需要关联 asm 的 jar 包,jar 包内部包含 asm 的类。
  • cglib-2.2.jar:使用此 jar 包需要关联 asm 的 jar 包,否则运行时报错。

4.2 CGLIB类库

名称描述
net.sf.cglib.core底层字节码处理类,他们大部分与 ASM 有关系
net.sf.cglib.transform编译期或运行期类和类文件的转换
net.sf.cglib.proxy实现创建代理和方法拦截器的类
net.sf.cglib.reflect实现快速反射和C#风格代理的类
net.sf.cglib.util集合排序等工具类
net.sf.cglib.beansJavaBean相关的工具类

4.3 例子

说了这么多,可能大家还是不知道 CGLIB 是干什么用的。下面我们将使用一个简单的例子来演示如何使用 CGLIB 对一个方法进行拦截。
首先,我们需要在工程的 POM 文件中引入 CGLIB 的 dependency,这里我们使用的是 2.2.2 版本。

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>2.2.2</version>
</dependency>

依赖包下载后,我们就可以干活了,按照国际惯例,写个 hello world

public class SampleClass {
    public void test(){
        System.out.println("hello world");
    }

    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(SampleClass.class);
        enhancer.setCallback(new MethodInterceptor() {
            @Override
            public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
                System.out.println("before method run...");
                Object result = proxy.invokeSuper(obj, args);
                System.out.println("after method run...");
                return result;
            }
        });
        SampleClass sample = (SampleClass) enhancer.create();
        sample.test();
    }
}

mian 函数中,我们通过一个 Enhancer 和一个 MethodInterceptor 来实现对方法的拦截,运行程序后输出为:

before method run...
hello world
after method run...

4.4 常见的 API

4.4.1 Enhancer
  • Enhancer 可能是 CGLIB 中最常用的一个类,和 Java1.3动态代理 中引入的 Proxy 类差不多。
  • 和Proxy 不同的是,Enhancer 既能够代理普通的 class ,也能够代理接口
  • Enhancer 创建一个被代理对象的子类并且拦截所有的方法调用(包括从 Object 中继承的 toString 和 hashCode 方法)。
    • Enhancer 不能够拦截 final 方法,例如 Object.getClass() 方法,这是由于 Java final 方法语义决定的。
    • 基于同样的道理,Enhancer 也不能对 fianl 类进行代理操作。这也是 Hibernate 为什么不能持久化 final class 的原因。
public class SampleClass {
    public String test(String input){
        return "hello world";
    }
}

下面我们将以这个类作为主要的测试类,来测试调用各种方法

@Test
public void testFixedValue(){
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(SampleClass.class);
    enhancer.setCallback(new FixedValue() {
        @Override
        public Object loadObject() throws Exception {
            return "Hello cglib";
        }
    });
    SampleClass proxy = (SampleClass) enhancer.create();
    System.out.println(proxy.test(null)); //拦截test,输出Hello cglib
    System.out.println(proxy.toString()); 
    System.out.println(proxy.getClass());
    System.out.println(proxy.hashCode());
}

输出:

Hello cglib
Hello cglib
class com.zeus.cglib.SampleClass$$EnhancerByCGLIB$$e3ea9b7

java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Number

    at com.zeus.cglib.SampleClass$$EnhancerByCGLIB$$e3ea9b7.hashCode(<generated>)
    ...
  • 上述代码中,FixedValue 用来对所有拦截的方法返回相同的值,从输出我们可以看出来:

    • Enhancer 对 非final方法test()、toString()、hashCode()进行了拦截,没有对getClass进行拦截。

    • 由于 hashCode() 方法需要返回一个 Number,但是我们返回的是一个 String,这解释了上面的程序中为什么会抛出异常。

  • Enhancer.setSuperclass 用来设置父类型,从 toString() 可以看出,使用 CGLIB 生成的类为被代理类的一个子类,形如:SampleClass$$EnhancerByCGLIB$$e3ea9b7

  • Enhancer.create(Object…) 方法是用来创建增强对象的,其提供了很多不同参数的方法用来匹配被增强类的不同构造方法。(虽然类的构造方法只是 Java字节码 层面的函数,但是 Enhancer 却不能对其进行操作。Enhancer 同样不能操作 static 或者 final 类)。

    • 我们也可以先使用Enhancer.createClass() 来创建字节码( .class ),然后用字节码动态的生成增强后的对象。

可以使用一个 InvocationHandler 作为回调,使用 invoke 方法来替换直接访问类的方法,但是你必须注意死循环。因为 invoke 中调用的任何原代理类方法,均会重新代理到 invoke 方法中。

public void testInvocationHandler() throws Exception{
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(SampleClass.class);
    enhancer.setCallback(new InvocationHandler() {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if(method.getDeclaringClass() != Object.class && method.getReturnType() == String.class){
                return "hello cglib";
            }else{
                throw new RuntimeException("Do not know what to do");
            }
        }
    });
    SampleClass proxy = (SampleClass) enhancer.create();
    Assert.assertEquals("hello cglib", proxy.test(null));
    Assert.assertNotEquals("Hello cglib", proxy.toString());
}

为了避免这种死循环,我们可以使用 MethodInterceptorMethodInterceptor 的例子在前面的 hello world 中已经介绍过了,这里就不浪费时间了。

有些时候我们可能只想对特定的方法进行拦截,对其他的方法直接放行,不做任何操作,使用Enhancer处理这种需求同样很简单,只需要一个 CallbackFilter 即可:

@Test
public void testCallbackFilter() throws Exception{
    Enhancer enhancer = new Enhancer();
    CallbackHelper callbackHelper = new CallbackHelper(SampleClass.class, new Class[0]) {
        @Override
        protected Object getCallback(Method method) {
            if(method.getDeclaringClass() != Object.class && method.getReturnType() == String.class){
                return new FixedValue() {
                    @Override
                    public Object loadObject() throws Exception {
                        return "Hello cglib";
                    }
                };
            }else{
                return NoOp.INSTANCE;
            }
        }
    };
    enhancer.setSuperclass(SampleClass.class);
    enhancer.setCallbackFilter(callbackHelper);
    enhancer.setCallbacks(callbackHelper.getCallbacks());
    SampleClass proxy = (SampleClass) enhancer.create();
    Assert.assertEquals("Hello cglib", proxy.test(null));
    Assert.assertNotEquals("Hello cglib",proxy.toString());
    System.out.println(proxy.hashCode());
}
4.4.1.1 Java回调函数详解
4.4.1.1.1 什么是回调函数(CallBack)

在编写程序时,有时候会调用许多 API 中实现实现的函数,但某些方法需要我们传入一个方法,以便在需要的时候调用我们传入进去的函数。这个被传入的函数称为回调函数(Callback function)。

打个比方,有一个餐馆,提供炒菜的服务,但是会让我们选择做菜的方式,我们去这家餐馆里面吃饭,想吃小龙虾,我们告诉他想吃小龙虾后,他询问我们要以何种方式去进行烹饪,是煎炒烹炸还是避风塘。

在上面的例子中,炒菜是我们需要调用的方法,也是 API 库中所提供的,而炒菜的方式,则是我们去选择的,可以我们自己去定义的。

在这里插入图片描述

这个就可以回调函数,有库函数(Librart function)来执行我们传入的回调函数(Callback function

4.4.1.1.2 在Java中实现回调函数

Callable接口

Interface Callable<V>

在 Java1.8 官方文档中给出的内容为

  • 参数类型:V - 回调方法的返回值类型

  • 已经实现的子接口:DocumentationTool.DocumentationTaskJavaCompiler.CompilationTask

  • 这个接口位函数试接口

    • @FunctionalInterface
      public interface Callable<V>
      
  • 返回结果可能引发异常,这个接口与 Runnable 非常相似,这两个接口的设计可以在实例化后,开启新的线程,与 Runnable 的差别是,Runnable 不能返回参数也不能抛出异常

例子:

import java.util.Random;
import java.util.concurrent.Callable;

public class CallableExample  implements Callable {
    @Override
    public Object call() throws Exception {
        Random generator = new Random();
        Integer randomNumber = generator.nextInt(5);
        Thread.sleep(randomNumber * 1000);
        return randomNumber;
    }
}

测试:

@Test
public void callabledTest(){
    ExecutorService executorService = Executors.newCachedThreadPool();
    CallableExample callableExample = new CallableExample();
    Future<Object> future = executorService.submit(callableExample);
    executorService.shutdown();
    try{
        System.out.println(future.get());
    }catch (Exception e){
        e.printStackTrace();
    }
}

返回值:

3

Callback接口

已知实现此接口的类 AuthorizeCallback, ChoiceCallback, ConfirmationCallback, LanguageCallback, NameCallback, PasswordCallback, RealmCallback, RealmChoiceCallback, TextInputCallback, TextOutputCallback

这个接口的实现了会被传递给 CallbackHandler,允许有能力的底层服务去回应这个回调方法,已便进行诸如数据检索等信息。回调函数不检索或显示底层安全服务请求的信息。回调实现只是提供了将这些请求传递给应用程序的方法,并且对于应用程序,如果合适的话,可以将请求的信息返回给底层的安全服务。

这个接口是可以自己定义的,定制适用于当前业务的callback 接口类型来表示不同类型的回调函数。

callback接口的源码

public interface Callback { }

CallbackHandler 接口

方法:handle(Callback [] callbacks) ,这个方法是用来处理处理 callback 类型的

官方实例:

 public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {

   for (int i = 0; i < callbacks.length; i++) {
      if (callbacks[i] instanceof TextOutputCallback) {

          // display the message according to the specified type
          TextOutputCallback toc = (TextOutputCallback)callbacks[i];
          switch (toc.getMessageType()) {
          case TextOutputCallback.INFORMATION:
              System.out.println(toc.getMessage());
              break;
          case TextOutputCallback.ERROR:
              System.out.println("ERROR: " + toc.getMessage());
              break;
          case TextOutputCallback.WARNING:
              System.out.println("WARNING: " + toc.getMessage());
              break;
          default:
              throw new IOException("Unsupported message type: " +
                                  toc.getMessageType());
          }

      } else if (callbacks[i] instanceof NameCallback) {

          // prompt the user for a username
          NameCallback nc = (NameCallback)callbacks[i];

          // ignore the provided defaultName
          System.err.print(nc.getPrompt());
          System.err.flush();
          nc.setName((new BufferedReader
                  (new InputStreamReader(System.in))).readLine());

      } else if (callbacks[i] instanceof PasswordCallback) {

          // prompt the user for sensitive information
          PasswordCallback pc = (PasswordCallback)callbacks[i];
          System.err.print(pc.getPrompt());
          System.err.flush();
          pc.setPassword(readPassword(System.in));

      } else {
          throw new UnsupportedCallbackException
                  (callbacks[i], "Unrecognized Callback");
      }
   }
 }

 // Reads user password from given input stream.
 private char[] readPassword(InputStream in) throws IOException {
    // insert code to read a user password from the input stream
 }

通过传入不同的已经实现了 Callback 接口的实现类,通过分析不同实现类的类型来进行不同的处理,调用形参实现类内的方法(回调)。

4.4.1.1.3 一般来说如何使用

在一般工作中,我们都是自己定义接口,写实现类,来进行回调的。

自定义的回调函数实例

  • 这个是Callback接口类,我们一会儿要是用它来创造内部匿名类,来实现这个接口,完成字表的筛选工作:
import java.util.List;

public interface CallBackInterface {
    Object process(List<String> list);
}
  • 这个是处理端,通过handler方法,调用传入的 CallBackInterface 类型中的方法,来对字表进行操作
import lombok.Data;

import java.util.ArrayList;
import java.util.List;
@Data
public class WorldListHandler {
    List<String> stringList = new ArrayList<>();
    public void execute(CallBackInterface callBackInterface){
        Object process = callBackInterface.process(stringList);
        System.out.println(process);
    }

}
  • 使用 CallBackInterface 接口并实现它,来让 Handler 来调用它其中的 process 方法来完成对字表的筛选
@Test
public void callableTest2(){
    List<String> list = Arrays.asList("123","asd","1432","fsd","543","987","tre");
    WorldListHandler worldListHandler = new WorldListHandler();
    worldListHandler.setStringList(list);
    worldListHandler.execute(new CallBackInterface() {
        @Override
        public Object process(List<String> list) {
            List<String> collect = list.stream().filter(e -> e.contains("1")).collect(Collectors.toList());
            worldListHandler.setStringList(collect);
            return true;
        }
    });
    worldListHandler.getStringList().forEach(e-> System.out.println(e));
}

结果:

true
123
1432

true 为 process 的返回值,剩下的为我们筛选出字表中包含有 1 的字符串。

4.4.2 ImmutableBean

通过名字就可以知道,不可变的 Bean 。ImmutableBean 允许创建一个原来对象的包装类,这个包装类是不可变的,任何改变底层对象的包装类操作都会抛出 IllegalStateException 。但是我们可以通过直接操作底层对象来改变包装类对象。这有点类似于 Guava 中的不可变视图。为了对 ImmutableBean 进行测试,这里需要再引入一个bean:

public class SampleBean {
    private String value;

    public SampleBean() {
    }

    public SampleBean(String value) {
        this.value = value;
    }
    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }
}

然后编写测试类如下:

@Test(expected = IllegalStateException.class)
public void testImmutableBean() throws Exception{
    SampleBean bean = new SampleBean();
    bean.setValue("Hello world");
    SampleBean immutableBean = (SampleBean) ImmutableBean.create(bean); //创建不可变类
    Assert.assertEquals("Hello world", immutableBean.getValue()); 
    bean.setValue("Hello world, again"); // 可以通过底层对象来进行修改
    Assert.assertEquals("Hello world, again", immutableBean.getValue());
    immutableBean.setValue("Hello cglib"); // 直接修改将throw exception
}

要是报以下的错误:

java.lang.Exception: Unexpected exception, expected<java.lang.IllegalStateException> but was<net.sf.cglib.core.CodeGenerationException>

	at org.junit.internal.runners.statements.ExpectException.evaluate(ExpectException.java:30)
	at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
	at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
	at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
	at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
	at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
	at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
	at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69)
	at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
	at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:235)
	at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:54)
Caused by: net.sf.cglib.core.CodeGenerationException: java.lang.reflect.InaccessibleObjectException-->Unable to make protected final java.lang.Class java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int,java.security.ProtectionDomain) throws java.lang.ClassFormatError accessible: module java.base does not "opens java.lang" to unnamed module @108c4c35
	at net.sf.cglib.core.ReflectUtils.defineClass(ReflectUtils.java:464)
	at net.sf.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:339)
	at net.sf.cglib.core.AbstractClassGenerator$ClassLoaderData$3.apply(AbstractClassGenerator.java:96)
	at net.sf.cglib.core.AbstractClassGenerator$ClassLoaderData$3.apply(AbstractClassGenerator.java:94)
	at net.sf.cglib.core.internal.LoadingCache$2.call(LoadingCache.java:54)
	at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
	at net.sf.cglib.core.internal.LoadingCache.createEntry(LoadingCache.java:61)
	at net.sf.cglib.core.internal.LoadingCache.get(LoadingCache.java:34)
	at net.sf.cglib.core.AbstractClassGenerator$ClassLoaderData.get(AbstractClassGenerator.java:119)
	at net.sf.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:294)
	at net.sf.cglib.beans.ImmutableBean$Generator.create(ImmutableBean.java:70)
	at net.sf.cglib.beans.ImmutableBean.create(ImmutableBean.java:42)
	at cn.bugstack.springframework.test.ApiTest.testImmutableBean(ApiTest.java:39)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:78)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:567)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
	at org.junit.internal.runners.statements.ExpectException.evaluate(ExpectException.java:19)
	... 17 more

请更换 JDK 的版本,这是由于 JDK 8 中有关反射相关的功能自从 JDK 9 开始就已经被限制了,为了兼容原先的版本,需要在运行项目时添加 --add-opens java.base/java.lang=ALL-UNNAMED 选项来开启这种默认不被允许的行为。如果是通过 IDEA 来运行项目,那么可以在 “Edit Configurations” 中 ——> “VM options” 输入框中输入该选项来完成,最终结果如下图所示:

在这里插入图片描述

结果:

在这里插入图片描述

4.4.3 Bean generator

CGLIB 提供的一个操作 bean 的工具,使用它能够在运行时动态的创建一个 bean 。

@Test
public void testBeanGenerator() throws Exception{
    BeanGenerator beanGenerator = new BeanGenerator();
    beanGenerator.addProperty("value", String.class);
    Object myBean = beanGenerator.create();
    Method setter = myBean.getClass().getMethod("setValue", String.class);
    setter.invoke(myBean, "Hello cglib");

    Method getter = myBean.getClass().getMethod("getValue");
    Assert.assertEquals("Hello cglib", getter.invoke(myBean));
}

在上面的代码中,我们使用 CGLIB 动态的创建了一个和 SampleBean 相同的 Bean 对象,包含一个属性 value 以及 getter 、 setter 方法。

结果:

在这里插入图片描述

4.4.4 Bean Copier

CGLIB 提供的能够从一个 bean 复制到另一个 bean 中,而且其还提供了一个转换器,用来在转换的时候对 bean 的属性进行操作。

public class OtherSampleBean {
    private String value;

    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }
}

测试:

@Test
public void testBeanCopier() throws Exception{
    BeanCopier copier = BeanCopier.create(SampleBean.class, OtherSampleBean.class, false); //设置为true,则使用converter
    SampleBean myBean = new SampleBean();
    myBean.setValue("Hello cglib");
    OtherSampleBean otherBean = new OtherSampleBean();
    copier.copy(myBean, otherBean, null); //设置为true,则传入converter指明怎么进行转换
    assertEquals("Hello cglib", otherBean.getValue());
}
4.4.5 BulkBean

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

@Test
public void testBulkBean() throws Exception{
    BulkBean bulkBean = BulkBean.create(SampleBean.class, new String[]{"getValue"}, new String[]{"setValue"}, new Class[]{String.class});
    SampleBean bean = new SampleBean();
    bean.setValue("Hello world");
    Object[] propertyValues = bulkBean.getPropertyValues(bean);
    assertEquals(1, bulkBean.getPropertyValues(bean).length);
    assertEquals("Hello world", bulkBean.getPropertyValues(bean)[0]);
    bulkBean.setPropertyValues(bean, new Object[]{"Hello cglib"});
    assertEquals("Hello cglib", bean.getValue());
}

使用注意:

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

BeanMap 类实现了Java Map,将一个bean对象中的所有属性转换为一个 String-to-Obejct 的 Java Map

@Test
public void testBeanMap() throws Exception{
    BeanGenerator generator = new BeanGenerator();
    generator.addProperty("username", String.class);
    generator.addProperty("password", String.class);
    Object bean = generator.create();
    Method setUserName = bean.getClass().getMethod("setUsername", String.class);
    Method setPassword = bean.getClass().getMethod("setPassword", String.class);
    setUserName.invoke(bean, "admin");
    setPassword.invoke(bean, "password");
    BeanMap map = BeanMap.create(bean);
    Assert.assertEquals("admin", map.get("username"));
    Assert.assertEquals("password", map.get("password"));
}
4.4.7 keyFactory

keyFactory 类用来动态生成接口的实例,接口需要只包含一个 newInstance 方法,返回一个 Object 。keyFactory 为构造出来的实例动态生成了 Object.equals 和 Object.hashCode 方法,能够确保相同的参数构造出的实例为单例的。

public interface SampleKeyFactory {
    Object newInstance(String first, int second);
}

我们首先构造一个满足条件的接口,然后进行测试

@Test
public void testKeyFactory() throws Exception{
    SampleKeyFactory keyFactory = (SampleKeyFactory) KeyFactory.create(SampleKeyFactory.class);
    Object key = keyFactory.newInstance("foo", 42);
    Object key1 = keyFactory.newInstance("foo", 42);
    Assert.assertEquals(key,key1);//测试参数相同,结果是否相等
}
4.4.8 Mixin(混合)

Mixin 能够让我们将多个对象整合到一个对象中去,前提是这些对象必须是接口的实现。可能这样说比较晦涩,以代码为例:

public class MixinInterfaceTest {
    interface Interface1{
        String first();
    }
    
    interface Interface2{
        String second();
    }

    class Class1 implements Interface1{
        @Override
        public String first() {
            return "first";
        }
    }

    class Class2 implements Interface2{
        @Override
        public String second() {
            return "second";
        }
    }

    interface MixinInterface extends Interface1, Interface2{

    }

    @Test
    public void testMixin() throws Exception{
        Mixin mixin = Mixin.create(new Class[]{Interface1.class, Interface2.class, MixinInterface.class}, new Object[]{new Class1(), new Class2()});
        MixinInterface mixinDelegate = (MixinInterface) mixin;
        assertEquals("first", mixinDelegate.first());
        assertEquals("second", mixinDelegate.second());
    }
}

Mixin 类比较尴尬,因为他要求 Minix 的类(例如 MixinInterface )实现一些接口。既然被 Minix 的类已经实现了相应的接口,那么我就直接可以通过纯 Java 的方式实现,没有必要使用 Minix 类。

4.4.9 String switcher

用来模拟一个 String 到 int 类型的 Map 类型。如果在 Java7 以后的版本中,类似一个 switch 语句。

@Test
public void testStringSwitcher() throws Exception{
    String[] strings = new String[]{"one", "two"};
    int[] values = new int[]{10,20};
    StringSwitcher stringSwitcher = StringSwitcher.create(strings, values, true);
    assertEquals(10, stringSwitcher.intValue("one"));
    assertEquals(20, stringSwitcher.intValue("two"));
    assertEquals(-1, stringSwitcher.intValue("three"));
}
4.4.10 Interface Maker

正如名字所言,Interface Maker 用来创建一个新的 Interface

@Test
public void testInterfaceMarker() throws Exception{
    Signature signature = new Signature("foo", Type.DOUBLE_TYPE, new Type[]{Type.INT_TYPE});
    InterfaceMaker interfaceMaker = new InterfaceMaker();
    interfaceMaker.add(signature, new Type[0]);
    Class iface = interfaceMaker.create();
    assertEquals(1, iface.getMethods().length);
    assertEquals("foo", iface.getMethods()[0].getName());
    assertEquals(double.class, iface.getMethods()[0].getReturnType());
}

上述的 Interface Maker 创建的接口中只含有一个方法,签名为 double foo(int)。Interface Maker与上面介绍的其他类不同,它依赖 ASM 中的 Type 类型。由于接口仅仅只用做在编译时期进行类型检查,因此在一个运行的应用中动态的创建接口没有什么作用。但是 InterfaceMaker 可以用来自动生成代码,为以后的开发做准备。

4.4.11 Method delegate

MethodDelegate 主要用来对方法进行代理

interface BeanDelegate{
    String getValueFromDelegate();
}

测试:

@Test
public void testMethodDelegate()  throws Exception{
    SampleBean bean = new SampleBean();
    bean.setValue("Hello cglib");
    BeanDelegate delegate = (BeanDelegate) MethodDelegate.create(bean, "getValue", BeanDelegate.class);
    assertEquals("Hello cglib", delegate.getValueFromDelegate());
}

关于 Method.create 的参数说明:

  1. 第二个参数为即将被代理的方法
  2. 第一个参数必须是一个无参数构造的 bean 。因此 MethodDelegate.create 并不是你想象的那么有用
  3. 第三个参数为只含有一个方法的接口。当这个接口中的方法被调用的时候,将会调用第一个参数所指向 bean 的第二个参数方法

缺点:

  1. 为每一个代理类创建了一个新的类,这样可能会占用大量的永久代堆内存
  2. 你不能代理需要参数的方法
    1. 如果你定义的接口中的方法需要参数,那么代理将不会工作,并且也不会抛出异常;
    2. 如果你的接口中方法需要其他的返回类型,那么将抛出 IllegalArgumentException
4.4.12 MulticastDelegate
  1. 多重代理和方法代理差不多,都是将代理类方法的调用委托给被代理类。使用前提是需要一个接口,以及一个类实现了该接口
  2. 通过这种interface的继承关系,我们能够将接口上方法的调用分散给各个实现类上面去。
  3. 多重代理的缺点是接口只能含有一个方法,如果被代理的方法拥有返回值,那么调用代理类的返回值为最后一个添加的被代理类的方法返回值
public interface DelegatationProvider {
    void setValue(String value);
}

public class SimpleMulticastBean implements DelegatationProvider {
    private String value;
    @Override
    public void setValue(String value) {
        this.value = value;
    }

    public String getValue() {
        return value;
    }
}

测试:

@Test
public void testMulticastDelegate() throws Exception{
    MulticastDelegate multicastDelegate = MulticastDelegate.create(DelegatationProvider.class);
    SimpleMulticastBean first = new SimpleMulticastBean();
    SimpleMulticastBean second = new SimpleMulticastBean();
    multicastDelegate = multicastDelegate.add(first);
    multicastDelegate  = multicastDelegate.add(second);

    DelegatationProvider provider = (DelegatationProvider) multicastDelegate;
    provider.setValue("Hello world");

    assertEquals("Hello world", first.getValue());
    assertEquals("Hello world", second.getValue());
}
4.4.13 Constructor delegate

为了对构造函数进行代理,我们需要一个接口,这个接口只含有一个 Object newInstance(…) 方法,用来调用相应的构造函数

interface SampleBeanConstructorDelegate{
    Object newInstance(String value);
}

测试:

/**
 * 对构造函数进行代理
 * @throws Exception
 */
@Test
public void testConstructorDelegate() throws Exception{
    SampleBeanConstructorDelegate constructorDelegate = (SampleBeanConstructorDelegate) ConstructorDelegate.create(
            SampleBean.class, SampleBeanConstructorDelegate.class);
    SampleBean bean = (SampleBean) constructorDelegate.newInstance("Hello world");
    assertTrue(SampleBean.class.isAssignableFrom(bean.getClass()));
    System.out.println(bean.getValue());
}
4.4.14 Parallel Sorter(并行排序器)

能够对多个数组同时进行排序,目前实现的算法有归并排序和快速排序

@Test
public void testParallelSorter() throws Exception{
    Integer[][] value = {
            {4, 3, 9, 0},
            {2, 1, 6, 0}
    };
    ParallelSorter.create(value).mergeSort(0);
    for(Integer[] row : value){
        int former = -1;
        for(int val : row){
            assertTrue(former < val);
            former = val;
        }
    }
}
4.4.15 FastClass

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

@Test
public void testFastClass() throws Exception{
    FastClass fastClass = FastClass.create(SampleBean.class);
    FastMethod fastMethod = fastClass.getMethod("getValue",new Class[0]);
    SampleBean bean = new SampleBean();
    bean.setValue("Hello world");
    assertEquals("Hello world",fastMethod.invoke(bean, new Object[0]));
}

4.5 注意

由于 CGLIB 的大部分类是直接对Java字节码进行操作,这样生成的类会在Java的永久堆中。如果动态代理操作过多,容易造成永久堆满,触发 OutOfMemory 异常。

4.6 CGLIB和Java动态代理的区别

  1. Java动态代理只能够对接口进行代理,不能对普通的类进行代理(因为所有生成的代理类的父类为 Proxy,Java 类继承机制不允许多重继承);CGLIB 能够代理普通类;
  2. Java动态代理使用Java原生的反射 API 进行操作,在生成类上比较高效;CGLIB 使用 ASM 框架直接对字节码进行操作,在类的执行过程中比较高

CGLIB相关的文章:

  • http://jnb.ociweb.com/jnb/jnbNov2005.html
  • http://www.iteye.com/topic/799827
  • http://mydailyjava.blogspot.kr/2013/11/cglib-missing-manual.html
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值