SpringBoot极简教程 · Spring Boot java成长之路禅与计算机程序设计艺术

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

2022-01-19  本文已影响0人  pq217

代理模式

假如现在有个厂家生产了一台生成冰淇淋的机器,支持蛋筒和杯装冰淇淋,点击不同口味的按键,不同的大小,投放硬币,就可以根据选择的口味生成大/中/小的草莓冰淇淋,原味冰淇淋,巧克力冰淇淋(这台机器就相当于一个类,蛋筒和杯装是两个不同的方法,点击的口味的按钮和规格按钮相当于参数,冰淇淋是方法的返回值)。
代码模拟

接口:

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.草莓 杯装冰淇淋(大)

过了一段时间厂家领导要做市场调研,想收集每天大家都选择了什么口味,选择了什么规格,然后做市场分析(相当于一个Before 拦截器)。
为了应付这个工作,没办法厂家雇了一个售货员看着机器,以后大家有啥需求不再直接去操作机器,而是告诉这个售货员,这个售货员先把需求记录下来写在记录本上,然后再去按需操作机器,再把冰淇淋递给用户(这个售货员就是这台机器的代理)。

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

/**
 * @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.草莓 杯装冰淇淋(大)

动态代理

通过找个售货员方式(代理模式),很轻松实现每次客户下完需求,把需求记录下来,然后再进行正常交易。 后来厂家越做越大,冰淇淋机越来越多,而且又引进了很多新机器,领导对新的机器质量不放心,于是规定说新机器不光要采集用户需求,又要查看机器产出的冰淇淋是否达标(相当于一个Around 拦截器)。这样就导致每个机器不光要配一个售货员(代理),每个售货员干的活还不一样,导致人力成本和培训成本急速提高。
这时候来了个代理公司说我们可以帮你解决这个问题,只要你们每次约定好哪个冰淇淋机,计划好额外的工作(拦截器),我们就培养出一个售货员来负责冰淇淋机 ,你们只要专心生产机器并做好计划就好(代理公司根据约定培养出销售员的过程就是动态代理)。
那么重点问题就来了,厂家代理公司怎么做约定, 代理公司就说了我们约定好,每次来了客户的需求,我们售货员会把需求的信息打包出来,打包的信息包含用户的需求,机器的信息,开始的按钮(点击之后可以返回冰淇淋),由于现在只是计划阶段并没有实际的需求,我们只是打包信心的格式规定好,你们只要计划好针对这个打包的信息如何处理,并制定好拦截工作计划(以下统称拦截计划)即可(定义拦截器)。

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;

}

MethodInvocation 这个名字是按照spring起的,因为spring的MethodInvocation 都是各种继承的,不方便看。所以重写一个,接下来还是使用spring的MethodInvocation(因为一样)

厂家代理公司商量好了这个约定的返回信息格式,厂家发给代理公司拦截计划当然肯定不能瞎鸡写,必须也有一个格式,这要格式就是只要能处理这个打包信息并且执行打包信息的开始按钮返回冰淇淋即可
拦截计划的格式模拟代码

@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都加上

上一篇下一篇

猜你喜欢

热点阅读