一、前言
CGlib,即Code Generation Library,是Java中广泛使用的动态代理类库,尤其是AOP框架。相比于JDK动态代理,它不要求被代理的类实现一个或多个接口,它的底层通过一个小而快的字节码处理框架ASM来转换字节码生成新的类,而且正是因为它直接生成字节码,所以效率比JDK动态代理要高。
二、CGlib生成动态代理类的方式
CGlib提供了好几种产生动态代理类的方法,基本都是是利用net.sf.cglib.proxy.Enhancer这个类。首先创建要代理的类:
public class Student {
String name;
public Student() {}
public Student(String n) {
name = n;
}
public void setName(String name) {
this.name = name;
}
public void printName() {
System.out.println("Student name is: " + name);
}
public final void finalTest() {
System.out.println("This is a final method");
}
}
方法一:直接用Enhancer的静态方法生成
// 定义一个实现了MethodInterceptor接口的回调类,类似JDK动态代理的InvocationHandler
class CGLibProxy implements MethodInterceptor {
@SuppressWarnings("unchecked")
public <T> T getProxy(Class<T> cls) {
// 传入两个参数分别代表要被代理类和代理运作时的回调类,就类似给一个按钮绑定listener一样
return (T) Enhancer.create(cls, this);
}
// 实现回调处理方法,类似InvocationHandler中的invoke方法
@Override
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
System.out.println("~~~ Before Log ~~~");
System.out.println("calling method: " + proxy.getSignature().getName());
/*
* proxy是代理方法,所以这里必须要通过proxy.invokeSuper(obj, args)来调用原来Student类
* 中的方法,如果这里是proxy.invoke(obj, args),则调用的还是proxy方法本身,从而导致无限
* 递归,注意一定不要调用错误了
*/
Object result = proxy.invokeSuper(obj, args);
System.out.println("~~~ After Log ~~~");
return result;
}
}
当需要创建一个动态代理对象的时候,首先初始化一个CGLibProxy的实例,然后调用它的getProxy方法,传入的参数是被代理类的Class,在我们的例子中就是Student.class,测试代码如下:
public static void main(String[] args) {
CGLibProxy cgLibProxy = new CGLibProxy();
// 这里返回的student就是一个动态代理类了,其实它指向的是Student的一个子类
Student student = cgLibProxy.getProxy(Student.class);
student.setName("Joey");
student.printName();
student.finalTest();
}
输出结果如下:
~~~ Before Log ~~~
calling method: setName
~~~ After Log ~~~
~~~ Before Log ~~~
calling method: printName
Student name is: Joey
~~~ After Log ~~~
This is a final method
可以看到在调用setName()和printName()方法的前后都打印了日志,说明动态代理是成功的,这里有两点要注意:
- Enhancer的静态create方法默认是使用被代理类的一个无参构造函数来初始化的,所以被代理类Student必须要有一个public的无参构造函数,如果这个无参构造函数是被设置为了private则会报错;
- CGlib是基于继承实现的,final方法无法被子类override,所以在调用Student类的final方法时并没有被代理的效果
方法二:通过Enhancer的对象来创建动态代理类
这个方法跟方法一大致相同,只是需要稍微修改一下getProxy()方法的代码:
public <T> T getProxy(Class<T> cls) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(cls);
enhancer.setCallback(this);
return (T) enhancer.create();
}
在方法一中我们直接传入了要被代理类的Class和回调处理类,而在方法二中,需要通过Enhancer的对象来set这两个参数值,最后通过无参的create方法来产生动态代理类,这个测试的输出结果是一样的。
需要注意的是,无参的create()方法默认调用被代理类Student的无参构造函数来初始化,如果希望通过有参的构造函数初始化Student,也可以调用create()的一个重载版本:
enhancer.create(new Class<?>[]{String.class}, new Object[]{"Jack"});
需要传入两个参数,第一个是Student构造函数的参数列表的Class数组,第二个是对应的值。
三、 CGlib中的拦截器
CGlib除了效率比JDK动态代理更高以外,它还增加了一些实用的功能,拦截器就是其中之一。之前的例子中我们对于被代理类Student中的每一个非final方法都是采用同一套代理逻辑,即在方法执行前后都打印一下输出,但是在实际的应用中可能需要对不同的方法产生不同的代理效果。举个例子,在web开发中,我们希望每一个service方法可以被事务管理,即每一个方法执行前要开始事务,执行后要提交事务,但是对于另外的一些辅助方法,如isEmpty()这种,我们并不需要做这些数据库事务操作,也许只要打印日志就可以了,如果我们对于这些类全都采用同样的代理逻辑去生成代理类明显是没有意义的,也很容易产生问题,所以我们需要一个拦截器,对不同的方法产生不同代理逻辑。
首先给Student类增加两个方法用于测试:
public void myservice() {
//我们希望这种以service结尾的方法在调用前打开事务,调用后提交事务
System.out.println("\t***Student service method***");
}
public boolean hasName() {
//我们希望这种辅助方法不参与事务的管理,只要打印日志即可
System.out.println("\t***Student hasName() method***");
return null == name;
}
重新写一个产生动态代理对象的类:
public class MultipleProxy {
// 这两个常数分别对应于callbackClasses数组的下标
private static final int TRANSACTION_MANAGE = 0;
private static final int LOG_RECORD = 1;
// 把所有回调类都放到一个数组中,拦截器根据得到的返回结果来从数组中获取对应的回调逻辑类
private static Callback[] callbackClasses = new Callback[]{new TransactionManagerProxy(), new LogRecordProxy()};
// 静态内部类,处理跟事务相关逻辑的动态代理
private static class TransactionManagerProxy implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
System.out.println("~~~ Begin Transaction ~~~");
Object result = proxy.invokeSuper(obj, args);
System.out.println("~~~ Commit Transaction ~~~");
return result;
}
}
// 静态内部类,处理跟日志相关、跟事务无关的动态代理
private static class LogRecordProxy implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
System.out.println("~~~ Before invoking method ~~~");
Object result = proxy.invokeSuper(obj, args);
System.out.println("~~~ After invoking method ~~~");
return result;
}
}
// 获取代理类的方法
public static <T> T getProxy(Class<?> cls) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(cls);
// 把所有回调类的数组给它保存
enhancer.setCallbacks(callbackClasses);
// 通过匿名内部类设置过滤器,它将accept方法的返回值作为数组下标去获取对应的callback类
enhancer.setCallbackFilter(new CallbackFilter() {
@Override
public int accept(Method method) {
String methodName = method.getName();
// 方法名以service结尾的话则使用事务相关的回调类
if (methodName.endsWith("service")) {
return TRANSACTION_MANAGE;
} else {
// 否则使用日志记录的回调类
return LOG_RECORD;
}
}
});
// 构造动态代理类
return (T) enhancer.create();
}
}
这个类提供一个静态的方法getProxy()去获取动态代理对象,在获取动态代理对象之前,它给enhancer设置了一个拦截器,拦截器中的accept方法会根据被调用方法的名字来返回不同的下标,最后enhancer会利用这个返回的下标去执行不同的回调类中方法。测试代码如下:
Student student = MultipleProxy.getProxy(Student.class);
student.myservice();
student.hasName();
输出结果如下:
~~~ Begin Transaction ~~~
***Student service method***
~~~ Commit Transaction ~~~
~~~ Before invoking method ~~~
***Student hasName() method***
~~~ After invoking method ~~~
从输出结果我们可以看到,在调用myservice方法前后的输出和调用hasName方法前后的输出是不一样的,从而实现了更灵活的动态绑定。
四、总结
使用CGlib产生动态代理对象主要有两种方法:
- 使用Enhancer的静态create()方法,同时传入要被代理类的Class和实现了MethodInterceptor接口的回调类;
- 构造一个Enhancer类的实例,用这个实例的create方法产生动态代理对象。create方法还有多个重载版本,分别对应被代理类的无参构造函数和有参构造函数的情况
CGlib使用中的一些注意点:
- CGlib是采用继承的方式实现动态代理的,因此final类不能代理,非final类中的final方法也不能被代理
- 使用无参的create方法产生动态代理对象时,被代理类需要有一个非private的无参构造函数
- 可以给enhancer设置拦截器,从而在调用代理类方法时根据不同的情况运行不同的代理逻辑
本系列文章
五、参考资料
https://my.oschina.net/bairrfhoinn/blog/144812
http://www.cnblogs.com/icejoywoo/archive/2011/06/05/2072970.html
https://my.oschina.net/JoeyXieIsCool/blog/608640