【设计模式】--- 装饰器模式、静态代理模式和动态代理模式

本文源码地址:https://github.com/nieandsun/mybatis-study


1 引子

看过上篇文章《【Mybatis源码探索】 — Mybatis查询过程核心源码解读 — 先聊聊selectOne方法》对Executor和StatementHandler的源码解析后,相信你肯定对装饰器模式有了一定的认识,本篇文章将对该模式做具体的归纳总结。同时考虑到由于静态代理模式和装饰器模式过于相似 的原因本篇文章也会对静态代理模式以及动态代理模式进行归纳总结 —》 这里之所以带上动态代理模式是因为 想要更深入的探索mybatis源码,必须对动态代理模式有一个比较深入的理解。


2 业务场景介绍

无论是铅笔制造公司还是钢笔制造公司,它们制造那么多的笔肯定是为了卖给别人,为了描述卖笔这个行为,可以为此定义一个抽象接口:

//抽象接口,用来描述笔制造厂的行为
public interface PenFactory {
    void salePens(String color);
}

铅笔制造公司卖的是铅笔,所以可以为此生成一个具体的实现类:

//具体实现类,卖自己工厂生产的铅笔
public class PencilFactory implements PenFactory {
    @Override
    public void salePens(String color) {
        System.err.println("卖" + color + "颜色的铅笔");
    }
}

当然铅笔制造公司肯定可以在卖自己生产的笔之前专门派自己公司的人做市场调研,然后再将笔卖给具体的用户。。。但是这样肯定会增加他们的人力成本。
其实现在随着社会分工的细化,铅笔制造公司完全可以只专注于自己的本行工作 — 制造更好的铅笔。而市场调研、将笔卖给每一个用户,甚至对用户使用满意度等的调查都由代理商去完成 —> 虽然代理商肯定会从中赚取差价,但是假若你自己卖只能卖10元一支,而代理商通过宣传等手段能卖20元1支,这样人家替你卖,卖出1支还是给你10元,你是不是肯定没吃亏???


3 静态代理模式

基于以上场景,假设我是一个非常专一的代理商,这辈子就只想做好一件事,就是想做好铅笔的代理,那我这个代理商的代码就可以这样写:

//静态代理 -------专一的代理商
//【注意】 这里要和铅笔工厂实现一样的接口----PenFactory
public class SpecificProxy implements PenFactory {
	//这辈子只做铅笔的代理
    private PenFactory penFactory = new PencilFactory();

    @Override
    public void salePens(String color) {
        beforeEnhance();
        penFactory.salePens(color);
        afterEnhance();
    }
    private void beforeEnhance() {
        System.out.println("在卖笔之前对市场进行调研。。。。");
    }
    private void afterEnhance() {
        System.out.println("卖了一段时间的笔之后,对市场进行总结评估");
    }
}

其实这就是所谓的静态代理模式。


4 装饰器模式

还是基于2中的场景,假设我是一个稍微有点野心的代理商,我不止想做铅笔的代理,我甚至还想做钢笔制造厂的代理、圆珠笔制造厂的代理,甚至毛笔,碳素笔。。。那该怎么搞?

如果想照搬静态代理方式的话,那肯定就不能只有一个PenFactory接口了:可以定义一个铅笔工厂的接口,在该接口里有一个卖铅笔的方法,然后写一个铅笔工厂的具体实现类;再定义一个钢笔工厂的接口,在接口里弄一个卖钢笔的方法,然后写一个钢笔的具体实现类。。。建立代理商类的时候一一实现这些工厂的接口、包装具体的工厂对象、对各个方法进行前后增强。。。显然这样代码会变得及其冗余!!! 而且这种方式违背了设计模式的开闭原则 —> 代理一种新类型的笔,就要在代理商的代码里新实现一个接口,这很有可能会殃及到原来的代理业务代码。

那到底该怎么办呢?其实就算不懂设计模式,我觉得大家肯定也会写出如下的代码:

//其实这就是装饰器模式了 --- 铅笔、钢笔、毛笔各种笔的代理商
//【注意】 这里要和铅笔工厂、钢笔工厂等各种笔工厂实现一样的接口----PenFactory
public class PensProxy implements PenFactory {

    private PenFactory penFactory;

    /***
     * 只要是笔我都可以代理,由调用者指明我到底代理哪种笔
     * @param penFactory
     */
    public PensProxy(PenFactory penFactory) {
        this.penFactory = penFactory;
    }

    @Override
    public void salePens(String color) {
        beforeEnhance();
        penFactory.salePens(color);
        afterEnhance();
    }

    private void beforeEnhance() {
        System.out.println("在卖笔之前对市场进行调研。。。。");
    }

    private void afterEnhance() {
        System.out.println("卖了一段时间的笔之后,对市场进行总结评估");
    }
}

其实这就是所谓的装饰器模式。我想读到这里肯定有人会懵逼: 这不也是对各个工厂做代理么?为什么这个就叫装饰器模式而3中所讲的就叫代理模式啊。。。其实对于这个问题我觉得可以从以下几个方面去理解:

(1)我讲的这个栗子不好,因为从最开始就一直在说工厂、代理商这些概念。。。很容易让人有一种先入为主的感觉
(2)回看上篇文章《【Mybatis源码探索】 — Mybatis查询过程核心源码解读 — 先聊聊selectOne方法》中提到的Executor和StatementHandler的源码,再加上我举得栗子,怎么就不能说装饰器模式的具体实现也是对目标类的一种代理呢???
(3)其实也可以这么理解:上面的类并不真正干活,它其实主要做的任务是卖东西之前的市场调查和卖东西之后的市场评估,真正的卖东西这个事,它其实是调用的被代理对象的服务 —> 即对被代理对象的一种装饰。


5 动态代理模式

接着继续前面的场景,假设我是一个野心更大的代理商,我不仅想代理笔,还想代理玩具、代理家具、代理电子产品。。。
那无论是照搬静态代理模式、还是装饰器模式,都不可避免的会修改代理类 —> 这当然不是我们开发者想要的结果 —》违背开闭原则。

那该怎么办呢? 这时候就不得不提动态代理模式了。动态代理模式有两个关键的组件:Proxy和InvocationHandler,接下来对其进行一一介绍。


5.1 Proxy — 具体的代理对象生成组件

Proxy并不是具体的代理对象,而是代理对象的生成对象,它有一个方法newProxyInstance(...)就是专门用来生成具体的代理对象的。newProxyInstance()有三个参数,具体的解释如下:

package com.nrsc.pattern.dynamicproxy;
import com.nrsc.pattern.factory.PenFactory;
import com.nrsc.pattern.factory.PencilFactory;
import java.lang.reflect.Proxy;

public class Main1 {

    public static void main(String[] args) {
        //新建铅笔工厂类
        PenFactory pencilFactory = new PencilFactory();
        //新建InvocationHandler的具体实现类 --- 对该类的解释请看下面的注释
        NrscInvocationHandler nrscInvocationHandler = new NrscInvocationHandler();
        nrscInvocationHandler.setTarget(pencilFactory);

        /***
         * 第一个参数为类加载器 ---- 传入的是【被代理类】所用的类加载器,因为这样生成的代理类就可以代理【被代理类】了
         *
         * 第二个参数为【被代理类】实现的所有接口
         *    ---Proxy.newProxyInstance(...)方法的返回值类型为Object,而Object对象只有equals()、hashCode()、
         *    ----toString()等几个方法,代理类如果想代理【被代理类】的方法,肯定就得强转为和【被代理类】一样的类型
         *    ---凭什么可以随便强转呢??? 就是因为Proxy生成的代理对象其实也实现了和【被代理对象】一样的接口,
         *    ---而这些接口就是通过该参数传递给Proxy的
         *    ---其实这也是为什么JDK进行动态代理,必须要求【被代理类】必须至少实现一个接口的原因
         *
         * 第三个参数为InvocationHandler的具体实现类:
         *    ----这个对象里封装了真正的【被代理对象】,以及对【被代理对象】方法的具体调用,当然在方法调用前后,我们可以对其进行增强
         *    ----Proxy在创建具体的代理对象的时候既然需要这个对象,那它创建出来的具体的代理对像肯定会使用这个对象,怎么使用呢???
         *    ----其实很简单,就是当你拿到代理对象时(这个代理对象肯定是已经强转为具体类型的对象了),
         *    ----可以直接按照【代理的那个对象】原有的方法调用方式进行法调用,但是呢,我既然是一个代理对象,
         *    ----肯定不会让你直接调用【代理的那个对象】的方法,而是调用创建代理对象时传入的InvocationHandler对象的invoke(...)方法。
         */
        PenFactory penFactoryProxy = (PenFactory) Proxy.newProxyInstance(
                pencilFactory.getClass().getClassLoader(),
                pencilFactory.getClass().getInterfaces(),
                nrscInvocationHandler);


        /****
         * 可以按照PenFactory的方法调用方式进行方法调用,但是真正走的是nrscInvocationHandler中的invoke(...)方法
         */
        penFactoryProxy.salePens("蓝");

    }
}

5.2 InvocationHandler — 封装被代理对象、调用被代理对象的方法并对方法进行增强

InvocationHandler其实是一个接口,它里面只有一个抽象方法invoke,其源码如下:

package java.lang.reflect;
public interface InvocationHandler {
   /****
    * 第一个参数: 其实就是由Proxy生成的具体的代理对象,这个参数一般不会用到---》因为假设A代理对象调用m1方法时,
    * 不会直接进入m1方法,而是会进入invoke(..)方法;进入到invoke(...)方法后,拿到的proxy其实就是A代理对象,
    * 如果你想用proxy进行其他方法的调用,它还是不会直接进入到其他方法,而是会又进入到invoke(...)方法。。。从而形成死循环
    * 
    * 第二个参数:其实就是代理对象调用的被代理对象的具体方法,但是如果想调用到具体的被代理对象,肯定还需要一个被代理对象
    * 【这里要参考静态代理或装饰器模式去理解 --- 真正干活的是被代理对象】
    * 
    * 第三个参数:其实就是代理对象调用的具体方法的参数 
    */
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

从上面的注释可以知道,要想调用到具体的被代理对象的方法,InvocationHandler 对象里必须得包含【被代理对象】,这里写了一个具体的实现如下:

package com.nrsc.pattern.dynamicproxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class NrscInvocationHandler implements InvocationHandler {
    /***
     * 被代理的对象 --- 其实该对象是真正提供服务的对象
     *            --- 如同装饰器模式一样,这个类并不真正干活,它其实主要做的任务是卖东西之前的市场调查和卖东西之后的市场评估
     *            --- 真正的卖东西这个事,它其实是调用的被代理对象的服务
     */
    private Object target;


    public void setTarget(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        beforeEnhance();
        //通过反射调用被代理对象的具体方法
        //【联系静态代理模式和装饰器模式】--这里调用的方法肯定是被代理对象的方法 -- 即实际的工厂里的方法
        Object res = method.invoke(target, args);
        afterEnhance();
        return res;
    }

    private void beforeEnhance() {
        System.out.println("卖东西之前对市场进行调研。。。。");
    }

    private void afterEnhance() {
        System.out.println("卖了一段时间之后,对市场进行总结评估");
    }
}

5.3 另一种代码书写方式 — 代理对象的生成直接写在InvocationHandler对象里

其实搞明白了动态代理模式的使用原理之后,完全可以将代理对象的生成放在InvocationHandler对象里,代码如下:

package com.nrsc.pattern.dynamicproxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class DynamicProxyCompany implements InvocationHandler {

    private Object target;

    public void setTarget(Object target) {
        this.target = target;
    }

    /***
     * 这里的this就是指的当前的InvocationHandler
     * @return
     */
    public Object getProxyInstance() {
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        beforeEnhance();
        //通过反射调用被代理对象的具体方法
        //【联系静态代理模式和装饰器模式】--这里调用的方法肯定是被代理对象的方法 -- 即实际的工厂里的方法
        Object res = method.invoke(target, args);
        afterEnhance();
        return res;
    }

    private void beforeEnhance() {
        System.out.println("卖东西之前对市场进行调研。。。。");
    }

    private void afterEnhance() {
        System.out.println("卖了一段时间之后,对市场进行总结评估");
    }
}

这样代理类的生成、调用就可以变成下面的样子了:

package com.nrsc.pattern.dynamicproxy;

import com.nrsc.pattern.factory.PenFactory;
import com.nrsc.pattern.factory.PencilFactory;
import com.nrsc.pattern.factory.ToyFactory;
import com.nrsc.pattern.factory.YellowDuckToyFactory;
public class Main2 {
    public static void main(String[] args) {

        //新建代理商公司
        DynamicProxyCompany dynamicProxyCompany = new DynamicProxyCompany();

        //指定代理商公司具体要代理的工厂
        dynamicProxyCompany.setTarget(new PencilFactory());
        //由代理商公司找到具体的笔代理人 --- 即生成具体的代理对象   
        //			--- 将代理对象强转为PenFactory,因为如果不强转的话生成的代理类将是一个Object对象,
        //			--- 而Object对象只有equals()、hashCode()、toString()等几个方法,无法调用卖笔的方法
        //			--- 这应该就是为什么JDK的动态代理为什么需要被代理的对象必须至少实现一个接口的原因
        PenFactory pencilFactoryProxy = (PenFactory) dynamicProxyCompany.getProxyInstance();
        //具体的笔代理人卖笔
        pencilFactoryProxy.salePens("红");

        //指定代理商公司具体要代理的工厂
        dynamicProxyCompany.setTarget(new YellowDuckToyFactory());
        //由代理商公司找到具体的玩具代理人
        ToyFactory toyFactory = (ToyFactory) dynamicProxyCompany.getProxyInstance();
        //具体的玩具代理人卖玩具
        toyFactory.saleToys(10000);
    }
}

6 总结

共同点:

无论是静态代理模式、装饰器模式还是动态代理模式他们都需要被代理的类(当然对于装饰器模式也可以说是被装饰的类)至少实现一个接口

不同点:

静态代理模式:会在代理对象里实例化好被代理的对象 —》 这种模式更适合于对某个n久不变的逻辑进行代理。

装饰器模式: 并不会在代理对象(或者说叫装饰器对象)里直接实例化好具体要代理(或者说要装饰)的对象,而是将这个对象作为代理对象的一个属性,需要调用者来具体指定到底代理哪个具体的类 —》这种模式比较适合对某一个类型的对象进行代理(或者说装饰)如上篇文章《【Mybatis源码探索】 — Mybatis查询过程核心源码解读 — 先聊聊selectOne方法》中提到的Executor和StatementHandler,以及本文说的笔工厂。

动态代理模式: 动态代理模式我觉得其实更像是装饰器模式的加强版。
(1)它的代理对象并不是确定的,而是通过每次调用Proxy.newProxyInstance(…)方法生成的,这和其他两种模式都不一样;
(2) InvocationHandler对像里封装了被代理对象以及对被代理对象的方法的调用和方法增强
(3)代理对象进行方法调用时可以按照被代理对象的方法调用方式进行调用,但是并不会直接调用到被代理对象,而是调用到 InvocationHandler对像里的invoke(…)方法


动态代理的适用情况比较多:比如说我提到的代理玩具制造厂、文具制造厂、电子产品制造厂的综合代理类;比如说前面讲过的spring事务;之后要继续探索的Mybatis mapper调用方式底层原理等。


7 扩展 — Proxy.newProxyInstance底层原理简析

在这里插入图片描述

发布了189 篇原创文章 · 获赞 187 · 访问量 39万+

猜你喜欢

转载自blog.csdn.net/nrsc272420199/article/details/103897699