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";
}
}
要组合Interface1
和Interface2
的实现,我们需要创建一个扩展它们的接口:
public interface MixinInterface extends Interface1, Interface2 { }
通过create()
方法,我们可以将Class1
和Class2
的行为包含到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
上的方法将调用Class1
和Class2
的实现。
7. 总结
在这篇文章中,我们探讨了cglib及其最常用的构造。我们使用Enhancer
类创建了一个代理。我们使用了BeanCreator
,最后创建了一个混入,包含了其他类的行为。
Spring框架广泛使用cglib。Spring的一个cglib代理示例是在方法调用上添加安全约束。Spring安全首先通过代理检查(如果通过)是否满足特定的安全检查,只有在验证成功后才委派给实际方法。在这篇文章中,我们看到了如何为自己的目的创建这样的代理。
这些示例和代码片段的实现可以在GitHub项目中找到——这是一个Maven项目,可以直接导入并运行,因为它已经配置好了。