Contents

CGLIB 简介

1. 概述

在本文中,我们将研究*cglib (代码生成库)库。它是许多 Java 框架(如HibernateSpring* )中使用的字节检测库。字节码工具允许在程序的编译阶段之后操作或创建类。

2. Maven依赖

要在您的项目中使用cglib,只需添加一个 Maven 依赖项(可以在此处 找到最新版本):

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

3. CGLIB

Java 中的类在运行时动态加载。Cglib正在使用 Java 语言的这一特性,使向已经运行的 Java 程序添加新类成为可能。

Hibernate使用 cglib 来生成动态代理。例如,它不会返回存储在数据库中的完整对象,但会返回存储类的检测版本,该版本会根据需要从数据库中延迟加载值。

流行的模拟框架,如Mockito,使用cglib来模拟方法。模拟是一个检测类,其中方法被空实现替换。

我们将研究cglib 中最有用的构造。

4. 使用cglib实现代理

假设我们有一个PersonService类,它有两个方法:

public class PersonService {
    public String sayHello(String name) {
        return "Hello " + name;
    }
    public Integer lengthOfName(String name) {
        return name.length();
    }
}

请注意,第一个方法返回String,第二个方法返回Integer

4.1. 返回相同的值

我们想创建一个简单的代理类来拦截对sayHello()方法的调用。Enhancer 类允许我们通过使用Enhancer类中的setSuperclass()方法动态扩展PersonService类来创建代理:

Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(PersonService.class);
enhancer.setCallback((FixedValue) () -> "Hello Tom!");
PersonService proxy = (PersonService) enhancer.create();
String res = proxy.sayHello(null);
assertEquals("Hello Tom!", res);

FixedValue 是一个回调接口,它简单地从代理方法返回值。在代理上执行*sayHello()*方法会返回代理方法中指定的值。

4.2. 根据方法签名返回值

我们的代理的第一个版本有一些缺点,因为我们无法决定代理应该拦截哪个方法,以及应该从超类调用哪个方法。我们可以使用*MethodInterceptor * 接口来拦截对代理的所有调用,并决定是要进行特定调用还是执行超类中的方法:

Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(PersonService.class);
enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> {
    if (method.getDeclaringClass() != Object.class && method.getReturnType() == String.class) {
        return "Hello Tom!";
    } else {
        return proxy.invokeSuper(obj, args);
    }
});
PersonService proxy = (PersonService) enhancer.create();
assertEquals("Hello Tom!", proxy.sayHello(null));
int lengthOfName = proxy.lengthOfName("Mary");
 
assertEquals(4, lengthOfName);

在本例中,当方法签名不是来自Object类时,我们将拦截所有调用,这意味着不会拦截toString()hashCode()方法。除此之外,我们只拦截来自返回StringPersonService的方法。对lengthOfName()方法的调用不会被拦截,因为它的返回类型是Integer

5. BeanGenerator

cglib中另一个有用的构造是BeanGenerator 类。它允许我们动态创建 bean 并使用 setter 和 getter 方法添加字段。代码生成工具可以使用它来生成简单的 POJO 对象:

BeanGenerator beanGenerator = new BeanGenerator();
beanGenerator.addProperty("name", String.class);
Object myBean = beanGenerator.create();
Method setter = myBean.getClass().getMethod("setName", String.class);
setter.invoke(myBean, "some string value set by a cglib");
Method getter = myBean.getClass().getMethod("getName");
assertEquals("some string value set by a cglib", getter.invoke(myBean));

6. Mixin

mixin是一种允许将多个对象组合成一个的结构。我们可以包含几个类的行为并将该行为公开为单个类或接口。cglib Mixins 允许将多个对象组合成一个对象。但是,为了做到这一点,mixin 中包含的所有对象都必须由接口支持。

假设我们要创建两个接口的混合。我们需要定义接口及其实现:

public interface Interface1 {
    String first();
}
public interface Interface2 {
    String second();
}
public class Class1 implements Interface1 {
    @Override
    public String first() {
        return "first behaviour";
    }
}
public class Class2 implements Interface2 {
    @Override
    public String second() {
        return "second behaviour";
    }
}

要组合Interface1Interface2的实现,我们需要创建一个扩展它们的接口:

public interface MixinInterface extends Interface1, Interface2 { }

通过使用Mixin 类的create()方法,我们可以将Class1Class2的行为包含到MixinInterface 中:

Mixin mixin = Mixin.create(
  new Class[]{ Interface1.class, Interface2.class, MixinInterface.class },
  new Object[]{ new Class1(), new Class2() }
);
MixinInterface mixinDelegate = (MixinInterface) mixin;
assertEquals("first behaviour", mixinDelegate.first());
assertEquals("second behaviour", mixinDelegate.second());

调用mixinDelegate上的方法将调用Class1Class2 的实现。