【设计模式】代理模式(动态代理) 向右看齐 2022-09-29 05:24 202阅读 0赞 *前言*:本篇文章为阅读《Head First设计模式》一书中的代理模式一章后整理而来,本篇博文主要介绍该章节提到的动态代理(保护代理),后续会补上该章节中讲到的远程代理和虚拟代理。 -------------------- ## 一、使用Java API的代理,创建一个保护代理 ## Java在java.lang.reflect包中有自己的代理支持,通过这个包可以在运行时动态的创建代理类,实现一个或多个接口,并将方法的调用转发到所指定的类。因为实际的代理类是在运行时创建的,因此称这种Java技术为:动态代理。 接下来要利用Java的动态代理创建一个代理实现(保护代理)。在此之前,先来看一下类图,了解一下动态代理是怎么一回事。 ![这里写图片描述][SouthEast] Java为我们创建了Proxy类,但需要告诉Proxy类我们要做什么。但是我们不能将代码放入Proxy类中,因为Proxy不是我们直接创建的。所以这时需要将代码放入InvocationHandler中。InvocationHandler的工作是响应代理的任何调用,可以把InvocationHandler当成代理收到方法调用后,请求做实际工作的对象。 -------------------- ## 二、实现动态代理 ## 假设我们需要实现一个约会服务系统。该服务系统中涉及到一个接口PersonBean,通过该接口允许设置或获取一个人的信息。 PersonBean接口代码如下: package com.pattern.proxy.dynamic; /** * PersonBean接口,通过该接口允许设置或取得一个人的信息 * * @date:2017年3月13日 下午9:54:02 */ public interface PersonBean { // 获取姓名 String getName(); // 获取性别 String getGender(); // 获取兴趣爱好 String getInterests(); // HotOrNot评分 int getHotOrNotRating(); void setName(String name); void setGender(String gender); void setInterests(String interests); void setHotOrNotRating(int rating); } PersonBeanImpl实现PersonBean接口,代码如下: package com.pattern.proxy.dynamic; /** * 实现PersonBean接口 * * @date:2017年3月13日 下午10:04:12 */ public class PersonBeanImpl implements PersonBean { private String name; private String gender; private String interests; private int rating; private int ratingCount = 0; @Override public String getName() { return name; } @Override public String getGender() { return gender; } @Override public String getInterests() { return interests; } @Override public int getHotOrNotRating() { if(ratingCount == 0) return 0; return rating / ratingCount; } @Override public void setName(String name) { this.name = name; } @Override public void setGender(String gender) { this.gender = gender; } @Override public void setInterests(String interests) { this.interests = interests; } @Override public void setHotOrNotRating(int rating) { this.rating += rating; ratingCount++; } } 现在有如下需求,该系统允许用户设置自己的信息,但是不应该允许用户篡改别人的数据。但是HotOrNot评分则相反,用户不能更改自己的评分,但是可以给他人评分。在目前PersonBean实现中所有的方法都是公开的,任何人都可以调用。 所以现在我们需要解决这些问题,要修正这些问题,必须创建两个代理:一个代理访问自己的PersonBean对象,另一个访问其他用户的PersonBean对象。创建这种代理,我们必须使用Java API的动态代理。Java会为我们创建两个代理,我们只需要提供handler来处理代理转发来的方法。我们需要写两个InvocationHandler,其中一个给拥有者使用,另一个给非拥有者使用。当代理的方法被调用时,代理就会把这个调用转发给InvocationHandler,但这个并不是通过调用InvocationHandler的相应方法做到的。那是如何做到的?让我们看一下InvocationHandler接口: ![这里写图片描述][SouthEast 1] InvocatonHandler只有一个invoke()的方法,不管代理被调用的是何种方法,处理器被调用的一定是invoke()方法。工作过程如下: > 1.假设proxy的setHotOrNotRating()方法被调用。 > > > proxy.setHotOrNotRating(9); > > 2.proxy会接着调用InvocationHandler的invoke()方法。 > > > invoke(Object proxy, Method method, Object\[\] args) > > 3.handler决定如何处理请求。 > > > return method.invoke(person, args); 实现OwnerInvocationHandler,代码如下: package com.pattern.proxy.dynamic; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; /** * 调用处理器实现了InvocationHandler接口 * 当用户需要查看或设置自己的信息时,调用该处理器进行访问控制 * * @date:2017年3月13日 下午10:17:58 */ public class OwnerInvocationHandler implements InvocationHandler { private PersonBean personBean; // OwnerInvocationHandler持有PersonBean类对象的引用 public OwnerInvocationHandler(PersonBean personBean) { this.personBean = personBean; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws IllegalAccessException { try { if(method.getName().startsWith("get")) { // 用户允许查看自己的相关信息 return method.invoke(personBean, args); } else if(method.getName().equals("setHotOrNotRating")) { // 用户不能自己给自己打分 throw new IllegalAccessException(); } else if(method.getName().startsWith("set")) { // 用户可以对其他信息进行设置 return method.invoke(personBean, args); } } catch (InvocationTargetException e) { e.printStackTrace(); } // 如果调用的是其他的方法,一律处理,返回null return null; } } 实现NonOwnerInvocationHandler,代码如下: package com.pattern.proxy.dynamic; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; /** * 用户访问其他用户时通过该处理类进行访问处理 * * @date:2017年3月13日 下午10:37:45 */ public class NonOwnerInvocationHandler implements InvocationHandler { private PersonBean personBean; public NonOwnerInvocationHandler(PersonBean personBean) { this.personBean = personBean; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if(method.getName().startsWith("get")) { // 允许访问获取其他用户的信息 return method.invoke(personBean, args); } else if(method.getName().equals("setHotOrNotRating")) { // 可以给其他用户进行打分 return method.invoke(personBean, args); } else if(method.getName().startsWith("set")) { // 不能更改其他用户的信息 throw new IllegalAccessException(); } // 对其他的方法调用不处理,放回null return null; } } 接下来,我们需要创建动态Proxy类,并实例化Proxy对象。我们创建拥有者代理,该代理可以将它的方法调用转发给OwnerInvocationHandler,代码如下: /** * 获取用户处理自己信息的代理对象 * * 此方法需要一个PersonBean对象作为参数,然后返回 * 他的代理,因为代理和被代理对象(主题subject) * 实现了相同的接口,该方法最终返回一个PersonBean * * @param personBean * @return */ public PersonBean getOwnerProxy(PersonBean personBean) { return (PersonBean) Proxy.newProxyInstance( // 通过Proxy类的静态方法newProxyInstance创建代理 personBean.getClass().getClassLoader(), // 将PersonBean的类载入器当作参数 personBean.getClass().getInterfaces(), // 代理需要实现的接口 new OwnerInvocationHandler(personBean)); // 处理器 } 非拥有者的代理类创建代码如下: /** * 获取用户处理他人信息的代理对象 * * @param personBean * @return */ public PersonBean getNonOwnerProxy(PersonBean personBean) { return (PersonBean) Proxy.newProxyInstance( personBean.getClass().getClassLoader(), personBean.getClass().getInterfaces(), new NonOwnerInvocationHandler(personBean)); } 最后我们来测试下,测试代码如下: package com.pattern.proxy.dynamic; import java.lang.reflect.Proxy; public class ProxyTestDriver { private PersonBean personBean; public ProxyTestDriver() { /* * 初始化数据库 * initializeDatabase(); * */ personBean = new PersonBeanImpl(); personBean.setName("Joe Javabean"); personBean.setInterests("Singing"); personBean.setGender("male"); personBean.setHotOrNotRating(7); } public static void main(String[] args) { ProxyTestDriver testDriver = new ProxyTestDriver(); testDriver.driver(); } public void driver() { /* * 从数据库中读取一个人信息 * PersonBean personBean = getPersonFromDataBase("Joe Javabean"); */ PersonBean joe = personBean; // 创建处理自己信息的代理对象 PersonBean ownerProxy = getOwnerProxy(joe); System.out.println("Name is " + ownerProxy.getName()); System.out.println("before set interests:" + ownerProxy.getInterests()); // 用户修改了自己的爱好 ownerProxy.setInterests("reading"); System.out.println("after set interests:" + ownerProxy.getInterests()); try { // 用户尝试修改自己的评分 ownerProxy.setHotOrNotRating(9); } catch (Exception e) { System.out.println("can't set rating from owner proxy"); } System.out.println("rating is " + ownerProxy.getHotOrNotRating()); System.out.println("========================================"); // 创建处理他人信息的代理类 PersonBean nonOwnerProxy = getNonOwnerProxy(joe); System.out.println("Name is " + nonOwnerProxy.getName()); System.out.println("before set interests:" + nonOwnerProxy.getInterests()); try { // 尝试修改他人兴趣 nonOwnerProxy.setInterests("painting"); } catch (Exception e) { System.out.println("can't set interests from non owner proxy"); } System.out.println("after set interests:" + nonOwnerProxy.getInterests()); nonOwnerProxy.setHotOrNotRating(5); System.out.println("rating is " + nonOwnerProxy.getHotOrNotRating()); } /** * 获取用户处理自己信息的代理对象 * * 此方法需要一个PersonBean对象作为参数,然后返回 * 他的代理,因为代理和被代理对象(主题subject) * 实现了相同的接口,该方法最终返回一个PersonBean * * @param personBean * @return */ public PersonBean getOwnerProxy(PersonBean personBean) { return (PersonBean) Proxy.newProxyInstance( // 通过Proxy类的静态方法newProxyInstance创建代理 personBean.getClass().getClassLoader(), // 将PersonBean的类载入器当作参数 personBean.getClass().getInterfaces(), // 代理需要实现的接口 new OwnerInvocationHandler(personBean)); // 处理器 } /** * 获取用户处理他人信息的代理对象 * * @param personBean * @return */ public PersonBean getNonOwnerProxy(PersonBean personBean) { return (PersonBean) Proxy.newProxyInstance( personBean.getClass().getClassLoader(), personBean.getClass().getInterfaces(), new NonOwnerInvocationHandler(personBean)); } } 执行结果: ![这里写图片描述][SouthEast 2] [SouthEast]: /images/20220708/7498467322c6403cb0648ba22ec7feb0.png [SouthEast 1]: /images/20220708/b3fcb85babd3431a8c924ec5d7b956ce.png [SouthEast 2]: /images/20220708/bfc7128e50aa4fc2922d94d8221844bd.png
还没有评论,来说两句吧...