cglib 入门前篇

用户头像
Rayjun
关注
发布于: 3 小时前
cglib 入门前篇

cglib 是一个功能强大、高性能、高质量的字节码操作库,主要用于在运行时扩展 Java 类或者根据接口生成对象。



cglib 在一些开源框架中使用很广泛,比如 Spring, 数据库开源库 Hibernate,以及测试框架 mockito。正是因为 cglib 把脏活累活都干了,这些框架使用才很方便。



这是一个开源的库,cglib 本身的实现基于 asm 库。相比于 asm 库,cglib 的接口更友好,使用起来更简单。



下面介绍 cglib 的主要接口和类以及基于 cglib 的动态代理实现。



💡本文基于 OpenJDK11



1. Enhancer



首先要说到的就是 Enhancer 这个类,这个是 cglib 中使用的最多的类。之前 JDK 中使用反射来实现动态代理时,必须要基于接口来生成动态代理类,而 Enhancer 可以直接基于类来代理类。



Enhancer 可以生成被代理类的子类,并且拦截所有方法的调用,也就是通常所说的增强



需要注意,Enhancer 不能增强构造函数,也不能增强被 final 修饰的,或者被 static 和 final 修饰的方法



如果不想直接生成一个对象,cglib 也可以生成一个 Class 对象,用这个 Class 对象生成对象或者其他操作。



Enhancer 的使用分为两步,传入目标类型,设置回调。支持不同类型回调是 cglib 最强大的地方。

Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(HelloImpl.class);
//enhancer.setInterfaces(HelloImpl.class.getInterfaces()); // 也可以使用接口
enhancer.setCallback(...); // 设置回调



在 Enhancer 创建一个代理类之后所实现的行为要通过这些回调来实现。



常见的的回调类型如下:

  • FixedValue:返回一个固定的值

  • InvocationHandler:增强方法,添加额外的功能

  • MethodInterceptor:与 InvocationHandler 功能类似,但是控制的权限更多

  • LazyLoader:可以延迟加载被代理的对象,而且每个对象只会被创建一次

  • Dispatcher:与 LazyLoader 功能基本相同,但是在获取对象时,每次都会创建不同的对象

  • ProxyRefDispatcher:与 Dispatcher 功能类似,但是会多一个被代理对象的参数

  • NoOp:什么也不做



FixedValue

对于 FixedValue 类型的回调,调用所有的方法都会返回这个固定的值,如果类型不匹配,就会报错。



Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(HelloImpl.class);
enhancer.setCallback(new FixedValue() {
@Override
public Object loadObject() throws Exception {
System.out.println("Hello cglib");
return "Hello cglib";
}
});
HelloImpl proxy = (HelloImpl) enhancer.create();
proxy.sayHello("ray"); // 会打印 Hello cglib



除了 static 和 final 类型的方法,其他所有的方法都会执行上面的代码,打印 Hello cglib。但是需要注意的是,如果某个方法返回的类型和上面的代理行为不一致就会报错,java.lang.Object 中的方法也是一样。



proxy.hashCode(); // java.lang.ClassCastException



InvocationHandler



看到这个是不是很熟悉,这个与 JDK 反射自带的 InvocationHandler 基本一致。



Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(HelloImpl.class);
enhancer.setCallback(new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] objects) throws Throwable {
if (method.getReturnType() == String.class) {
System.out.println("Hello cglib");
return "Hello cglib";
} else {
System.out.println("Invoke other method");
return method.invoke(proxy, objects); // 这里可能会出现无限循环
}
}
});
HelloImpl proxy = (HelloImpl) enhancer.create();
proxy.sayHello("ray");



只想改变其中部分方法的行为,其他的方法还的行为不变,最简单的思路就是如果不是目标方法就会调用本来的实现,假设要调用 hashcode() 方法:



proxy.hashCode();



这个代码并不会正常返回结果,而是进入无限循环,这是因为这个代理对象的每一个可以被代理的方法都被代理了,在调用被代理了的方法时,会重复进入到 InvocationHandler#invoke 这个方法中,然后进入死循环。



解决办法如下。

MethodInterceptor

在 MethodInterceptor 中,有一个 MethodProxy 参数,这个就可以用来执行父类方法。



Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(HelloImpl.class);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] params, MethodProxy methodProxy) throws Throwable {
if (method.getReturnType() == String.class) {
System.out.println("Hello cglib");
return "Hello cglib";
} else {
return methodProxy.invokeSuper(o, params); // 对上面无限问题的改善
}
}
});
HelloImpl proxy = (HelloImpl) enhancer.create();
proxy.sayHello("ray");
proxy.hashCode(); // 这个时候,调用 hashcode 方法就可以正常工作了



LazyLoader

用来延迟加载对象。



在下面这个例子中,使用 LazyLoader 来延迟加载一个 ArrayList 对象。

public class HelloImpl implements Hello {
private ArrayList<String> data;
public ArrayList<String> getData() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(ArrayList.class);
enhancer.setCallback(new LazyLoader() {
@Override
public Object loadObject() throws Exception {
System.out.println("Begin invoke lazyloader");
ArrayList<String> data = new ArrayList<>();
data.add("hello");
data.add("cglib");
System.out.println("End invoke lazyloader");
return data;
}
});
return (ArrayList<String>) enhancer.create();
}
}



HelloImpl helloImpl = new HelloImpl();
ArrayList<String> data = helloImpl.getData(); // 在调用这个方法的时候,ArrayList 不会被创建
System.out.println(data.get(0));
System.out.println(data.get(1)); // 每次调用都使用同一个对象,不会重复创建对象



上面代码的执行结果如下:

Begin invoke lazyloader
End invoke lazyloader
hello
cglib



在执行 data.get(0) 的时候,ArrayList 才会被创建,也就是说只有在被使用的,才会去创建对象,而且每个对象只会被创建一次。



Dispatcher

Dispatcher 与 LazyLoader 的不同之处在于,每次去获取对象的时候都会创建一个新的对象,而不是复用同一个对象。



public class HelloImpl implements Hello {
private ArrayList<String> data;
public ArrayList<String> getDataDispatcher() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(ArrayList.class);
enhancer.setCallback(new Dispatcher() {
@Override
public Object loadObject() throws Exception {
System.out.println("Begin invoke dispatcher");
ArrayList<String> data = new ArrayList<>();
data.add("hello");
data.add("cglib");
System.out.println("End invoke Dispatcher");
return data;
}
});
return (ArrayList<String>) enhancer.create();
}
}



HelloImpl helloimpl = new HelloImpl();
ArrayList<String> data = helloimpl.getDataDispatcher();
System.out.println(data.get(0));
System.out.println(data.get(1));// 每次调用都会获取不同送的对象



上面代理的执行结果如下,每次使用对象都会创建一个新的对象。

Begin invoke dispatcher
End invoke Dispatcher
hello
Begin invoke dispatcher
End invoke Dispatcher
cglib



ProxyRefDispatcher

ProxyRefDispatcher 与 Dispatcher 功能类似,但是多了一个参数,使用这个回调同样要注意无限循环的问题。

Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(HelloImpl.class);
enhancer.setCallback(new ProxyRefDispatcher() {
@Override
public Object loadObject(Object proxy) throws Exception {
System.out.println("Invoke");
return proxy.hashCode(); // 同样可能会导致无限循环
}
});
HelloImpl proxy = (HelloImpl) enhancer.create();
proxy.hashCode(); // 这样调用会无限循环



NoOp

这个回调什么也不做,会完全继承被代理类的功能,所以 NoOp 不能使用接口来创建代理,只能使用类。



Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(HelloImpl.class);
//enhancer.setInterfaces(HelloImpl.class.getInterfaces()); //不能使用接口,只能使用类
enhancer.setCallback(new NoOp() {
});
Hello proxy = (Hello) enhancer.create();
proxy.sayHello("Ray");



这个回调在一些特定的情况下还是挺有用的,看下面的例子。



CallbackFilter

在上面的例子中,都是给 Enhancer 设置了单个回调,其实每个 Enhancer 可以设置多个回调,这里就需要用到 CallbackFilter



比如现在就需要对 sayHello 方法进行处理,其他的方法保持父类的行为就可以,按照这个要求,实现的 CallbackFilter 如下:

public class CallbackHelperImpl extends CallbackHelper {
public CallbackHelperImpl(Class superclass, Class[] interfaces) {
super(superclass, interfaces);
}
@Override
protected Object getCallback(Method method) {
if (method.getName() == "sayHello") { // 对这个方法就行增强,其他的方法不改变
return new FixedValue() {
@Override
public Object loadObject() throws Exception {
System.out.println("Hello cglib");
return "Hello cglib";
}
};
} else { // NoOp 就可以在这种情况下使用
return new NoOp() {
};
}
}
}



Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(HelloImpl.class);
CallbackHelper callbackHelper = new CallbackHelperImpl(HelloImpl.class, null);
enhancer.setCallbackFilter(callbackHelper);
enhancer.setCallbackTypes(callbackHelper.getCallbackTypes());
enhancer.setCallbacks(callbackHelper.getCallbacks());
HelloImpl proxy = (HelloImpl) enhancer.create();
proxy.sayHello("Ray");
proxy.hashCode(); // 其他方法的调用不受影响



调用结果如下,只有 sayHello 方法会被拦截,其他的方法不会有变动。

Hello cglib
1647766367



2. 实现动态代理

上面介绍了 Enhancer 之后,实现动态代理应该就不难了。



InvocationHandler 和 MethodInterceptor 都可以用来实现动态代理,下面是两种实现。



InvocationHandler 实现



public class CGlibHelloProxyInvocationHandler implements InvocationHandler {
private Object target;
public Object bind(Object obj) {
Enhancer enhancer = new Enhancer();
enhancer.setInterfaces(obj.getClass().getInterfaces());
//enhancer.setSuperclass(obj.getClass().getSuperclass());
this.target = obj;
enhancer.setCallback(this);
return enhancer.create(); // 生成代理类
}
@Override
public Object invoke(Object o, Method method, Object[] params) throws Throwable {
long begin = System.currentTimeMillis();
Object result = method.invoke(target, params);
System.out.printf("Invoke time " + (System.currentTimeMillis() - begin) + " ms");
return result;
}
}



使用上面的实现来创建代理并调用方法:

HelloImpl helloImpl = new HelloImpl();
CGlibHelloProxyInvocationHandler helloProxyInvocationHandler = new CGlibHelloProxyInvocationHandler();
Hello proxy = (Hello) helloProxyInvocationHandler.bind(helloImpl);
proxy.sayHello("ray");



这里需要注意,如果使用接口创建代理对象,第一行代码使用 Hello helloImpl = new HelloImpl() 或者 HelloImpl helloImpl = new HelloImpl() 创建对象传入都可以,如果使用是父类创建代理对象,那么只能使用第二种。



MethodInceptor 实现

public class CGlibHelloProxyMethodInterceptor implements MethodInterceptor {
private Object target;
public Object bind(Object obj) {
Enhancer enhancer = new Enhancer();
enhancer.setInterfaces(obj.getClass().getInterfaces());
//enhancer.setSuperclass(obj.getClass());
this.target = obj;
enhancer.setCallback(this);
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] params, MethodProxy methodProxy) throws Throwable {
long begin = System.currentTimeMillis();
Object result = method.invoke(target, params);
System.out.printf("Invoke time " + (System.currentTimeMillis() - begin) + " ms");
return result;
}
}



然后创建代理对象并调用:

HelloImpl hello = new HelloImpl();
CGlibHelloProxyMethodInterceptor cGlibHelloProxy = new CGlibHelloProxyMethodInterceptor();
HelloImpl proxyObject = (HelloImpl) cGlibHelloProxy.bind(hello);
proxyObject.sayHello("ray");



通常来说,使用 MethodInceptor 方式来实现更好,因为可以避免出现上面说到的无限循环的问题。



cglib 相比于 Java 反射实现动态代理的优势就是不受类的限制,可以自由的选择根据接口或者类来生成新的代理对象。



cglib 中的 Proxy



在 JDK 中,动态代理主要由 java.lang.reflect.Proxy 来实现,在 cglib 中,同样也实现了 Proxy,功能于 JDK 中的功能基本一致,其中用到的 InvocationHandler 就是前面介绍的回调。



这样做是为了 JDK1.3 以前的版本也能使用动态代理的功能。

3. 总结



这篇文章主要介绍了 cglib 的如果通过 Enhancer 去生成代理类,可以同时支持接口子类的两种方式同时也介绍了通过 Enhancer 来实现动态代理。



cglib 的能力远不止这些,下篇文章将介绍 cglib 的其他功能。

REF

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



文 / Rayjun

本文首发于微信公众号



欢迎关注微信公众号



发布于: 3 小时前 阅读数: 4
用户头像

Rayjun

关注

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

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

评论

发布
暂无评论
cglib 入门前篇