zoukankan      html  css  js  c++  java
  • Java学习笔记——动态代理

      所谓动态,也就是说这个东西是可变的,或者说不是一生下来就有的。提到动态就不得不说静态,静态代理,个人觉得是指一个代理在程序中是事先写好的,不能变的,就像上一篇"Java学习笔记——RMI"中的远程代理,其中客户端服务对象就是一个远程服务对象的代理,这个代理可以使得客户在操作时感觉像在操作本地对象一样,远程对象对于客户是透明的。我们可以看出这里的远程代理,是在程序中事先写好的,而本节我们要讨论的远程代理,是由JVM根据反射机制,在程序运行时动态生成的。(以上是本人的理解,如果有不正确的地方,还望读者指正)

      上面的类图展示了Java动态代理的中类之间的关系。其中Proxy就是由JVM产生的动态代理类,但是我们不能直接在其中定义我们想要的方法,因为这是由JVM自动生成的,而我们无权去修改,既然我们不能去修改Proxy,那么我们怎么才能让代码为我们服务呢?眼睛往右边看一点点,myInvocationHandler,对!我们可以把我们的代码放在这里。实际上我们每次去找代理为我们办事的时候,代理总会把我们的请求交给myInvocationHandler去处理。

      下面以一个例子来详细讲解。本例子摘自《Head First 设计模式》,本人对文章的代码进行少许的修改,并加入了自己的理解说明。

      例子中的要求:假设现在有一个在线交友社区系统,每个人可以在线浏览自己和他人的信息,还可以为他人进行打分,但是用户不能为自己进行打分。每个人都会将自己的基本信息展示出来,这些信息可以被自己修改,但是任何他人不得修改自己的个人信息,情理之中嘛。所以这里我们需要创建两个代理,来分别为自己和他人服务,从而可以实现权限控制的目的,使得每个人都可以执行自己权限以内的操作,而对于权限之外的操作是绝对不允许的。

      下面开始我们的设计:

      首先我们需要定义一个公共接口,这个接口用来被代理和真正的主题所使用,从而可以控制代理和正在的主题具有相同的操作方法。公共接口的代码如下: 

     1 package pattern.proxy.dynamic;
     2 
     3 /**
     4  * 人物对象接口
     5  * @author CS_Xiaochao
     6  *
     7  */
     8 public interface Person {
     9     
    10     /*
    11      * 声明了一系列的方法
    12      */
    13     String getName();
    14     String getGender();
    15     String getInterests();
    16     double getHotOrNotRating();   //用于获取用户的投票信息
    17     
    18     void setName(String name);
    19     void setGender(String gender);
    20     void setInterests(String interests);
    21     void setHotOrNotRating(int rating);  //设置用户的投票信息
    22 }

    然后我们来实现它,实现代码如下:

     1 package pattern.proxy.dynamic;
     2 
     3 /**
     4  * 主题
     5  * @author CS_Xiaochao
     6  *
     7  */
     8 public class PersonImpl implements Person{
     9 
    10     private String name;
    11     private String gender;
    12     private String interests;
    13     private int rating;
    14     private int ratingCount = 0;
    15 
    16     public PersonImpl() {
    17         super();
    18         // TODO Auto-generated constructor stub
    19     }
    20 
    21     public PersonImpl(String name, String gender, String interests, int rating,
    22             int ratingCount) {
    23         super();
    24         this.name = name;
    25         this.gender = gender;
    26         this.interests = interests;
    27         this.rating = rating;
    28         this.ratingCount = ratingCount;
    29     }    
    30 
    31     /**
    32      * 重载了toString方法,方便打印信息
    33      */
    34     @Override
    35     public String toString() {
    36         
    37         return "name:" + name + "
    " +
    38                 "gender:" + gender + "
    " +
    39                 "interests:" + interests + "
    " +
    40                 "rating:" + rating + "
    " +
    41                 "ratingCount:" + ratingCount;
    42     }
    43 
    44     public double getHotOrNotRating() {
    45         if (ratingCount == 0) {
    46             return 0;
    47         } else {
    48             return (double)rating / ratingCount;
    49         }
    50     }
    51 
    52     public void setHotOrNotRating(int rating) {
    53         this.rating = rating;
    54         ratingCount++;
    55     }
    56 
    57     public String getName() {
    58         return name;
    59     }
    60 
    61     public void setName(String name) {
    62         this.name = name;
    63     }
    64 
    65     public String getGender() {
    66         return gender;
    67     }
    68 
    69     public void setGender(String gender) {
    70         this.gender = gender;
    71     }
    72 
    73     public String getInterests() {
    74         return interests;
    75     }
    76 
    77     public void setInterests(String interests) {
    78         this.interests = interests;
    79     }
    80 
    81     public int getRating() {
    82         return rating;
    83     }
    84 
    85     public void setRating(int rating) {
    86         this.rating = rating;
    87     }
    88 
    89     public int getRatingCount() {
    90         return ratingCount;
    91     }
    92 
    93     public void setRatingCount(int ratingCount) {
    94         this.ratingCount = ratingCount;
    95     }
    96 }

      真正的干货来啦,让我们开始为Person创建动态代理。之前说过,我们需要两个代理来为我们服务,一个是为自己服务的OwnerInvocationHandler,这个是真正为代理做实际工作的对象,由前面类图中我们可以看到,当用户请求代理为我们服务时,代理实际上是把用户请求转交给了InvocationHandler,对于这里就是OwnerInvocationHandler,它控制用户自己可以修改、查看个人信息,但是绝对不允许为自己投票。具体代码如下:

     1 package pattern.proxy.dynamic;
     2 
     3 import java.lang.reflect.InvocationHandler;
     4 import java.lang.reflect.InvocationTargetException;
     5 import java.lang.reflect.Method;
     6 
     7 /**
     8  * 服务于自己的做实际工作的代理对象
     9  * @author CS_Xiaochao
    10  *
    11  */
    12 public class OwnerInvocationHandler implements InvocationHandler {
    13     
    14     private Person person;
    15 
    16     public OwnerInvocationHandler() {
    17         super();
    18         // TODO Auto-generated constructor stub
    19     }
    20 
    21     public OwnerInvocationHandler(Person person) {
    22         super();
    23         this.person = person;        
    24     }
    25         
    26     @Override
    27     public Object invoke(Object proxy, Method method, Object[] args)
    28             throws IllegalAccessException {
    29         Object result = null;
    30         try{
    31             if(method.getName().equals("setHotOrNotRating")){
    32                 //对于自己,是不允许为自己投票的
    33                 throw new IllegalAccessException();
    34             }else{
    35                 //但是自己可以修改、查看自己的个人信息
    36                 result = method.invoke(person, args);                
    37             }
    38         }catch(InvocationTargetException e){
    39             e.printStackTrace();
    40         }        
    41         return result;
    42     }
    43 
    44 }

      另一个是为他人服务的对象NonOwnerInvocationHandler,它允许别人查看我的个人信息,并为我投票,但是绝对不能修改我的个人信息。具体代码如下:

     1 package pattern.proxy.dynamic;
     2 
     3 import java.lang.reflect.InvocationHandler;
     4 import java.lang.reflect.InvocationTargetException;
     5 import java.lang.reflect.Method;
     6 
     7 /**
     8  * 为他人服务的真正做实际工作的对象
     9  * @author CS_Xiaochao
    10  *
    11  */
    12 public class NonOwnerInvocationHandler implements InvocationHandler {
    13 
    14     private Person person;
    15 
    16     public NonOwnerInvocationHandler() {
    17         super();
    18         // TODO Auto-generated constructor stub
    19     }
    20 
    21     public NonOwnerInvocationHandler(Person person) {
    22         super();
    23         this.person = person;
    24     }
    25 
    26     @Override
    27     public Object invoke(Object proxy, Method method, Object[] args)
    28             throws IllegalAccessException {
    29         Object result = null;
    30         try{        
    31             if(method.getName().equals("setHotOrNotRating") 
    32                     || method.getName().startsWith("get")){
    33                 //对于他人,可以查看我的个人信息,也可以为我投票
    34                 result = method.invoke(person, args);
    35             }else if(method.getName().startsWith("set")){
    36                 //但是绝对不可以修改我的个人信息
    37                 throw new IllegalAccessException();
    38             }
    39         }catch (InvocationTargetException e) {
    40             e.printStackTrace();
    41         }
    42         return result;
    43     }
    44 }

      最后,我们用一个驱动类来测试我们的代码,具体代码如下:

     1 package pattern.proxy.dynamic;
     2 
     3 import java.lang.reflect.Proxy;
     4 import java.util.HashMap;
     5 import java.util.Map;
     6 
     7 /**
     8  * 驱动测试类
     9  */
    10 public class MatchMakingTestDrive {
    11     
    12     //模拟数据库
    13     private static Map<String, Person> tb_person;    
    14     static{
    15         tb_person = new HashMap<String, Person>();
    16         tb_person.put("jim", new PersonImpl("jim", "M", "basketball", 0, 0));
    17         tb_person.put("lucy", new PersonImpl("lucy", "F", "dance", 0, 0));
    18         tb_person.put("lily", new PersonImpl("lily", "F", "sing", 0, 0));
    19         tb_person.put("jone", new PersonImpl("jone", "M", "football", 0, 0));
    20         tb_person.put("kitty", new PersonImpl("kitty", "F", "play piano", 0, 0));
    21     }
    22     /**
    23      * 模拟根据用户名来从数据库库中查找个人信息
    24      * @param name 用户名
    25      * @return
    26      */
    27     private static Person getPersonInfo(String name){
    28         Person person = null;
    29         if(tb_person != null){
    30             person = tb_person.get(name);
    31         }        
    32         return person;
    33     }
    34     
    35     /**
    36      * 创建一个Person的代理
    37      * @param person
    38      * @return
    39      */
    40     private static Person getOwnerProxy(Person person){
    41         return (Person) Proxy.newProxyInstance(
    42                 person.getClass().getClassLoader(), 
    43                 person.getClass().getInterfaces(), 
    44                 new OwnerInvocationHandler(person));
    45     }
    46     
    47     /**
    48      * 创建一个Person的代理
    49      * @param person
    50      * @return
    51      */
    52     private static Person getNonOwenerProxy(Person person){
    53         return (Person) Proxy.newProxyInstance(
    54                 person.getClass().getClassLoader(), 
    55                 person.getClass().getInterfaces(), 
    56                 new NonOwnerInvocationHandler(person));
    57     }
    58 
    59     /**
    60      * @param args
    61      */
    62     public static void main(String[] args) {        
    63         Person jim = getPersonInfo("jim");
    64         System.out.println(jim.getName() + "的个人信息:
    " + jim.toString());
    65         Person ownerProxy = getOwnerProxy(jim);        
    66         ownerProxy.setInterests("LOL");
    67         try{
    68             ownerProxy.setHotOrNotRating(0);  //设置自己的投票信息
    69         }catch (Exception e) {
    70             System.err.println("对不起,不允许设置自己的投票信息!");
    71         }
    72         System.out.println(jim.getName() + "的个人信息(修改):
    " + jim.toString());
    73         
    74         Person kitty = getPersonInfo("kitty");
    75         System.out.println(kitty.getName() + "的个人信息:
    " + kitty.toString());
    76         Person nonOwnerProxy = getNonOwenerProxy(kitty);        
    77         ownerProxy.setInterests("DOTA");
    78         try{
    79             nonOwnerProxy.setHotOrNotRating(5);  //设置别人的投票信息
    80         }catch (Exception e) {
    81             System.err.println("对不起,不允许设置自己的投票信息!");
    82         }
    83         System.out.println(kitty.getName() + "的个人信息(修改):
    " + kitty.toString());
    84     }
    85 
    86 }

      程序的执行结果如下:

     1 jim的个人信息:
     2 name:jim
     3 gender:M
     4 interests:basketball
     5 rating:0
     6 ratingCount:0
     7 对不起,不允许设置自己的投票信息!
     8 jim的个人信息(修改):
     9 name:jim
    10 gender:M
    11 interests:LOL
    12 rating:0
    13 ratingCount:0
    14 kitty的个人信息:
    15 name:kitty
    16 gender:F
    17 interests:play piano
    18 rating:0
    19 ratingCount:0
    20 kitty的个人信息(修改):
    21 name:kitty
    22 gender:F
    23 interests:play piano
    24 rating:5
    25 ratingCount:1

    接下来,我们来大致分析一下动态代理的执行过程:

    当我们执行代码:

    Person ownerProxy = getOwnerProxy(jim);

    的时候,实际上我们是在执行:

    private static Person getOwnerProxy(Person person){
            return (Person) Proxy.newProxyInstance(
                    person.getClass().getClassLoader(), 
                    person.getClass().getInterfaces(), 
                    new OwnerInvocationHandler(person));
        }

    上面的代码调用了Proxy类的newProxyInstance方法,其源码如下:

     1 public static Object newProxyInstance(ClassLoader loader,
     2                       Class<?>[] interfaces,
     3                       InvocationHandler h)
     4     throws IllegalArgumentException
     5     {
     6     if (h == null) {
     7         throw new NullPointerException();
     8     }
     9 
    10     /*
    11      * Look up or generate the designated proxy class.
    12      */
    13     Class cl = getProxyClass(loader, interfaces);
    14 
    15     /*
    16      * Invoke its constructor with the designated invocation handler.
    17      */
    18     try {
    19         Constructor cons = cl.getConstructor(constructorParams);
    20         return (Object) cons.newInstance(new Object[] { h });
    21     } catch (NoSuchMethodException e) {
    22         throw new InternalError(e.toString());
    23     } catch (IllegalAccessException e) {
    24         throw new InternalError(e.toString());
    25     } catch (InstantiationException e) {
    26         throw new InternalError(e.toString());
    27     } catch (InvocationTargetException e) {
    28         throw new InternalError(e.toString());
    29     }
    30     }

    其本质上是创建了一个代理类的实例,然后我们可以将其当做被代理类来使用,只是中间加入了一些我们事先定义的权限控制。然后我们通过代理来调用主题的相关方法:

    ownerProxy.setInterests("LOL");

    我们的目的是希望通过代理来执行我们需要的操作,但是代理会接着调用InvocationHandler的invoke方法,在这里也就是:

    public Object invoke(Object proxy, Method method, Object[] args)
                throws IllegalAccessException {
            Object result = null;
            try{
                if(method.getName().equals("setHotOrNotRating")){
                    //对于自己,是不允许为自己投票的
                    throw new IllegalAccessException();
                }else{
                    //但是自己可以修改、查看自己的个人信息
                    result = method.invoke(person, args);                
                }
            }catch(InvocationTargetException e){
                e.printStackTrace();
            }        
            return result;
        }

    其中参数中的proxy对应ownerProxy,method对应setInterests,args对应"LOL",这里就是真正的权限控制的地方,invoke中的代码会决定将当前操作拦截,还是转发给真正的主题去处理。这就达到了我们之前所要求的的利用代理来进行权限控制的目的。

  • 相关阅读:
    CodeIgniter框架对数据库查询结果进行统计
    PHP的内存回收(GC)
    使用ajax请求后端程序时,关于目标程序路径问题
    JavaScript中的各种X,Y,Width,Height
    Qt编写气体安全管理系统7-设备监控
    Qt编写气体安全管理系统6-地图监控
    Qt编写气体安全管理系统5-数据监控
    Qt编写气体安全管理系统4-通信协议
    Qt编写气体安全管理系统3-用户模块
    Qt编写气体安全管理系统2-界面框架
  • 原文地址:https://www.cnblogs.com/xiaochao-cs-whu/p/3770543.html
Copyright © 2011-2022 走看看