cglib入门后篇

在前面一篇文章中,详解介绍了 cglib 的 Enhancer 及配合使用的各种回调,然后使用 Enhancer 实现了动态代理。

在这篇文章中, 再来介绍一下 cglib 的其他能力。

本文基于 OpenJDK11

1. Bean 操作

Java Bean 是最常用的类型,cglib 提供了很多工具来操作这些 Bean,以满足各类需求。

Immutable Bean

ImmutableBean 用来生成不可变对象,如果强行修改,将会抛出 IllegalStateException

对原底线所有的改变都会反应到这个不可变对象。也就是可以通过修改原对象来修改这个不可变的对象。

1
2
3
4
5
6
7
HelloImpl helloImpl = new HelloImpl();
helloImpl.setValue("ray");
HelloImpl immutableBean = (HelloImpl) ImmutableBean.create(helloImpl);
helloImpl.setValue("hello");
System.out.println(helloImpl.getValue().equals("hello")); // true
System.out.println(immutableBean.getValue().equals("hello")); // true
immutableBean.setValue("Hello ray"); //java.lang.IllegalStateException: Bean is immutable

Bean Generator

BeanGenerator 在运行时创建一个新的 Bean。在使用第三方库时,不确定类型,就可以使用这种方式来动态创建 Bean。

1
2
3
4
5
6
7
8
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");
System.out.println("Hello cglib!".equals(getter.invoke(myBean))); // true

Bean Copier

BeanCopier 用来复制对象,可以复制同类型的 bean,也可以复制不同类型的 bean。

1
2
3
4
5
6
BeanCopier copier = BeanCopier.create(HelloImpl.class, HelloImpl.class, false); // 这里也可以是在不同的 bean 之间复制
HelloImpl bean = new HelloImpl();
bean.setValue("Hello cglib!");
HelloImpl otherBean = new HelloImpl();
copier.copy(bean, otherBean, null);
System.out.println("Hello cglib!".equals(otherBean.getValue())); // true

而且还可以通过传入 Converter 参数来实现自定义拷贝规则,需要把 BeanCopier.create 的第三个参数设置为 true。

1
2
3
4
5
6
7
8
9
10
11
BeanCopier copier = BeanCopier.create(HelloImpl.class, HelloImpl.class, true); // 这里也可以是在不同的 bean 之间复制
HelloImpl bean = new HelloImpl();
bean.setValue("Hello cglib!");
HelloImpl otherBean = new HelloImpl();
copier.copy(bean, otherBean, new Converter() {
@Override
public Object convert(Object value, Class target, Object context) {
return value;
}
});
System.out.println("Hello cglib!".equals(otherBean.getValue())); // true

Bulk Bean

BulkBean 可以通过传数数组的方式来传入 Bean 的 get 和 set 方法,以及各个属性的类型来访问对象,而不用通过方法调用的方式来完成。

1
2
3
4
5
6
7
8
9
10
BulkBean bulkBean = BulkBean.create(HelloImpl.class,
new String[]{"getValue"},
new String[]{"setValue"},
new Class[]{String.class});
HelloImpl bean = new HelloImpl();
bean.setValue("Hello world!");
System.out.println(1 == bulkBean.getPropertyValues(bean).length);
System.out.println("Hello world!".equals(bulkBean.getPropertyValues(bean)[0]));
bulkBean.setPropertyValues(bean, new Object[] {"Hello cglib!"});
System.out.println("Hello cglib!".equals(bean.getValue())); // true

Bean Map

BeanMap 实现了 java.util.Map,可以把一个 Java 对象转化成 String-to-Object 键值对的 Map。

1
2
3
4
HelloImpl bean = new HelloImpl();
BeanMap map = BeanMap.create(bean);
bean.setValue("Hello cglib!");
System.out.println("Hello cglib!".equals(map.get("value")));

2. 黑魔法

cglib 中提供了很多的工具类,可以用来实现一些不常用,但有时候又很重要的功能。

Key Factory

Key Factory 可以用于动态创建对象,这个工厂方法只需要包含 newInstance() 方法,返回一个 Object,通过这种方法生成的对象动态实现了 equals 和 hashcode 方法,可以保证相同参数构造出来的对象是同一个。

生成的对象可以用作 Map 的 key。

这个工具类在 cglib 的内部被大量使用。

1
2
3
public interface SampleKeyFactory {
Object newInstance(String first, int second);
}

1
2
3
4
5
SampleKeyFactory keyFactory = (SampleKeyFactory) KeyFactory.create(SampleKeyFactory.class);
Object key = keyFactory.newInstance("foo", 42);
Map<Object, String> map = new HashMap<Object, String>();
map.put(key, "Hello cglib!");
System.out.println("Hello cglib!".equals(map.get(keyFactory.newInstance("foo", 42)))); // 如果传入的参数不变,每次创建的对象是一样的

Mixin

在 Scala 中, Mixin 已经很常见了,可以将多个对象组合到一个对象中,为了支持这个操作,要求这些对象都是基于接口来实现的。而且还需要声明一个额外的接口来生成组合对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public interface Interface2 {
String second();
}

public class Class1 implements Interface1 {
@Override
public String first() {
return "first";
}
}

public class Class2 implements Interface2 {
@Override
public String second() {
return "second";
}
}

// 额外声明的接口
public interface MixinInterface extends Interface1, Interface2 {
/* 为空 */
}

1
2
3
4
Mixin mixin = Mixin.create(new Class[]{Interface1.class, Interface2.class, MixinInterface.class}, new Object[]{new Class1(), new Class2()});
MixinInterface mixinDelegate = (MixinInterface) mixin;
System.out.println("first".equals(mixinDelegate.first()));
System.out.println("second".equals(mixinDelegate.second()));

但 Mixin 这个功能模拟的不彻底,因为为了组合对象,还需要声明一个单独的接口,既然如此,为什么不直接使用 Java 的方法来实现。

String Switcher

这个工具类用来模拟 Java 中的 switch,并且可以接收 String,这点在 Java7 以后就支持了,对于 Java7 以前的版本,这个还有用。

1
2
3
4
5
6
String[] strings = new String[]{"one", "two"};
int[] values = new int[]{10, 20};
StringSwitcher stringSwitcher = StringSwitcher.create(strings, values, true);
System.out.println(10 == stringSwitcher.intValue("one")); // true
System.out.println(20 == stringSwitcher.intValue("two")); // true
System.out.println(-1 == stringSwitcher.intValue("three")); // true

Interface Maker

InterfaceMaker 可以用来动态生成一个接口

1
2
3
4
5
6
7
8
// 创建接口
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(); // 获得接口
System.out.println(1 == iface.getMethods().length); // true
System.out.println("foo".equals(iface.getMethods()[0].getName())); // true
System.out.println(double.class == iface.getMethods()[0].getReturnType()); // true

Fast Class and Fast Members

FastClass 可以提供比 Java 中反射更快的执行速度。

1
2
3
4
5
FastClass fastClass = FastClass.create(HelloImpl.class);
FastMethod fastMethod = fastClass.getMethod(HelloImpl.class.getMethod("getValue"));
HelloImpl myBean = new HelloImpl();
myBean.setValue("Hello cglib!");
System.out.println("Hello cglib!".equals(fastMethod.invoke(myBean, new Object[0]))); // true

除了上面的 FastMethod,还可以使用 FastConstructor,但没有 FastField,这个好理解,对于一个属性,自然就不需要加速了。

Java 中的反射是通过 JNI 本地调用来执行反射的代码,而 FastClass 则是直接生成字节码文件被 JVM 执行。

但是在 Java1.5 之后,反射代码执行的性能已经提升了不少,在新版本的 JVM 上,就没必要使用 FastClass 了,但是在老版本的 JVM 上,对性能的提升还是很可观的。

3.方法委托

方法委托这个概念来自于 C# 中,类似 C++ 中的函数指针,可以运行时改变委托的值。cglib 中也提供了相应的实现。

Method Delegate

Method Delagate 允许构造 C# 风格的方法委托,新建一个委托接口,然后将 HelloImpl 实例和 getValue 方法生成一个新的对象,就可以通过这个委托对象来调用方法。

1
2
3
public interface BeanDelegate {
String getValueFromDelegate();
}

1
2
3
4
5
HelloImpl bean = new HelloImpl();
bean.setValue("Hello cglib!");
BeanDelegate delegate = (BeanDelegate) MethodDelegate.create(
bean, "getValue", BeanDelegate.class);
System.out.println("Hello cglib!".equals(delegate.getValueFromDelegate())); // true

在使用 MethodDelegate.create 工厂方法时,需要注意,它只能代理没有参数的方法

Multicast Delegate

MulticastDelegate 可以接收多个对象方法的委托,而且方法可以有参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
public interface DelegatationProvider {
void setValue(String value);
}

public class SimpleMulticastBean implements DelegatationProvider {
private String value;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}

1
2
3
4
5
6
7
8
9
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 cglib!");
System.out.println("Hello cglib!".equals(first.getValue())); // true
System.out.println("Hello cglib!".equals(second.getValue())); // true

MulticastDelegate 要求提供委托的接口只能有一个方法,这样在实现对第三方库进行委托代理的时候就会很困难,因为要创建很多委托代理接口。

而且还有一点,如果这个被委托的方法有返回值,只能接收最后一个对象的返回值,其他的返回值都会丢失。

Constructor Delegate

构造函数的委托相对简单,只需要定义一个有 newInstance() 方法的接口,返回值是 Object,这个方法还可以有任意个参数。

1
2
3
public interface SampleBeanConstructorDelegate {
Object newInstance();
}

1
2
3
4
SampleBeanConstructorDelegate constructorDelegate = (SampleBeanConstructorDelegate) ConstructorDelegate.create(
HelloImpl.class, SampleBeanConstructorDelegate.class);
HelloImpl bean = (HelloImpl) constructorDelegate.newInstance();
System.out.print(HelloImpl.class.isAssignableFrom(bean.getClass()));

4. 其他

Parallel Sorter

cglib 中甚至提供了一个排序器,号称效率要超过 Java 自带的排序工具。

这个排序器可以对多维数组进行排序,而且可以对不同行使用不同的排序规则,可以选择归并排序或者快速排序。

使用方式如下:

1
2
3
4
5
6
7
8
9
10
11
12
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) {
System.out.println(former < val); // true
former = val;
}
}

mergeSort 为例,有四个重载方法,最多可以有四个参数。

1
2
3
4
public void mergeSort(int index, int lo, int hi, Comparator cmp) {
chooseComparer(index, cmp);
super.mergeSort(lo, hi - 1);
}

第一个表示从哪一列开始使用归并排序,第二个表示从哪一行(包括)开始,第三个表示截止到哪一行(不包含),第四个参数是自定义的比较器。

看起来就不怎么好用。实际上,Java 自带的排序已经很好用了,这个排序工具不推荐使用。

而且它还有一个明显的 bug,如果把上面的 Integer[][] 换成 int[][],就会报 java.lang.ClassCastException 异常。

REF

[1] https://dzone.com/articles/cglib-missing-manual

文 / Rayjun

© 2020 Rayjun    PowerBy Hexo