简述CGLIB常用API

2020-06-23 16:23:56 浏览数 (1)

CGLIB简介

CGLIB,即Code Generation Library,是一个强大的、高性能的代码生成库。其被广泛应用于AOP框架(例如Spring)中,用以提供方法拦截操作。Hibernate作为一个比较受欢迎的ORM框架,同样使用CGLIB来代理单端(多对一和一对一)关联(延迟提取集合使用的另一种机制)。CGLIB作为一个开源项目,其代码托管在github,地址为:https://github.com/cglib/cglib

CGLIB的github简介:CGLIB - 字节码生成库,是用于生成和转换Java字节码的高级API。它被AOP、测试、数据访问框架用于生成动态代理对象和拦截字段访问。(原文:cglib - Byte Code Generation Library is high level API to generate and transform Java byte code. It is used by AOP, testing, data access frameworks to generate dynamic proxy objects and intercept field access.)

CGLIB提供两种类型的JAR包:

  • cglib-nodep-x.x.x.jar:使用nodep包不需要关联ASM的jar包,也就是jar包内部包含ASM的类库。
  • cglib-x.x.x.jar:使用此jar包需要另外提供ASM的jar包,否则运行时报错,建议选用不包含ASM类库的jar包,可以方便控制ASM的。

本文中使用的CGLIB依赖为:

代码语言:javascript复制
        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib-nodep</artifactId>
            <version>3.2.10</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.22</version>
        </dependency>

CGLIB基本原理

  • 基本原理:动态生成一个要代理类的子类(被代理的类作为继承的父类),子类重写要代理的类的所有不是final的方法。在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。它比使用Java反射的JDK动态代理要快,因为它采用了整形变量建立了方法索引。
  • 底层实现:使用字节码处理框架ASM,用于转换字节码并生成新的类。不鼓励直接使用ASM,因为它要求必须对JVM内部结构包括class文件的格式和JVM指令集都很熟悉,否则一旦出现错误将会是JVM崩溃级别的异常
  • 劣势:对于final方法或者final的类,无法进行代理。

CGLIB的包结构

  • net.sf.cglib.core:底层字节码处理类,它们大部分与ASM有关系,在使用者角度来看不需要过多关注此包。
  • net.sf.cglib.transform:编译期或运行期类和类文件的转换。
  • net.sf.cglib.proxy:实现创建代理和方法拦截器的类。
  • net.sf.cglib.reflect:反射相关工具类。
  • net.sf.cglib.util:集合排序等工具类。
  • net.sf.cglib.beans:JavaBean相关的工具类。

CGLIB常用API介绍

下面介绍一下CGLIB中常用的API,先建立一个模特接口类和普通模特类:

代码语言:javascript复制
public class SampleClass {

	public String sayHello(String name) {
		return String.format("%s say hello!", name);
	}
}

public interface SampleInterface {

	String sayHello(String name);
}

Enhancer

Enhancer,即(字节码)增强器。它是CGLIB库中最常用的一个类,功能JDK动态代理中引入的Proxy类差不多,但是Enhancer既能够代理普通的Java类,也能够代理接口。Enhancer创建一个被代理对象的子类并且拦截所有的方法调用(包括从Object中继承的toString和hashCode方法)。Enhancer不能够拦截final方法,例如Object.getClass()方法,这是由于final关键字的语义决定的。基于同样的道理,Enhancer也不能对fianl类进行代理操作。这也是Hibernate为什么不能持久化final关键字修饰的类的原因。

代码语言:javascript复制
public class EnhancerClassDemo {

	public static void main(String[] args) throws Exception {
		Enhancer enhancer = new Enhancer();
		enhancer.setSuperclass(SampleClass.class);
        //使用FixedValue,拦截返回值,每次返回固定值"Doge say hello!"
		enhancer.setCallback((FixedValue) () -> "Doge say hello!");
		SampleClass sampleClass = (SampleClass) enhancer.create();
		System.out.println(sampleClass.sayHello("throwable-10086"));
		System.out.println(sampleClass.sayHello("throwable-doge"));
		System.out.println(sampleClass.toString());
		System.out.println(sampleClass.getClass());
		System.out.println(sampleClass.hashCode());
	}
}

输出结果:

代码语言:javascript复制
Doge say hello!
Doge say hello!
Doge say hello!
class club.throwable.cglib.SampleClass$$EnhancerByCGLIB$$6f6e7a68
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Number
......

上述代码中,FixedValue用来对所有拦截的方法返回相同的值,从输出我们可以看出来,Enhancer对非final方法test()、toString()、hashCode()进行了拦截,没有对getClass进行拦截。由于hashCode()方法需要返回一个Number,但是我们返回的是一个String,这解释了上面的程序中为什么会抛出异常。

Enhancer3setSuperclass()用来设置父类型,从toString()方法可以看出,使用CGLIB生成的类为被代理类的一个子类,类简写名称为SampleClass$$EnhancerByCGLIB$$e3ea9b7

Enhancer#create(Class[] argumentTypes, Object[] arguments)方法是用来创建增强对象的,其提供了很多不同参数的方法用来匹配被增强类的不同构造方法。我们也可以先使用Enhancer#createClass()来创建字节码(.class),然后用字节码加载完成后的类动态生成增强后的对象。Enhancer中还有其他几个方法名为create的方法,提供不同的参数选择,具体可以自行查阅。

下面再举个例子说明一下使用Enhancer代理接口:

代码语言:javascript复制
public class EnhancerInterfaceDemo {

	public static void main(String[] args) throws Exception {
		Enhancer enhancer = new Enhancer();
		enhancer.setInterfaces(new Class[]{SampleInterface.class});
		enhancer.setCallback((FixedValue) () -> "Doge say hello!");
		SampleInterface sampleInterface = (SampleInterface) enhancer.create();
		System.out.println(sampleInterface.sayHello("throwable-10086"));
		System.out.println(sampleInterface.sayHello("throwable-doge"));
		System.out.println(sampleInterface.toString());
		System.out.println(sampleInterface.getClass());
		System.out.println(sampleInterface.hashCode());
	}
}

输出结果和上一个例子一致。

Callback

Callback,即回调。值得注意的是,它是一个标识接口(空接口,没有任何方法),它的回调时机是生成的代理类的方法被调用的时候。也就是说,生成的代理类的方法被调用的时候,Callback的实现逻辑就会被调用。Enhancer通过setCallback()setCallbacks()设置Callback设置了多个Callback实例将会按照设置的顺序进行回调。CGLIB中提供的Callback的子类有以下几种:

  • NoOp
  • FixedValue
  • InvocationHandler
  • MethodInterceptor
  • Dispatcher
  • LazyLoader

NoOp

NoOp,No Operation,也就是不做任何操作。这个回调实现只是简单地把方法调用委托给了被代理类的原方法(也就是调用原始类的原始方法),不做任何其它的操作,所以不能使用在接口代理。

代码语言:javascript复制
public class NoOpDemo {

	public static void main(String[] args) throws Exception{
		Enhancer enhancer = new Enhancer();
		enhancer.setSuperclass(SampleClass.class);
		enhancer.setCallback(NoOp.INSTANCE);
		SampleClass sampleClass = (SampleClass) enhancer.create();
		System.out.println(sampleClass.sayHello("throwable"));
	}
}

输出结果:

代码语言:javascript复制
throwable say hello!

FixedValue

FixedValue,Fixed Value,即固定值。它提供了一个loadObject()方法,不过这个方法返回的不是代理对象,而是原方法调用想要的结果。也就是说,在这个Callback里面,看不到任何原方法的信息,也就没有调用原方法的逻辑,不管原方法是什么都只会调用loadObject()并返回一个固定结果。需要注意的是,如果loadObject()方法的返回值并不能转换成原方法的返回值类型,那么会抛出类型转换异常(ClassCastException)。

最前面的Enhancer两个例子就是用FixedValue做分析的,这里不再举例。

InvocationHandler

InvocationHandler全类名为net.sf.cglib.proxy.InvocationHandler,它的功能和JDK动态代理中的java.lang.reflect.InvocationHandler类似,提供了一个Object invoke(Object proxy, Method method, Object[] objects)方法。需要注意的是:所有对invoke方法的参数proxy对象的方法调用都会被委托给同一个InvocationHandler,所以可能会导致无限循环(因为invoke中调用的任何原代理类方法,均会重新代理到invoke方法中)。举个简单的例子:

代码语言:javascript复制
public class InvocationHandlerDeadLoopDemo {

	public static void main(String[] args) throws Exception{
		Enhancer enhancer = new Enhancer();
		enhancer.setSuperclass(SampleClass.class);
		enhancer.setCallback(new InvocationHandler() {
			@Override
			public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
				return method.invoke(o, objects);
			}
		});
		SampleClass sampleClass = (SampleClass) enhancer.create();
		System.out.println(sampleClass.sayHello("throwable"));
	}
}

上面的main方法执行后会直接爆栈,因为method#invoke()方法会重新调用InvocationHandler的invoke方法,形成死循环。正确的使用例子如下:

代码语言:javascript复制
public class InvocationHandlerDemo {

	public static void main(String[] args) throws Exception {
		Enhancer enhancer = new Enhancer();
		enhancer.setSuperclass(SampleClass.class);
		enhancer.setCallback(new InvocationHandler() {
			@Override
			public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
				if (!Objects.equals(method.getDeclaringClass(), Object.class) && Objects.equals(String.class, method.getReturnType())) {
					return String.format("%s say hello!", objects);
				}
				return "No one say hello!";
			}
		});
		SampleClass sampleClass = (SampleClass) enhancer.create();
		System.out.println(sampleClass.sayHello("throwable"));
	}
}

输出结果:

代码语言:javascript复制
throwable say hello!

MethodInterceptor

MethodInterceptor,即方法拦截器,这是一个功能很强大的接口,它可以实现类似于AOP编程中的环绕增强(Around Advice)。它只有一个方法public Object intercept(Object obj,java.lang.reflect.Method method,Object[] args,MethodProxy methodProxy) throws Throwable。设置了MethodInterceptor后,代理类的所有方法调用都会转而执行这个接口中的intercept方法而不是原方法。如果需要在intercept方法中执行原方法可以使用参数method基于代理实例obj进行反射调用,但是使用方法代理methodProxy效率会更高(反射调用比正常的方法调用的速度慢很多)。MethodInterceptor的生成效率不高,它的优势在于调用效率,它需要产生不同类型的字节码,并且需要生成一些运行时对象(InvocationHandler就不需要)。注意,在使用MethodProxy调用invokeSuper方法相当于通过方法代理直接调用原类的对应方法,如果调用MethodProxy的invoke会进入死循环导致爆栈,原因跟InvocationHandler差不多。

代码语言:javascript复制
public class MethodInterceptorDemo {

	public static void main(String[] args) throws Exception {
		Enhancer enhancer = new Enhancer();
		enhancer.setSuperclass(SampleClass.class);
		enhancer.setCallback(new MethodInterceptor() {
			@Override
			public Object intercept(Object obj, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
				System.out.println("Before invoking sayHello...");
				Object result = methodProxy.invokeSuper(obj, objects);
				System.out.println("After invoking sayHello...");
				return result;
			}
		});
		SampleClass sampleClass = (SampleClass) enhancer.create();
		System.out.println(sampleClass.sayHello("throwable"));
	}
}

输出结果:

代码语言:javascript复制
Before invoking sayHello...
After invoking sayHello...
throwable say hello!

这个例子就是Spring的AOP中的环绕增强(Around Advice)的简化版,这里没有改变原来的方法的行为,只是在方法调用前和调用后织入额外的逻辑。

Dispatcher

Dispatcher,即分发器,提供一个方法Object loadObject() throws Exception;,同样地返回一个代理对象,这个对象同样可以代理原方法的调用。Dispatcher的loadObject()方法在每次发生对原方法的调用时都会被调用并返回一个代理对象来调用原方法。Dispatcher可以类比为Spring中的Prototype类型。

代码语言:javascript复制
public class DispatcherDemo {

	private static final AtomicInteger COUNTER = new AtomicInteger(0);

	public static void main(String[] args) throws Exception {
		Enhancer enhancer = new Enhancer();
		SampleInterfaceImpl impl = new SampleInterfaceImpl();
		enhancer.setInterfaces(new Class[]{SampleInterface.class});
		enhancer.setCallback(new Dispatcher() {
			@Override
			public Object loadObject() throws Exception {
				COUNTER.incrementAndGet();
				return impl;
			}
		});
		SampleInterface sampleInterface = (SampleInterface) enhancer.create();
		System.out.println(sampleInterface.sayHello("throwable-1"));
		System.out.println(sampleInterface.sayHello("throwable-2"));
		System.out.println(COUNTER.get());
	}

	private static class SampleInterfaceImpl implements SampleInterface{

		public SampleInterfaceImpl(){
			System.out.println("SampleInterfaceImpl init...");
		}

		@Override
		public String sayHello(String name) {
			return "Hello i am SampleInterfaceImpl!";
		}
	}
}

输出结果:

代码语言:javascript复制
SampleInterfaceImpl init...
Hello i am SampleInterfaceImpl!
Hello i am SampleInterfaceImpl!
2

计数器输出为2,印证了每次调用方法都会回调Dispatcher中的实例进行调用。

LazyLoader

LazyLoader,即懒加载器,它只提供了一个方法Object loadObject() throws Exception;,loadObject()方法会在第一次被代理类的方法调用时触发,它返回一个代理类的对象,这个对象会被存储起来然后负责所有被代理类方法的调用,就像它的名字说的那样,一种lazy加载模式。如果被代理类或者代理类的对象的创建比较麻烦,而且不确定它是否会被使用,那么可以选择使用这种lazy模式来延迟生成代理。LazyLoader可以类比为Spring中的Lazy模式的Singleton。

代码语言:javascript复制
public class LazyLoaderDemo {

	private static final AtomicInteger COUNTER = new AtomicInteger(0);

	public static void main(String[] args) throws Exception {
		Enhancer enhancer = new Enhancer();
		SampleInterfaceImpl impl = new SampleInterfaceImpl();
		enhancer.setInterfaces(new Class[]{SampleInterface.class});
		enhancer.setCallback(new LazyLoader() {
			@Override
			public Object loadObject() throws Exception {
				COUNTER.incrementAndGet();
				return impl;
			}
		});
		SampleInterface sampleInterface = (SampleInterface) enhancer.create();
		System.out.println(sampleInterface.sayHello("throwable-1"));
		System.out.println(sampleInterface.sayHello("throwable-2"));
		System.out.println(COUNTER.get());
	}

	private static class SampleInterfaceImpl implements SampleInterface{

		public SampleInterfaceImpl(){
			System.out.println("SampleInterfaceImpl init...");
		}

		@Override
		public String sayHello(String name) {
			return "Hello i am SampleInterfaceImpl!";
		}
	}
}

输出结果:

代码语言:javascript复制
SampleInterfaceImpl init...
Hello i am SampleInterfaceImpl!
Hello i am SampleInterfaceImpl!
1

计数器输出为1,印证了LazyLoader中的实例只回调了1次,这就是懒加载。

BeanCopier

JavaBean属性拷贝器,提供从一个JavaBean实例中拷贝属性到另一个JavaBean实例中,注意类型必须完全匹配属性才能拷贝成功(原始类型和其包装类不属于相同类型)。它还提供了一个net.sf.cglib.core.Converter转换器回调接口让使用者控制拷贝的过程。注意,BeanCopier内部使用了缓存和基于ASM动态生成BeanCopier的子类实现的转换方法中直接使用实例的Getter和Setter方法,拷贝速度极快(BeanCopier属性拷贝比直接的Setter、Getter稍慢,稍慢的原因在于首次需要动态生成BeanCopier的子类,一旦子类生成完成之后就和直接的Setter、Getter效率一致,但是效率远远高于其他使用反射的工具类库)。

代码语言:javascript复制
public class BeanCopierDemo {

	private static final Map<String, BeanCopier> CACHE = new ConcurrentHashMap<>();

	public static void main(String[] args) throws Exception {
		//这里useConverter设置为false,调用copy方法的时候不能传入转换器实例
		BeanCopier beanCopier;
		String key = generateCacheKey(Person.class, Person.class);
		if (CACHE.containsKey(key)) {
			beanCopier = CACHE.get(key);
		} else {
			beanCopier = BeanCopier.create(Person.class, Person.class, false);
			CACHE.put(key, beanCopier);
		}
		Person person = new Person();
		person.setId(10086L);
		person.setName("throwable");
		person.setAge(25);
		Person newPerson = new Person();
		beanCopier.copy(person, newPerson, null); //这里转换器实例要传null
		System.out.println(newPerson);
	}

	private static String generateCacheKey(Class<?> source, Class<?> target) {
		return String.format("%s-%s", source.getName(), target.getName());
	}

	@ToString
	@Data
	private static class Person {

		private Long id;
		private String name;
		private Integer age;
	}
}

输出结果:

代码语言:javascript复制
BeanCopierDemo.Person(id=10086, name=throwable, age=25)

在使用BeanCopier时候最好缓存BeanCopier实例,因为构造BeanCopier实例是一个耗时的操作。

ImmutableBean

ImmutableBean,即不可变的Bean。ImmutableBean允许创建一个原来对象的包装类,这个包装类是不可变的,任何改变底层对象的包装类操作都会抛出IllegalStateException。但是我们可以通过直接操作底层对象来改变包装类对象。这有点类似于Guava中的不可变视图或者JDK中的不可变集合。

代码语言:javascript复制
public class ImmutableBeanDemo {

	public static void main(String[] args) throws Exception {
		Person person = new Person();
		person.setName("throwable");
		Person immutablePerson = (Person) ImmutableBean.create(person);
		System.out.println(immutablePerson.getName());
		person.setName("doge");
		System.out.println(immutablePerson.getName());
		immutablePerson.setName("throwable-doge");
		System.out.println(immutablePerson.getName());
	}

	@Data
	private static class Person {

		private String name;
	}
}

输出结果:

代码语言:javascript复制
throwable
doge
Exception in thread "main" java.lang.IllegalStateException: Bean is immutable
...

BeanGenerator

BeanGenerator,即Bean生成器,使用它能够在运行时动态的创建一个JavaBean。可以直接设置父类,生成的JavaBean就是父类类型的实例。

代码语言:javascript复制
public class BeanGeneratorDemo {

	public static void main(String[] args) throws Exception {
		BeanGenerator beanGenerator = new BeanGenerator();
		beanGenerator.addProperty("name", String.class);
		Object target = beanGenerator.create();
		Method setter = target.getClass().getDeclaredMethod("setName", String.class);
		Method getter = target.getClass().getDeclaredMethod("getName");
		setter.invoke(target, "throwable");
		System.out.println(getter.invoke(target));
	}
}

输出结果:

代码语言:javascript复制
throwable

BulkBean

相比于BeanCopier,BulkBean创建时候依赖于确定的目标类型,Setter和Getter方法名称列表以及参数类型,它将copy的动作拆分为getPropertyValues()setPropertyValues()两个方法,允许自定义处理属性。

代码语言:javascript复制
public class BulkBeanDemo {

	public static void main(String[] args) throws Exception {
		BulkBean bulkBean = BulkBean.create(
				Person.class,
				new String[]{"getName"},
				new String[]{"setName"},
				new Class[]{String.class});
		Person person = new Person();
		person.setName("throwable");
		Object[] propertyValues = bulkBean.getPropertyValues(person);
		System.out.println(Arrays.toString(propertyValues));
		bulkBean.setPropertyValues(person, new Object[]{"doge"});
		System.out.println(person.getName());
	}

	@Data
	private static class Person {

		private String name;
	}
}

输出结果:

代码语言:javascript复制
[throwable]
doge

BeanMap

BeanMap类实现了JDK的java.util.Map接口,将一个JavaBean对象中的所有属性转换为一个String-To-Obejct的Map实例。

代码语言:javascript复制
public class BeanMapDemo {

	public static void main(String[] args) throws Exception{
		Person person = new Person();
		person.setName("throwable");
		BeanMap beanMap = BeanMap.create(person);
		System.out.println(beanMap);
		System.out.println(beanMap.get("name"));
	}

	@Data
	private static class Person {

		private String name;
	}
}

输出结果:

代码语言:javascript复制
{name=throwable}
throwable

KeyFactory

KeyFactory源码中的注释是:Generates classes to handle multi-valued keys, for use in things such as Maps and Sets. Code for equals and hashCode methods follow the the rules laid out in Effective Java by Joshua Bloch.(翻译一下:通过生成类来处理多值键,以便在诸如Map和集合之类的东西中使用。equals和hashCode方法的代码遵循Joshua Bloch在《Effective Java》中列出的规则)。

什么叫multi-valued keys?

就是有多个键的组合,一起作为一个Key。

比如a b c是一个组合,一起作为一个key,2 3也可以是作为一个key。

KeyFactory就是用来生成这样一组Key的,通过两组的equals,hashCode等方法判断是否为同一组key的场景。为了描述Key的组合,需要定义一个接口,仅提供一个方法,叫做newInstance(),且返回值为Object,这个是使用KeyFactory的要求。

代码语言:javascript复制
public class KeyFactoryDemo {

	public static void main(String[] args) throws Exception {
		KeyFactoryInterface keyFactoryInterface1 = (KeyFactoryInterface) KeyFactory.create(KeyFactoryInterface.class);
		KeyFactoryInterface keyFactoryInterface2 = (KeyFactoryInterface) KeyFactory.create(KeyFactoryInterface.class);
		System.out.println(keyFactoryInterface1 == keyFactoryInterface2);
		System.out.println(keyFactoryInterface1.equals(keyFactoryInterface2));
		Object key1 = keyFactoryInterface1.newInstance(1, "doge");
		Object key2 = keyFactoryInterface1.newInstance(1, "doge");
		System.out.println(key1.equals(key2));
		key2 = keyFactoryInterface1.newInstance(1, "doge10086");
		System.out.println(key1.equals(key2));
	}

	interface KeyFactoryInterface {

		Object newInstance(Integer a, String b);
	}
}

输出结果:

代码语言:javascript复制
false
true
true
false

Mixin

Mixin能够让我们将多个接口的多个实现合并到同一个接口的单个实现。

代码语言:javascript复制
public class MixinDemo {

	interface InterfaceFirst {

		String first();
	}

	interface InterfaceSecond {

		String second();
	}

	static class ImplFirst implements InterfaceFirst {

		@Override
		public String first() {
			return "I am first";
		}
	}

	static class ImplSecond implements InterfaceSecond {

		@Override
		public String second() {
			return "I am second";
		}
	}

	interface MixinImpl extends InterfaceFirst, InterfaceSecond {

	}

	public static void main(String[] args) throws Exception {
		Mixin mixin = Mixin.create(new Class[]{InterfaceFirst.class, InterfaceSecond.class, MixinImpl.class},
				new Object[]{new ImplFirst(), new ImplSecond()});
		MixinImpl mixinImpl = (MixinImpl) mixin;
		System.out.println(mixinImpl.first());
		System.out.println(mixinImpl.second());
	}
}

输出结果:

代码语言:javascript复制
I am first
I am second

StringSwitcher

用来模拟一个String到int类型的Map类型。如果在Java7以后的版本中,类似一个switch块的逻辑。

代码语言:javascript复制
public class StringSwitcherDemo {

	public static void main(String[] args) throws Exception {
		StringSwitcher stringSwitcher = StringSwitcher.create(new String[]{"one", "two"}, new int[]{1, 2}, true);
		System.out.println(stringSwitcher.intValue("one"));
		System.out.println(stringSwitcher.intValue("two"));
	}
}

输出结果:

代码语言:javascript复制
1
2

InterfaceMaker

接口生成器,底层依赖ASM的相关API。

代码语言:javascript复制
public class InterfaceMakerDemo {

	public static void main(String[] args) throws Exception {
		Signature signature = new Signature("foo", Type.DOUBLE_TYPE, new Type[]{Type.INT_TYPE});
		InterfaceMaker interfaceMaker = new InterfaceMaker();
		interfaceMaker.add(signature, new Type[0]);
		Class<?> clazz = interfaceMaker.create();
		Method[] methods = clazz.getMethods();
		System.out.println(methods.length);
		Method foo = methods[0];
		System.out.println(foo.getReturnType());
		System.out.println(Arrays.toString(foo.getParameterTypes()));
	}
}

输出结果:

代码语言:javascript复制
1
double
[int]

上述的InterfaceMaker创建的接口中只含有一个方法,签名为double foo(int)。InterfaceMaker与上面介绍的其他类不同,它依赖ASM中的Type类型。由于接口仅仅只用做在编译时期进行类型检查,因此在一个运行的应用中动态的创建接口没有什么作用。但是InterfaceMaker可以用来自动生成接口代码,为以后的开发做准备。

MethodDelegate

方法代理,个人认为作用不太大,这里仅举例。

代码语言:javascript复制
public class MethodDelegateDemo {

	interface MethodDelegateInterface {

		String getValueFromDelegate();
	}

	static class Delegate {

		private String value;

		public String getValue() {
			return value;
		}

		public Delegate setValue(String value) {
			this.value = value;
			return this;
		}
	}

	public static void main(String[] args) throws Exception {
		Delegate delegate = new Delegate();
		delegate.setValue("throwable");
		MethodDelegate methodDelegate = MethodDelegate.create(delegate, "getValue", MethodDelegateInterface.class);
		MethodDelegateInterface delegateInterface = (MethodDelegateInterface) methodDelegate;
		System.out.println(delegateInterface.getValueFromDelegate());
	}
}

输出结果:

代码语言:javascript复制
throwable

MulticastDelegate

多重代理,个人认为作用不太大,这里仅举例。

代码语言:javascript复制
public class MulticastDelegateDemo {

	public interface DelegateProvider {

		void setValue(String value);
	}

	static class MulticastBean implements DelegateProvider {

		private String value;

		@Override
		public void setValue(String value) {
			this.value = value;
		}

		public String getValue() {
			return value;
		}
	}

	public static void main(String[] args) throws Exception {
		MulticastDelegate multicastDelegate = MulticastDelegate.create(DelegateProvider.class);
		MulticastBean first = new MulticastBean();
		MulticastBean second = new MulticastBean();
		multicastDelegate = multicastDelegate.add(first);
		multicastDelegate = multicastDelegate.add(second);
		DelegateProvider provider = (DelegateProvider) multicastDelegate;
		provider.setValue("throwable");
		System.out.println(first.getValue());
		System.out.println(second.getValue());
	}
}

输出结果:

代码语言:javascript复制
throwable
throwable

ConstructorDelegate

构造器代理,个人认为作用不太大,这里仅举例。

代码语言:javascript复制
public class ConstructorDelegateDemo {

	public interface ConstructorInterface {

		Object newInstance(String value);
	}

	static class ConstructorImpl {

		private String value;

		public ConstructorImpl(String value) {
			this.value = value;
		}

		public String getValue() {
			return value;
		}

		public ConstructorImpl setValue(String value) {
			this.value = value;
			return this;
		}
	}

	public static void main(String[] args) throws Exception {
		ConstructorInterface constructorInterface =
				(ConstructorInterface) ConstructorDelegate.create(ConstructorImpl.class, ConstructorInterface.class);
		ConstructorImpl constructorImpl = (ConstructorImpl) constructorInterface.newInstance("throwable");
		System.out.println(ConstructorImpl.class.isAssignableFrom(constructorImpl.getClass()));
		System.out.println(constructorImpl.getValue());
	}
}

输出结果:

代码语言:javascript复制
true
throwable

ParallelSorter

并行排序器,能够对多个数组同时进行排序,目前实现的算法有归并排序(mergeSort)和快速排序(quickSort),查看源码的时候发现Float和Double类的比较直接用大于或者小于,有可能造成这两个类型的数据排序不准确(应该使用Float或Double的compare方法进行比较)。

代码语言:javascript复制
public class ParallelSorterDemo {

	public static void main(String[] args) throws Exception {
		Integer[][] array = new Integer[][]{
				{4, 3, 9, 0},
				{2, 1, 6, 0}
		};
		ParallelSorter.create(array).quickSort(0);
		for (Integer[] row : array) {
			System.out.println(Arrays.toString(row));
		}
	}
}

输出结果:

代码语言:javascript复制
[0, 3, 4, 9]
[0, 1, 2, 6]

FastClass

FastClass就是对Class对象进行特定的处理,认知上可以理解为索引类,比如通过数组保存method引用,因此FastClass引出了一个index下标的新概念,比如getIndex(String name, Class[] parameterTypes)就是以前的获取method的方法。通过数组存储method,constructor等class信息,从而将原先的反射调用,转化为class.index的直接调用以提高效率,从而体现所谓的FastClass。

代码语言:javascript复制
public class FastClassDemo {

	public static void main(String[] args) throws Exception {
		FastClass fastClass = FastClass.create(SampleClass.class);
		FastMethod fastMethod = fastClass.getMethod("sayHello", new Class[]{String.class});
		SampleClass sampleClass = new SampleClass();
		System.out.println(fastMethod.invoke(sampleClass, new Object[]{"throwable"}));
		System.out.println(fastMethod.getIndex());
	}
}

输出结果:

代码语言:javascript复制
throwable say hello!
0

实际上,在接口或者代理类的方法比较少的时候,使用FastClass进行方法调用有可能比原生反射方法调用Method#invoke()高,但是实际还是需要进行测试和分析,不能盲目一概而论。

小结

本文简单分析了一下CGLIB中的常用API,其实在实现AOP、动态代理和反射调用的时候,最常用的是字节码增强器Enhancer、回调(Callback)以及快类(FastClass),掌握它们的使用方式有利于进行AOP编程以及反射性能创新性提升。

(本文完 r-a-20181216 c-1-d)

本文是Throwable的原创文章,转载请提前告知作者并且标明出处。 博客内容遵循 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 协议 本文永久链接是:https://cloud.tencent.com/developer/article/1650089

0 人点赞