设计模式之代理模式

目录

一、定义

二、结构

三、代码实现

四、AOP的基础

五、动态代理

5.1 Invocationhandler 和 Proxy

5.2 代码实现

六、特点

优点

缺点

七、适用场景


网络已经是现在人离不开的东西了,我们每天都要上网浏览信息。上网的方式也很简单。

但是,当我们要访问一些国外的网站时,就发现访问不了了。

 

这时候就出现了一些VPN,他可以帮助我们去访问一些国内不能访问的网站,也就是说他代理了这个访问过程,把结果返回给了我们。这就是代理模式。

 

一、定义

代理模式:给某一个对象提供一个代理或占位符,并由代理对象来控制对原对象的访问。

用上面的例子也比较好理解,也就是出现了一个代理类来代理原本的访问。

二、结构

代理模式一般由三个角色组成:

  1. Subject(抽象主题角色):声明了真实主题和代理主题的共同接口。

  2. Proxy(代理主题角色):具体的代理类,它包含了真实主题的引用,也可以有自己的一些附属操作。

  3. RealSubject(真实主题角色):实现了真实的业务操作。

三、代码实现

实现一下上面的例子体会一下代理模式。

先实现一个抽象主题web,用来处理用户的请求。

public interface Web {
​
    // 处理请求
    void processRequest();
    
}

然后写一个可以正常访问的网站实现类:

public class Bilibili implements Web{
​
    @Override
    public void processRequest() {
        System.out.println("你正在访问哔哩哔哩");
    }
}

这时候用户可以正常访问:

public class Client {
​
    public static void main(String[] args) {
        Web web = new Bilibili();
        web.processRequest();
    }
}

 

然后再来一个不能正常访问的网站:

public class Youtube implements Web{
​
    @Override
    public void processRequest() {
        System.out.println("你正在访问youtube");    
    }
    
}

假设这个时候中间有墙了,我们不能直接访问了,就需要一个VPN代理类帮我们访问。并且这个VPN会有一些其他操作。

你要先登录VPN,然后充钱才能访问,最后要关掉VPN

public class VPN implements Web{
​
    private Web web;
​
    public VPN() {
    }
​
    public void setWeb(Web web) {
        login();
        charge();
        this.web = web;
        close();
    }
​
    @Override
    public void processRequest() {
        web.processRequest();
    }
​
    private void login() {
        System.out.println("登录VPN");
    }
​
    private void charge() {
        System.out.println("给VPN充值");
    }
​
    private void close() {
        System.out.println("关闭VPN");
    }
}

有了VPN,我们就能访问之前访问不了的网站了。

public class Client {
​
    public static void main(String[] args) {
        Web web = new Youtube();
        VPN vpn = new VPN();
        vpn.setWeb(web);
        vpn.processRequest();
    }
}

 

这就是代理模式的实现。

四、AOP的基础

AOP:面向切面编程

什么叫面前切面呢?我们依然可以用前面的例子来解释。

本来所有的网站都是可以访问的,不过就是向一个IP地址发起请求,他再返回数据给你。

但是有一个保安被安排插在了这个过程中间,你的请求全部被保安给拦截了

 

这个保安要检查你请求的地址,是否是我们允许的地址。如果是,就给你放行;如果不是,就不让你请求,自然你就访问不到了。

这个过程并不是在网站上动手脚,而是从切面插入一个检测。

再举一个例子,我们的业务基本都是一个线性的流程。

 

而这时候老板给你提了一个要求,我们要在所有的业务里,添加一个日志功能。你怎么办?难道在每一个业务里的步骤里,一个个去加日志的输出吗?不用,我们利用面向切面的思想,直接把日志功能横插在所有业务中间。

 

这就是AOP。

那AOP的基础是什么?就是本篇文章的重点代理模式。

回想一下我们代码实现的VPN是怎么做的?它代理了用户的真实请求,帮用户去做这个请求,再把数据返回给用户。这个过程他可以有自己的收费等功能。

再比如日志功能,我们用一个代理类去代理业务里的步骤,并在代理类里加上日志功能。这样就能完成AOP的开发了。

五、动态代理

前面的代理模式,我们是将代理类写好,并且创建出来了。但是这种方式有局限性,并且会占用内存。我们更希望是在运行时需要代理类的时候,代理类再生成。于是就有了动态代理。

5.1 Invocationhandler 和 Proxy

Java里的动态代理依赖Invocationhandler和Proxy两个类。我们现来看一下这两个类的作用。

首先是Invocationhandler,这是一个很简单的接口,里面就定义了一个方法。这个接口是用来

public interface InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

源码里的注释是这样说的:

Processes a method invocation on a proxy instance and returns the result. This method will be invoked on an invocation handler when a method is invoked on a proxy instance that it is associated with.

处理代理实例上的方法调用并返回结果。 当在与其关联的代理实例上调用方法时,将在调用方法时调用此方法。

看不懂没关系,大概知道这个invoke方法是最终代理类代理方法的实现。后面配合代码才更好理解。

然后是Proxy,他的作用是用来创建一个代理类的。最常用的方法就是newProxyInstance()

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)

这个方法用来返回一个代理类,三个参数分别是:

  1. classLoader, 决定了用哪一个classLoader来创建代理对象。

  2. interfaces,一个接口数组,生产的代理类就会去实现这些接口。

  3. InvocationHandler, 就是前面提到的,用来执行代理方法的。

5.2 代码实现

光看定义太难懂了,我们还是用一个实际例子+写代码来配合理解吧。

我们的系统一般都会适用数据库(Database),数据库一般有增删改查的功能。数据库有很多种,比如MySQL, Oracle等,他们有各自实现增删改查功能的方法。

我们先把这个场景抽象:

public interface Database {
​
    void add();
    
    void delete();
    
    void update();
    
    void query();
    
}
public class MySQL implements Database{
​
    @Override
    public void add() {
        // MySQL具体的处理
        // ...
        System.out.println("add");
    }
​
    @Override
    public void delete() {
        // MySQL具体的处理
        // ...
        System.out.println("delete");
    }
​
    @Override
    public void update() {
        // MySQL具体的处理
        // ...
        System.out.println("update");
    }
​
    @Override
    public void query() {
        // MySQL具体的处理
        // ...
        System.out.println("query");
    }
}
​
public class Oracle implements Database{
​
    @Override
    public void add() {
        // Oracle具体的处理
        // ...
        System.out.println("add");
    }
​
    @Override
    public void delete() {
        // Oracle具体的处理
        // ...
        System.out.println("delete");
    }
​
    @Override
    public void update() {
        // Oracle具体的处理
        // ...
        System.out.println("update");
    }
​
    @Override
    public void query() {
        // Oracle具体的处理
        // ...
        System.out.println("query");
    }
}

现在开发人员就可以去调用各种数据库进行操作了。

public class Client {
    public static void main(String[] args) {
        Database database = new MySQL();
        database.add();
        database.delete();
        database.query();
        database.update();
    }
}

 

现在,老板让你添加一个日志功能,需要输出你具体调用了哪一个数据库,和调用了哪一个方法。

我们用动态代理实现,写一个DataBaseHandler类去实现InvocationHandler接口。

p
ublic class DatabaseHandler implements InvocationHandler {
​
    private Database database;
​
    public DatabaseHandler(Database database) {
        this.database = database;
    }
​
    // 获取代理类
    public Database getDatabaseProxy() {
        return (Database) Proxy.newProxyInstance(this.getClass().getClassLoader(), database.getClass().getInterfaces(),
                this);
    }
​
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        log(database.getClass().getName(), method.getName());
        return method.invoke(database, args);
    }
​
    private void log(String databaseName, String methodName) {
        System.out.println("[Debug]  调用了" + databaseName + "的" + methodName + "方法");
    }
​
​
}

代码不长,我们一点点来理解。

首先这个类有一个成员变量database接口,并且给一个有参构造函数。

然后提供了一个方法getDatabaseProxy()

public Database getDatabaseProxy() {
        return (Database) Proxy.newProxyInstance(this.getClass().getClassLoader(), database.getClass().getInterfaces(),
                this);
    }

这个方法就是调用了Proxy的newProxyInstance方法。这里的classLoader就传入DatabaseHandler的classLoader;接口传入了成员变量database的接口,通过getClass().getClassLoader()即可获取。最后一个参数自然就是this了。

这一步就是去生成了代理类,返回的就是一个实现了Database接口的代理类。

然后来看一下invoke函数

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        log(database.getClass().getName(), method.getName());
        return method.invoke(database, args);
    }
​
    private void log(String databaseName, String methodName) {
        System.out.println("[Debug]  调用了" + databaseName + "的" + methodName + "方法");
    }
​

由getDatabaseProxy()创建出来的代理类,调用方法最终会走到invoke()方法。这个方法的三个参数分别就是创建的代理类,调用的方法,和方法参数。

这里我们调用method.invoke(database, args)去调用原本实际database的对应方法,然后在这一句前,添加了一个log方法。通过反射获取到了调用方法的名称。这就是在切面添加了一个日志功能。

最后看一下用户的调用。

public class Client {
    public static void main(String[] args) {
        Database database = new MySQL();
        DatabaseHandler handler = new DatabaseHandler(database);
        Database proxy = handler.getDatabaseProxy();
        proxy.add();
        proxy.update();
        proxy.query();
        proxy.delete();
​
    }
}

首先我们要指定使用哪一个数据库,然后创建一个DatabaseHandler。通过getDatabaseProxy()去获取代理类,最终代理类去运行。最后的结果:

 

感受一下,这里我们没有去写一个代理类,而是交给Proxy去生成,然后InvocationHandler的invoke方法去实际执行。这个代理类,是在运行阶段生成的,不是我们自己去new出来的。

六、特点

优点

代理模式是最常用的设计模式之一,因为他包含以下优点:

  1. 可以使真实业务角色责任更纯粹,不用包含一些公共业务。

  2. 公共业务交给了代理类,实现了解耦。

  3. 提供了面向切面编程的基础,使一个横向业务更容易编写

  4. 动态代理可以代理多个实现了同一个接口的类。

缺点

增加代理类可能会导致业务处理速度变慢。

七、适用场景

以下场景适合适用代理模式

  1. 当业务无法直接访问某个类时,需要一个代理类去代理访问。

  2. 当需要横向添加一些功能,比如日志功能时,可以使用代理模式。

  • 3
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值