用一个小故事模拟Spring Aop(一): 动态代理和责任链

代理模式

假如现在有个厂家生产了一台生产冰淇淋的机器,支持蛋筒&杯装两种形式,支持选择口味、大小
投放硬币,就可以根据选择生成冰淇淋
(这台机器就相当于一个类,蛋筒和杯装是两个不同的方法,选择口味和规格按钮相当于参数,冰淇淋是返回值)。

代码模拟

接口:

public interface IceCreamMachine {
    /**
     * 模拟生产杯装冰淇淋
     * @param taste 草莓/原味/巧克力
     * @param size 大/中/小
     * @return 冰淇淋
     */
    String cup(String taste, String size);
    /**
     * 模拟生产蛋筒冰淇淋
     * @param taste 草莓/原味/巧克力
     * @param size 大/中/小
     * @return 冰淇淋
     */
    String eggCone(String taste, String size);
}

实现

/**
 * @Author wmf
 * @Date 2022/1/18 15:37
 * @Description 模拟一个冰淇淋机
 */
public class IceCreamMachine1 implements IceCreamMachine {
    /**
     * 模拟生产杯装冰淇淋
     * @param taste 草莓/原味/巧克力
     * @param size 大/中/小
     * @return 冰淇淋
     */
    @Override
    public String cup(String taste, String size) {
        System.out.println("开始生产杯装冰淇淋");
        return taste + " 杯装冰淇淋("+size+")";
    }

    /**
     * 模拟生产蛋筒冰淇淋
     * @param taste 草莓/原味/巧克力
     * @param size 大/中/小
     * @return 冰淇淋
     */
    @Override
    public String eggCone(String taste, String size) {
        System.out.println("开始生产蛋筒冰淇淋");
        return taste + " 蛋筒冰淇淋("+size+")";
    }
}

写个测试类来跟进整个过程

/**
 * @Author wmf
 * @Date 2022/1/18 15:45
 * @Description
 */
public class ChainApplication {
    public static void main(String[] args) {
        IceCreamMachine machine = new IceCreamMachine1();
        String iceCream = machine.cup("草莓", "大");
        System.out.println(iceCream); // 正常输出: 草莓 杯装冰淇淋(大)
    }
}

输出:

1.开始生产杯装冰淇淋 
2.草莓 杯装冰淇淋(大)

过了一段时间厂家领导要做市场调研,想收集每天大家都选择了什么口味、什么规格,做市场分析。
为了应付这个工作,厂家雇了一个售货员看管机器,以后客户从售货员处购买,这个售货员先把需求记录下来,然后再去按需操作机器,再把冰淇淋给用户(这个售货员就是这台机器的代理)。

这种实现起来也很简单,最简单的方式就是代理模式

/**
 * @Author wmf
 * @Date 2022/1/18 15:58
 * @Description 机器的代理
 */
public class IceCreamMachineProxy implements IceCreamMachine {
    public IceCreamMachineProxy(IceCreamMachine iceCreamMachine) {
        this.iceCreamMachine = iceCreamMachine;
    }
    private IceCreamMachine iceCreamMachine;
    @Override
    public String cup(String taste, String size) {
        System.out.println("模拟记录需求"+taste+size);
        return iceCreamMachine.cup(taste, size);
    }
    @Override
    public String eggCone(String taste, String size) {
        System.out.println("模拟记录需求"+taste+size);
        return iceCreamMachine.eggCone(taste, size);
    }
}

测试一下

public class ChainApplication {
    public static void main(String[] args) {
        IceCreamMachine machine = new IceCreamMachine1();
        IceCreamMachine machineProxy = new IceCreamMachineProxy(machine);
        String iceCream = machineProxy.cup("草莓", "大");
        System.out.println(iceCream); 
    }
}

输出:

1.模拟记录需求(草莓 大) 
2.开始生产杯装冰淇淋 
3.草莓 杯装冰淇淋(大)

动态代理

通过售货员方式(代理模式),很轻松实现需求记录。
后来厂家越做越大,冰淇淋机越来越多,引进了很多新机器,领导对新的机器质量不放心,于是规定说新机器不光要采集用户需求,又要检查产出的冰淇淋是否达标。
这样导致每个机器不光要配一个售货员(代理),每个售货员干的活还不一样,人力成本和培训成本急速提高。
这时候来了个代理公司说可以帮解决问题,只要厂家每次约定好某个冰淇淋机,计划好额外的工作(拦截器),我们就培养出一个售货员 ,而厂家只要专心生产机器并做好拦截计划就好(代理公司根据约定培养出销售员的过程就是动态代理)。

image.png

关于拦截计划,双方约定了一个规范,否则随便写会造成阅读障碍。

拦截计划的上下文

机器:机器的信息
产品:蛋筒或草莓
需求:客户的需求
开始:实际开始生产冰淇淋

代码模拟就是

/**
 * @Author wmf
 * @Date 2022/1/19 10:05
 * @Description 信息的格式
 */
public interface MethodInvocation {
    /**
     * 机器的信息
     * @return
     */
    Object getThis();
    /**
     * 方法的信息(杯装还是蛋筒)
     * @return
     */
    Method getMethod();
    /**
     * 客户需求的信息(草莓/原味/巧克力 大/中/小)
     * @return
     */
    Object[] getArguments();
    /**
     * 开始生产标识
     * @return
     * @throws Throwable
     */
    Object proceed() throws Throwable;

}

拦截计划的格式即为针对上下文如何处理

@FunctionalInterface
public interface MethodInterceptor {
    Object invoke(MethodInvocation invocation) throws Throwable;
}

两方规定好了这些规范,接下来就是厂家定制计划的时候,比如为了食品监督他们定制的计划可能是这样的

Object invoke(MethodInvocation invocation) {
    //1.从打包信息invocation获取需求(invocation.getArguments())记在小本上
    //2.开始生产冰淇淋(invocation.proceed())
    //3.把生产出的冰淇淋拍个照发给厂家微信
}

对应的代码模拟

MethodInterceptor interceptor = new MethodInterceptor() {
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println("记录需求:"+invocation.getArguments());
        Object proceed = invocation.proceed();
        System.out.println("对生产出的冰淇淋拍照"+proceed);
        return proceed;
    }
};

厂家的冰淇淋机和拦截计划都准备好了,代理公司需要根据你的机器和拦截计划给你定制培训一个售货员(生成动态代理)

我们以jdk动态代理的方式模拟代理公司的这个过程(不懂jdk动态代理自己补吧)

/**
 * @Author wmf
 * @Date 2022/1/12 18:23
 * @Description 代理公司
 */
public class ProxyCompany implements AopProxy, InvocationHandler { // AopProxy相当于国家给所有代理公司下发的一个标准

    public ProxyCompany() {
    }
    /**
     * 设置拦截计划
     * @param interceptor
     */
    public void setInterceptor(MethodInterceptor interceptor) {
        this.interceptor = interceptor;
    }
    /**
     * 绑定冰淇淋机
     * @param target
     */
    public void setTarget(Object target) {
        this.target = target;
    }
    /**
     * 附加工作
     */
    MethodInterceptor interceptor;
    /**
     * 冰淇淋机
     */
    Object target;

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

    @Override
    public Object getProxy(ClassLoader classLoader) {
        return null;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 准备上下文
        MethodInvocation invocation = new MethodInvocation() {
            @Override
            public Method getMethod() {
                return method;
            }
            @Override
            public Object[] getArguments() {
                return args;
            }
            @Override
            public Object proceed() throws Throwable {
                return method.invoke(target, args);
            }
            @Override
            public Object getThis() {
                return target;
            }
            @Override
            public AccessibleObject getStaticPart() {
                return null;
            }
        };
        // 需求来了之后按拦截计划去执行
        return interceptor.invoke(invocation);
    }
}

测试整个过程:

public class ChainApplication {
    public static void main(String[] args) {
        // 厂家的冰淇淋机
        IceCreamMachine machine = new IceCreamMachine1();
        // 厂家定制拦截计划
        MethodInterceptor interceptor = new MethodInterceptor() {
            @Override
            public Object invoke(MethodInvocation invocation) throws Throwable {
                System.out.println("记录需求:"+invocation.getArguments()[0]);
                Object proceed = invocation.proceed();
                System.out.println("对生产出的冰淇淋拍照"+proceed);
                return proceed;
            }
        };
        // 代理公司
        ProxyCompany proxyCompany = new ProxyCompany();
        // 绑定冰淇淋机到代理公司
        proxyCompany.setTarget(machine);
        // 绑定拦截计划到代理公司
        proxyCompany.setInterceptor(interceptor);
        // 生成售货员(机器的代理)
        IceCreamMachine saler = (IceCreamMachine) proxyCompany.getProxy();
        String iceCream = saler.eggCone("原味", "中");
    }
}

输出:

1.记录需求:原味 
2.开始生产蛋筒冰淇淋 
3.对生产出的冰淇淋拍照 原味 蛋筒冰淇淋(中)

就这样过了一段时间,厂家代理公司配合很好,厂家专注维护机器制定拦截计划代理公司专注培养代理售货员,分工明确,单一职责。

责任链模式

又过了一段时间,出现新需求,一台机器要加多个拦截计划:既要市场调研又要食品监督,而且拒绝修改原有的拦截计划,只想新加入拦截计划让新旧拦截计划一起生效,这样一来代理公司又要做变动了

首先第一步,一个机器对应多个拦截计划

/** * 附加工作列表 */ 
List<MethodInterceptor> interceptors;

现在有多个计划,每个计划都包含了开始生产的标识,那怎么执行呐,循环执行肯定不行,人家客户要一个冰淇淋你可能给人家生成多个(点了多次开始按钮)。
代理公司想出了解决方案,给每个售货员配一个调度员,这个调度员一个个执行拦截计划,当某个拦截计划写着点击开始按钮时,不是实际的点击开始,而是进行下一个拦截计划(相当于把开始按钮调包了),最后再没有拦截计划时再实际点击开始按钮,这里有点绕,看下面的例子

厂家一台机器有两个计划

食品监督计划:

Object invoke(MethodInvocation invocation) {
    //1.口味/规格记在食品监督小本上
    //2.开始生产冰淇淋(invocation.proceed())
    //3.生产出的冰淇淋拍个照发给厂家微信
}

市场调研计划:

Object invoke(MethodInvocation invocation) {
    //1.口味/规格记在市场调研小本上
    //2.开始生产冰淇淋(invocation.proceed())
}

实际来客户购买冰淇淋时,售货员拿到两个计划交给召唤出的调度员,调度员是这么调度的

按第一个计划(食品监督计划)先走
1.1 口味/规格记在食品监督小本上
1.2 开始生产冰淇淋(拿到这个指令时,调度员实际开始进行下一个计划)
├── 2.1 口味/规格记在市场调研小本上
├── 2.2 开始生产冰淇淋 ***此时没有下一个拦截计划,真正点击开始按钮***
1.3 生产出的冰淇淋拍个照发给厂家微信

所以调度员的任务就是调包开始按钮,记录好拦截计划列表执行到第几个,一个个执行,形成一条链

示意图如下:

image.png

代码模拟:

/**
 * @Author wmf
 * @Date 2022/1/19 13:59
 * @Description 调度员
 */
public class Dispatcher {
    /**
     * 原打包信息
     */
    private MethodInvocation methodInvocation;
    /**
     * 调包后的打包的信息
     */
    private MethodInvocation changelingMethodInvocation;
    /**
     * 拦截计划列表
     */
    private List<MethodInterceptor> chain;

    /**
     * 执行拦截计划的游标
     */
    private Integer index = -1;

    public Dispatcher(MethodInvocation methodInvocation, List<MethodInterceptor> chain) throws Throwable {
        this.chain = chain;
        Dispatcher that = this;
        // 存储调度员原打包的信息
        this.methodInvocation = methodInvocation;
        // 调包打包信息里面的开始按钮
        this.changelingMethodInvocation = new MethodInvocation() {
            @Override
            public Object getThis() {
                return methodInvocation.getThis();
            }
            // 不管
            @Override
            public AccessibleObject getStaticPart() {
                return null;
            }
            @Override
            public Method getMethod() {
                return methodInvocation.getMethod();
            }
            @Override
            public Object[] getArguments() {
                return methodInvocation.getArguments();
            }
            /** 调包开始按钮,工作执行计划的开始按钮实际上指向调度员的proceed**/
            @Override
            public Object proceed() throws Throwable {
                return that.proceed();
            }
        };
    }

    /**
     * 调度员的工作
     * @return
     * @throws Throwable
     */
    public Object proceed() throws Throwable {
        Object re;
        // 最后一次,没有拦截计划了,开始生产冰淇淋
        if (index == chain.size()-1) {
            re =  methodInvocation.proceed();
        }else{ // 还有下一个拦截计划,继续按照下一个拦截计划执行
            re = chain.get(++index).invoke(changelingMethodInvocation);
        }
        return re;
    }
}

修改代理公司培养售货员逻辑,改动的地方标注了(改)

/**
 * @Author wmf
 * @Date 2022/1/12 18:23
 * @Description 代理公司
 */
public class ProxyCompany implements AopProxy, InvocationHandler { // AopProxy相当于国家给所有代理公司下发的一个标准

    public ProxyCompany() {
    }
    /**
     * (改)添加拦截计划
     * @param interceptor
     */
    public void addInterceptor(MethodInterceptor interceptor) {
        this.interceptors.add(interceptor);
    }
    /**
     * 绑定冰淇淋机
     * @param target
     */
    public void setTarget(Object target) {
        this.target = target;
    }
    /**
     * (改)附加工作列表
     */
    List<MethodInterceptor> interceptors = new ArrayList<>();
    /**
     * 冰淇淋机
     */
    Object target;

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

    @Override
    public Object getProxy(ClassLoader classLoader) {
        return null;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 打包的信息 上面提到过
        MethodInvocation invocation = new MethodInvocation() {
            @Override
            public Method getMethod() {
                return method;
            }
            @Override
            public Object[] getArguments() {
                return args;
            }
            @Override
            public Object proceed() throws Throwable {
                return method.invoke(target, args);
            }
            @Override
            public Object getThis() {
                return target;
            }
            @Override
            public AccessibleObject getStaticPart() {
                return null;
            }
        };
        // (改)召唤一个调度员
        Dispatcher dispatcher = new Dispatcher(invocation, interceptors);
        // (改)需求来了之后让调度员去执行
        return dispatcher.proceed();
    }
}

测试一下

public class ChainApplication {
    public static void main(String[] args) {
        // 厂家的冰淇淋机
        IceCreamMachine machine = new IceCreamMachine1();
        // 厂家定制食品监督计划
        MethodInterceptor interceptor1 = new MethodInterceptor() {
            @Override
            public Object invoke(MethodInvocation invocation) throws Throwable {
                System.out.println("记录需求至食品监督本:"+invocation.getArguments()[0]);
                Object proceed = invocation.proceed();
                System.out.println("拍照传给厂家微信:"+proceed);
                return proceed;
            }
        };
        // 厂家定制市场调研计划
        MethodInterceptor interceptor2 = new MethodInterceptor() {
            @Override
            public Object invoke(MethodInvocation invocation) throws Throwable {
                System.out.println("记录需求至市场调研本:"+invocation.getArguments()[0]);
                return invocation.proceed();
            }
        };
        // 代理公司
        ProxyCompany proxyCompany = new ProxyCompany();
        // 绑定冰淇淋机
        proxyCompany.setTarget(machine);
        // 绑定两个拦截计划
        proxyCompany.addInterceptor(interceptor1);
        proxyCompany.addInterceptor(interceptor2);
        // 生成售货员(机器的代理)
        IceCreamMachine saler = (IceCreamMachine) proxyCompany.getProxy();
        String iceCream = saler.eggCone("原味", "中");
    }
}

输出:

记录需求至食品监督本:原味
记录需求至市场调研本:原味
开始生产蛋筒冰淇淋
拍照传给厂家微信:原味 蛋筒冰淇淋(中)

完全满足了厂家需求,调度员使用这种方式完成了拦截计划一个接一个的执行,就是一条责任链

如上Dispatcher调包的方式还是使用的类似代理模式,写法有点丑,售货员完全可以把打包信息的工作也交给调度员,而调度员实现调包的方式也可以同过继承覆盖来实现,于是优化下代码,(按spring的命名 Dispatcher以下修改为ReflectiveMethodInvocation),优化后的代码如下

ProxyCompany:

/**
 * @Author wmf
 * @Date 2022/1/12 18:23
 * @Description 代理公司
 */
public class ProxyCompany implements AopProxy, InvocationHandler { // AopProxy相当于国家给所有代理公司下发的一个标准

    public ProxyCompany() {
    }
    /**
     * 设置拦截计划
     * @param interceptor
     */
    public void addInterceptor(MethodInterceptor interceptor) {
        this.interceptors.add(interceptor);
    }
    /**
     * 绑定冰淇淋机
     * @param target
     */
    public void setTarget(Object target) {
        this.target = target;
    }
    /**
     * 附加工作列表
     */
    List<MethodInterceptor> interceptors = new ArrayList<>();
    /**
     * 冰淇淋机
     */
    Object target;

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

    @Override
    public Object getProxy(ClassLoader classLoader) {
        return null;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 召唤一个调度员,并把打包的工作也交给调度员
        ReflectiveMethodInvocation dispatcher = new ReflectiveMethodInvocation(target, method, args, interceptors);
        // 需求来了之后按拦截计划去执行
        return dispatcher.proceed();
    }
}

ReflectiveMethodInvocation(原Dispatcher):

/**
 * @Author wmf
 * @Date 2022/1/17 18:05
 * @Description 调度员本身就是一个打包信息,所以继承了MethodInvocation,自己重新实现了proceed
 */
public class ReflectiveMethodInvocation implements MethodInvocation {

    private Object target;

    private Method method;

    private Object[] args;

    /**
     * 拦截计划列表
     */
    List<MethodInterceptor> chain;

    private int index = -1;

    public ReflectiveMethodInvocation(Object target, Method method, Object[] args, List<MethodInterceptor> chain) {
        this.target = target;
        this.method = method;
        this.args = args;
        this.chain = chain;
    }

    @Override
    public Method getMethod() {
        return this.method;
    }

    @Override
    public Object[] getArguments() {
        return args;
    }

    @Override
    public Object proceed() throws Throwable {
        Object re;
        if (index == chain.size()-1) {
            re =  method.invoke(target, args);
        }else{
            re = chain.get(++index).invoke(this);
        }
        return re;
    }

    @Override
    public Object getThis() {
        return target;
    }

    @Override
    public AccessibleObject getStaticPart() {
        return null;
    }
}

测试方法还是不变,输出结果页一样(写法干净了很多)

以上就是整个spring aop动态代理+方法拦截+责任链模式的整个思路,下面对比下源码

对比spring

ReflectiveMethodInvocation对比spring ReflectiveMethodInvocation:

image.png

ProxyCompany(使用的是jdk动态代理)对比spring的JdkDynamicAopProxy:

image.png

MethodInterceptor和MethodInvocation就是直接用的spring的,所以不用对比了。

over~

后期会再扩展一下,把整个aop都加上

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