jdk 的动态代理和CGLIB代理

本文详细介绍了Java中的动态代理模式及其应用场景,并对比了JDK动态代理与CGLIB代理的不同之处。通过示例代码展示了如何使用这两种代理方式实现权限控制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

 

http://blog.youkuaiyun.com/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为该方法的参数数组。这个抽象方法在代理类中动态实现。


(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)。

 

 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代码 复制代码   收藏代码
  1. public class TableDAO {   
  2.     public void create(){   
  3.         System.out.println("create() is running !");   
  4.     }   
  5.     public void query(){   
  6.         System.out.println("query() is running !");   
  7.     }   
  8.     public void update(){   
  9.         System.out.println("update() is running !");   
  10.     }   
  11.     public void delete(){   
  12.         System.out.println("delete() is running !");   
  13.     }   
  14. }  


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

Java代码 复制代码   收藏代码
  1. public class TableDAOFactory {   
  2.     private static TableDAO tDao = new TableDAO();   
  3.     public static TableDAO getInstance(){   
  4.         return tDao;   
  5.     }   
  6. }  


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

Java代码 复制代码   收藏代码
  1. public class Client {   
  2.   
  3.     public static void main(String[] args) {   
  4.         TableDAO tableDao = TableDAOFactory.getInstance();   
  5.         doMethod(tableDao);   
  6.     }   
  7.     public static void doMethod(TableDAO dao){   
  8.         dao.create();   
  9.         dao.query();   
  10.         dao.update();   
  11.         dao.delete();   
  12.     }   
  13. }  

 


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

Java代码 复制代码   收藏代码
  1. public class AuthProxy implements MethodInterceptor {   
  2.     private String name ;   
  3.     //传入用户名称   
  4.     public AuthProxy(String name){   
  5.         this.name = name;   
  6.     }   
  7.     public Object intercept(Object arg0, Method arg1, Object[] arg2,   
  8.             MethodProxy arg3) throws Throwable {   
  9.         //用户进行判断   
  10.         if(!"张三".equals(name)){   
  11.             System.out.println("你没有权限!");   
  12.             return null;   
  13.         }   
  14.         return arg3.invokeSuper(arg0, arg2);   
  15.     }   
  16. }  

 


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

Java代码 复制代码   收藏代码
  1. public static TableDAO getAuthInstance(AuthProxy authProxy){   
  2.     Enhancer en = new Enhancer();   
  3.     //进行代理   
  4.     en.setSuperclass(TableDAO.class);   
  5.     en.setCallback(authProxy);   
  6.     //生成代理实例   
  7.     return (TableDAO)en.create();   
  8. }  

 

 


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

Java代码 复制代码   收藏代码
  1. public static void haveAuth(){   
  2.     TableDAO tDao = TableDAOFactory.getAuthInstance(new AuthProxy("张三"));   
  3.     doMethod(tDao);   
  4. }   
  5. public static void haveNoAuth(){   
  6.     TableDAO tDao = TableDAOFactory.getAuthInstance(new AuthProxy("李四"));   
  7.     doMethod(tDao);   
  8. }  

 

 


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

Java代码 复制代码   收藏代码
  1. public class AuthProxyFilter implements CallbackFilter{   
  2.     public int accept(Method arg0) {   
  3.         if(!"query".equalsIgnoreCase(arg0.getName()))   
  4.             return 0;   
  5.         return 1;   
  6.     }   
  7.   
  8. }  

 


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

Java代码 复制代码   收藏代码
  1. public static TableDAO getAuthInstanceByFilter(AuthProxy authProxy){   
  2.     Enhancer en = new Enhancer();   
  3.     en.setSuperclass(TableDAO.class);   
  4.     en.setCallbacks(new Callback[]{authProxy,NoOp.INSTANCE});   
  5.     en.setCallbackFilter(new AuthProxyFilter());   
  6.     return (TableDAO)en.create();   
  7. }  

 


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

Java代码 复制代码   收藏代码
  1. public static void haveAuthByFilter(){   
  2.     TableDAO tDao = TableDAOFactory.getAuthInstanceByFilter(new AuthProxy("张三"));   
  3.     doMethod(tDao);   
  4.   
  5.     tDao = TableDAOFactory.getAuthInstanceByFilter(new AuthProxy("李四"));   
  6.     doMethod(tDao);   
  7. }  


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

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值