美文网首页
用一个小故事模拟Spring Aop(四): PointCut&

用一个小故事模拟Spring Aop(四): PointCut&

作者: pq217 | 来源:发表于2022-01-24 11:17 被阅读0次

    PointCut

    承接上文

    厂家代理工厂又合作了一段时间,厂家又开始出现新情况了,他要某些机器拦截而某些不管,或者某些方法拦截其它不管,代理工厂一想完全可以啊,只要你在指定拦截计划时加判断就可以了吗,类似如下

    MethodBeforeAdvice advice1 = (method, args1, target) -> {
        // 只拦截蛋筒
        if (method.getName().equals("eggCone")) {
            System.out.println("记录需求至市场调研本:" + args1[0]);
        }
    };
    

    厂家回复了6个字: “太麻烦,不想写”, 苦逼的代理公司只能想方案。于是把任务交给需求人员~你是负责收集需求的,你肯定要把用户需求想办法描述出来
    需求人员抹了一把汗,还好自己留了一手,就是那个装拦截计划的盒子(Advisor),这把他要发挥作用了,之前只装拦截计划(Advice),这把给他升级一下,让他除了装拦截计划(织入),再装拦截要求(拦截哪个机器或哪个产品,切面),这样我只要定义一个拦截要求的格式,厂家按照这个格式发给我,我就能完成任务了

    拦截要求包含两个,拦截机器(Class)的要求,拦截产品(Method)的要求,
    拦截机器(Class)的要求格式如下

    @FunctionalInterface
    public interface ClassFilter {
        /**
         * 具体的要求,也就是我给你一台机器,根据读要求可以看出是否拦截
         * @param clazz 某个机器
         * @return 是否拦截
         */
        boolean matches(Class<?> clazz);
    }
    

    拦截产品(Method)的要求格式如下

    @FunctionalInterface
    public interface MethodMatcher {
        /**
         * 具体的产品要求,比如给出某个机器的蛋筒产品,能按要求返回是否拦截
         * @param method 产品
         * @param targetClass 机器
         * @return 是否拦截
         */
        boolean matches(Method method, Class<?> targetClass);
    }
    

    二者共同组成一个拦截要求

    /**
     * 拦截要求
     */
    public interface Pointcut {
        /**
         * 机器的拦截要求
         */
        ClassFilter getClassFilter();
    
        /**
         * 产品的拦截要求
         */
        MethodMatcher getMethodMatcher();
    }
    

    也就是代理工厂人员规定好了这个格式,厂家按这个格式给我发具体要求(代码就是实现这个接口),代理工厂就按厂家的要求确定什么情况下执行拦截,什么情况下不执行拦截

    装有拦截要求装拦截计划的盒子(Advisor)就叫做PointcutAdvisor,他的抽象如下

    /**
     * 首先它是一个装拦截计划的盒子,所以继承了Advisor
     */
    public interface PointcutAdvisor extends Advisor {
        /**
         * 其次它有装有拦截要求
         */
        Pointcut getPointcut();
    }
    

    定义好这些后,需求人员首先要能接受PointcutAdvisor,新增方法

    /**
     * 设置工作计划盒子(新增)
     * @param advisor
     */
    public void addAdvisor(Advisor advisor) {
        this.advisors.add(advisor);
    }
    

    由于之前是有适配负责人厂家新拦截计划转换为老拦截计划,所以读取新拦截要求的任务也交给他,他去负责读拦截要求然后确定是否返回老拦截计划,由于需要确定具体是哪个机器&哪个产品,所以还要jdk部门和cglib部门再获取老拦截计划的时候要携带机器&产品信息,适配负责人才能针对具体的机器&产品信息确定有哪些要执行的拦截计划,由于两个部门只对接需求人员,所以此时需求人员完整代码如下

    /**
     * @Author wmf
     * @Date 2022/1/19 17:05
     * @Description 需求人员
     */
    public class AdvisedSupport {
        /**
         * 附加新拦截计划盒子列表
         */
        List<Advisor> advisors = new ArrayList<>();
        /**
         * 绑定的机器
         */
        Object target;
        /**
         * 是否有规范(是否有实现的接口)
         */
        Boolean isImpl;
        /**
         * 负责映射新老工作计划的人
         */
        DefaultAdvisorChainFactory chainFactory = new DefaultAdvisorChainFactory();
        /**
         * 获取原格式工作计划(改!需要提供机器&产品信息)
         * @return
         */
        List<MethodInterceptor> getInterceptors(Method method, Class<?> targetClass) {
            // 把机器&产品信息送给适配负责人
            return chainFactory.getInterceptors(this, method, targetClass);
        }
        /**
         * 设置工作计划
         * @param advice
         */
        public void addAdvice(Advice advice) {
            this.advisors.add(() -> advice);
        }
        /**
         * 设置工作计划盒子(新增)
         * @param advisor
         */
        public void addAdvisor(Advisor advisor) {
            this.advisors.add(advisor);
        }
        /**
         * 绑定机器
         * @param target
         */
        public void setTarget(Object target) {
            this.target = target;
        }
    
        /**
         * 设置是否有规范(是否有实现的接口)
         * @param impl
         */
        public void setImpl(Boolean impl) {
            isImpl = impl;
        }
    }
    

    适配负责人,此时需要读拦截需求,再根据需求人员提供的机器&产品信息,确定是否返回拦截计划,代码如下

    /**
     * 模拟负责映射新工作计划到老工作计划的适配负责人
     */
    public class DefaultAdvisorChainFactory {
        /**
         * 给分配一个适配器管理员(这里spring用的单例模式)
         */
        DefaultAdvisorAdapterRegistry registry = new DefaultAdvisorAdapterRegistry();
        /**
         * 主要工作就是查看需求配置,获得新工作计划转换为老工作计划
         * @param config
         * @return
         */
        public List<MethodInterceptor> getInterceptors(AdvisedSupport config, Method method, Class<?> targetClass) {
            List<MethodInterceptor> interceptors = new ArrayList<>();
            for (Advisor advisor : config.advisors) {
                // 携带了拦截要求(新增)
                if (advisor instanceof PointcutAdvisor) {
                    PointcutAdvisor pointcutAdvisor = (PointcutAdvisor) advisor;
                    Pointcut pointcut = pointcutAdvisor.getPointcut();
                    // 机器拦截要求
                    ClassFilter classFilter = pointcut.getClassFilter();
                    // 不符合机器拦截要求(跳过)
                    if (!classFilter.matches(targetClass)) {
                        continue;
                    }
                    // 产品拦截要求
                    MethodMatcher methodMatcher = pointcut.getMethodMatcher();
                    // 不符合产品拦截要求(跳过)
                    if (!methodMatcher.matches(method, targetClass)) {
                        continue;
                    }
                }
                // 让适配其人员去实际转换
                MethodInterceptor[] inters = registry.getInterceptors(advisor);
                interceptors.addAll(Arrays.asList(inters));
            }
            return interceptors;
        }
    }
    

    好了,代理工厂的准备工作做完,下面测试一下

    /**
     * @Author wmf
     * @Date 2022/1/18 15:45
     * @Description 整个aop的测试类
     */
    @SuppressWarnings("ALL")
    public class ImitateApplication {
        public static void main(String[] aa) {
            // 厂家的冰淇淋机
            IceCreamMachine1 machine = new IceCreamMachine1();
            // 厂家定制市场调研计划
            MethodBeforeAdvice advice = (method, args, target) -> System.out.println("记录需求至市场调研本:" + args[0]);
            // 厂家定制蛋筒市场调研计划
            PointcutAdvisor advisor = new PointcutAdvisor() {
                @Override
                public Advice getAdvice() {
                    return (MethodBeforeAdvice) (method, args, target) -> {
                        System.out.println("记录需求至蛋筒市场调研本:" + args[0]);
                    };
                }
                @Override
                public Pointcut getPointcut() {
                    return new Pointcut() {
                        @Override
                        public ClassFilter getClassFilter() {
                            return new ClassFilter() {
                            // 所有机器都拦截
                                @Override 
                                public boolean matches(Class<?> clazz) {
                                    return true;
                                }
                            };
                        }
    
                        @Override
                        public MethodMatcher getMethodMatcher() {
                            return new MethodMatcher() {
                            // 产品只拦截蛋筒
                                @Override
                                public boolean matches(Method method, Class<?> targetClass) {
                                    return method.getName().equals("eggCone");
                                }
                            };
                        }
                    };
                }
            };
            // 代理工厂
            ProxyFactory proxyFactory = new ProxyFactory();
            // 绑定冰淇淋机
            proxyFactory.setTarget(machine);
            // 没有规范
            proxyFactory.setImpl(true);
            // 绑定两个工作计划
            proxyFactory.addAdvice(advice);
            proxyFactory.addAdvisor(advisor);
            // 生成售货员(机器的代理)
            IceCreamMachine saler = (IceCreamMachine) proxyFactory.getProxy();
            saler.eggCone("原味", "中");
            saler.cup("原味", "中");
        }
    }
    

    输出

    记录需求至市场调研本:原味
    记录需求至蛋筒市场调研本:原味
    开始生产蛋筒冰淇淋
    记录需求至市场调研本:原味
    开始生产杯装冰淇淋
    

    可以看出定制的蛋筒市场调研拦截计划,只在生产蛋筒时实际的进行了拦截
    其实这样的写法可能相对之前更复杂了,但是更符合单一职责(先判断是否符合需求再执行拦截计划,而不是执行了拦截计划后再各种判断),而且针对复杂完全可以把常用的拦截方式再封装,比如按名字拦截,spring就有一个NameMatchMethodPointcutAdvisor,可自行参考
    再用图梳理整个过程

    image.png

    对比spring

    命名都一样,自己对比

    Spirng使用代理工厂

    spring中使用aop

    以上就是模拟spring AOP 的整个过程,但是我们平时的写法完全不一样,平时我们都是新建一个带@Aspect的Bean 然后写@Pointcut@Before/@Around/@After(上网有很多例子)。
    因为spring是控制翻转,我是定义好这些标签,spring内部给打包成Advisor并去执行生成代理的过程(对比之前的例子相当于厂家把所有机器贴上要做拦截计划标签,告诉代理工厂·一个扫描范围。代理工厂自己去一扫描并生成Advisor,最终给机器配备代理售货员)。

    spring使用代理工厂

    接下来简单看看spring怎么做的,首先spring创建bean的时候会通过后置处理器去给bean创建代理(有代理的话)
    方法位置:AbstractAutoProxyCreator

    • 主要是bean后置处理器的postProcessAfterInitialization中的wrapIfNecessary方法(循环依赖出现时例外,但最终都是走wrapIfNecessary方法)
      image.png
    • 跟进wrapIfNecessary方法内部看一下
      image.png

    1.根据注解,一般是带@Aspect的bean,当然也可以直接实现Advisor等方式获得扫描到的拦截器(Advice或Advisor)
    2.通过createProxy()方法创建代理

    • 再进入createProxy方法内部看一下
    image.png

    这就和之前我们模拟的代码基本一个套路了,对接上了

    over~

    相关文章

      网友评论

          本文标题:用一个小故事模拟Spring Aop(四): PointCut&

          本文链接:https://www.haomeiwen.com/subject/jblbhrtx.html