CGLIB 简介
1. 概述
在本文中,我们将研究*cglib (代码生成库)库。它是许多 Java 框架(如Hibernate或Spring* )中使用的字节检测库。字节码工具允许在程序的编译阶段之后操作或创建类。
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()方法。除此之外,我们只拦截来自返回String的PersonService的方法。对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";
}
}
要组合Interface1和Interface2的实现,我们需要创建一个扩展它们的接口:
public interface MixinInterface extends Interface1, Interface2 { }
通过使用Mixin 类的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 的实现。