写点什么

cglib 入门后篇

用户头像
Rayjun
关注
发布于: 2020 年 11 月 10 日
cglib入门后篇

在前面以偏文章中,详解介绍了 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")); // trueSystem.out.println(immutableBean.getValue().equals("hello")); // trueimmutableBean.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")); // trueSystem.out.println(20 == stringSwitcher.intValue("two")); // trueSystem.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); // trueSystem.out.println("foo".equals(iface.getMethods()[0].getName())); // trueSystem.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())); // trueSystem.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

本文首发于微信公众号



欢迎关注我的微信公账号


发布于: 2020 年 11 月 10 日阅读数: 49
用户头像

Rayjun

关注

程序员,王小波死忠粉 2017.10.17 加入

非著名程序员,还在学习如何写代码,公众号同名

评论

发布
暂无评论
cglib入门后篇