用一个小故事模拟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
,可自行参考
再用图梳理整个过程
对比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
方法) - 跟进
wrapIfNecessary
方法内部看一下
1.获取当前bean的Advice或Advisor(大概是扫描@Aspect
注解的bean,生成Advice或Advisor)
2.通过createProxy()
方法创建代理
- 再进入
createProxy
方法内部看一下
这就和之前我们模拟的代码基本一个套路了,对接上了
over~
共有 0 条评论