设计模式 08 代理模式

代理模式(Proxy Pattern)属于 结构型模式

代理模式就是一个代理对象来间接访问对象,常用于无法直接访问某个对象或访问某个对象不方便的情况。

实际上代理在生活中处处都存在,比如房屋中介就是代理,Apple 的授权经销商就是代理,访问国外网站所用的代理服务器也是代理,Spring 框架的 AOP 也是通过代理模式实现的。

这些代理都有一个共同特点,就是使用的 一致性和中间环节的 透明性,也就是说找代理做的事情需要与找对象本身做的事情是一样的,只是中间环节隐藏了而已。

代理模式分为 静态代理动态代理

静态代理一般包含以下角色:

  • 动作 : 一般使用接口或者抽象类来实现。
  • 真实角色 : 被代理的角色。
  • 代理角色 : 代理真实角色 ; 代理真实角色后 , 一般会做一些附属的操作。
  • 客户 : 使用代理角色来进行一些操作。

代码实现1

1、定义租赁操作

/**
 * 租赁操作
 */
public interface Rent {

    /**
     * 租房
     */
    void rentHouse();
}

2、定义房东

/**
 * 房东
 */
public class Landlord implements Rent{

    @Override
    public void rentHouse() {
        System.out.println("房东出租房子");
    }
}

3、定义中介

/**
 * 中介
 */
public class Intermediary implements Rent{

    /**
     * 房东
     */
    private Landlord landlord;

    public Intermediary() {
    }

    public Intermediary(Landlord landlord) {
        this.landlord = landlord;
    }

    @Override
    public void rentHouse() {
        // 看房
        seeHouse();
        // 签合同
        contract();
        // 租房
        landlord.rentHouse();
        // 收取费用
        toll();
    }

    /**
     * 看房
     */
    public void seeHouse() {
        System.out.println("中介带你看房");
    }

    /**
     * 签合同
     */
    public void contract() {
        System.out.println("签租赁合同");
    }

    /**
     * 收取费用
     */
    public void toll() {
        System.out.println("收中介费");
    }
}

4、租客租房

// 房东
Landlord landlord = new Landlord();
// 中介给房东代理
Proxy proxy = new Proxy(landlord);
// 租房。不用面对房东,直接找中介租房即可
proxy.rentHouse();

在这个过程中,租客直接接触的是中介,见不到房东,但是租客依旧通过代理租到了房东的房子。

代码实现2

日常工作中最常见的就是增删改查业务,这里以实现增删改查代码的日志插入为例理解静态代理。

1、定义用户服务

/**
 * 用户服务
 */
public interface UserService {

    /**
     * 新增
     */
    public void add();

    /**
     * 删除
     */
    public void delete();

    /**
     * 修改
     */
    public void update();

    /**
     * 查询
     */
    public void query();
}

2、定义用户服务实现类

/**
 * 用户服务实现类
 */
public class UserServiceImpl implements UserService {

    @Override
    public void add() {
        System.out.println("新增了一个用户");
    }

    @Override
    public void delete() {
        System.out.println("删除了一个用户");
    }

    @Override
    public void update() {
        System.out.println("修改了一个用户");
    }

    @Override
    public void query() {
        System.out.println("查询了一个用户");
    }
}

3、定义用户服务代理类

/**
 * 用户服务代理类
 */
public class UserServiceProxy implements UserService {

    /**
     * 用户服务实现类
     */
    private UserServiceImpl userService;

    public void setUserService(UserServiceImpl userService) {
        this.userService = userService;
    }

    @Override
    public void add() {
        log("add");
        userService.add();
    }

    @Override
    public void delete() {
        log("delete");
        userService.delete();
    }

    @Override
    public void update() {
        log("update");
        userService.update();
    }

    @Override
    public void query() {
        log("query");
        userService.query();
    }

    /**
     * 打印日志
     *
     * @param msg 消息
     */
    public void log(String msg) {
        System.out.println("使用了" + msg + "方法");
    }
}

4、客户端使用

// 用户服务实现类
UserServiceImpl userService = new UserServiceImpl();
// 用户服务代理类
UserServiceProxy proxy = new UserServiceProxy();
// 代理用户服务
proxy.setUserService(userService);
// 代理实现新增
proxy.add();
// 代理实现删除
proxy.delete();
// 代理实现修改
proxy.update();
// 代理实现查询
proxy.query();

执行结果为:

使用了add方法
新增了一个用户
使用了delete方法
删除了一个用户
使用了update方法
修改了一个用户
使用了query方法
查询了一个用户

使用代理类实现在不改动原有业务代码的情况下增加了日志。

优缺点

1、可以使得真实角色更加轻松,不用再去关注一些琐碎的事情。

2、公共的业务由代理来完成,实现了业务的分工。

3、公共业务发生变化时扩展更加方便。

类变多了,多了代理类,工作量变大了,开发效率降低。

我们想要静态代理的优点,又不想要静态代理的缺点,所以 , 就有了 动态代理

  • 动态代理的角色和静态代理的一样。
  • 动态代理的代理类是动态生成的,静态代理的代理类是提前写好的。
  • 动态代理分为两类 : 一类是基于接口 , 一类是基于类
  • 基于接口的动态代理: JDK 动态代理。
  • 基于类的动态代理: CGLIB 动态代理。
  • 现在用的比较多的是 Javassist 来生成动态代理。
  • 这里使用 JDK 的原生代码来实现,其余的道理都是一样的。

JDK 的动态代理需要了解两个类: ProxyInvocationHandler。查看 JDK 帮助文档:

Proxy:代理类

Proxy 提供了创建动态代理类和实例的静态方法,它也是由这些方法创建的所有动态代理类的超类。

代理接口是由代理类实现的接口。 _代理实例_是代理类的一个实例。 每个代理实例都有一个关联的 _调用处理程序_对象,它实现了接口 InvocationHandler

newProxyInstance 方法:

public static Object newProxyInstance(ClassLoader loader,
                                      Class[] interfaces,
                                      InvocationHandler h) throws IllegalArgumentException

返回指定接口的代理类的实例,该接口将方法调用分派给指定的调用处理程序。

参数:

  • loader – 类加载器来定义代理类。
  • interfaces – 代理类实现的接口列表。
  • h – 调度方法调用的调用处理函数。

返回值:

具有由指定的类加载器定义并实现指定接口的代理类的指定调用处理程序的代理实例。

异常:

IllegalArgumentException:非法参数异常。

InvocationHandler:调用处理程序

InvocationHandler 是由代理实例的 调用处理程序实现的 接口 。 每个代理实例都有一个关联的调用处理程序。

invoke 方法:

Object invoke(Object proxy,
              Method method,
              Object[] args) throws Throwable

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

参数:

  • proxy – 调用该方法的代理实例。
  • method – 所述方法对应于调用代理实例上的接口方法的实例。方法对象的声明类将是该方法声明的接口,它可以是代理类继承该方法的代理接口的超级接口。
  • args – 包含的方法调用传递代理实例的参数值的对象的阵列,或 null 如果接口方法没有参数。原始类型的参数包含在适当的原始包装器类的实例中,例如java.lang.Integer 或 java.lang.Boolean。

代码实现1

抽象角色和真实角色和之前的一样。

1、定义租赁操作

/**
 * 租赁操作
 */
public interface Rent {

    /**
     * 租房
     */
    void rentHouse();
}

2、定义房东

/**
 * 房东
 */
public class Landlord implements Rent {

    @Override
    public void rentHouse() {
        System.out.println("房东出租房子");
    }
}

3、定义中介

/**
 * 中介
 */
public class Intermediary implements InvocationHandler {

    /**
     * 租赁操作
     */
    private Rent rent;

    /**
     * 代理租赁
     *
     * @param rent 需要租赁的对象
     */
    public void setRent(Rent rent) {
        this.rent = rent;
    }

    /**
     * 生成代理对象
     *
     * @return 代理对象
     */
    public Object getProxy() {

        // 重点是第二个参数,获取要代理的抽象角色,之前都是一个角色,现在可以代理一类角色
        return Proxy.newProxyInstance(this.getClass().getClassLoader(), rent.getClass().getInterfaces(), this);
    }

    /**
     * 处理代理实例上的方法调用并返回结果
     *
     * @param proxy  代理类
     * @param method 代理类的调用处理程序的方法对象
     * @param args   包含的方法调用传递代理实例的参数值的对象的阵列
     * @return 代理对象
     * @throws Throwable 错误
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 看房
        seeHouse();
        // 签合同
        contract();
        // 动态代理租房业务:本质利用反射实现
        Object result = method.invoke(rent, args);
        // 收取费用
        toll();
        return result;
    }

    /**
     * 看房
     */
    public void seeHouse() {
        System.out.println("中介带你看房");
    }

    /**
     * 签合同
     */
    public void contract() {
        System.out.println("签租赁合同");
    }

    /**
     * 收取费用
     */
    public void toll() {
        System.out.println("收中介费");
    }

}

4、租客租房

// 房东
Landlord landlord = new Landlord();
// 中介
Intermediary intermediary = new Intermediary();
// 中介给房东提供代理服务
intermediary.setRent(landlord);
// 动态生成对应的代理类
Rent proxy = (Rent) intermediary.getProxy();
// 代理类执行租房操作
proxy.rentHouse();

一个动态代理 , 一般代理某一类业务 , 一个动态代理可以代理多个类,代理的是接口。

代码实现2

使用动态代理再来实现前面的增删改查业务。

1、定义用户服务

/**
 * 用户服务
 */
public interface UserService {

    /**
     * 新增
     */
    public void add();

    /**
     * 删除
     */
    public void delete();

    /**
     * 修改
     */
    public void update();

    /**
     * 查询
     */
    public void query();
}

2、定义用户服务实现类

/**
 * 用户服务实现类
 */
public class UserServiceImpl implements UserService {

    @Override
    public void add() {
        System.out.println("新增了一个用户");
    }

    @Override
    public void delete() {
        System.out.println("删除了一个用户");
    }

    @Override
    public void update() {
        System.out.println("修改了一个用户");
    }

    @Override
    public void query() {
        System.out.println("查询了一个用户");
    }
}

3、定义用户服务代理类

/**
 * 用户服务代理类
 */
public class UserServiceProxy implements InvocationHandler {

    /**
     * 目标对象
     */
    private Object target;

    /**
     * 代理目标对象
     *
     * @param target 目标对象
     */
    public void setTarget(Object target) {
        this.target = target;
    }

    /**
     * 生成代理对象
     *
     * @return 代理对象
     */
    public Object getProxy() {
        return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }

    /**
     * 处理代理实例上的方法调用并返回结果
     *
     * @param proxy  代理类
     * @param method 代理类的调用处理程序的方法对象
     * @param args   包含的方法调用传递代理实例的参数值的对象的阵列
     * @return 代理对象
     * @throws Throwable 错误
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 打印日志
        log(method.getName());
        // 执行代理方法
        return method.invoke(target, args);
    }

    /**
     * 打印日志
     * @param msg 消息
     */
    public void log(String msg) {
        System.out.println("使用了" + msg + "方法");
    }
}

4、客户端使用

// 用户服务实现类
UserServiceImpl userService = new UserServiceImpl();
// 用户服务代理类
UserServiceProxy userServiceProxy = new UserServiceProxy();
// 代理用户服务
userServiceProxy.setTarget(userService);
// 动态生成代理对象
UserService proxy = (UserService) userServiceProxy.getProxy();
// 代理实现新增
proxy.add();
// 代理实现删除
proxy.delete();
// 代理实现修改
proxy.update();
// 代理实现查询
proxy.query();

执行结果为:

使用了add方法
新增了一个用户
使用了delete方法
删除了一个用户
使用了update方法
修改了一个用户
使用了query方法
查询了一个用户

这样就用动态代理实现了增删改查业务,而且由于 目标对象为 Object,需要代理其他类时只需要转化为对应的类即可,十分易于扩展。

优缺点

1、静态代理有的它都有,静态代理没有的,它也有。

2、可以使得真实角色更加轻松,不用再去关注一些琐碎的事情。

3、公共的业务由代理来完成,实现了业务的分工。

4、公共业务发生变化时扩展更加方便。

5、动态代理可以代理一类业务。

6、动态代理可以代理多个类,代理的是接口。

1、需要对实现动态代理的类和方法有一定了解,学习成本较静态代理更高。

2、动态代理的使用逻辑更为复杂,不如静态代理好理解。

按职责来划分,通常有以下使用场景:

1、远程代理。

2、虚拟代理。

3、Copy-on-Write 代理。

4、保护(Protect or Access)代理。

5、Cache代理。

6、防火墙(Firewall)代理。

7、同步化(Synchronization)代理。

8、智能引用(Smart Reference)代理。

1、和 适配器模式的区别:适配器模式主要改变所考虑对象的接口,而代理模式不能改变所代理类的接口。

2、和 装饰器模式的区别:装饰器模式为了增强功能,而代理模式是为了加以控制。

Original: https://www.cnblogs.com/codesail/p/16535403.html
Author: 程序航
Title: 设计模式 08 代理模式

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/575064/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • vnpy源码阅读学习(5):关于MainEngine的代码阅读

    在入口文件中,我们看到了除了窗体界面的产生,还有关于 MainEngine和 EventEngin部分。今天来学习下 MainEngine的代码。 首先在run代码中,我们看到以下…

    Java 2023年6月7日
    0106
  • 数据库的基本信息,都在这几张表里了

    话说生产环境的数据库是不能本地直连的,所以公司一般都会提供一个比较简陋的数据库查询页面,在可控的范围内,支持你提交一些查询、变更SQL,满足你的查库功能。但是因为不能直接使用Nav…

    Java 2023年6月5日
    076
  • java基础篇 —— java创建对象有哪几种方法?

    java创建对象的方法 1.用new 语句创建对象,这是最常用的创建对象的方式。 2.运用反射机制,调用Java.lang.Class 或者java.lang.reflect.Co…

    Java 2023年6月5日
    0101
  • 百行以内实现复杂数学表达式计算

    一改以前 本次先上代码 package good;//Evaluate complex expressionsimport java.io.IOException;import j…

    Java 2023年6月5日
    0102
  • 指定一个目录下所有的java文件,把里面的内容格式化输出在md文件

    指定一个目录下所有的java 文件,把里面的内容格式化输出在md 文件 import java.io.;/ * @author Mxhlin * @Email fuhua277@1…

    Java 2023年6月7日
    060
  • IDEA插件清单

    那片笑声让我想起我的那些花儿在我生命每个角落静静为我开着我曾以为我会永远守在她身旁今天我们已经离去在人海茫茫她们都老了吧 她们在哪里呀幸运的是我曾陪她们开放 Original: h…

    Java 2023年6月5日
    077
  • DDD从入门到精通:基础篇

    这篇文章主要还是表述清楚DDD相关的基础概念,因为DDD入门有一定的专业名词,还是得有个基本的了解。 先讲解下领域模型作用: 对软件需求进行设计,维持其内在逻辑的一致性 1)划分边…

    Java 2023年6月15日
    0109
  • Map集合

    一、什么是Map: 首先map是一个集合,一种按照键存储元素的容器。在map中键可以是任意类型的对象,map中不能有重复的键,每一个键都有一个对应的值。 二、Map集合的特点: 1…

    Java 2023年6月9日
    0117
  • Effective Java 第三版——82. 线程安全文档化

    Tips书中的源代码地址:https://github.com/jbloch/effective-java-3e-source-code注意,书中的有些代码里方法是基于Java 9…

    Java 2023年5月29日
    0110
  • 【RocketMQ】MQ消息发送

    消息发送 首先来看一个RcoketMQ发送消息的例子: @Service public class MQService { @Autowired DefaultMQProducer…

    Java 2023年6月8日
    064
  • Pycharm 运行和终端都无法走代理

    404. 抱歉,您访问的资源不存在。 可能是网址有误,或者对应的内容被删除,或者处于私有状态。 代码改变世界,联系邮箱 contact@cnblogs.com 园子的商业化努力-困…

    Java 2023年6月8日
    085
  • 二分搜索树节点的插入及查找

    目录 二分搜索树节点的插入 Java 实例代码 二分搜索树节点的查找 Java 实例代码 二分搜索树节点的插入 首先定义一个二分搜索树,Java 代码表示如下: BST.java …

    Java 2023年6月5日
    0145
  • 2、内置注解

    @Ove rri d e 定义iava.lan .Override 中此注释只适用于修辞万法《表示一个方法声明打算 重写超类中的另一个方氵去声明. @Deprecated : 定义…

    Java 2023年6月8日
    095
  • [Java编程思想] 第四章 控制执行流程

    4.1 true和false Java不允许将一个数字作为布尔值使用。 4.2 迭代 while、do-while和for控制着循环,有时将其划分为”迭代语句&#822…

    Java 2023年6月5日
    0116
  • java单例的几种实现方法

    java单例的几种实现方法: 方式1: public class Something { private Something() {} private static class L…

    Java 2023年5月29日
    088
  • fastposter发布1.4.2 跨语言的海报生成器

    fastposter发布1.4.2 跨语言的海报生成器 fastposter发布1.4.2 跨语言的海报生成器,一分钟完成海报开发 future: 完善docker镜像 引入异步a…

    Java 2023年6月5日
    0105
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球