天天看点

CGLIB动态代理介绍

目录

cglib库介绍

cglib api

简单代理

使用methodinterceptor

使用callbackfilter

总结

参考

代理提供了一个可扩展的机制来控制被代理对象的访问,其实说白了就是在对象访问的时候加了一层封装。jdk从1.3版本起就提供了一个动态代理,它使用起来非常简单,但是有个明显的缺点:需要目标对象实现一个或多个接口。假如你想代理没有接口的类呢?可以使用cglib库。

cglib是一个强大的、高性能的代码生成库。它被广泛使用在基于代理的aop框架(例如spring aop和dynaop)提供方法拦截。hibernate作为最流行的orm工具也同样使用cglib库来代理单端关联(集合懒加载除外,它使用另外一种机制)。easymock和jmock作为流行的java测试库,它们提供mock对象的方式来支持测试,都使用了cglib来对没有接口的类进行代理。

在实现内部,cglib库使用了asm这一个轻量但高性能的字节码操作框架来转化字节码,产生新类。除了cglib,像groovy和beanshell这样的脚本语言同样使用asm来生成java字节码。asm使用了一个类似于sax分析器的机制来达到高性能。我们不建议直接使用asm,因为这样需要对jvm非常了解,包括类文件格式和指令集。

CGLIB动态代理介绍

上图展示了cglib库相关框架以及语言之间的关系。另外提醒下,类似于spring aop和hibernate这些框架它们经常同时使用cglib和jdk动态代理来满足各自需要。hibernate使用jdk动态代理为webshere应用服务实现一个事务管理适配器;spring aop则默认使用jdk动态代理来代理接口,除非你强制使用cglib。

cglib库的代码量不多,但是由于缺乏文档导致学习起来比较困难。2.1.2版本的cglib库组织如下所示:

net.sf.cglib.core:底层字节码操作类;大部分与asp相关。

net.sf.cglib.transform:编译期、运行期的class文件转换类。

net.sf.cglib.proxy:代理创建类、方法拦截类。

net.sf.cglib.reflect:更快的反射类、c#风格的代理类。

net.sf.cglib.util:集合排序工具类

net.sf.cglib.beans:javabean相关的工具类

对于创建动态代理,大部分情况下你只需要使用proxy包的一部分api即可。

上面已经提到,cglib库是基于asm的上层应用。对于代理没有实现接口的类,cglib非常实用。本质上来说,对于需要被代理的类,它只是动态生成一个子类以覆盖非final的方法,同时绑定钩子回调自定义的拦截器。值得说的是,它比jdk动态代理还要快。

CGLIB动态代理介绍

cglib库中经常用来代理类的api关联图如上所示。net.sf.cglib.proxy.callback只是一个用于标记的接口,net.sf.cglib.proxy.enhancer使用的所有回调都会继承这个接口。

net.sf.cglib.proxy.methodinterceptor是最常用的回调类型,在基于代理的aop实现中它经常被用来拦截方法调用。这个接口只有一个方法:

如果net.sf.cglib.proxy.methodinterceptor被设置为方法回调,那么当调用代理方法时,它会先调用methodinterceptor.intercept方法,然后再调用被代理对象的方法(如下图所示)。methodinterceptor.intercept方法的第一个参数是代理对象,第二个、第三个参数分别是被拦截的方法和方法的参数。如果想调用被代理对象的原始方法,可以通过使用java.lang.reflect.method对象来反射调用,或者使用net.sf.cglib.proxy.methodproxy对象。我们通常使用net.sf.cglib.proxy.methodproxy因为它更快。在intercept方法中,自定义代码可以在原始方法调用前或调用后注入。

CGLIB动态代理介绍

net.sf.cglib.proxy.methodinterceptor满足了所有的代理需求,但对于某些特定场景它可能使用起来不太方便。为了方便使用和高性能,cglib提供了另外一些特殊的回调类型。例如,

net.sf.cglib.proxy.fixedvalue:在强制一个特定方法返回固定值,在特定场景下非常有用且性能高。

net.sf.cglib.proxy.noop:它直接透传到父类的方法实现。

net.sf.cglib.proxy.lazyloader:在被代理对象需要懒加载场景下非常有用,如果被代理对象加载完成,那么在以后的代理调用时会重复使用。

net.sf.cglib.proxy.dispatcher:与net.sf.cglib.proxy.lazyloader差不多,但每次调用代理方法时都会调用loadobject方法来加载被代理对象。

net.sf.cglib.proxy.proxyrefdispatcher:与dispatcher相同,但它的loadobject方法支持传入代理对象。

我们通常对于被代理类的所有方法都使用同样的回调(如上图figure 3所示),但我们也可以使用net.sf.cglib.proxy.callbackfilter来对不同的方法使用不同的回调。这种细粒度的控制是jdk动态代理没有提供的,jdk中的java.lang.reflect.invocationhandler的invoke方法只能应用于被代理对象的所有方法。

除了代理类之外,cglib也可以通过java.lang.reflect.proxy插入替换的方式来代理接口以支持jdk1.3之前的代理,但由于这种替换代理很少用,因此这里省略相关的代理api。

现在让我们看看怎么使用cglib来创建代理吧。

cglib代理的核心是net.sf.cglib.proxy.enhancer类。对于创建一个cglib代理,你最少得有一个被代理类。现在我们先使用内置的noop回调:

这个方法的返回值是一个目标类对象的代理。在上面这个例子中,net.sf.cglib.proxy.enhancer配置了单个net.sf.cglib.proxy.callback。可以看到,使用cglib创建一个简单代理是很容易的。除了创建一个新的net.sf.cglib.proxy.enhancer对象,你也可以直接使用net.sf.cglib.proxy.enhancer类中的静态辅助方法来创建代理。但我们更推荐使用例子中的方法,因为你可以通过配置net.sf.cglib.proxy.enhancer对象来对产生的代理进行更精细的控制。

值得注意的是,我们传入目标类作为代理的父类。不同于jdk动态代理,我们不能使用目标对象来创建代理。目标对象只能被cglib创建。在例子中,默认的无参构造方法被使用来创建目标对象。如果你希望cglib创建一个有参数的实例,你应该使用net.sf.cglib.proxy.enhancer.create(class[], object[])。该方法的第一个参数指明参数类型,第二个参数指明参数值。参数中的原子类型需要使用包装类。

我们可以将net.sf.cglib.proxy.noop回调替换成自定义的net.sf.cglib.proxy.methodinterceptor来得到更强大的代理。代理的所有方法调用都会被分派给net.sf.cglib.proxy.methodinterceptor的intercept方法。intercept方法然后调用底层对象。

假设你想对目标对象的方法调用进行授权检查,如果授权失败,那么抛出一个运行时异常authorizationexception。接口authorization.java如下:

接口net.sf.cglib.proxy.methodinterceptor的实现如下:

在intercept方法中,先检查授权,如果授权通过,那么intercept方法调用目标对象的方法。由于性能原因,我们使用cglib的net.sf.cglib.proxy.methodproxy对象而不是一般的java.lang.reflect.method反射对象来调用原始方法。

net.sf.cglib.proxy.callbackfilter允许你在方法级别设置回调。假设你有一个persistenceserviceimpl类,它有两个方法:save和load。save方法需要进行授权检查,而load方法不需要。

persistenceserviceimpl类实现了persistenceservice接口,但这个不是必须的。persistenceserviceimpl的net.sf.cglib.proxy.callbackfilter实现如下:

accept方法将代理方法映射到回调。方法返回值是一个回调对象数组中的下标。下面是persistenceserviceimpl的代理创建实现:

在例子中,authorizationinterceptor应用于save方法,noop.instance应用于load方法。你可以通过net.sf.cglib.proxy.enhancer.setinterfaces(class[])指明代理需要实现的接口,但这个不是必须的。

cglib是一个强大的高性能的代码生成库。作为jdk动态代理的互补,它对于那些没有实现接口的类提供了代理方案。在底层,它使用asm字节码操纵框架。本质上来说,cglib通过产生子类覆盖非final方法来进行代理。它比使用java反射的jdk动态代理方法更快。cglib不能代理一个final类或者final方法。通常来说,你可以使用jdk动态代理方法来创建代理,对于没有接口的情况或者性能因素,cglib是一个很好的选择。

<a href="http://www.lizjason.com/downloads/">complete source code for this article</a>

<a href="http://cglib.sourceforge.net/">cglib library</a>

<a href="http://www.springframework.org/">spring framework</a>

<a href="http://java.sun.com/j2se/1.5.0/docs/api/java/lang/reflect/proxy.html">jdk dynamic proxy</a>

<a href="http://www.easymock.org/">easymock</a>

<a href="http://www.jmock.org/">jmock</a>

<a href="http://dynaop.dev.java.net/">dynaop</a>

<a href="http://www.onjava.com/lpt/a/5250">a good introduction to asm by eugene kuleshov</a>