所谓动态,也就是说这个东西是可变的,或者说不是一生下来就有的。提到动态就不得不说静态,静态代理,个人觉得是指一个代理在程序中是事先写好的,不能变的,就像上一篇"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 + "\n" + 38 "gender:" + gender + "\n" + 39 "interests:" + interests + "\n" + 40 "rating:" + rating + "\n" + 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() + "的个人信息:\n" + 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() + "的个人信息(修改):\n" + jim.toString()); 73 74 Person kitty = getPersonInfo("kitty"); 75 System.out.println(kitty.getName() + "的个人信息:\n" + 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() + "的个人信息(修改):\n" + 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中的代码会决定将当前操作拦截,还是转发给真正的主题去处理。这就达到了我们之前所要求的的利用代理来进行权限控制的目的。