前言:本篇文章为阅读《Head First设计模式》一书中的代理模式一章后整理而来,本篇博文主要介绍该章节提到的动态代理(保护代理),后续会补上该章节中讲到的远程代理和虚拟代理。
一、使用Java API的代理,创建一个保护代理
Java在java.lang.reflect包中有自己的代理支持,通过这个包可以在运行时动态的创建代理类,实现一个或多个接口,并将方法的调用转发到所指定的类。因为实际的代理类是在运行时创建的,因此称这种Java技术为:动态代理。
接下来要利用Java的动态代理创建一个代理实现(保护代理)。在此之前,先来看一下类图,了解一下动态代理是怎么一回事。
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接口:
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));
}
}
执行结果: