Java在java.lang.reflect包中有自己的代理支持,利用这个包我们可以在运行时动态地创建一个代理类,实现一个或多个接口,并将方法的调用转发到你所指定的类(继承了InvocationHandler的处理器类),因为实际的代理类是在运行时创建的,我们称这个java技术为:动态代理。在代码开始执行时,还没有proxy类,它是根据需要从我们传入的接口集创建的。

从Java1.2开始RMI可以利用reflection API直接将客户调用分派给远程服务,我们不需要真的产生skeleton。

到了Java5,连stub都不需要产生了,因为此时的RMI和动态代理搭配使用,动态代理动态产生stub,远程对象的stub是java.lang.reflect.Proxy实例,连同一个调用处理器,它是自动产生的,用来处理所有把客户的本地调用变成远程调用的细节。所以我们不再需要rmic。客户和远程对象的沟通的一切都在幕后处理掉了。

在本章,我们就利用java的动态代理创建一个保护代理。创建保护代理,我们必须使用Java API的动态代理。保护代理就是保护对象不要直接访问主题。保护代理可以控制在每一种情况下允许哪一种请求。

保护模式的类图:

代理模式——保护代理(三)_java


类图中的代理包含两个类,一个是Proxy类,另一个是RealInvocationHandler类。其中Proxy类是由Java产生的,而且实现了完整的Subject接口。接口InvocationHandler也是java提供的,RealInvocationHandler实现了InvocationHandler接口,Proxy上的任何方法调用都会被传入此类。RealInvocationHandler控制对象RealSubject方法的访问

我们举一例子来说明保护代理模式。假如我们想控制一个博客的作者不能为自己的文章投票,其他人才可以投票,这样一个场景。我们用保护代理模式控制这两种人可以操作的权限。

第一步:我们设计一个ArticleBean。

接口ArticleBean:

public interface ArticleBean {
String getAuthor();
String getGender();
String getArticleName();
int getTicket();

void setAuthor(String author);
void setGender(String gender);
void setArticleName(String articleName);
void setTicket(int ticket);
}

实现ArticleBeanImpl:

package impl;

import inter.ArticleBean;

public class ArticleBeanImpl implements ArticleBean {

private String author;
private String gender;
private String articleName;
private int ticket = 0;

@Override
public String getAuthor() {
return author;
}

@Override
public String getGender() {
return gender;
}

@Override
public String getArticleName() {
return articleName;
}

@Override
public int getTicket() {
return ticket;
}

@Override
public void setAuthor(String author) {
this.author = author;
}

@Override
public void setGender(String gender) {
this.gender = gender;
}

@Override
public void setArticleName(String articleName) {
this.articleName = articleName;
}

@Override
public void setTicket(int ticket) {
this.ticket = this.ticket + ticket;
}
}

第二步:创建InvocationHandler

这里要创建两个处理器类。其中一个是OwnerInvocationHandler文章拥有者,在处理器中要阻止它调用setTicket()投票方法;另外一个是NonOwnerInvocationHandler投票人,在它的处理器中要阻止它调用除了setTicket方法外的set方法,这样是为了禁止投票人去修改文章的相关信息,投票人是没有权利这么做的。这些处理器类都要继承Java提供的InvocationHandler接口,实现其中的invoke方法,到时通过代理类Proxy调用的方法都会被传入处理器类中来。处理器类是我们唯一能直接访问到真实主题的地方。
注意:
invoke(Object proxy,Method method,Object[] args)的第一个参数proxy其实是没有用处的,因为它一直都是null,methd.invoke()方法,需要把原来的具体实现类作为参数传递进去,method.invoke(articleBean,args)相当于articleBean.method(args)。

OwnerInvocationHandler文章拥有者:

package impl;

import inter.ArticleBean;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class OwnerInvocationHandler implements InvocationHandler {

private ArticleBean articleBean;

public OwnerInvocationHandler(ArticleBean articleBean){
this.articleBean = articleBean;
}


@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if(method.getName().startsWith("get")){
return method.invoke(articleBean,args);
}else if(method.getName().startsWith("setTicket")){
//作者不能投自己票
System.out.println("不能给自己的文章投票!");
throw new IllegalAccessException();
}else if(method.getName().startsWith("set")){
return method.invoke(articleBean,args);
}

}catch (Exception e){
e.printStackTrace();
}
return null;
}
}

NonOwnerInvocationHandler投票人:

package impl;


import inter.ArticleBean;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class NonOwnerInvocationHandler implements InvocationHandler {

private ArticleBean article;

public NonOwnerInvocationHandler(ArticleBean article){
this.article = article;
}


@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

try{
if(method.getName().startsWith("get")){
return method.invoke(article,args);//对查询是全部提供的
}else if(method.getName().startsWith("setTicket")){
return method.invoke(article,args); //对投票方法是开放的
}else if(method.getName().startsWith("set")){
throw new IllegalAccessException(); //对于除作者以外的人,其他set方法是关闭的
}
}catch (Exception e){
e.printStackTrace();
}
return null;
}
}

第三步:创建Proxy类并实例化Proxy对象

获取文章拥有者的代理:

//文章所有者的代理
private ArticleBean getOwnerProxy(ArticleBean articleBean){
ArticleBean ownerProxy = (ArticleBean)Proxy.newProxyInstance(articleBean.getClass().getClassLoader(),articleBean.getClass().getInterfaces(),new OwnerInvocationHandler(articleBean));
return ownerProxy;
}

获取投票人的代理:

//投票人的代理
private ArticleBean getNonOwnerProxy(ArticleBean articleBean){
ArticleBean nonOwnerProxy = (ArticleBean) Proxy.newProxyInstance(articleBean.getClass().getClassLoader(),articleBean.getClass().getInterfaces(),new NonOwnerInvocationHandler(articleBean));
return nonOwnerProxy;
}

通过代理来控制访问权限。Proxy代理可以访问的方法就是newProxyInstance方法的第二个参数传入的接口列表,具体调用哪个对象的这些接口就是第一个参数传入的对象,第三个参数是处理器类实例。往后,通过Proxy代理调用的方法都会被传入处理器类中。因此真正访问到真实主题的类就是处理器类。

第四步:测试。

import impl.ArticleBeanImpl;
import impl.NonOwnerInvocationHandler;
import impl.OwnerInvocationHandler;
import inter.ArticleBean;

import java.lang.reflect.Proxy;
public class TestProtectionProxy {
public static void main(String[] args) {
TestProtectionProxy t = new TestProtectionProxy();
t.test();
}
public void test(){
ArticleBean articleBean = getActicle(); //获取一篇文章
ArticleBean ownerProxy = getOwnerProxy(articleBean); //获取文章拥有者的代理
ArticleBean nonOwnerProxy = getNonOwnerProxy(articleBean);获取投票人的代理
//投票人开始投票
try{
nonOwnerProxy.setTicket(1);
String articleN = nonOwnerProxy.getArticleName();
int num = nonOwnerProxy.getTicket();
String s = articleN +"已获得:" + num + "票";
System.out.println(s);
}catch (Exception e){
e.printStackTrace();
}
//文章拥有者投自己一票,会报错,因为不能投自己的
try{
ownerProxy.setTicket(1);
}catch (Exception e){
System.out.println("不能给自己的文章投票!");
}
}
//文章所有者的代理
private ArticleBean getOwnerProxy(ArticleBean articleBean){
ArticleBean ownerProxy = (ArticleBean)Proxy.newProxyInstance(articleBean.getClass().getClassLoader(),articleBean.getClass().getInterfaces(),new OwnerInvocationHandler(articleBean));
return ownerProxy;
}

//投票人的代理
private ArticleBean getNonOwnerProxy(ArticleBean articleBean){
ArticleBean nonOwnerProxy (ArticleBean) Proxy.newProxyInstance(articleBean.getClass().getClassLoader(),articleBean.getClass().getInterfaces(),new NonOwnerInvocationHandler(articleBean));
return nonOwnerProxy;
}

//测试需要
private ArticleBean getActicle(){
ArticleBean bean = new ArticleBeanImpl();
bean.setArticleName("论人文精神的重要性");
bean.setAuthor("Wongkyunban");
bean.setGender("Boy");
bean.setTicket(0);
return bean;
}
}

测试结果:

论人文精神的重要性已获得:1票
不能给自己的文章投票!

​最后给出Github上的demo代码​​。

谢谢阅读。