结构型模式-代理模式

结构型模式

结构型模式描述如何将类或者对象按某种布局组成更大的结构,它分为类结构型模式和对象结构型结构模式,前者采用继承机制来组织接口和类,后者采用组合或者聚合方式来组合对象。
由于组合关系或者聚合关系比继承关系耦合度低,满足"合成复用原则",所以对象结构型模式比类结构型模式具有更大的灵活性
结构型模式分为如下7中:

  • 代理模式
  • 适配器模式
  • 装饰者模式
  • 桥接模式
  • 外观模式
  • 组合模式
  • 享元模式

代理模式

概述

由于某些原因需要给某对象提供一个代理以控制对对象的访问,这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。
java中代理按照代理类生成的时机不同又分为静态代理和动态代理,静态代理类在编译时就生成,而动态代理类则是在java运行时动态生成,动态代理又有jdk和cglib代理两种。

结构

代理模式分为三种角色

  • 抽象主题类:通过接口或者抽象类声明真实主题和代理对象实现的业务方法
  • 真实主题类:实现了抽象主题中具体的业务,是代理对象所代表的真实对象,是最终要引用的对象
  • 代理类:提供了与真实主题相同的接口,其内部含有真实主题的引用,他可以访问,控制或扩展真实主题的功能。

静态代理

【例】如果要买火车票的话,需要去火车站买票,坐车到火车站,排队等一系列操作,显然比较麻烦。而火车站在多个地方都有代售点,我们去代售点买票就方便很多了,这个例子其实就是典型的代理模式,火车站是目标对象,代售点是代理对象。类图如下


静态代理模式.png

抽象代理类

/**
 * 卖火车票接口(抽象主题类)
 */
public interface SellTickets {
    void sell();
}

具体代理类

/**
 * 火车站类(具体主题类)
 */
public class TrainStation implements SellTickets{
    @Override
    public void sell() {
        System.out.println("火车站买票");
    }
}

代理类

/**
 * 代售点(代理主题类)
 * 类图中聚合了火车站,在代售点内部使用的还是候车站购票
 */
public class ProxyPoint implements SellTickets{

    // 声明火车站对象
    private TrainStation trainStation = new TrainStation();

    @Override
    public void sell() {
        System.out.println("代售点收取一些服务费用");
        trainStation.sell();
    }
}

客户端测试类

public class Client {
    public static void main(String[] args) {
        //创建代理对象
        ProxyPoint proxyPoint = new ProxyPoint();

        // 调用方法进行购票
        proxyPoint.sell();
    }
}

JDK动态代理

java中提供了一个动态代理类Proxy,Proxy 并不是我们上述所说的代理对象类,而是提供了一个创建代理对象的静态方法(newProxyInstance方法)来获取代理对象
抽象代理类

/**
 * 卖火车票接口(抽象主题类)
 */
public interface SellTickets {
    void sell();
}

具体代理类

/**
 * 火车站类(具体主题类)
 */
public class TrainStation implements SellTickets {
    @Override
    public void sell() {
        System.out.println("火车站买票");
    }
}

代理工厂类

/**
 * 代理对象的工厂类,代理类也实现了对应的接口
 */
public class ProxyFactory {

    //声明目标对象
    private TrainStation trainStation = new TrainStation();

    // 返回代理对象方法
    public SellTickets getProxyObject(){
        /**
         * 返回代理对象
         * ClassLoader loader 类加载器,用于加载代理类,可以通过目标对象获取类加载器
         * Class<?>[] interfaces 代理类实现的接口的字节码对象
         * InvocationHandler h 代理对象的调用处理程序
         */
        SellTickets proxyObject = (SellTickets)Proxy.newProxyInstance(
                trainStation.getClass().getClassLoader(),
                trainStation.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    /**
                     * Object proxy 代理对象 和proxyObject是同一个对象,在invoke基本不用
                     * Method method 对接口中的方法进行封装的method对象
                     * Object[] args 调用方法的实际参数
                     * 返回值:方法的返回值
                     */
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("代售点收取一些服务费用");
                        // 执行目标对象方法
                        Object obj = method.invoke(trainStation, args);
                        return obj;
                    }
                }
        );
        return proxyObject;
    }
}

测试类

public class Client {
    public static void main(String[] args) {
        // 获取代理对象
        // 1,获取代理工厂对象
        ProxyFactory factory = new ProxyFactory();
        // 2,使用factory 对象的方法获取代理对象
        SellTickets proxyObject = factory.getProxyObject();
        //3,调用买电脑方法
        proxyObject.sell();

    }
}

CGLIB动态代理

如果没有接口,只定义了具体类,很显然jdk代理就无法使用了,因为jdk代理要求必须定义接口,对接口进行代理

<dependency>
  <groupId>cglib</groupId>
  <artifactid>cglib</artifactid>
  <version>2.2.2</version>
</dependecy>

具体的代理类

/**
 * 火车站类(具体主题类)
 */
public class TrainStation {

    public void sell() {
        System.out.println("火车站买票");
    }
}

代理工厂


/**
 * 代理对象工厂,用来获取代理对象
 */
public class ProxyFactory implements MethodInterceptor {

    private TrainStation trainStation = new TrainStation();

    public TrainStation getProxyObject(){
        // 1,创建Enhancer对象,类似于jdk中的Proxy类
        Enhancer enhancer = new Enhancer();
        // 2,设置父类字节码对象
        enhancer.setSuperclass(TrainStation.class);
        // 3,设置回调函数
        enhancer.setCallback(this);
        //4,创建代理对象
        TrainStation proxyObject = (TrainStation)enhancer.create();
        return proxyObject;
    }

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("代售点收取一些服务费用");
        Object invoke = method.invoke(trainStation, args);
        return invoke;
    }
}

测试类

public class Client {

    public static void main(String[] args) {
        // 创建代理工厂对象
        ProxyFactory factory = new ProxyFactory();
        // 获取代理对象
        TrainStation proxyObject = factory.getProxyObject();
        // 调用代理对象中的sell方法买票
        proxyObject.sell();
    }
}

三种代理的对比

  • jdk代理和cglib代理
    使用cglib实现动态代理,cglib底层采用ASM字节码生成框架,是用字节码技术生成代理类,在jdk1.6以前比使用java反射效率要高,唯一需要注意的就是,cglib不能对声明final的类进行代理,因为cglib原理是动态生成被代理类的子类
  • 动态代理和静态代理
    动态代理与静态代理相比较,最大的好处就是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理。这样在接扣数量比较多的时候,我们可以进行灵活的处理,而不需要像静态代理那样每个方法都进行中转
    如果接口增加一个方法,静态代理模式除了所有实现类需要实现这个方法外,所有的代理类也需要实现此方法,增加了代码维护的复杂度。而动态代理不会出现该问题。

代理模式的优点

  • 代理模式在客户端与目标对象之间起到了一个中介者作用和保护目标对象的作用
  • 代理对象可以扩展目标对象的功能
  • 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度

代理模式的缺点

  • 增加了系统的复杂度
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 224,014评论 6 522
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 95,832评论 3 402
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 171,062评论 0 366
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 60,620评论 1 300
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 69,636评论 6 399
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 53,148评论 1 314
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 41,522评论 3 427
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 40,501评论 0 278
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 47,032评论 1 324
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 39,069评论 3 344
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 41,211评论 1 354
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,848评论 5 350
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 42,541评论 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 33,013评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 34,136评论 1 275
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 49,712评论 3 380
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 46,240评论 2 363

推荐阅读更多精彩内容