在前面以偏文章中,详解介绍了 cglib 的 Enhancer 及配合使用的各种回调,然后使用 Enhancer 实现了动态代理。
在这篇文章中, 再来介绍一下 cglib 的其他能力。
💡本文基于 OpenJDK11
1. Bean 操作
Java Bean 是最常用的类型,cglib 提供了很多工具来操作这些 Bean,以满足各类需求。
Immutable Bean
ImmutableBean
用来生成不可变对象,如果强行修改,将会抛出 IllegalStateException
。
对原底线所有的改变都会反应到这个不可变对象。也就是可以通过修改原对象来修改这个不可变的对象。
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。
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。
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。
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 方法,以及各个属性的类型来访问对象,而不用通过方法调用的方式来完成。
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。
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 的内部被大量使用。
public interface SampleKeyFactory {
Object newInstance(String first, int second);
}
复制代码
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
已经很常见了,可以将多个对象组合到一个对象中,为了支持这个操作,要求这些对象都是基于接口来实现的。而且还需要声明一个额外的接口来生成组合对象。
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 {
/* 为空 */
}
复制代码
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 以前的版本,这个还有用。
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
可以用来动态生成一个接口。
// 创建接口
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 中反射更快的执行速度。
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 方法生成一个新的对象,就可以通过这个委托对象来调用方法。
public interface BeanDelegate {
String getValueFromDelegate();
}
复制代码
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
可以接收多个对象方法的委托,而且方法可以有参数。
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;
}
}
复制代码
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
,这个方法还可以有任意个参数。
public interface SampleBeanConstructorDelegate {
Object newInstance();
}
复制代码
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 自带的排序工具。
这个排序器可以对多维数组进行排序,而且可以对不同行使用不同的排序规则,可以选择归并排序或者快速排序。
使用方式如下:
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
为例,有四个重载方法,最多可以有四个参数。
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
本文首发于微信公众号
欢迎关注我的微信公账号
评论