用一个小故事模拟Spring-Aop(三)--Advice&适配器

本文探讨了如何通过引入新拦截计划格式(A和B)简化厂家和代理工厂的合作。通过创建Advice和Adapter接口,厂家定制的拦截计划变得更加直观,同时通过适配器机制,实现了新旧格式的无缝转换,提高了工作效率并遵循开闭原则。
摘要由CSDN通过智能技术生成

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()这句话,十分冗余),既然只有两种,那我们就告诉你选哪种,然后干什么,你们自己去写拦截计划吧,代理工厂一听,没办法,谁叫你是客户,满足你(厂家相当于程序员,代理工厂相当于框架,框架肯定为程序员服务,得用的爽,要不就没人用了)
为了应对这个问题,代理工厂相出个方案来,我们约定好两种新格式拦截计划(比原来简单)A和B,你给我A我就在制作冰淇淋前干,给我B我就在之后干,为了让你用得方便,新格式也不包含打包信息了(MethodInvocation),就直接给你什么机器,什么产品,什么口味规格这些你完全理解的信息就可以了

新的拦截计划格式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) {
    // 在这里自己填想干什么...
}

这样的新格式拦截计划比之前简单多了,不用写什么时候点击开始制作按钮(因为选A就是在之前干,选B就是在之后干,定死了)

用代码模拟下
新格式拦截计划的抽象

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),其它人还是该干嘛干嘛就行了

image.png

解决问题之前,首先要修改的就是需求人员,原来的需求人员接受的是老拦截计划,现在要接受新的,
而且需求人员留了一个心眼,你这总变来变去的,难保以后不再弄点什么,所以用个盒子把新拦截计划包起来,以后再改我直接往盒子里加东西
这个装新拦截工作计划的盒子抽象模拟如下

/**
 * @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),他一个个查看适配器,如果哪个支持,再返回老格式拦截计划(MethodInterceptor),而适配负责人就不用自己判断了,只负责沟通需求人员,拿到Advice交给适配器管理人员并获取结果即可

image.png

下面抽象一下适配器管理人员

/**
 * 适配器管理人员的抽象
 */
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

命名都一样,自己对比

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值