Java JDK动态代理和cglib动态代理

Java JDK动态代理和cglib动态代理

http://m635674608.iteye.com/blog/1435221

jdk的代理分为静态代理和动态代理,静态代理用的很少,一般都是动态代理,CGLIB代理是生成的目标类的子类,所以类和方法不能声明为final的,要不然就会有问题jdk的代理是必须要实现接口的,而CGLIB不是,但必须要引入jar包,spring中默认使用jdk 的动态代理(实现接口了),除强制设置CGLIB的代理

http://blog.csdn.net/feng_sundy/archive/2007/02/07/1504332.aspx

代理模式

代理模式的作用是:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个客户不想或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。

代理模式一般涉及到的角色有:

抽象角色:声明真实对象和代理对象的共同接口;

代理角色:代理对象角色内部含有对真实对象的引用,从而可以操作真实对象,同时代理对象提供与真实对象相同的接口以便在任何时刻都能代替真实对象。同时,代理对象可以在执行真实对象操作时,附加其他的操作,相当于对真实对象进行封装。

真实角色:代理角色所代表的真实对象,是我们最终要引用的对象。(参见文献1)

以下以《Java与模式》中的示例为例:

抽象角色:

abstract public class Subject

{

abstract public void request();

}

真实角色:实现了Subject的request()方法。

public class RealSubject extends Subject

{

public RealSubject()

{

}

public void request()

{

System.out.println("From real subject.");

}

}

代理角色:

public class ProxySubject extends Subject

{

private RealSubject realSubject; //以真实角色作为代理角色的属性

public ProxySubject()

{

}

public void request()//该方法封装了真实对象的request方法

{

preRequest();

if( realSubject == null )

{

realSubject = new RealSubject();

}

realSubject.request(); //此处执行真实对象的request方法

postRequest();

}

private void preRequest()

{

//something you want to do before requesting

}

private void postRequest()

{

//something you want to do after requesting

}

}

客户端调用:

Subject sub=new ProxySubject();

Sub.request();

由以上代码可以看出,客户实际需要调用的是RealSubject类的request()方法,现在用ProxySubject来代理RealSubject类,同样达到目的,同时还封装了其他方法(preRequest(),postRequest()),可以处理一些其他问题。

另外,如果要按照上述的方法使用代理模式,那么真实角色必须是事先已经存在的,并将其作为代理对象的内部属性。但是实际使用时,一个真实角色必须对应一个代理角色,如果大量使用会导致类的急剧膨胀;此外,如果事先并不知道真实角色,该如何使用代理呢?这个问题可以通过Java的动态代理类来解决。

2.动态代理类

Java动态代理类位于Java.lang.reflect包下,一般主要涉及到以下两个类:

(1).Interface InvocationHandler:该接口中仅定义了一个方法Object:invoke(Object obj,Method method, J2EEjava语言JDK1.4APIjavalangObject.html">Object[] args)。在实际使用时,第一个参数obj一般是指代理类,method是被代理的方法,如上例中的request(),args为该方法的参数数组。这个抽象方法在代理类中动态实现。

http://llying.iteye.com/blog/220452

CGlib是什么?CGlib是一个强大的,高性能,高质量的Code生成类库。它可以在运行期扩展Java类与实现Java接口。当然这些实际的功能是asm所提供的,asm又是什么?Java字节码操控框架,ASM是一套java字节码生成架构,它可以动态生成二进制格式的stub类或其它代理类,或者在类被java虚拟机装入内存之前,动态修改类。具体是什么大家可以上网查一查,毕竟我们这里所要讨论的是cglib,cglib就是封装了asm,简化了asm的操作,实现了在运行期动态生成新的class。可能大家还感觉不到它的强大,现在就告诉你。实际上CGlib为spring aop提供了底层的一种实现;为hibernate使用cglib动态生成VO/PO (接口层对象)。下面我们将通过一个具体的事例来看一下CGlib体验一下CGlib。* CGlib 2.13* ASM 2.23以一个实例在简单介绍下cglib的应用。我们模拟一个虚拟的场景,模拟对表的操作。1. 开始我们对表提供了CRUD方法。我们现在创建一个对Table操作的DAO类。

Java代码

    publicclassTableDAO{ publicvoidcreate(){ System.out.println("create()isrunning!"); } publicvoidquery(){ System.out.println("query()isrunning!"); } publicvoidupdate(){ System.out.println("update()isrunning!"); } publicvoiddelete(){ System.out.println("delete()isrunning!"); } }

OK,它就是一个javaBean,提供了CRUD方法的javaBean。下面我们创建一个DAO工厂,用来生成DAO实例。

Java代码

    publicclassTableDAOFactory{ privatestaticTableDAOtDao=newTableDAO(); publicstaticTableDAOgetInstance(){ returntDao; } }

接下来我们创建客户端,用来调用CRUD方法。

Java代码

    publicclassClient{ publicstaticvoidmain(String[]args){ TableDAOtableDao=TableDAOFactory.getInstance(); doMethod(tableDao); } publicstaticvoiddoMethod(TableDAOdao){ dao.create(); dao.query(); dao.update(); dao.delete(); } }

OK,完成了,CRUD方法完全被调用了。当然这里并没有CGlib的任何内容。问题不会这么简单的就结束,新的需求来临了。2.变化随之而来,Boss告诉我们这些方法不能开放给用户,只有“张三”才有权使用。阿~!怎么办,难道我们要在每个方法上面进行判断吗?好像这么做也太那啥了吧,对了对了Proxy可能是最好的解决办法。jdk的代理就可以解决了。 好了我们来动手改造吧。等等jdk的代理需要实现接口,这样,我们的dao类需要改变了。既然不想改动dao又要使用代理,我们这就请出CGlib。我们只需新增一个权限验证的方法拦截器。

Java代码

    publicclassAuthProxyimplementsMethodInterceptor{ privateStringname; //传入用户名称publicAuthProxy(Stringname){ this.name=name; } publicObjectintercept(Objectarg0,Methodarg1,Object[]arg2, MethodProxyarg3)throwsThrowable{ //用户进行判断if(!"张三".equals(name)){ System.out.println("你没有权限!"); returnnull; } returnarg3.invokeSuper(arg0,arg2); } }

当然不能忘了对我们的dao工厂进行修改,我们提供一个使用代理的实例生成方法

Java代码

    publicstaticTableDAOgetAuthInstance(AuthProxyauthProxy){ Enhanceren=newEnhancer(); //进行代理en.setSuperclass(TableDAO.class); en.setCallback(authProxy); //生成代理实例return(TableDAO)en.create(); }

我们这就可以看看客户端的实现了。添加了两个方法用来验证不同用户的权限。

Java代码

    publicstaticvoidhaveAuth(){ TableDAOtDao=TableDAOFactory.getAuthInstance(newAuthProxy("张三")); doMethod(tDao); } publicstaticvoidhaveNoAuth(){ TableDAOtDao=TableDAOFactory.getAuthInstance(newAuthProxy("李四")); doMethod(tDao); }

OK,"张三"的正常执行,"李四"的没有执行。看到了吗?简单的aop就这样实现了难道就这样结束了么?3. Boss又来训话了,不行不行,现在除了"张三"其他人都用不了了,现在不可以这样。他们都来向我反映了,必须使用开放查询功能。哈哈,现在可难不倒我们了,因为我们使用了CGlib。当然最简单的方式是去修改我们的方法拦截器,不过这样会使逻辑变得复杂,且不利于维护。还好CGlib给我们提供了方法过滤器(CallbackFilter),CallbackFilte可以明确表明,被代理的类中不同的方法,被哪个拦截器所拦截。下面我们就来做个过滤器用来过滤query方法。

Java代码

    publicclassAuthProxyFilterimplementsCallbackFilter{ publicintaccept(Methodarg0){ if(!"query".equalsIgnoreCase(arg0.getName())) return0; return1; } }

OK,可能大家会对return 0 or 1感到困惑,用到的时候就会讲解,当然下面就会用到了。我们在工场中新增一个使用了过滤器的实例生成方法。

Java代码

    publicstaticTableDAOgetAuthInstanceByFilter(AuthProxyauthProxy){ Enhanceren=newEnhancer(); en.setSuperclass(TableDAO.class); en.setCallbacks(newCallback[]{authProxy,NoOp.INSTANCE}); en.setCallbackFilter(newAuthProxyFilter()); return(TableDAO)en.create(); }

看到了吗setCallbacks中定义了所使用的拦截器,其中NoOp.INSTANCE是CGlib所提供的实际是一个没有任何操作的拦截器,他们是有序的。一定要和CallbackFilter里面的顺序一致。明白了吗?上面return返回的就是返回的顺序。也就是说如果调用query方法就使用NoOp.INSTANCE进行拦截。现在看一下客户端代码。

Java代码

    publicstaticvoidhaveAuthByFilter(){ TableDAOtDao=TableDAOFactory.getAuthInstanceByFilter(newAuthProxy("张三")); doMethod(tDao); tDao=TableDAOFactory.getAuthInstanceByFilter(newAuthProxy("李四")); doMethod(tDao); }

ok,现在"李四"也可以使用query方法了,其他方法仍然没有权限。哈哈,当然这个代理的实现没有任何侵入性,无需强制让dao去实现接口。

—————————————————————————————–

动态代理有2种实现方法:1.使用jdk自带的反射2.cglib技术动态生成字节码。spring的aop就是使用了动态代理技术实现的。

下面转帖一个,里面说的很详细(http://www.cnblogs.com/jqyp/archive/2010/08/20/1805041.html):

(另外一篇http://www.iteye.com/topic/1116696详细介绍了aop 的多种实现方法,非常好。)

代理模式代理模式是常用的java设计模式,他的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。按照代理的创建时期,代理类可以分为两种。静态代理:由程序员创建或特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。动态代理:在程序运行时,运用反射机制动态创建而成。

首先看一下静态代理:1、Count.java

Java代码

    packagenet.battier.dao;/***定义一个账户接口**@authorAdministrator**/publicinterfaceCount{//查看账户方法publicvoidqueryCount();//修改账户方法publicvoidupdateCount();}

2、CountImpl.java

Java代码

    packagenet.battier.dao.impl;importnet.battier.dao.Count;/***委托类(包含业务逻辑)**@authorAdministrator**/publicclassCountImplimplementsCount{@OverridepublicvoidqueryCount(){System.out.println("查看账户方法…");}@OverridepublicvoidupdateCount(){System.out.println("修改账户方法…");}}、CountProxy.javapackagenet.battier.dao.impl;importnet.battier.dao.Count;/***这是一个代理类(增强CountImpl实现类)**@authorAdministrator**/publicclassCountProxyimplementsCount{privateCountImplcountImpl;/***覆盖默认构造器**@paramcountImpl*/publicCountProxy(CountImplcountImpl){this.countImpl=countImpl;}@OverridepublicvoidqueryCount(){System.out.println("事务处理之前");//调用委托类的方法;countImpl.queryCount();System.out.println("事务处理之后");}@OverridepublicvoidupdateCount(){System.out.println("事务处理之前");//调用委托类的方法;countImpl.updateCount();System.out.println("事务处理之后");}}

3、TestCount.java

Java代码

    packagenet.battier.test;importnet.battier.dao.impl.CountImpl;importnet.battier.dao.impl.CountProxy;/***测试Count类**@authorAdministrator**/publicclassTestCount{publicstaticvoidmain(String[]args){CountImplcountImpl=newCountImpl();CountProxycountProxy=newCountProxy(countImpl);countProxy.updateCount();countProxy.queryCount();}}

观察代码可以发现每一个代理类只能为一个接口服务,这样一来程序开发中必然会产生过多的代理,而且,所有的代理操作除了调用的方法不一样之外,其他的操作都一样,则此时肯定是重复代码。解决这一问题最好的做法是可以通过一个代理类完成全部的代理功能,那么此时就必须使用动态代理完成。再来看一下动态代理:JDK动态代理中包含一个类和一个接口:InvocationHandler接口:public interface InvocationHandler {public Object invoke(Object proxy,Method method,Object[] args) throws Throwable;}参数说明:Object proxy:指被代理的对象。Method method:要调用的方法Object[] args:方法调用时所需要的参数

可以将InvocationHandler接口的子类想象成一个代理的最终操作类,替换掉ProxySubject。

Proxy类:Proxy类是专门完成代理的操作类,可以通过此类为一个或多个接口动态地生成实现类,此类提供了如下的操作方法:public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h) throws IllegalArgumentException参数说明:ClassLoader loader:类加载器Class<?>[] interfaces:得到全部的接口InvocationHandler h:得到InvocationHandler接口的子类实例

Ps:类加载器在Proxy类中的newProxyInstance()方法中需要一个ClassLoader类的实例,ClassLoader实际上对应的是类加载器,在Java中主要有一下三种类加载器;Booststrap ClassLoader:此加载器采用C++编写,一般开发中是看不到的;Extendsion ClassLoader:用来进行扩展类的加载,一般对应的是jre\lib\ext目录中的类;AppClassLoader:(默认)加载classpath指定的类,是最常使用的是一种加载器。

动态代理与静态代理类对照的是动态代理类,动态代理类的字节码在程序运行时由Java反射机制动态生成,无需程序员手工编写它的源代码。动态代理类不仅简化了编程工作,而且提高了软件系统的可扩展性,因为Java 反射机制可以生成任意类型的动态代理类。java.lang.reflect 包中的Proxy类和InvocationHandler 接口提供了生成动态代理类的能力。

动态代理示例:1、BookFacade.java

Java代码

    packagenet.battier.dao;publicinterfaceBookFacade{publicvoidaddBook();}

2、BookFacadeImpl.java

Java代码

    packagenet.battier.dao.impl;importnet.battier.dao.BookFacade;publicclassBookFacadeImplimplementsBookFacade{@OverridepublicvoidaddBook(){System.out.println("增加图书方法。。。");}}、BookFacadeProxy.javapackagenet.battier.proxy;importjava.lang.reflect.InvocationHandler;importjava.lang.reflect.Method;importjava.lang.reflect.Proxy;/***JDK动态代理代理类**@authorstudent**/publicclassBookFacadeProxyimplementsInvocationHandler{privateObjecttarget;/***绑定委托对象并返回一个代理类*@paramtarget*@return*/publicObjectbind(Objecttarget){this.target=target;//取得代理对象returnProxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);//要绑定接口(这是一个缺陷,cglib弥补了这一缺陷)}@Override/***调用方法*/publicObjectinvoke(Objectproxy,Methodmethod,Object[]args)throwsThrowable{Objectresult=null;System.out.println("事物开始");//执行方法result=method.invoke(target,args);

      if(method.getName().equals("addBook")){System.out.println("调用了addBook方法。。。。");}

    System.out.println("事物结束");returnresult;}}

3、TestProxy.java

Java代码

    packagenet.battier.test;importnet.battier.dao.BookFacade;importnet.battier.dao.impl.BookFacadeImpl;importnet.battier.proxy.BookFacadeProxy;publicclassTestProxy{publicstaticvoidmain(String[]args){BookFacadeProxyproxy=newBookFacadeProxy();BookFacadebookProxy=(BookFacade)proxy.bind(newBookFacadeImpl());bookProxy.addBook();}}

但是,JDK的动态代理依靠接口实现,如果有些类并没有实现接口,则不能使用JDK代理,这就要使用cglib动态代理了。

Cglib动态代理JDK的动态代理机制只能代理实现了接口的类,而不能实现接口的类就不能实现JDK的动态代理,cglib是针对类来实现代理的,他的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理。示例1、BookFacadeCglib.java

Java代码

    packagenet.battier.dao;publicinterfaceBookFacade{publicvoidaddBook();}

2、BookCadeImpl1.java

Java代码

    packagenet.battier.dao.impl;/***这个是没有实现接口的实现类**@authorstudent**/publicclassBookFacadeImpl1{publicvoidaddBook(){System.out.println("增加图书的普通方法…");}}

3、BookFacadeProxy.java

Java代码

    packagenet.battier.proxy;importjava.lang.reflect.Method;importnet.sf.cglib.proxy.Enhancer;importnet.sf.cglib.proxy.MethodInterceptor;importnet.sf.cglib.proxy.MethodProxy;/***使用cglib动态代理**@authorstudent**/publicclassBookFacadeCglibimplementsMethodInterceptor{privateObjecttarget;/***创建代理对象**@paramtarget*@return*/publicObjectgetInstance(Objecttarget){this.target=target;Enhancerenhancer=newEnhancer();enhancer.setSuperclass(this.target.getClass());//回调方法enhancer.setCallback(this);//创建代理对象returnenhancer.create();}@Override//回调方法publicObjectintercept(Objectobj,Methodmethod,Object[]args,MethodProxyproxy)throwsThrowable{System.out.println("事物开始");proxy.invokeSuper(obj,args);

      if(method.getName().equals("addBook")){System.out.println("调用了addBook方法。。。。");}

    System.out.println("事物结束");returnnull;}}

4、TestCglib.java

Java代码

    packagenet.battier.test;importnet.battier.dao.impl.BookFacadeImpl1;importnet.battier.proxy.BookFacadeCglib;publicclassTestCglib{publicstaticvoidmain(String[]args){BookFacadeCglibcglib=newBookFacadeCglib();BookFacadeImpl1bookCglib=(BookFacadeImpl1)cglib.getInstance(newBookFacadeImpl1());bookCglib.addBook();}}

(2).Proxy:该类即为动态代理类,作用类似于上例中的ProxySubject,其中主要包含以下内容:

Protected Proxy(InvocationHandler h):构造函数,估计用于给内部的h赋值。

Static Class getProxyClass (ClassLoader loader, Class[] interfaces):获得一个代理类,其中loader是类装载器,interfaces是真实类所拥有的全部接口的数组。

Static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h):返回代理类的一个实例,返回后的代理类可以当作被代理类使用(可使用被代理类的在Subject接口中声明过的方法)。

所谓Dynamic Proxy是这样一种class:它是在运行时生成的class,在生成它时你必须提供一组interface给它,然后该class就宣称它实现了这些interface。你当然可以把该class的实例当作这些interface中的任何一个来用。当然啦,这个Dynamic Proxy其实就是一个Proxy,它不会替你作实质性的工作,在生成它的实例时你必须提供一个handler,由它接管实际的工作。(参见文献3)

在使用动态代理类时,我们必须实现InvocationHandler接口,以第一节中的示例为例:

抽象角色(之前是抽象类,此处应改为接口):

public interface Subject

{

abstract public void request();

}

具体角色RealSubject:同上;

public class RealSubject extends Subject

{

public RealSubject()

{

}

public void request()

{

System.out.println("From real subject.");

}

}

代理角色:

import java.lang.reflect.Method;

import java.lang.reflect.InvocationHandler;

public class DynamicSubject implements InvocationHandler {

private Object sub;

public DynamicSubject() {

}

public DynamicSubject(Object obj) {

sub = obj;

}

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

System.out.println("before calling " + method);

method.invoke(sub,args);

System.out.println("after calling " + method);

return null;

}

}

该代理类的内部属性为Object类,实际使用时通过该类的构造函数DynamicSubject(Object obj)对其赋值;此外,在该类还实现了invoke方法,该方法中的

method.invoke(sub,args);

其实就是调用被代理对象的将要被执行的方法,方法参数sub是实际的被代理对象,args为执行被代理对象相应操作所需的参数。通过动态代理类,我们可以在调用之前或之后执行一些相关操作。

客户端:

import java.lang.reflect.InvocationHandler;

import java.lang.reflect.Proxy;

import java.lang.reflect.Constructor;

import java.lang.reflect.Method;

public class Client

{

static public void main(String[] args) throws Throwable

{

RealSubject rs = new RealSubject(); //在这里指定被代理类

InvocationHandler ds = new DynamicSubject(rs); //初始化代理类

Class cls = rs.getClass();

//以下是分解步骤

/*

Class c = Proxy.getProxyClass(cls.getClassLoader(),cls.getInterfaces()) ;

Constructor ct=c.getConstructor(new Class[]{InvocationHandler.class});

Subject subject =(Subject) ct.newInstance(new Object[]{ds});

*/

//以下是一次性生成

Subject subject = (Subject) Proxy.newProxyInstance(cls.getClassLoader(),

cls.getInterfaces(),ds );

subject.request();

}

通过这种方式,被代理的对象(RealSubject)可以在运行时动态改变,需要控制的接口(Subject接口)可以在运行时改变,控制的方式(DynamicSubject类)也可以动态改变,从而实现了非常灵活的动态代理关系(参见文献2)。

是不是因为心痛的麻木了,我才笑得最美丽。

Java JDK动态代理和cglib动态代理

相关文章:

你感兴趣的文章:

标签云: