CGLIB(Code Generation Library)详解

本文深入探讨了CGLIB库的功能与应用,包括其在AOP框架中的作用及如何通过字节码操作生成代理类。并通过实例展示了如何使用CGLIB对方法进行拦截。

什么是CGLIB

CGLIB是一个强大的、高性能的代码生成库。其被广泛应用于AOP框架(Spring、dynaop)中,用以提供方法拦截操作。Hibernate作为一个比较受欢迎的ORM框架,同样使用CGLIB来代理单端(多对一和一对一)关联(延迟提取集合使用的另一种机制)。CGLIB作为一个开源项目,其代码托管在github,地址为:https://github.com/cglib/cglib

为什么使用CGLIB

CGLIB代理主要通过对字节码的操作,为对象引入间接级别,以控制对象的访问。我们知道Java中有一个动态代理也是做这个事情的,那我们为什么不直接使用Java动态代理,而要使用CGLIB呢?答案是CGLIB相比于JDK动态代理更加强大,JDK动态代理虽然简单易用,但是其有一个致命缺陷是,只能对接口进行代理。如果要代理的类为一个普通类、没有接口,那么Java动态代理就没法使用了。关于Java动态代理,可以参者这里Java动态代理分析

CGLIB组成结构

CGLIB底层使用了ASM(一个短小精悍的字节码操作框架)来操作字节码生成新的类。除了CGLIB库外,脚本语言(如Groovy何BeanShell)也使用ASM生成字节码。ASM使用类似SAX的解析器来实现高性能。我们不鼓励直接使用ASM,因为它需要对Java字节码的格式足够的了解

例子

说了这么多,可能大家还是不知道CGLIB是干什么用的。下面我们将使用一个简单的例子来演示如何使用CGLIB对一个方法进行拦截。

首先,我们需要在工程的POM文件中引入cglib的dependency,这里我们使用的是2.2.2版本

<dependency>
	<groupId>cglib<groupId>
	<artifactId>cglib<artifactId>
	<version>2.2.2<version>
dependency>

依赖包下载后,我们就可以干活了,按照国际惯例,写个hello world


publicclass SampleClass {

public void test(){
     System.out.println("hello world");
}

public static void main(String[] args) {
	Enhancer enhancer = new Enhancer();
	enhancer.setSuperclass(SampleClass.class);
	enhancer.setCallback(new MethodInterceptor() {
			  public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {                         System.out.println("before method run...");
			  Object result = proxy.invokeSuper(obj, args);
			  System.out.println("after method run...");
			  return result;            }        });
	SampleClass sample = (SampleClass) enhancer.create();
	sample.test();
}}

在mian函数中,我们通过一个Enhancer和一个MethodInterceptor来实现对方法的拦截,运行程序后输出为:

before methodrun...helloworldaftermethodrun... 1 2 3 在上面的程序中,我们引入了Enhancer和MethodInterceptor,可能有些读者还不太了解。别急,我们后面将会一一进行介绍。就目前而言,一个使用CGLIB的小demo就完成了

常用的API

目前网络上对CGLIB的介绍资料比较少,造成对cglib的学习困难。这里我将对cglib中的常用类进行一个介绍。为了避免解释的不清楚,我将为每个类都配有一个demo,用来做进一步的说明。首先就从Enhancer开始吧。

Enhancer

Enhancer可能是CGLIB中最常用的一个类,和Java1.3动态代理中引入的Proxy类差不多(如果对Proxy不懂,可以参考这里)。和Proxy不同的是,Enhancer既能够代理普通的class,也能够代理接口。Enhancer创建一个被代理对象的子类并且拦截所有的方法调用(包括从Object中继承的toString和hashCode方法)。Enhancer不能够拦截final方法,例如Object.getClass()方法,这是由于Java final方法语义决定的。基于同样的道理,Enhancer也不能对fianl类进行代理操作。这也是Hibernate为什么不能持久化final class的原因。

publicclass SampleClass { public String test(String input){ return"hello world"; }} 1 2 3 4 5 下面我们将以这个类作为主要的测试类,来测试调用各种方法

@TestpublicvoidtestFixedValue(){ 
Enhancer enhancer = new Enhancer();   
enhancer.setSuperclass(SampleClass.class);  
enhancer.setCallback(new FixedValue() {
public Object loadObject() throws Exception {   
return"Hello cglib";        }    });   
SampleClass proxy = (SampleClass) enhancer.create(); 
System.out.println(proxy.test(null)); //拦截test,输出Hello cglib 
System.out.println(proxy.toString());    
System.out.println(proxy.getClass());   
System.out.println(proxy.hashCode());}

程序的输出为:

Hello cglibHello cglibclass com.zeus.cglib.SampleClass$$EnhancerByCGLIB$$e3ea9b7java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Number    at com.zeus.cglib.SampleClass$$EnhancerByCGLIB$$e3ea9b7.hashCode()    ...

上述代码中,FixedValue用来对所有拦截的方法返回相同的值,从输出我们可以看出来,Enhancer对非final方法test()、toString()、hashCode()进行了拦截,没有对getClass进行拦截。由于hashCode()方法需要返回一个Number,但是我们返回的是一个String,这解释了上面的程序中为什么会抛出异常。

Enhancer.setSuperclass用来设置父类型,从toString方法可以看出,使用CGLIB生成的类为被代理类的一个子类,形如:SampleClass$$EnhancerByCGLIB$$e3ea9b7

Enhancer.create(Object…)方法是用来创建增强对象的,其提供了很多不同参数的方法用来匹配被增强类的不同构造方法。(虽然类的构造放法只是Java字节码层面的函数,但是Enhancer却不能对其进行操作。Enhancer同样不能操作static或者final类)。我们也可以先使用Enhancer.createClass()来创建字节码(.class),然后用字节码动态的生成增强后的对象。

可以使用一个InvocationHandler(如果对InvocationHandler不懂,可以参考这里)作为回调,使用invoke方法来替换直接访问类的方法,但是你必须注意死循环。因为invoke中调用的任何原代理类方法,均会重新代理到invoke方法中。

publicvoidtestInvocationHandler() throws Exception{    Enhancer enhancer = new Enhancer();    enhancer.setSuperclass(SampleClass.class);    enhancer.setCallback(new InvocationHandler() {        @Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {            if(method.getDeclaringClass() != Object.class && method.getReturnType() == String.class){                return"hello cglib";            }else{                thrownew RuntimeException("Do not know what to do");            }        }    });    SampleClass proxy = (SampleClass) enhancer.create();    Assert.assertEquals("hello cglib", proxy.test(null));    Assert.assertNotEquals("Hello cglib", proxy.toString());}

转载于:https://my.oschina.net/guoenzhou/blog/1838709

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值