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

88 阅读6分钟

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(其实就相当于写了个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~