Advice&Advisor
承接上文
上文最终使用的例子如下
public class ImitateApplication {
public static void main(String[] args) {
// 厂家的冰淇淋机
IceCreamMachine2 machine = new IceCreamMachine2();
// 厂家定制食品监督计划
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();
}
};
// 代理工厂
ProxyFactory proxyFactory = new ProxyFactory();
// 绑定冰淇淋机
proxyFactory.setTarget(machine);
// 没有规范
proxyFactory.setImpl(false);
// 绑定两个拦截计划
proxyFactory.addInterceptor(interceptor1);
proxyFactory.addInterceptor(interceptor2);
// 生成售货员(机器的代理)
IceCreamMachine2 saler = (IceCreamMachine2) proxyFactory.getProxy();
String iceCream = saler.eggCone("原味", "中");
}
}
这样下去
厂家
和代理工厂
又配合了一段时间,厂家
定制了很多种拦截计划
,总结出一个规律:其实我们这些拦截计划
无外乎就两种吗,一个是制作冰淇淋之前干点事,一个是制作后干点事,再不就是两种的结合,原拦截计划
一般如下
Object invoke(MethodInvocation invocation) {
//1.从打包信息invocation获取需求(invocation.getArguments())记在小本上
//2.开始生产冰淇淋(invocation.proceed())
//3.把生产出的冰淇淋拍个照发给厂家微信
}
发现每次我们都要写一遍
开始生产冰淇淋(invocation.proceed())
这句话(对照代码就是每次都要写nvocation.proceed()
这句话,十分冗余),既然固定几种,那就告诉你选哪种,然后干什么,你们代理工厂
自己去写拦截计划
吧,代理工厂没办法,谁叫你是客户,满足你。
为了应对这个问题,
代理工厂
相出个方案来,约定好两种新格式拦截计划
(比原来简单)before和after,你给我before我就在制作冰淇淋前干,给我after我就在之后干,为了用得方便,新格式只需要包含什么机器,什么产品,什么口味规格这些完全易于理解的信息。
新的拦截计划格式A(在制作冰淇淋前干)
//target是机器,method是产品(蛋筒或杯装) args是用户的需求(口味和规格)
void before(Method method, Object[] args, @Nullable Object target) {
// 在这里自己填想干什么...
}
新的拦截计划格式B(在制作冰淇淋后干)
//target是机器,method是产品(蛋筒或杯装) args是用户的需求(口味和规格),returnValue是制作出的冰淇淋
void afterReturning(@Nullable Object returnValue, Method method, Object[] args, @Nullable Object target) {
// 在这里自己填想干什么...
}
这样的
新格式拦截计划
比之前简单多了,节省了学习成本
用代码模拟下
新格式拦截计划
的抽象
public interface Advice {
}
然后之前和之后分别建一个,之前叫MethodBeforeAdvice
,之后叫AfterReturningAdvice
public interface MethodBeforeAdvice extends Advice {
//target是机器,method是产品(蛋筒或杯装) args是用户的需求(口味和规格)
void before(Method method, Object[] args, Object target);
}
public interface AfterReturningAdvice extends Advice {
//target是机器,method是产品(蛋筒或杯装) args是用户的需求(口味和规格),returnValue是制作出的冰淇淋
void afterReturning(Object returnValue, Method method, Object[] args, Object target);
}
这是厂家在定制拦截计划就类似这样写就可以了
new MethodBeforeAdvice() {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("记录需求至市场调研本:"+args[0]);
}
};
对比原来的
MethodInterceptor interceptor2 = new MethodInterceptor() {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("记录需求至市场调研本:"+invocation.getArguments()[0]);
return invocation.proceed();
}
};
轻松多了,参数也更好理解了(不用理解MethodInvocation是什么了)
这个方案提出来后,
厂家
十分满意,问题又回到代理工厂
了,工厂的员工只识别原来的拦截计划(MethodInterceptor)
,要让他们全重新学新拦截计划
格式吗?代价有点大(相当于把之前的工厂代码都改了),于是想到个注意,找一个适配人员
专门负责把新拦截计划
格式转成老拦截计划格式
(Advice转换成MethodInterceptor),其它人还是该干嘛干嘛就行了
解决问题之前,首先要修改的就是需求人员
,原来的需求人员
接受的是老拦截计划
,现在要接受新的,
而且需求人员留了一个心眼,你这总变来变去的,难保以后不再弄点什么,所以用个盒子把新拦截计划
包起来,以后再改我直接往盒子里加东西
这个装新拦截工作计划
的盒子抽象模拟如下
/**
* @Author wmf
* @Date 2022/1/22 17:15
* @Description 装工作计划的盒
*/
public interface Advisor {
// 获取新工作计划
Advice getAdvice();
}
需求人员名字由ProxyConfig变为AdvisedSupport为了和spring对应
/**
* @Author wmf
* @Date 2022/1/19 17:05
* @Description 需求人员
*/
public class AdvisedSupport {
/**
* 附加新拦截计划盒子列表(改)
*/
List<Advisor> advisors = new ArrayList<>();
/**
* 绑定的机器
*/
Object target;
/**
* 是否有规范(是否有实现的接口)
*/
Boolean isImpl;
/**
* 设置新拦截计划(改)
* @param advice
*/
public void addAdvice(Advice advice) {
this.advisors.add(() -> advice);
}
/**
* 绑定机器
* @param target
*/
public void setTarget(Object target) {
this.target = target;
}
/**
* 设置是否有规范(是否有实现的接口)
* @param impl
*/
public void setImpl(Boolean impl) {
isImpl = impl;
}
}
然后就是加个人专门做新拦截计划
到老拦截计划
的映射,怎么映射呐,两种Advice,只需要分别包装成两个Interceptor
public class MethodBeforeInterceptor implements MethodInterceptor {
private final MethodBeforeAdvice advice;
public MethodBeforeInterceptor(Advice advice) {
this.advice = (MethodBeforeAdvice) advice;
}
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis());
return mi.proceed();
}
}
public class AfterReturningInterceptor implements MethodInterceptor {
private AfterReturningAdvice advice;
public AfterReturningInterceptor(Advice advice) {
this.advice = (AfterReturningAdvice) advice;
}
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
Object proceed = mi.proceed();
advice.afterReturning(proceed, mi.getMethod(), mi.getArguments(), mi.getThis());
return proceed;
}
}
这样转换就方便了,比如要把MethodBeforeAdvice转换为MethodBeforeInterceptor,只需
new MethodBeforeInterceptor(advice);
就搞定了!
下面模拟这个负责映射的适配人员
,命名为DefaultAdvisorChainFactory
(对应spring), 他的主要工作就是沟通需求人员
,获得新拦截计划
转换为老拦截计划
/**
* 模拟负责适配新拦截计划到老拦截计划的工作人员
*/
public class DefaultAdvisorChainFactory {
/**
* 主要工作就是查看需求配置,获得新拦截计划转换为老拦截计划
* @param config
* @return
*/
public List<MethodInterceptor> getInterceptors(AdvisedSupport config) {
List<MethodInterceptor> interceptors = new ArrayList<>();
for (Advisor advisor : config.advisors) {
Advice advice = advisor.getAdvice();
// MethodBeforeAdvice转换为MethodBeforeInterceptor
// AfterReturningAdvice转换为AfterReturningInterceptor
if (advice instanceof MethodBeforeAdvice) {
interceptors.add(new MethodBeforeInterceptor((MethodBeforeAdvice) advice));
} else if (advice instanceof AfterReturningAdvice) {
interceptors.add(new AfterReturningInterceptor((AfterReturningAdvice) advice));
}
}
return interceptors;
}
}
把这个负责映射的适配人员
的电话给需求人员
,于是需求人员
就有了新能力,利用用这个适配人员
把新格式拦截计划
转为老格式拦截计划
/**
* @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() {
return chainFactory.getInterceptors(this);
}
/**
* 设置新拦截计划(改)
* @param advice
*/
public void addAdvice(Advice advice) {
this.advisors.add(() -> advice);
}
/**
* 绑定机器
* @param target
*/
public void setTarget(Object target) {
this.target = target;
}
/**
* 设置是否有规范(是否有实现的接口)
* @param impl
*/
public void setImpl(Boolean impl) {
isImpl = impl;
}
}
测试一下
public class ImitateApplication {
public static void main(String[] args) {
// 厂家的冰淇淋机
IceCreamMachine1 machine = new IceCreamMachine1();
// 厂家按新格式定制市场调研计划
MethodBeforeAdvice advice1 = (method, args1, target) -> System.out.println("记录需求至市场调研本:" + args1[0]);
// 代理工厂
ProxyFactory proxyFactory = new ProxyFactory();
// 绑定冰淇淋机
proxyFactory.setTarget(machine);
// 没有规范
proxyFactory.setImpl(true);
// 绑定两个拦截计划
proxyFactory.addAdvice(advice1);
// 生成售货员(机器的代理)
IceCreamMachine saler = (IceCreamMachine) proxyFactory.getProxy();
String iceCream = saler.eggCone("原味", "中");
}
}
输出
记录需求至市场调研本:原味
开始生产蛋筒冰淇淋
看到效果了,现在厂家
干的事比原来简单多了,写法简洁明了
适配器
代理工厂
完成了改造满足了厂家
的需求,但代理工厂
有前瞻性的领导提前发现了个潜在的问题: 这样做确实方便了,但是缺乏灵活性了,现在有两种Advice对应两种MethodInterceptor,将来可能出现第三种或更多种,甚至一个Advice对应多个MethodInterceptor,每次新增,适配人员都要新学习对应关系(需要修改DefaultAdvisorChainFactory的代码),适配人员
的工作变的很繁琐
既然提前预见了这个问题,那就做好应对方法,方案:做一些
适配器
,每个适配器
都有如下两个功能
1.给适配器某个类型的新格式拦截计划(Advice)
,适配器
会告诉你它是否支持这种Advice
2.给适配器某个支持的类型的格式拦截计划(Advice)
,适配器
还会给你自动转换为老格式的工作计化thodInterceptor)
下面对适配器
做一个抽象
/**
* @Author wmf
* @Date 2022/1/22 14:34
* @Description 适配器的抽象
*/
public interface AdvisorAdapter {
/**
* 传入advice,返回是否支持
*/
boolean supportsAdvice(Advice advice);
/**
* 传入advice,返回MethodInterceptor
*/
MethodInterceptor getInterceptor(Advisor advisor);
}
下面分别做两个Advice
的适配器
class MethodBeforeAdviceAdapter implements AdvisorAdapter {
@Override
public boolean supportsAdvice(Advice advice) {
return (advice instanceof MethodBeforeAdvice);
}
@Override
public MethodInterceptor getInterceptor(Advisor advisor) {
return new MethodBeforeInterceptor(advisor.getAdvice());
}
}
class MethodBeforeAdviceAdapter implements AdvisorAdapter {
@Override
public boolean supportsAdvice(Advice advice) {
return (advice instanceof MethodBeforeAdvice);
}
@Override
public MethodInterceptor getInterceptor(Advisor advisor) {
return new AfterReturningInterceptor(advisor.getAdvice());
}
}
有了这些适配器,代理工厂在
适配人员
下面设一个适配器管理人员
,原适配人员
升值为适配负责人
,他的职责管理所有适配器
,当适配负责人
给他一个新格式拦截计划(Advice)
,他一个个查看适配器
,哪个支持用哪个,而适配负责人
就不用自己判断了,只负责沟通需求人员
,拿到Advice
交给适配器管理人
员并获取结果即可
下面抽象一下适配器管理人员
/**
* 适配器管理人员的抽象
*/
public interface AdvisorAdapterRegistry {
/**
* 能力1.添加适配器
* @param adapter
*/
void registerAdvisorAdapter(AdvisorAdapter adapter);
/**
* 能力2.给一个老格式工作计划转换为n个新格式工作计划
* @param advisor
* @return
*/
MethodInterceptor[] getInterceptors(Advisor advisor);
}
实现如下
/**
* 适配器管理人员
*/
public class DefaultAdvisorAdapterRegistry implements AdvisorAdapterRegistry {
private final List<AdvisorAdapter> adapters = new ArrayList<>(3);
/**
* 先添加两个现有的适配器
*/
public DefaultAdvisorAdapterRegistry() {
registerAdvisorAdapter(new MethodBeforeAdviceAdapter());
registerAdvisorAdapter(new AfterReturningAdviceAdapter());
}
/**
* 转换
* @param advice
* @return
*/
@Override
public MethodInterceptor[] getInterceptors(Advisor advisor) {
List<MethodInterceptor> interceptors = new ArrayList<>();
// 一个个适配器看
for (AdvisorAdapter adapter : this.adapters) {
// 如果支持
if (adapter.supportsAdvice(advisor.getAdvice())) {
// 转换
interceptors.add(adapter.getInterceptor(advisor));
}
}
return interceptors.toArray(new MethodInterceptor[0]);
}
/**
* 注册适配器
* @param adapter
*/
@Override
public void registerAdvisorAdapter(AdvisorAdapter adapter) {
this.adapters.add(adapter);
}
}
由于
适配器
这种事是公司统一定的,为了防止多个人员所持适配器不同混乱,也为了节约资源,所以公司规定,整个公司只能有一个适配器管理人员
实现这种想法就是单例模式(大家都会,就不模拟了)
回到适配负责人
,他的工作就很简单了,只负责沟通需求人员
,拿到Advice
交给适配器管理人
员并获取结果即可
/**
* 模拟负责映射新拦截计划到老拦截计划的适配负责人
*/
public class DefaultAdvisorChainFactory {
/**
* 给分配一个适配器管理员(这里spring用的单例模式)
*/
DefaultAdvisorAdapterRegistry registry = new DefaultAdvisorAdapterRegistry();
/**
* 主要工作就是查看需求配置,获得新拦截计划转换为老拦截计划
* @param config
* @return
*/
public List<MethodInterceptor> getInterceptors(AdvisedSupport config) {
List<MethodInterceptor> interceptors = new ArrayList<>();
for (Advice advice : config.advices) {
// 让适配其人员去实际转换
MethodInterceptor[] inters = registry.getInterceptors(advice);
interceptors.addAll(Arrays.asList(inters));
}
return interceptors;
}
}
可以看到代码也没有if elseif了(阿里不推荐if else)
回到问题本身,现在问题解决了,如果需要新增Advice和Interceptor,只需要新增然后定义一个适配器即可,原来的人还是该干嘛干嘛,不用变了(符合了开闭原则)
上面的很多代码命名都有Advisor,其实Spring实现也有很多Advisor,只不过被我替换为Advice,因为Advisor下一章再讲
解决了这个问题,
代理工厂
发现了虽然服务升级,但是老格式的拦截计划
还是要支持,毕竟还是最灵活的方式,所以规定需求人员也可以接受老格式的拦截计划
,只要适配负责人
看到老格式的拦截计划
不做转换即可
老格式的拦截计划
也是新格式拦截计划
的一种,体现在代码上就是MethodInterceptor
继承了Advice
@FunctionalInterface
public interface MethodInterceptor extends Advice {
Object invoke(MethodInvocation invocation) throws Throwable;
}
适配器管理人员
修改为
/**
* 适配器管理人员
*/
public class DefaultAdvisorAdapterRegistry implements AdvisorAdapterRegistry {
private final List<AdvisorAdapter> adapters = new ArrayList<>(3);
/**
* 先添加两个现有的适配器
*/
public DefaultAdvisorAdapterRegistry() {
registerAdvisorAdapter(new MethodBeforeAdviceAdapter());
registerAdvisorAdapter(new AfterReturningAdviceAdapter());
}
/**
* 转换
* @param advisor
* @return
*/
@Override
public MethodInterceptor[] getInterceptors(Advisor advisor) {
List<MethodInterceptor> interceptors = new ArrayList<>();
Advice advice = advisor.getAdvice();
// 如果是老格式不转换(新增)
if (advice instanceof MethodInterceptor) {
interceptors.add((MethodInterceptor) advice);
}
// 一个个适配器看
for (AdvisorAdapter adapter : this.adapters) {
// 如果支持
if (adapter.supportsAdvice(advice)) {
// 转换
interceptors.add(adapter.getInterceptor(advisor));
}
}
return interceptors.toArray(new MethodInterceptor[0]);
}
/**
* 注册适配器
* @param adapter
*/
@Override
public void registerAdvisorAdapter(AdvisorAdapter adapter) {
this.adapters.add(adapter);
}
}
再来测试
public class ImitateApplication {
public static void main(String[] args) {
// 厂家的冰淇淋机
IceCreamMachine1 machine = new IceCreamMachine1();
// 厂家按新格式定制市场调研计划
MethodBeforeAdvice advice1 = (method, args1, target) -> System.out.println("记录需求至市场调研本:" + args1[0]);
// 厂家定制老格式食品监督计划
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;
}
};
// 代理工厂
ProxyFactory proxyFactory = new ProxyFactory();
// 绑定冰淇淋机
proxyFactory.setTarget(machine);
// 没有规范
proxyFactory.setImpl(true);
// 绑定两个拦截计划
proxyFactory.addAdvice(advice1);
proxyFactory.addAdvice(interceptor1);
// 生成售货员(机器的代理)
IceCreamMachine saler = (IceCreamMachine) proxyFactory.getProxy();
String iceCream = saler.eggCone("原味", "中");
}
}
输出:
记录需求至市场调研本:原味
记录需求至食品监督本:原味
开始生产蛋筒冰淇淋
拍照传给厂家微信:原味 蛋筒冰淇淋(中)
都生效了
对比spring
命名都一样,自行比对即可