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

news2024/12/23 15:01:03

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/46584.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【DL with Pytorch】第 4 章 : 卷积神经网络

&#x1f50e;大家好&#xff0c;我是Sonhhxg_柒&#xff0c;希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流&#x1f50e; &#x1f4dd;个人主页&#xff0d;Sonhhxg_柒的博客_CSDN博客 &#x1f4c3; &#x1f381;欢迎各位→点赞…

nablet Elements released处理视频的组件

nablet Elements released处理视频的组件 mediaEngine-一个转码工厂&#xff0c;为视频工作流从贡献到分发提供动力。 HeightScreen-AI驱动的工具&#xff0c;用于将视频转换为垂直屏幕&#xff0c;自动选择感兴趣的区域。 Shrynk-AI驱动的解决方案&#xff0c;可自动完成高亮编…

电商的噩梦:全国快递网点停摆65%?

我是卢松松&#xff0c;点点上面的头像&#xff0c;欢迎关注我哦&#xff01; 今年应该是所有行业最难最艰苦的一年&#xff0c;据龚文祥爆料&#xff0c;全国当前有三分之一的地区停发快递&#xff0c;电商到了最艰难的时刻&#xff0c;所有电商交易立即下降30%。而知名数码博…

Java中使用JDBC操作Postgresql

目录 在Rocky Linux上安装postgresql 用IntelliJ创建JavaFx项目 画一个表格 建立数据库访问 在Rocky Linux上安装postgresql Rocky的仓库中自带了postgresql安装包&#xff0c;我们直接用dnf安装即可&#xff1a; dnf install postgresql-server -y 安装好之后&#xff0c;…

大数据下一代变革之必研究数据湖技术Hudi原理实战双管齐下-下

文章目录集成Spark开发Spark编程读写示例DeltaStreamer集成Flink环境准备sql-clent使用启动插入数据流式读取Bucket索引Hudi Catalog集成Spark开发 Spark编程读写示例 通过IDE如Idea编程实质上和前面的spark-shell和spark-sql相似&#xff0c;其他都是Spark编程的知识&#x…

自定义对象_JavaScript

自定义对象_JavaScript 学习路线&#xff1a;JavaScript基础语法&#xff08;输出语句&#xff09;->JavaScript基础语法&#xff08;变量&#xff09;->JavaScript基础语法&#xff08;数据类型&#xff09;->JavaScript基础语法&#xff08;运算符&#xff09;->…

IB心理学如何记住大量的内容?

将在这篇文章中探讨一下怎样在IB心理学中搞定studies。 一. 为什么要记studies&#xff1f; 学IB心理学的同学们都知道&#xff0c;这个课程很大一部分就是学习&#xff0c;理解&#xff0c;并在考试中熟练应用心理学中的研究&#xff08;studies&#xff09;。为了在考试中应答…

CAS:1516551-46-4,BCN-琥珀酰亚胺酯,BCN-NHS点击试剂供应

一&#xff1a;产品描述 1、名称&#xff1a; BCN-NHS BCN-活性酯 BCN-NHS 酯 丙烷环辛炔-活性酯 BCN-琥珀酰亚胺酯 BCN-succinimidylester 2、CAS编号&#xff1a;1516551-46-4 3、质量控制&#xff1a;95% 4、分子量&#xff1a;291.30 5、分子式&#xff1a;C15H…

Redis哨兵模式详解

文章目录一、概念二、原理三、多哨兵模式的工作过程四、客户端的工作过程五、应用1、配置sentinel哨兵&#xff08;单个&#xff09;2、启动哨兵3、模拟主服务器意外宕机情况一、概念 在 Redis 主从复制模式中&#xff0c;因为系统不具备自动恢复的功能&#xff0c;所以当主服…

传奇战盟GOM引擎登录器配置教程

战盟GOM引擎配置器教程&#xff0c;先到战盟官方网站下载登录器配置器&#xff0c;下载好后按下面说明使用。战盟GOM登录器教程大分类目录引导说明 一、解压配置器文件包后&#xff0c;打开KEY文件夹然后选择KEY 二、复制你选择好的 Key.Lic 复制到 战盟GOM配置器 相同路径文件…

外汇天眼:乐天证券扩大了交易工具!进入数字资产市场!

根据一份商业声明&#xff0c;这些从今天开始可以访问的新成员包括在纽约证券交易所、纳斯达克和上海证券交易所上市的公司。 随着客户对接触受监管市场的需求不断增长&#xff0c;该经纪商将其产品范围扩大到涵盖货币、商品、股票和指数。新股票的加入不仅有助于提高其客户的交…

网络面试-0x11 TCP为什么需要三次握手和四次挥手?

网络面试-0x11 TCP为什么需要三次握手和四次挥手&#xff1f; 一、三次握手 三次握手[three-way-handshake]&#xff1a;客户端和服务器总共发送3个包&#xff0c;以建立TCP连接。 什么是连接了&#xff1f;主要作用是什么&#xff1f; 连接&#xff1a; 主要作用&#xff1a;为…

基于Redis实现特殊的消息队列

特殊场景的消息队列 消息队列使用比较多的产品kafka&#xff0c;在各个领域都发挥了很大的作用&#xff0c;但是在以下的几种场景是无法满足需求。 场景 消息重复概率比较高时&#xff0c;需要对重复消息进行合并处理避免浪费有限的资源&#xff0c;减少延迟需要根据业务自定…

正大国际期货:投资外盘期货如何用布林线判断走势?

外盘期货的投资者越来越多&#xff0c;每个投资者判断大盘走势的方式也不尽相同&#xff0c;那么今天正大IxxxuanI就来简单的和大家说说外盘期货投资如何用布林线判断走势吧。 1、当布林线多条轨道向上或是向下运行时&#xff0c;这能非常有效的说明走势强劲程度。投资者应牢牢…

接触非线性分析不收敛? 写给ABAQUS初学者的N个经验

接触&#xff0c;在仿真分析中&#xff0c;绝对是个看似青铜实则王者级别的难题。一些通用的解决办法&#xff0c;在帮助文件的Interaction → Contact Difficulties and Diagnostics中找到&#xff0c;例如初始接触状况、穿透、突然分离造成的局部不稳定等等。 但是确实没有一…

slam定位学习笔记(七)-g2o学习

主要学习的是这篇文章&#xff0c;但大佬并没有在文章里面仔细的讲g2o&#xff0c;所以我在网上找了这几篇介绍g2o的文章&#xff0c;讲的十分详细&#xff0c;对入门十分友好&#xff1a;文章一、文章二、文章三&#xff0c;这三篇都是一个作者写的&#xff0c;主要是针对编程…

第五届“强网”拟态防御国际精英挑战赛——特邀战队篇

第五届“强网”拟态防御国际精英挑战赛即将在南京隆重开赛&#xff01;本届大赛面向全球顶尖CTF战队&#xff0c;在创新应用场景与技术的基础上&#xff0c;拓展升级赛道&#xff0c;全面覆盖典型网络设备。大赛汇集国内外60支精英战队&#xff0c;参赛阵容、数量再创新高。 本…

35岁了,月薪还不足2W,辞职又怕找不到工作,该何去何从?

今天看到网上有人在吐槽&#xff1a;“马上就35岁了&#xff0c;月薪还不到2W&#xff0c;公司发展缓慢&#xff0c;想离职又怕找不到工作&#xff0c;不知道怎么办&#xff1f;” 单看月薪两万&#xff0c;好像也不少&#xff0c;不过收入跟行业和地域也有很大关系。薪资&…

JWT详解

1、什么是token&#xff0c;解决了什么问题&#xff1f; token 就是常说的 “令牌”&#xff0c;本质上是全局唯一的字符串&#xff0c;用来唯一识别一个客户端&#xff0c;解决了session依赖单个web服务器的问题。单体应用时&#xff0c;用户的会话信息保存在session中&#…

如何在视频中加水印?分享这些实用的加水印方法给你

视频要怎么添加水印呢&#xff1f;在我们的日常生活中&#xff0c;短视频已经离不开我们的视野了&#xff0c;我们经常通过短视频来放松、查找资料或者是丰富知识。同样的&#xff0c;我们也可以通过自己的剪辑并发布一些视频到各个平台上获取流量。那么在这个过程中&#xff0…