1. 概述

本文将探讨cglib(Code Generation Library)库。它是一个字节码插桩库,在Hibernate和Spring等许多Java框架中被广泛应用。字节码插桩可以在程序编译阶段之后对类进行操作或创建。

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的mocking框架使用cglib来模拟方法。mock是一个仪器类,其中的方法被空实现替换。

我们将介绍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类允许我们使用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()方法不会被拦截。此外,我们只拦截PersonService返回String的方法。调用lengthOfName()方法不会被拦截,因为其返回类型是Integer

5. Bean Creator

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. 创建混入

混入是一种构造,允许将多个对象合并成一个。我们可以将几个类的行为组合在一起,作为一个单一的类或接口暴露出来。cglib的混入允许将多个对象合并到一个单独的对象中。然而,为了做到这一点,混入中的所有对象都必须由接口支持。

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

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 { }

通过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的实现。

7. 总结

在这篇文章中,我们探讨了cglib及其最常用的构造。我们使用Enhancer类创建了一个代理。我们使用了BeanCreator,最后创建了一个混入,包含了其他类的行为。

Spring框架广泛使用cglib。Spring的一个cglib代理示例是在方法调用上添加安全约束。Spring安全首先通过代理检查(如果通过)是否满足特定的安全检查,只有在验证成功后才委派给实际方法。在这篇文章中,我们看到了如何为自己的目的创建这样的代理。

这些示例和代码片段的实现可以在GitHub项目中找到——这是一个Maven项目,可以直接导入并运行,因为它已经配置好了。