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的动态代理。保护代理就是保护对象不要直接访问主题。保护代理可以控制在每一种情况下允许哪一种请求。
保护模式的类图:
代理模式——保护代理(三)
类图中的代理包含两个类,一个是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代码

谢谢阅读。

相关文章:

  • 2022-01-06
  • 2021-12-16
  • 2022-12-23
  • 2021-10-06
  • 2022-01-08
  • 2021-12-20
  • 2022-12-23
猜你喜欢
  • 2021-09-15
  • 2022-12-23
  • 2022-12-23
  • 2021-10-15
  • 2021-06-10
  • 2021-12-31
  • 2021-05-29
相关资源
相似解决方案