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");
除了 static 和 final 类型的方法,其他所有的方法都会执行上面的代码,打印 Hello cglib
。但是需要注意的是,如果某个方法返回的类型和上面的代理行为不一致就会报错,java.lang.Object 中的方法也是一样。
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()
方法:
这个代码并不会正常返回结果,而是进入无限循环,这是因为这个代理对象的每一个可以被代理的方法都被代理了,在调用被代理了的方法时,会重复进入到 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();
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();
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 {
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 方法会被拦截,其他的方法不会有变动。
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());
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());
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
本文首发于微信公众号
欢迎关注微信公众号
评论