博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Cglib及其基本使用
阅读量:5096 次
发布时间:2019-06-13

本文共 5660 字,大约阅读时间需要 18 分钟。

前言

最近一直在看Spring源码,其实我之前一直知道AOP的基本实现原理:

  • 如果针对接口做代理默认使用的是JDK自带的Proxy+InvocationHandler
  • 如果针对类做代理使用的是Cglib
  • 即使针对接口做代理,也可以将代理方式配置成走Cglib的

之后要看AOP源码了,Proxy+InvocationHandler这套我已经很熟了,想着如果把Cglib研究研究,应该看AOP源码的时候会更快一些,因此本文就研究一下Cglib是什么、如何一些基本的使用、底层实现原理。

 

Cglib是什么

Cglib是一个强大的、高性能的代码生成包,它广泛被许多AOP框架使用,为他们提供方法的拦截。下图是我网上找到的一张Cglib与一些框架和语言的关系:

对此图总结一下:

  • 最底层的是字节码Bytecode,字节码是Java为了保证“一次编译、到处运行”而产生的一种虚拟指令格式,例如iload_0、iconst_1、if_icmpne、dup等
  • 位于字节码之上的是ASM,这是一种直接操作字节码的框架,应用ASM需要对Java字节码、Class结构比较熟悉
  • 位于ASM之上的是CGLIB、Groovy、BeanShell,后两种并不是Java体系中的内容而是脚本语言,它们通过ASM框架生成字节码变相执行Java代码,这说明在JVM中执行程序并不一定非要写Java代码----只要你能生成Java字节码,JVM并不关心字节码的来源,当然通过Java代码生成的JVM字节码是通过编译器直接生成的,算是最“正统”的JVM字节码
  • 位于CGLIB、Groovy、BeanShell之上的就是Hibernate、Spring AOP这些框架了,这一层大家都比较熟悉
  • 最上层的是Applications,即具体应用,一般都是一个Web项目或者本地跑一个程序

 

使用Cglib代码对类做代理

下面演示一下Cglib代码示例----对类做代理。首先定义一个Dao类,里面有一个select()方法和一个update()方法:

public class Dao {        public void update() {        System.out.println("PeopleDao.update()");    }        public void select() {        System.out.println("PeopleDao.select()");    }}

创建一个Dao代理,实现MethodInterceptor接口,目标是在update()方法与select()方法调用前后输出两句话:

public class DaoProxy implements MethodInterceptor {    @Override    public Object intercept(Object object, Method method, Object[] objects, MethodProxy proxy) throws Throwable {        System.out.println("Before Method Invoke");        proxy.invokeSuper(object, objects);        System.out.println("After Method Invoke");                return object;    }    }

intercept方法的参数名并不是原生的参数名,我做了自己的调整,几个参数的含义为:

  • Object表示要进行增强的对象
  • Method表示拦截的方法
  • Object[]数组表示参数列表,基本数据类型需要传入其包装类型,如int-->Integer、long-Long、double-->Double
  • MethodProxy表示对方法的代理,invokeSuper方法表示对被代理对象方法的调用

写一个测试类:

public class CglibTest {    @Test    public void testCglib() {        DaoProxy daoProxy = new DaoProxy();                Enhancer enhancer = new Enhancer();        enhancer.setSuperclass(Dao.class);        enhancer.setCallback(daoProxy);                Dao dao = (Dao)enhancer.create();        dao.update();        dao.select();    }    }

这是使用Cglib的通用写法,setSuperclass表示设置要代理的类,setCallback表示设置回调即MethodInterceptor的实现类,使用create()方法生成一个代理对象,注意要强转一下,因为返回的是Object。最后看一下运行结果:

Before Method InvokePeopleDao.update()After Method InvokeBefore Method InvokePeopleDao.select()After Method Invoke

符合我们的期望。

 

使用Cglib定义不同的拦截策略

再扩展一点点,比方说在AOP中我们经常碰到的一种复杂场景是:我们想对类A的B方法使用一种拦截策略、类A的C方法使用另外一种拦截策略

在本例中,即我们想对select()方法与update()方法使用不同的拦截策略,那么我们先定义一个新的Proxy:

public class DaoAnotherProxy implements MethodInterceptor {    @Override    public Object intercept(Object object, Method method, Object[] objects, MethodProxy proxy) throws Throwable {                System.out.println("StartTime=[" + System.currentTimeMillis() + "]");        method.invoke(object, objects);        System.out.println("EndTime=[" + System.currentTimeMillis() + "]");        return object;    }    }

方法调用前后输出一下开始时间与结束时间。为了实现我们的需求,实现一下CallbackFilter:

public class DaoFilter implements CallbackFilter {    @Override    public int accept(Method method) {        if ("select".equals(method.getName())) {            return 0;        }        return 1;    }    }

返回的数值表示顺序,结合下面的代码解释,测试代码要修改一下:

public class CglibTest {    @Test    public void testCglib() {        DaoProxy daoProxy = new DaoProxy();        DaoAnotherProxy daoAnotherProxy = new DaoAnotherProxy();                Enhancer enhancer = new Enhancer();        enhancer.setSuperclass(Dao.class);        enhancer.setCallbacks(new Callback[]{daoProxy, daoAnotherProxy, NoOp.INSTANCE});        enhancer.setCallbackFilter(new DaoFilter());                Dao dao = (Dao)enhancer.create();        dao.update();        dao.select();    }    }

意思是CallbackFilter的accept方法返回的数值表示的是顺序,顺序和setCallbacks里面Proxy的顺序是一致的。再解释清楚一点,Callback数组中有三个callback,那么:

  • 方法名为"select"的方法返回的顺序为0,即使用Callback数组中的0位callback,即DaoProxy
  • 方法名不为"select"的方法返回的顺序为1,即使用Callback数组中的1位callback,即DaoAnotherProxy

因此,方法的执行结果为:

StartTime=[1491198489261]PeopleDao.update()EndTime=[1491198489275]Before Method InvokePeopleDao.select()After Method Invoke

符合我们的预期,因为update()方法不是方法名为"select"的方法,因此返回1,返回1使用DaoAnotherProxy,即打印时间;select()方法是方法名为"select"的方法,因此返回0,返回0使用DaoProxy,即方法调用前后输出两句话。

这里要额外提一下,Callback数组中我特意定义了一个NoOp.INSTANCE,这表示一个空Callback,即如果不想对某个方法进行拦截,可以在DaoFilter中返回2,具体效果可以自己尝试一下。

 

构造函数不拦截方法

如果Update()方法与select()方法在构造函数中被调用,那么也是会对这两个方法进行相应的拦截的,现在我想要的是构造函数中调用的方法不会被拦截,那么应该如何做?先改一下Dao代码,加一个构造方法Dao(),调用一下update()方法:

public class Dao {        public Dao() {        update();    }        public void update() {        System.out.println("PeopleDao.update()");    }        public void select() {        System.out.println("PeopleDao.select()");    }}

如果想要在构造函数中调用update()方法时,不拦截的话,Enhancer中有一个setInterceptDuringConstruction(boolean interceptDuringConstruction)方法设置为false即可,默认为true,即构造函数中调用方法也是会拦截的。那么测试方法这么写:

public class CglibTest {    @Test    public void testCglib() {        DaoProxy daoProxy = new DaoProxy();        DaoAnotherProxy daoAnotherProxy = new DaoAnotherProxy();                Enhancer enhancer = new Enhancer();        enhancer.setSuperclass(Dao.class);        enhancer.setCallbacks(new Callback[]{daoProxy, daoAnotherProxy, NoOp.INSTANCE});        enhancer.setCallbackFilter(new DaoFilter());        enhancer.setInterceptDuringConstruction(false);                Dao dao = (Dao)enhancer.create();        dao.update();        dao.select();    }    }

运行结果为:

PeopleDao.update()StartTime=[1491202022297]PeopleDao.update()EndTime=[1491202022311]Before Method InvokePeopleDao.select()After Method Invoke

看到第一次update()方法的调用,即Dao类构造方法中的调用没有拦截,符合预期。

 

后记

本文演示了一些Cglib的基本用法,由于Cglib的原理探究篇幅比较长,就不放在本文写了,会在下一篇文章中写。

想要深入使用Cglib的朋友还需要多多尝试Cglib的各种API,才能更好地使用这个优秀的字节码生成框架。 

转载于:https://www.cnblogs.com/xrq730/p/6661692.html

你可能感兴趣的文章
MongoDB - The mongo Shell, Data Types in the mongo Shell
查看>>
《算法导论》笔记---第6章 堆排序
查看>>
win7利用tsmmc.msc远程管理工具
查看>>
免费的天气Web Service接口
查看>>
YTU 2596: 编程题B-日期格式
查看>>
ubuntu中pycharm配置opencv2环境
查看>>
hibernate.cfg.xml
查看>>
visual studio 2010问题修复
查看>>
hdoj2044
查看>>
【洛谷P1311】选择客栈
查看>>
关于内存对齐
查看>>
图论随笔
查看>>
VS2019 更新MSDN并创建快捷方式
查看>>
Android数据存储
查看>>
iOS 怎么查app的下载量
查看>>
分页查询 模糊查询 合体查询
查看>>
替换空格
查看>>
在人生路上对我影响最大的三位老师
查看>>
Polya定理与Burnside引理
查看>>
mybaits注解
查看>>