浅析JAVA设计模式之代理模式(七)

1 .CGLIB动态代理

       在《六》中,我们分析了JDK提供的动态代理的缺陷,并且提出了一种解决思路,并简单地对其进行了实现。其实《六》中的简单实现其实是对一种叫cglib动态代理的简单模拟。

       什么是从CGLIB

       CGLIB(CodeGeneration Library代码生产库)是一个开源项目,是一个强大的,高性能,高质量的代码生成类库,它可以在运行期扩展Java类与实现Java接口。Hibernate用它来实现PO(Persistant Object 持久化对象)字节码的动态生成。

       CGLIB包的底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类。除了CGLIB包,脚本语言例如GroovyBeanShell,也是使用ASM来生成java的字节码。当不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉。


       上图显示了和CGLIB包和一些框架和语言的关系图。一些框架如Spring AOP Hibernate,它们为了满足需要经常同时使用JDK的动态代理和CGLIB包。Hiberater使用JDK的动态代理实现一个专门为WebShere应用服务器的事务管理适配器;Spring AOP,如果不强制使用CGLIB包,默认情况是使用JDK的动态代理来代理接口。

       CGLIB包的基本代码很少,当学起来有一定的困难,主要是缺少文档,这也是开源软件的一个不足之处。目前CGLIB的版本(2.1.2,主要由以下部分组成:

·        net.sf.cglib.core
         Low-level bytecode manipulation classes; Most of them are related to ASM.

·        net.sf.cglib.transform
         Classes for class file transformations at runtime or build time

·        net.sf.cglib.proxy
         Classes for proxy creation and method interceptions

·        net.sf.cglib.reflect
         Classes for a faster reflection and C#-style delegates

·        net.sf.cglib.util
         Collection sorting utilities

·        net.sf.cglib.beans
        JavaBean related utilities

       大多时候,仅仅为了动态地创建代理,你仅需要使用到代理包中很少的一些API。通过使用CGLIB包可以为那些没有接口的类动态地创建代理对象,当然CGLIB也可以用来代理接口。

1.1 CGLIB动态代理的实现前分析

        CGLIB动态代理类的实现和《五》中介绍JDK动态代理实现非常相似,可参考《五》再阅读本文。

        首先CGLIB也提供了一个处理器接口(这里成为回调接口)net.sf.cglib.proxy.MethodInterceptor(相当于JDK代理的InvocationHandler接口),它自定义了一个intercept方法(相当于JDK代理的invoke方法),用于集中处理在动态代理类对象上的方法调用,通常在该方法中实现对委托类的代理访问。

      // 该方法负责集中处理动态代理类上的所有方法调用。第一个参数是代理类对象,第二个参数是委托类被调用的方法的类对象
       // 第三个是该方法的参数,第四个是生成在代理类里面,除了方法名不同,其他都和被代理方法一样的(参数,方法体里面的东西)一个方法的类对象。

       public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodproxy) throws Throwable

       然后CGLIB也提供了一个生成代理类的类net.sf.cglib.proxy.Enhancer(相当于JDK代理的java.lang.reflect.Proxy),它提供了一组静态方法来为一组接口动态地生成代理类及其对象。本文创建代理用到的Enhancer的静态方法如下:        

public Object intercept(Object proxy,Method method, Object[] args,  MethodProxy methodproxy) throws Throwable
       //为指定类装载器、接口及调用处理器生成动态代理类实例
        public static  Object create(Class class ,Callback callback h){}
       public static  Object create(Class class,Class[] interfaces,Callback h){}
       public static Object create(Classclass, Class[] interfaces, CallbackFilter filter, Callback[] hs)

       这个create方法相当于JDK代理的newProxyInstance方法,该方法的参数具体含义如下:
       Class class:指定一个被代理类的类对象。
       Class[]interfaces:如果要代理接口,指定一组被代理类实现的所有接口的类对象。
       Callback h:指定一个实现了处理器接口(这里称回调接口)的对象。

       CallbackFilter filter:指定一个方法过滤器

       Callback[]hs:指定一组实现了处理器接口的对象。

       下图就是用CGLIB创建动态代理的UML




1.2 如何使用 Java 动态代理:简单解释为五个步骤:

       1.  通过实现 MethodInterceptor接口创建自己的处理器;

       2.  通过给Enhancer类的create()方法传进被代理类的类对象、实现了MethodInterceptor接口的处理器对象,得到一个代理类对   象。

       3.  其中Enhancer类通过反射机制获得代理类的构造函数,有一个参数类型是处理器接口类型。

       4.  Enhancer类再通过构造函数对象创建动态代理类实例,构造时处理器对象作为参数被传入。

       5.  当客户端调用了代理类的方法时,该方法调用了处理器的intercept()方法并给intercept()方法传进委托类方法的类对象,intercept方法再调用委托类的真实方法。

      (1)建一个reallyCglibProxy包,所有程序都放在该包下,我在Spring的包库里面找到两个包:asm.jar和cblib-2.1.3.jar,最好在Spring里面同时拷贝这两个包到项目的类库下,不要分别从网上下载,可能会冲突。

      (2)建一个被代理类(RealSubject.java)。

package reallyCglibProxy;
//被代理类
public class RealSubject {
	public void print() {
		System.out.println("被代理的人郭襄");
	}
}

      (3)建一个用户自定义的处理器类LogHandler.java,需要实现处理接口,在intercept()方法里写上被代理类的方法调用前后要进行的动作。这个intercept()方法我们不用直接调用,是让将来自动生成的代理类去调用的,

       把《五》的工具类LonHanderReflectTool.java复制过来,用来反射生成的代理类。

package reallyCglibProxy;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import jdkDynamicProxy.LonHanderReflectTool;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

//相当于实现jdk的InvocationHandler接口
public class LogHandler implements MethodInterceptor{
	private Object delegate; //被代理类的对象
	//绑定被代理类的对象
	public Object bind(Object delegate)throws Exception{
		this.delegate=delegate;
	return Enhancer.create(delegate.getClass(),this);
	}  
	//相当于InvocationHandler接口里面的invoke()方法
	@Override
	public Object intercept(Object proxy, Method method, Object[] args,
			MethodProxy methodproxy) throws Throwable {
		Object result=null;
		System.out.println("我是代理人郭靖,开始代理");
		
		//method.invoke()或者methodproxy.invoke()都可以
		result=method.invoke(delegate,args);
		//result=methodproxy.invoke(delegate,args);
		System.out.println("我是代理人郭靖,代理完毕");

		//调用工具类反射jdk的Proxy生成的代理类,可参考《五》中这个工具类
		LonHanderReflectTool.ReflectClass(proxy.getClass().getName());
		return result;
	}
}

       (4)编写测试客户端(TestReallyCglibProxy.java)。

package reallyCglibProxy;
public class TestReallyCglibProxy {
	public static void main(String[] args)throws Exception {
		RealSubject sub1=new RealSubject();
		LogHandler hander=new LogHandler();
		RealSubject sub2=(RealSubject)hander.bind(sub1);
		sub2.print();
	}
}

输出结果:

我是代理人郭靖,开始代理

被代理的人郭襄

我是代理人郭靖,代理完毕


public class RealSubject$$EnhancerByCGLIB$$48574fb2 extends RealSubject implements Factory{
 private boolean CGLIB$BOUND;
 private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
 private static final Callback[] CGLIB$STATIC_CALLBACKS;
 private MethodInterceptor CGLIB$CALLBACK_0;
 private static final Method CGLIB$print$0$Method;
 private static final MethodProxy CGLIB$print$0$Proxy;
 private static final Object[] CGLIB$emptyArgs;
 private static final Method CGLIB$finalize$1$Method;
 private static final MethodProxy CGLIB$finalize$1$Proxy;
 private static final Method CGLIB$equals$2$Method;
 private static final MethodProxy CGLIB$equals$2$Proxy;
 private static final Method CGLIB$toString$3$Method;
 private static final MethodProxy CGLIB$toString$3$Proxy;
 private static final Method CGLIB$hashCode$4$Method;
 private static final MethodProxy CGLIB$hashCode$4$Proxy;
 private static final Method CGLIB$clone$5$Method;
 private static final MethodProxy CGLIB$clone$5$Proxy;

 public RealSubject$$EnhancerByCGLIB$$48574fb2(){}

public  void setCallback(int args0,Callback args1){}
public  void setCallbacks(Callback; args0){}
public static  void CGLIB$SET_STATIC_CALLBACKS(Callback; args0){}
public static  void CGLIB$SET_THREAD_CALLBACKS(Callback; args0){}
public  Callback getCallback(int args0){}
public  Callback[] getCallbacks(){}
public static  MethodProxy CGLIB$findMethodProxy(Signature args0){}
static  void CGLIB$STATICHOOK1(){}
final  void CGLIB$print$0(){}
private static final  void CGLIB$BIND_CALLBACKS(Object args0){}
final  void CGLIB$finalize$1()throws Throwable{}
final  boolean CGLIB$equals$2(Object args0){}
final  String CGLIB$toString$3(){}
final  int CGLIB$hashCode$4(){}
final  Object CGLIB$clone$5()throws CloneNotSupportedException{}
protected final  void finalize()throws Throwable{}
public final  boolean equals(Object args0){}
public final  String toString(){}
public final  int hashCode(){}
protected final  Object clone()throws CloneNotSupportedException{}
public  Object newInstance(Callback; args0){}
public  Object newInstance(Class; args0,Object; args1,Callback; args2){}
public  Object newInstance(Callback args0){}
public final  void print(){}

        从结果可以看出,成功自动生成了代理类RealSubject$$EnhancerByCGLIB$$48574fb2,并成功实现了代理的效果,而且还代理了RealSubject类从父类继承的finalize、equals、toString、hashCode、clone

这几个方法。

1.3 CBLIB方法过滤器

       如果现在有个要求,被代理类的print方法不用处理。当最简单的方式是修改方法拦截器(即intercept方法),在里面进行判断,如果是print()方法就不做任何逻辑处理,直接调用,这样子使得编写拦截器(相当于JDK代理里的处理器)逻辑变复杂,不易维护。我们可以使用CGLIB提供的方法过滤器(CallbackFilter),使得被代理类中不同的方法,根据我们的逻辑要求,调用不同的处理器,从而使用不同的拦截方法(处理器方法)。

1.3.1使用CBLIB方法过滤器

1)修改一下被代理类(RealSubject.java),增加一个方法print2

package reallyCglibProxy;
//被代理类
public class RealSubject {
	public void print() {
		System.out.println("被代理的人郭襄");
	}
	public void print2() {
		System.out.println("我是print2方法哦");
	}
}

2)新建一个用户自定义的方法过滤器类MyProxyFilter.java。

package reallyCglibProxy;

import java.lang.reflect.Method;

import net.sf.cglib.proxy.Callback;

import net.sf.cglib.proxy.CallbackFilter;

import net.sf.cglib.proxy.NoOp;

//自定义方法过滤器类

public class MyProxyFilterimplements CallbackFilter {

    public int accept(Method method) {

    /**

    *这句话可发现,所有被代理的方法都会被过滤一次。Enhancer的源代码中看到下面一段代码

    while(it1.hasNext()){

    MethodInfomethod=(MethodInfo)it1.next();

    MethodactualMethod=(it2!=null)?(Method)it2.next():null;

    intindex=filter.accept(actualMethod);

    if(index>=callbackTypes.length){

    thrownewIllegalArgumentException("Callbackfilterreturnedanindexthatistoolarge:"+index);

    }

    上段代码可以看到所有的被代理的方法,本例子中就是print、print2、从父类继承的finalize、equals、toString、

    hashCode、clone这几个方法)都被调用accept方法进行过滤,给每个方法返回一个整数,

    本例子是0或者1,从而选择不同的处理器。

    */

System.out.println(method.getDeclaringClass()+"类的"+method.getName()+"方法被检查过滤!");

    /* 

    如果调用是print方法,则要调用0号位的拦截器去处理

         */

    if("print".equals(method.getName()))   

    //0号位即LogHandler里面 new Callback[]{this,NoOp.INSTANCE}中的this,它在这个数组第0位置

return 1; 

    /*     

      1号位即LogHandler里面 new Callback[]{this,NoOp.INSTANCE}中的NoOp.INSTANCE,它在这个数组第1位置。

    NoOp.INSTANCE是指不做任何事情的拦截器。

在这里就是任何人都有权限访问print方法,即这个方法没有代理,直接调用

     */

return 0;   

    }

}

3)修改一下用户自定义的处理器类LogHandler.java

package reallyCglibProxy;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import jdkDynamicProxy.LonHanderReflectTool;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import net.sf.cglib.proxy.NoOp;
//相当于实现jdk的InvocationHandler接口
public class LogHandler implements MethodInterceptor{
	private Object delegate; //被代理类的对象
	//绑定被代理类的对象
	public Object bind(Object delegate)throws Exception{
		this.delegate=delegate;
/**
传进不同的拦截器(相当于JDK代理里面的处理器),NoOp.INSTANCE是cglib已经写好的不做任何事情的拦截器,传进去的new Callback[]是一个数组,现在数组有两个拦截器对象,分别是this,和NoOp.INSTANCE,它们分别在数组的第0位和第一位对应着自定义过滤器MyProxyFilter里的accept方法返回的整数,如果是0就调用this拦截器,如果是1就调用NoOp.INSTANCE所以自定义过滤器MyProxyFilter里的accept方法返回的整数最大就是拦截器数组的长度,比如本例子当中,只能是0或者1,不能是2,因为这个数组只有两个元素,最大位置就是1号位。
*/	
return Enhancer.create(delegate.getClass(), null, new MyProxyFilter(), new Callback[]{this,NoOp.INSTANCE});	}  
	//相当于InvocationHandler接口里面的invoke()方法
	
	public Object intercept(Object proxy, Method method, Object[] args,
			MethodProxy methodproxy) throws Throwable {
		Object result=null;
		System.out.println("我是代理人郭靖,开始代理");
		
		//method.invoke()或者methodproxy.invoke()都可以
		result=method.invoke(delegate,args);
		//result=methodproxy.invoke(delegate,args);
		System.out.println("我是代理人郭靖,代理完毕");

		//调用工具类反射jdk的Proxy生成的代理类,可参考《五》中这个工具类
		//LonHanderReflectTool.ReflectClass(proxy.getClass().getName());
		return result;
	}
}

 (4) 修改测试客户端(TestReallyCglibProxy.java)。

package reallyCglibProxy;
publicclass TestReallyCglibProxy {
	publicstaticvoid main(String[] args)throws Exception {
		RealSubject sub1=new RealSubject();
		LogHandler hander=new LogHandler();
		RealSubject sub2=(RealSubject)hander.bind(sub1);
		sub2.print();
        sub2.print2();
	}
}

输出结果:

class reallyCglibProxy.RealSubject类的print2方法被检查过滤!

class reallyCglibProxy.RealSubject类的print方法被检查过滤!

class java.lang.Object类的finalize方法被检查过滤!

class java.lang.Object类的equals方法被检查过滤!

class java.lang.Object类的toString方法被检查过滤!

class java.lang.Object类的hashCode方法被检查过滤!

class java.lang.Object类的clone方法被检查过滤!

被代理的人郭襄

我是代理人郭靖,开始代理

我是print2方法哦

我是代理人郭靖,代理完毕

 

       从结果可以看出,print方法没有被代理,print2方法被代理了,有了方法过滤器,被代理类被代理的方法(本文例子中就是print、print2、从父类继承的finalize、equals、toString、hashCode、clone这几个方法)都被调用accept方法进行过滤,给每个方法返回一个整数,本例子是0或者1,从而选择不同的处理器。

  

推荐文章:

浅析JAVA设计模式之代理模式(一)

http://blog.csdn.net/minidrupal/article/details/24807835

浅析JAVA设计模式之代理模式(二)

http://blog.csdn.net/minidrupal/article/details/24888271

浅析JAVA设计模式之代理模式(三)

http://blog.csdn.net/minidrupal/article/details/24985737

浅析JAVA设计模式之代理模式(四)

http://blog.csdn.net/minidrupal/article/details/25058433

浅析JAVA设计模式之代理模式(五)

http://blog.csdn.net/minidrupal/article/details/25093563

浅析JAVA设计模式之代理模式(六)

http://blog.csdn.net/minidrupal/article/details/27948355

Author: Piano
Introduction: 师

Sign: 读书得可道之道,实践不可道之道

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值