CGLIB动态代理
CGLIB动态代理
Section titled “CGLIB动态代理”maven引入CGLIB包,然后编写一个UserDao类,它没有接口,只有两个方法,select() 和 update()
public class UserDao { public void select() { System.out.println("UserDao 查询 selectById"); } public void update() { System.out.println("UserDao 更新 update"); }}
编写一个 LogInterceptor ,继承了 MethodInterceptor,用于方法的拦截回调
import java.lang.reflect.Method;import java.util.Date;
public class LogInterceptor implements MethodInterceptor { /** * @param object 表示要进行增强的对象 * @param method 表示拦截的方法 * @param objects 数组表示参数列表,基本数据类型需要传入其包装类型,如int-->Integer、long-Long、double-->Double * @param methodProxy 表示对方法的代理,invokeSuper方法表示对被代理对象方法的调用 * @return 执行结果 * @throws Throwable */ @Override public Object intercept(Object object, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { before(); Object result = methodProxy.invokeSuper(object, objects); // 注意这里是调用 invokeSuper 而不是 invoke,否则死循环,methodProxy.invokesuper执行的是原始类的方法,method.invoke执行的是子类的方法 after(); return result; } private void before() { System.out.println(String.format("log start time [%s] ", new Date())); } private void after() { System.out.println(String.format("log end time [%s] ", new Date())); }}
测试
import net.sf.cglib.proxy.Enhancer;
public class CglibTest { public static void main(String[] args) { DaoProxy daoProxy = new DaoProxy(); Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(Dao.class); // 设置超类,cglib是通过继承来实现的 enhancer.setCallback(daoProxy);
Dao dao = (Dao)enhancer.create(); // 创建代理类 dao.update(); dao.select(); }}
运行结果
log start time [Fri Dec 21 00:06:40 CST 2018]UserDao 查询 selectByIdlog end time [Fri Dec 21 00:06:40 CST 2018]log start time [Fri Dec 21 00:06:40 CST 2018]UserDao 更新 updatelog end time [Fri Dec 21 00:06:40 CST 2018]
还可以进一步多个 MethodInterceptor 进行过滤筛选
public class LogInterceptor2 implements MethodInterceptor { @Override public Object intercept(Object object, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { before(); // method.invoke(o, objects);是错的 因为o是代理对象 如果在这个类里面传入一个被代理对象替代o就可以 Object result = methodProxy.invokeSuper(object, objects); after(); return result; } private void before() { System.out.println(String.format("log2 start time [%s] ", new Date())); } private void after() { System.out.println(String.format("log2 end time [%s] ", new Date())); }}
// 回调过滤器: 在CGLib回调时可以设置对不同方法执行不同的回调逻辑,或者根本不执行回调。public class DaoFilter implements CallbackFilter { @Override public int accept(Method method) { if ("select".equals(method.getName())) { return 0; // Callback 列表第1个拦截器 } return 1; // Callback 列表第2个拦截器,return 2 则为第3个,以此类推 }}
再次测试
public class CglibTest2 { public static void main(String[] args) { LogInterceptor logInterceptor = new LogInterceptor(); LogInterceptor2 logInterceptor2 = new LogInterceptor2(); Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(UserDao.class); // 设置超类,cglib是通过继承来实现的 enhancer.setCallbacks(new Callback[]{logInterceptor, logInterceptor2, NoOp.INSTANCE}); // 设置多个拦截器,NoOp.INSTANCE是一个空拦截器,不做任何处理 enhancer.setCallbackFilter(new DaoFilter());
UserDao proxy = (UserDao) enhancer.create(); // 创建代理类 proxy.select(); proxy.update(); }}
运行结果
log start time [Fri Dec 21 00:22:39 CST 2018]UserDao 查询 selectByIdlog end time [Fri Dec 21 00:22:39 CST 2018]log2 start time [Fri Dec 21 00:22:39 CST 2018]UserDao 更新 updatelog2 end time [Fri Dec 21 00:22:39 CST 2018]
CGLIB 创建动态代理类的模式是:
- 查找目标类上的所有非final 的public类型的方法定义;
- 将这些方法的定义转换成字节码;
- 将组成的字节码转换成相应的代理的class对象;
- 实现 MethodInterceptor接口,用来处理对代理类上所有方法的请求
简单来说Enhancer会主要生成三个类Coder$$EnhancerByCGLIB$$8e91f654.class
,Coder$$EnhancerByCGLIB$$8e91f654$$FastClassByCGLIB$$4e5eb5aa.class
,Coder$$FastClassByCGLIB$$398819d0.class
。分别是代理类,代理类的索引类和被代理类的索引类。我们调用代理类的对应方法,首先会调用我们设置的继承了MethodInterceptor类的interceptor方法。然后再invokeSuper
方法,在这个方法中init
初始化了代理类的索引类和被代理类的索引类。然后调用代理类索引类的对应方法,根据初始化好的index,在调用我们代理类的方法,最后就是调用父类的方法。
代理类--->自定义方法拦截器--->代理类索引类getIndex()方法—>代理类索引类invoke()方法--->代理类--->被代理类。
4. JDK动态代理与CGLIB动态代理区别(面试常问)
Section titled “4. JDK动态代理与CGLIB动态代理区别(面试常问)”了解了JDK动态代理和CGLIB动态代理的原理后,现在来比较下两者的区别,这也是面试时几乎必问的一道面试题。
-
使用JDK动态代理,被代理类必须要实现接口,使用CGLIB动态代理,被代理类可以不实现接口
原因分析:
JDK动态代理生成的代理类继承了
java.lang.reflect.Proxy
,因为Java是单继承的,如果不通过实现接口的形式,无法对类进行扩展。
CGLIB动态代理生成的代理类实际上是被代理类的子类,所以被代理类可以不实现接口。
-
自动生成类的数量不同
JDK动态代理只会生成1个代理类,一般情况下名称为:
com.sun.proxy.$Proxy0
。CGLIB动态代理会生成好几个类,核心的3个分别是:
1)代理类:被代理类的子类,名称格式为
Coder$$EnhancerByCGLIB$$8e91f654
,包名和被代理类包名一致。2)代理类的索引类:名称格式为
Coder$$EnhancerByCGLIB$$8e91f654$$FastClassByCGLIB$$4e5eb5aa
,包名和被代理类包名一致。
3)被代理类的索引类:名称格式为
Coder$$FastClassByCGLIB$$398819d0
,包名和被代理类包名一致。 -
生成代理类技术不同
JDK动态代理使用JDK自带的ProxyGenerator类生成字节码文件。
CGLIB动态代理使用ASM框架生成字节码文件。
-
调用方式不同
JDK动态代理:代理类--->InvocationHandler.invoke()--->被代理类方法(用到了反射)。
CGLIB动态代理:代理类--->MethodInterceptor.intercept()--->代理类索引类getIndex()--->
代理类索引类invoke()--->代理类--->被代理类。(直接调用)