用一个小故事模拟Spring Aop(四): PointCut&Spirng使用代理工厂

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 advisors = new ArrayList<>();
    /**
     * 绑定的机器
     */
    Object target;
    /**
     * 是否有规范(是否有实现的接口)
     */
    Boolean isImpl;
    /**
     * 负责映射新老工作计划的人
     */
    DefaultAdvisorChainFactory chainFactory = new DefaultAdvisorChainFactory();
    /**
     * 获取原格式工作计划(改!需要提供机器&产品信息)
     * @return
     */
    List 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 getInterceptors(AdvisedSupport config, Method method, Class targetClass) {
        List 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(其实就相当于写了个PointcutAdvisor=Pointcut+Advice )。

因为spring是控制翻转,是定义好这些标签,spring内部给打包成Advisor并去执行生成代理的过程(对比之前的例子相当于厂家把所有机器贴上要做拦截计划标签,告诉代理工厂·一个扫描范围。代理工厂自己去一扫描并生成Advisor,最终给机器配备代理售货员)。

spring使用代理工厂

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

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

1.获取当前bean的Advice或Advisor(大概是扫描@Aspect注解的bean,生成Advice或Advisor)
2.通过createProxy()方法创建代理

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

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

over~

版权声明:
作者:Alex
链接:https://www.techfm.club/p/47419.html
来源:TechFM
文章版权归作者所有,未经允许请勿转载。

THE END
分享
二维码
< <上一篇
下一篇>>