在Java动态代理之JDK动态代理中,我们了解了JDK动态代理。它的好处是随JDK自带,不需要额外导入其他包,但是它除了继承自Object类的equals()
, hashCode()
和toString()
方法之外,只能代理实现的接口中的方法,那么如果碰巧我们需要代理的类没有实现接口怎么办呢?这时候就可以使用CGLIB动态代理,它是基于Java的继承机制实现的,因此只要不是final方法或static方法都可以进行代理。
注:CGLIB官方声明已不再维护,因此如果你正在使用JDK17及更高版本可以使用ByteBuddy代替
如何使用cglib
- 先创建一个Dog类作为要被代理的真实类,注意该类并未实现如何接口
public class Dog {
public void run() {
System.out.println("狗狗在跑");
}
public void bark() {
System.out.println("汪汪汪");
}
}
- 创建一个实现了MethodInterceptor接口的类,在intercept方法中写要增强的逻辑
public class MyMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("cglib动态代理执行中...");
return proxy.invokeSuper(obj, args);
}
}
intercept方法接收四个参数,分别是代理对象,被调用的方法,被调用方法的参数和一个用来帮助调用真实方法的MethodProxy对象。
MethodInterceptor的intercept方法和JDK动态代理中的InvocationHandler的invoke方法很类似:对代理对象继承的方法或是实现的接口的方法进行调用时,都会转交给它们来执行。
intercept方法参数也和invoke方法差不多,唯一的区别就是最后的MethodProxy对象。当我们执行它的invokeSuper方法时,会调用父类方法,也就是真实类的方法,这样可以避免导致产生StackOverFlowError的异常。相反,如果使用下面的方式则会产生无限递归的情况:
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
// ERROR
System.out.println(proxy.toString());
// ERROR
proxy.invoke(obj, args);
// ERROR
return method.invoke(obj, args);
}
因为上面的错误写法都是在cglib生成的代理对象上调用方法,因此又会被转交给intercept方法执行,从而无限套娃下去。
- 通过Enhancer类来设置代理对象的父类,代理类需要增强的逻辑(上一步写在MethodInterceptor中的内容)和实际创建代理对象
public class CglibProxyTest {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
// 设置Callback,也就是代理类需要增强的部分
enhancer.setCallback(new MyMethodInterceptor());
// 指定生成的代理对象的父类
enhancer.setSuperclass(Dog.class);
// 生成代理对象
Dog dogProxy = (Dog) enhancer.create();
dogProxy.bark();
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>");
dogProxy.run();
}
}
运行结果如下:
多种多样的Callback
Callback是cglib提供的一个接口,我们希望cglib增强的逻辑需要写在该接口的实现类中。
前面提到的MethodInterceptor也是Callback的一个子接口,它是功能最强大,最灵活的一个接口,但相应的性能也会差一些。
除了MethodInterceptor之外,cglib还提供了多个Callback的子接口,它们没有MethodInterceptor全能,但会更加高效。
下面介绍几个常用的Callback子接口:
LazyLoader
该接口可以将对代理方法的调用转发到另一个对象身上,由另一个对象来负责处理。
还是结合一个例子来看下如何使用LazyLoader吧:
- 定义一个柯基类
public class Corgi extends Dog {
@Override
public void bark() {
super.bark();
System.out.println("我是一只柯基");
}
}
- 实现LazyLoader接口
public class MyLazyLoader implements LazyLoader {
@Override
public Object loadObject() throws Exception {
return new Corgi();
}
}
该接口需要我们重写loadObject方法,当调用代理方法时,cglib会转发到loadObject返回的对象身上
- 设置callback为LazyLoader类型并测试运行
public class CglibProxyTest {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Dog.class);
enhancer.setCallback(new MyLazyLoader());
Dog dog = (Dog) enhancer.create();
dog.bark();
}
}
运行结果如下:
可以看到我们调用bark方法时,转为调用了柯基类的bark方法。
也许有同学会问LazyLoader和继承有什么区别:从功能上说二者区别不大,但LazyLoader只会在调用被代理的方法时才执行loadObject方法去创建对象,比较适合创建对象成本较高且不确定是否会执行方法调用的场景。
FixedValue
public interface FixedValue extends Callback {
Object loadObject() throws Exception;
}
FixedValue接口同样也只有一个loadObject方法,并且方法签名和LazyLoader的loadObject方法一模一样,但二者的功能却完全不同。
如果将Enhancer的callback设置为FixedValue类型,那么每次调用代理方法都会转为调用loadObject方法,因此FixedValue的loadObject方法返回的对象类型一定要与原方法匹配。
NoOp
正如接口名所言,该接口除了调用原方法之外什么都不做。
听起来非常奇怪吧,为什么cglib要多此一举设计NoOp这样一个看起来没有什么作用的接口呢?
其实,它主要是和CallbackFilter配合起来使用的,用来做到方法级别的代理。
回想一下上篇文章讲的JDK动态代理和本文介绍的cglib动态代理,都会对实现的接口或者继承的父类的所有方法进行代理,那如果不想代理所有的方法怎么办呢?
比如上面讲的FixedValue接口要求loadObject的返回值要与真实方法的返回值相匹配,那不同方法的返回值类型是不同的,显然不能将FixedValue应用到所有方法上,这时就需要配合NoOp和CallbackFilter来控制在哪些方法上应用FixedValue。
还是结合例子来看下应该如何使用,假设我们只想代理Dog类的toString方法:
- 定义MyFixedValue类实现FixedValue接口
public class MyFixedValue implements FixedValue {
@Override
public Object loadObject() throws Exception {
System.out.println("fixedValue");
return "这是一只经过cglib代理过的狗狗";
}
}
MyFixedValue的loadObject方法返回一个字符串,与toString的返回值类型相同,但对于Dog类的bark方法和run方法因为返回值类型不同所以不能进行代理。
- 定义一个CallbackFilter的实现类
public class MyCallbackFilter implements CallbackFilter {
@Override
public int accept(Method method) {
if (method.getName().equals("toString")) {
return 0;
}
return 1;
}
}
这里返回的0或1代表使用第几个Callback进行处理,如果调用的方法为toString,则使用第一个Callback进行处理,否则使用第二个。
- 为Enhancer按顺序设置Callback并进行测试
public class CglibProxyTest {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Dog.class);
// 设置CallbackFilter
enhancer.setCallbackFilter(new MyCallbackFilter());
// 这里设置callback的顺序要与CallbackFilter中保持一致
enhancer.setCallbacks(new Callback[]{new MyFixedValue(), NoOp.INSTANCE});
Dog dog = (Dog) enhancer.create();
System.out.println(dog.toString());
dog.bark();
}
}
运行结果如下:
可见此时只有toString方法被代理了,而bark方法并没有被代理。
另外,如果想保存cglib生成的字节码,可以使用下面的语句:
// ./代表保存到当前路径
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "./");