关于java:设计模式第十二篇车票购买场景中的代理模式讲解

3次阅读

共计 5710 个字符,预计需要花费 15 分钟才能阅读完成。

早在 Spring AOP 篇的解说中,我曾经写过对于 AOP 局部是如何用代理模式进行一个解决的,明天绝对标准的把这几种形式来整顿一下,因为代理模式相对来说代码简单一点点,所以咱们抉择先解说其概念,再应用代码具体演示

一 代理模式的概念

(一) 什么是代理模式

定义:给某个对象提供一个代理对象,用来管制对这个对象的拜访

简略的举个例子就是:买火车、飞机票等,咱们能够间接从车站售票窗口进行购买,这就是用户间接在官网购买,然而咱们很多中央的店铺或者一些路边的亭台中都能够进行火车票的代售,用户间接能够在代售点购票,这些中央就是代理对象

(二) 应用代理对象有什么益处呢?

  • 性能提供的这个类(火车站售票处),能够更加专一于次要性能的实现,比方安顿车次以及生产火车票等等
  • 代理类(代售点)能够在性能提供类提供办法的根底上进行减少实现更多的一些性能

这个动静代理的劣势,带给咱们很多不便,它能够帮忙咱们实现 无侵入式的代码扩大 ,也就是在不 用批改源码 的根底上,同时 加强办法

补充:毛病就是类的数量,以及复杂度,申请处理速度会绝对增大

(三) 分类

代理模式分为动态和动静代理两大种,动静代理又能分为大抵两种,所以根本的来说有三种

  • ① 动态代理
  • ② 动静代理

    • ① 基于接口的动静代理 —— JDK 动静代理
    • ② 基于子类的动静代理 —— Cglib 动静代理
    • ③ javassist 动静代理(这里不做演示)

阐明:

动态:由程序员创立代理类或特定工具主动生成源代码再对其编译,在程序运行前代理类的 .class 文件就曾经存在了。

动静:在程序运行时,使用反射机制动态创建而成

二 代码演示

咱们上面演示的背景是来自一个火车票买票的案例,这个案例即,例如买一张 800 块的火车票,你能够间接在火车站(不思考当初挪动 12306 等购买,只是例子别较真)买,或者去一个代理点买,然而代理点要赚钱的,所以你买 800 的火车票,你就须要给代理点 1000,收 200 手续费,先别管黑不黑心了,咱们先看看怎么实现

(一) 传统实现

创立官网售票处接口

public interface RailwayTicketProducer {
    /**
     * 售票服务
     * @param price
     */
    void saleTicket(float price);
}

上面天然就是实现类

public class RailwayTicketProducerImpl implements RailwayTicketProducer {public void saleTicket(float price) {System.out.println("代理销售火车票(扣 20% 手续费),【官网车站】收到车票钱:" + price);
    }
}

间接调用

public class Test {public static void main(String[] args) {RailwayTicketProducer ticketProducer = new RailwayTicketProducerImpl();
        ticketProducer.saleTicket(1000);
    }
}

(二) 动态代理

后面的官网售票处接口和实现类还是一样的,然而代理销售点和官网购票点不一样,其多了一个额定的解决,那就是收 20 % 手续费(真黑啊),所以意味着代理售票点的 售票办法 saleTicket 被加强了

所以咱们首先创立一个 代理售票点 ProxyRailwayTicketProducerImpl 类,而后实现官网售票点的接口 RailwayTicketProducer,通过组合引入的形式,将 RailwayTicketProducerImpl 引入,而后在它的 saleTicket 办法中就能够进行额定的业务书写或者说办法加强内容了

比方这里多了一个 price * 0.8f 的简略解决,这样就扣掉了两成的费用

public class ProxyRailwayTicketProducerImpl implements RailwayTicketProducer {

    private RailwayTicketProducerImpl ticketProducer;

    public ProxyRailwayTicketProducerImpl() {}

    public ProxyRailwayTicketProducerImpl(RailwayTicketProducerImpl ticketProducer) {this.ticketProducer = ticketProducer;}

    @Override
    public void saleTicket(float price) {ticketProducer.saleTicket(price * 0.8f);
    }
}

测试一下,先 new 一个实在的解决对象,而后创立代理对象,将实在对象传入,再通过代理对象调用 saleTicket,这样真正调用 saleTicket 的还是原先的 RailwayTicketProducerImpl,然而被加强的局部也同样被执行了,例如那个 乘以 0.8 的操作

public class Test {RailwayTicketProducerImpl ticketProducer = new RailwayTicketProducerImpl();
    // 代理对象
    ProxyRailwayTicketProducerImpl proxy = new ProxyRailwayTicketProducerImpl(ticketProducer);
        proxy.saleTicket(1000);
    }
}

测试后果:

代理销售火车票(扣 20% 手续费),【官网车站】收到车票钱:800.0

(三) 动静代理(基于接口 -jdk)

和后面的传统形式隔着有点远了,这里写残缺一下

创立官网售票处(类和接口)

RailwayTicketProducer 接口

public interface RailwayTicketProducer {
    /**
     * 售票服务
     * @param price
     */
    void saleTicket(float price);
}

RailwayTicketProducerImpl 类

实现类中,咱们前面只对销售车票办法进行了加强,售后服务并没有波及到

/**
 * 生产厂家具体实现
 */
public class RailwayTicketProducerImpl implements RailwayTicketProducer{public void saleTicket(float price) {System.out.println("代理销售火车票(扣 20% 手续费),【官网车站】收到车票钱:" + price);
    }
}

Client 类

这个类,就是客户类,在其中,通过代理对象,实现购票的需要

首先先来说一下如何创立一个代理对象:答案是 Proxy 类中的 newProxyInstance 办法

留神:既然叫做基于接口的动静代理,这就是说被代理的类,也就是文中官网销售车票的类起码必须实现一个接口,这是必要的!

public class Client {public static void main(String[] args) {RailwayTicketProducer producer = new RailwayTicketProducerImpl();

        // 动静代理
        RailwayTicketProducer proxyProduce = (RailwayTicketProducer) Proxy.newProxyInstance(producer.getClass().getClassLoader(),
                producer.getClass().getInterfaces(),new MyInvocationHandler(producer));

        // 客户通过代理买票
        proxyProduce.saleTicket(1000f);
    }
}

newProxyInstance 共有 三个参数 来解释一下:

  • ClassLoader:类加载器

    • 用于加载代理对象字节码,和被代理对象应用雷同的类加载器
  • Class[]:字节码数组

    • 为了使被代理对象和的代理对象具备雷同的办法,实现雷同的接口,可看做固定写法
  • InvocationHandler:如何代理,也就是想要加强的形式

    • 也就是说,咱们主须要 new 出 InvocationHandler,而后书写其实现类,是否写成匿名外部类能够本人抉择
    • 如上述代码中 new MyInvocationHandler(producer) 实例化的是我本人编写的一个 MyInvocationHandler 类,实际上能够在那里间接 new 出 InvocationHandler,而后重写其办法,其本质也是通过实现 InvocationHandler 的 invoke 办法实现加强

MyInvocationHandler 类

这个 invoke 办法具备拦挡的性能,被代理对象的任何办法被执行,都会通过 invoke

public class MyInvocationHandler implements InvocationHandler {

    private  Object implObject ;

    public MyInvocationHandler (Object implObject){this.implObject=implObject;}

    /**
     * 作用:执行被代理对象的任何接口办法都会通过该办法
     * 办法参数的含意
     * @param proxy   代理对象的援用
     * @param method  以后执行的办法
     * @param args    以后执行办法所需的参数
     * @return        和被代理对象办法有雷同的返回值
     * @throws Throwable
     */
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object returnValue = null;
        // 获取办法执行的参数
        Float price = (Float)args[0];
        // 判断是不是指定办法(以售票为例)if ("saleTicket".equals(method.getName())){returnValue = method.invoke(implObject,price*0.8f);
        }
        return returnValue;
    }
}

在此处,咱们获取到客户购票的金额,因为咱们应用了代理方进行购票,所以代理方会收取肯定的手续费,所以用户提交了 1000 元,实际上官网收到的只有 800 元,这也就是这种代理的实现形式,

运行后果:

代理销售火车票(扣 20% 手续费),【官网车站】收到车票钱:800.0

(四) 动静代理(基于子类 -cglib)

下面办法简略的实现起来也不是很难,然而惟一的规范就是,被代理对象必须提供一个接口,而当初所解说的这一种就是一种能够间接代理一般 Java 类的形式,同时在演示的时候,我会将代理办法间接以内部类的模式写出,就不独自创立类了,不便大家与下面对照

减少 cglib 依赖坐标

<dependencies>
    <dependency>
        <groupId>cglib</groupId>
        <artifactId>cglib</artifactId>
        <version>3.2.4</version>
    </dependency>
</dependencies>

TicketProducer 类

留神:这里只是一个一般类了

/**
 * 生产厂家
 */
public class TicketProducer {public void saleTicket(float price) {System.out.println("代理销售火车票(扣 20% 手续费),【官网车站】收到车票钱:" + price);
    }
}

Enhancer 类中的 create 办法就是用来创立代理对象的

而 create 办法又有两个参数

  • Class:字节码

    • 指定被代理对象的字节码
  • Callback:提供加强的办法

    • 与后面 invoke 作用是基本一致的
    • 个别写的都是该接口的子接口实现类:MethodInterceptor
public class Client {public static void main(String[] args) {
        // 因为下方匿名外部类,须要在此处用 final 润饰
        final TicketProducer ticketProducer = new TicketProducer();

        TicketProducer cglibProducer =(TicketProducer) Enhancer.create(ticketProducer.getClass(), new MethodInterceptor() {

            /**
             * 前三个三个参数和基于接口的动静代理中 invoke 办法的参数是一样的
             * @param o
             * @param method
             * @param objects
             * @param methodProxy   以后执行办法的代理对象
             * @return
             * @throws Throwable
             */
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                Object returnValue = null;
                // 获取办法执行的参数
                Float price = (Float)objects[0];
                // 判断是不是指定办法(以售票为例)if ("saleTicket".equals(method.getName())){returnValue = method.invoke(ticketProducer,price*0.8f);
                }
                return returnValue;
            }
        });
        cglibProducer.saleTicket(1000f);
    }
}

运行后果:

代理销售火车票(扣 20% 手续费),【官网车站】收到车票钱:800.0

三 结构图

  • 形象主题(Subject)类:通过接口或抽象类申明实在主题和代理对象实现的业务办法
  • 实在主题(Real Subject)类:代理对象所代表的实在对象,是最终要援用的对象,其实现了形象主题中的具体业务
  • 代理(Proxy)类:提供了与实在主题雷同的接口,其外部含有对实在主题的援用,它能够拜访、管制或扩大实在主题的性能
正文完
 0