用一个小故事模拟Spring Aop(一): 动态代理和责任链

48 阅读9分钟

代理模式

假如现在有个厂家生产了一台生产冰淇淋的机器,支持蛋筒&杯装两种形式,支持选择口味、大小 投放硬币,就可以根据选择生成冰淇淋 (这台机器就相当于一个类,蛋筒和杯装是两个不同的方法,选择口味和规格按钮相当于参数,冰淇淋是返回值)。

代码模拟

接口:

public interface IceCreamMachine {
	/**
	 * 模拟生产杯装冰淇淋
	 * @param taste 草莓/原味/巧克力
	 * @param size 大/中/小
	 * @return 冰淇淋
	 */
	String cup(String taste, String size);
	/**
	 * 模拟生产蛋筒冰淇淋
	 * @param taste 草莓/原味/巧克力
	 * @param size 大/中/小
	 * @return 冰淇淋
	 */
	String eggCone(String taste, String size);
}

实现

/**
 * @Author wmf
 * @Date 2022/1/18 15:37
 * @Description 模拟一个冰淇淋机
 */
public class IceCreamMachine1 implements IceCreamMachine {
	/**
	 * 模拟生产杯装冰淇淋
	 * @param taste 草莓/原味/巧克力
	 * @param size 大/中/小
	 * @return 冰淇淋
	 */
	@Override
	public String cup(String taste, String size) {
		System.out.println("开始生产杯装冰淇淋");
		return taste + " 杯装冰淇淋("+size+")";
	}

	/**
	 * 模拟生产蛋筒冰淇淋
	 * @param taste 草莓/原味/巧克力
	 * @param size 大/中/小
	 * @return 冰淇淋
	 */
	@Override
	public String eggCone(String taste, String size) {
		System.out.println("开始生产蛋筒冰淇淋");
		return taste + " 蛋筒冰淇淋("+size+")";
	}
}

写个测试类来跟进整个过程

/**
 * @Author wmf
 * @Date 2022/1/18 15:45
 * @Description
 */
public class ChainApplication {
	public static void main(String[] args) {
		IceCreamMachine machine = new IceCreamMachine1();
		String iceCream = machine.cup("草莓", "大");
		System.out.println(iceCream); // 正常输出: 草莓 杯装冰淇淋(大)
	}
}

输出:

1.开始生产杯装冰淇淋 
2.草莓 杯装冰淇淋(大)

过了一段时间厂家领导要做市场调研,想收集每天大家都选择了什么口味、什么规格,做市场分析。 为了应付这个工作,厂家雇了一个售货员看管机器,以后客户从售货员处购买,这个售货员先把需求记录下来,然后再去按需操作机器,再把冰淇淋给用户(这个售货员就是这台机器的代理)。

这种实现起来也很简单,最简单的方式就是代理模式

/**
 * @Author wmf
 * @Date 2022/1/18 15:58
 * @Description 机器的代理
 */
public class IceCreamMachineProxy implements IceCreamMachine {
	public IceCreamMachineProxy(IceCreamMachine iceCreamMachine) {
		this.iceCreamMachine = iceCreamMachine;
	}
	private IceCreamMachine iceCreamMachine;
	@Override
	public String cup(String taste, String size) {
		System.out.println("模拟记录需求"+taste+size);
		return iceCreamMachine.cup(taste, size);
	}
	@Override
	public String eggCone(String taste, String size) {
		System.out.println("模拟记录需求"+taste+size);
		return iceCreamMachine.eggCone(taste, size);
	}
}

测试一下

public class ChainApplication {
	public static void main(String[] args) {
		IceCreamMachine machine = new IceCreamMachine1();
		IceCreamMachine machineProxy = new IceCreamMachineProxy(machine);
		String iceCream = machineProxy.cup("草莓", "大");
		System.out.println(iceCream); 
	}
}

输出:

1.模拟记录需求(草莓 大) 
2.开始生产杯装冰淇淋 
3.草莓 杯装冰淇淋(大)

动态代理

通过售货员方式(代理模式),很轻松实现需求记录。 后来厂家越做越大,冰淇淋机越来越多,引进了很多新机器,领导对新的机器质量不放心,于是规定说新机器不光要采集用户需求,又要检查产出的冰淇淋是否达标。 这样导致每个机器不光要配一个售货员(代理),每个售货员干的活还不一样,人力成本和培训成本急速提高。 这时候来了个代理公司说可以帮解决问题,只要厂家每次约定好某个冰淇淋机,计划好额外的工作(拦截器),我们就培养出一个售货员 ,而厂家只要专心生产机器并做好拦截计划就好(代理公司根据约定培养出销售员的过程就是动态代理)。

image.png

关于拦截计划,双方约定了一个规范,否则随便写会造成阅读障碍。

拦截计划的上下文

机器:机器的信息
产品:蛋筒或草莓
需求:客户的需求
开始:实际开始生产冰淇淋

代码模拟就是

/**
 * @Author wmf
 * @Date 2022/1/19 10:05
 * @Description 信息的格式
 */
public interface MethodInvocation {
	/**
	 * 机器的信息
	 * @return
	 */
	Object getThis();
	/**
	 * 方法的信息(杯装还是蛋筒)
	 * @return
	 */
	Method getMethod();
	/**
	 * 客户需求的信息(草莓/原味/巧克力 大/中/小)
	 * @return
	 */
	Object[] getArguments();
	/**
	 * 开始生产标识
	 * @return
	 * @throws Throwable
	 */
	Object proceed() throws Throwable;

}

拦截计划的格式即为针对上下文如何处理

@FunctionalInterface
public interface MethodInterceptor {
	Object invoke(MethodInvocation invocation) throws Throwable;
}

两方规定好了这些规范,接下来就是厂家定制计划的时候,比如为了食品监督他们定制的计划可能是这样的

Object invoke(MethodInvocation invocation) {
    //1.从打包信息invocation获取需求(invocation.getArguments())记在小本上
    //2.开始生产冰淇淋(invocation.proceed())
    //3.把生产出的冰淇淋拍个照发给厂家微信
}

对应的代码模拟

MethodInterceptor interceptor = new MethodInterceptor() {
	@Override
	public Object invoke(MethodInvocation invocation) throws Throwable {
		System.out.println("记录需求:"+invocation.getArguments());
		Object proceed = invocation.proceed();
		System.out.println("对生产出的冰淇淋拍照"+proceed);
		return proceed;
	}
};

厂家的冰淇淋机和拦截计划都准备好了,代理公司需要根据你的机器和拦截计划给你定制培训一个售货员(生成动态代理)

我们以jdk动态代理的方式模拟代理公司的这个过程(不懂jdk动态代理自己补吧)

/**
 * @Author wmf
 * @Date 2022/1/12 18:23
 * @Description 代理公司
 */
public class ProxyCompany implements AopProxy, InvocationHandler { // AopProxy相当于国家给所有代理公司下发的一个标准

	public ProxyCompany() {
	}
	/**
	 * 设置拦截计划
	 * @param interceptor
	 */
	public void setInterceptor(MethodInterceptor interceptor) {
		this.interceptor = interceptor;
	}
	/**
	 * 绑定冰淇淋机
	 * @param target
	 */
	public void setTarget(Object target) {
		this.target = target;
	}
	/**
	 * 附加工作
	 */
	MethodInterceptor interceptor;
	/**
	 * 冰淇淋机
	 */
	Object target;

	/**
	 * 生成售货员(代理)
	 * @return
	 */
	@Override
	public Object getProxy() {
		return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
	}

	@Override
	public Object getProxy(ClassLoader classLoader) {
		return null;
	}

	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		// 准备上下文
		MethodInvocation invocation = new MethodInvocation() {
			@Override
			public Method getMethod() {
				return method;
			}
			@Override
			public Object[] getArguments() {
				return args;
			}
			@Override
			public Object proceed() throws Throwable {
				return method.invoke(target, args);
			}
			@Override
			public Object getThis() {
				return target;
			}
			@Override
			public AccessibleObject getStaticPart() {
				return null;
			}
		};
		// 需求来了之后按拦截计划去执行
		return interceptor.invoke(invocation);
	}
}

测试整个过程:

public class ChainApplication {
	public static void main(String[] args) {
		// 厂家的冰淇淋机
		IceCreamMachine machine = new IceCreamMachine1();
		// 厂家定制拦截计划
		MethodInterceptor interceptor = 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;
			}
		};
		// 代理公司
		ProxyCompany proxyCompany = new ProxyCompany();
		// 绑定冰淇淋机到代理公司
		proxyCompany.setTarget(machine);
		// 绑定拦截计划到代理公司
		proxyCompany.setInterceptor(interceptor);
		// 生成售货员(机器的代理)
		IceCreamMachine saler = (IceCreamMachine) proxyCompany.getProxy();
		String iceCream = saler.eggCone("原味", "中");
	}
}

输出:

1.记录需求:原味 
2.开始生产蛋筒冰淇淋 
3.对生产出的冰淇淋拍照 原味 蛋筒冰淇淋(中)

就这样过了一段时间,厂家代理公司配合很好,厂家专注维护机器制定拦截计划代理公司专注培养代理售货员,分工明确,单一职责。

责任链模式

又过了一段时间,出现新需求,一台机器要加多个拦截计划:既要市场调研又要食品监督,而且拒绝修改原有的拦截计划,只想新加入拦截计划让新旧拦截计划一起生效,这样一来代理公司又要做变动了

首先第一步,一个机器对应多个拦截计划

/** * 附加工作列表 */ 
List<MethodInterceptor> interceptors;

现在有多个计划,每个计划都包含了开始生产的标识,那怎么执行呐,循环执行肯定不行,人家客户要一个冰淇淋你可能给人家生成多个(点了多次开始按钮)。 代理公司想出了解决方案,给每个售货员配一个调度员,这个调度员一个个执行拦截计划,当某个拦截计划写着点击开始按钮时,不是实际的点击开始,而是进行下一个拦截计划(相当于把开始按钮调包了),最后再没有拦截计划时再实际点击开始按钮,这里有点绕,看下面的例子

厂家一台机器有两个计划

食品监督计划:

Object invoke(MethodInvocation invocation) {
    //1.口味/规格记在食品监督小本上
    //2.开始生产冰淇淋(invocation.proceed())
    //3.生产出的冰淇淋拍个照发给厂家微信
}

市场调研计划:

Object invoke(MethodInvocation invocation) {
    //1.口味/规格记在市场调研小本上
    //2.开始生产冰淇淋(invocation.proceed())
}

实际来客户购买冰淇淋时,售货员拿到两个计划交给召唤出的调度员,调度员是这么调度的

按第一个计划(食品监督计划)先走
1.1 口味/规格记在食品监督小本上
1.2 开始生产冰淇淋(拿到这个指令时,调度员实际开始进行下一个计划)
├── 2.1 口味/规格记在市场调研小本上
├── 2.2 开始生产冰淇淋 ***此时没有下一个拦截计划,真正点击开始按钮***
1.3 生产出的冰淇淋拍个照发给厂家微信

所以调度员的任务就是调包开始按钮,记录好拦截计划列表执行到第几个,一个个执行,形成一条链

示意图如下:

image.png

代码模拟:

/**
 * @Author wmf
 * @Date 2022/1/19 13:59
 * @Description 调度员
 */
public class Dispatcher {
	/**
	 * 原打包信息
	 */
	private MethodInvocation methodInvocation;
	/**
	 * 调包后的打包的信息
 	 */
	private MethodInvocation changelingMethodInvocation;
	/**
	 * 拦截计划列表
	 */
	private List<MethodInterceptor> chain;

	/**
	 * 执行拦截计划的游标
	 */
	private Integer index = -1;

	public Dispatcher(MethodInvocation methodInvocation, List<MethodInterceptor> chain) throws Throwable {
		this.chain = chain;
		Dispatcher that = this;
		// 存储调度员原打包的信息
		this.methodInvocation = methodInvocation;
		// 调包打包信息里面的开始按钮
		this.changelingMethodInvocation = new MethodInvocation() {
			@Override
			public Object getThis() {
				return methodInvocation.getThis();
			}
			// 不管
			@Override
			public AccessibleObject getStaticPart() {
				return null;
			}
			@Override
			public Method getMethod() {
				return methodInvocation.getMethod();
			}
			@Override
			public Object[] getArguments() {
				return methodInvocation.getArguments();
			}
			/** 调包开始按钮,工作执行计划的开始按钮实际上指向调度员的proceed**/
			@Override
			public Object proceed() throws Throwable {
				return that.proceed();
			}
		};
	}

	/**
	 * 调度员的工作
	 * @return
	 * @throws Throwable
	 */
	public Object proceed() throws Throwable {
		Object re;
		// 最后一次,没有拦截计划了,开始生产冰淇淋
		if (index == chain.size()-1) {
			re =  methodInvocation.proceed();
		}else{ // 还有下一个拦截计划,继续按照下一个拦截计划执行
			re = chain.get(++index).invoke(changelingMethodInvocation);
		}
		return re;
	}
}

修改代理公司培养售货员逻辑,改动的地方标注了(改)

/**
 * @Author wmf
 * @Date 2022/1/12 18:23
 * @Description 代理公司
 */
public class ProxyCompany implements AopProxy, InvocationHandler { // AopProxy相当于国家给所有代理公司下发的一个标准

	public ProxyCompany() {
	}
	/**
	 * (改)添加拦截计划
	 * @param interceptor
	 */
	public void addInterceptor(MethodInterceptor interceptor) {
		this.interceptors.add(interceptor);
	}
	/**
	 * 绑定冰淇淋机
	 * @param target
	 */
	public void setTarget(Object target) {
		this.target = target;
	}
	/**
	 * (改)附加工作列表
	 */
	List<MethodInterceptor> interceptors = new ArrayList<>();
	/**
	 * 冰淇淋机
	 */
	Object target;

	/**
	 * 生成售货员(代理)
	 * @return
	 */
	@Override
	public Object getProxy() {
		return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
	}

	@Override
	public Object getProxy(ClassLoader classLoader) {
		return null;
	}

	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		// 打包的信息 上面提到过
		MethodInvocation invocation = new MethodInvocation() {
			@Override
			public Method getMethod() {
				return method;
			}
			@Override
			public Object[] getArguments() {
				return args;
			}
			@Override
			public Object proceed() throws Throwable {
				return method.invoke(target, args);
			}
			@Override
			public Object getThis() {
				return target;
			}
			@Override
			public AccessibleObject getStaticPart() {
				return null;
			}
		};
		// (改)召唤一个调度员
		Dispatcher dispatcher = new Dispatcher(invocation, interceptors);
		// (改)需求来了之后让调度员去执行
		return dispatcher.proceed();
	}
}

测试一下

public class ChainApplication {
	public static void main(String[] args) {
		// 厂家的冰淇淋机
		IceCreamMachine machine = new IceCreamMachine1();
		// 厂家定制食品监督计划
		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();
			}
		};
		// 代理公司
		ProxyCompany proxyCompany = new ProxyCompany();
		// 绑定冰淇淋机
		proxyCompany.setTarget(machine);
		// 绑定两个拦截计划
		proxyCompany.addInterceptor(interceptor1);
		proxyCompany.addInterceptor(interceptor2);
		// 生成售货员(机器的代理)
		IceCreamMachine saler = (IceCreamMachine) proxyCompany.getProxy();
		String iceCream = saler.eggCone("原味", "中");
	}
}

输出:

记录需求至食品监督本:原味
记录需求至市场调研本:原味
开始生产蛋筒冰淇淋
拍照传给厂家微信:原味 蛋筒冰淇淋(中)

完全满足了厂家需求,调度员使用这种方式完成了拦截计划一个接一个的执行,就是一条责任链

如上Dispatcher调包的方式还是使用的类似代理模式,写法有点丑,售货员完全可以把打包信息的工作也交给调度员,而调度员实现调包的方式也可以同过继承覆盖来实现,于是优化下代码,(按spring的命名 Dispatcher以下修改为ReflectiveMethodInvocation),优化后的代码如下

ProxyCompany:

/**
 * @Author wmf
 * @Date 2022/1/12 18:23
 * @Description 代理公司
 */
public class ProxyCompany implements AopProxy, InvocationHandler { // AopProxy相当于国家给所有代理公司下发的一个标准

	public ProxyCompany() {
	}
	/**
	 * 设置拦截计划
	 * @param interceptor
	 */
	public void addInterceptor(MethodInterceptor interceptor) {
		this.interceptors.add(interceptor);
	}
	/**
	 * 绑定冰淇淋机
	 * @param target
	 */
	public void setTarget(Object target) {
		this.target = target;
	}
	/**
	 * 附加工作列表
	 */
	List<MethodInterceptor> interceptors = new ArrayList<>();
	/**
	 * 冰淇淋机
	 */
	Object target;

	/**
	 * 生成售货员(代理)
	 * @return
	 */
	@Override
	public Object getProxy() {
		return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
	}

	@Override
	public Object getProxy(ClassLoader classLoader) {
		return null;
	}

	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		// 召唤一个调度员,并把打包的工作也交给调度员
		ReflectiveMethodInvocation dispatcher = new ReflectiveMethodInvocation(target, method, args, interceptors);
		// 需求来了之后按拦截计划去执行
		return dispatcher.proceed();
	}
}

ReflectiveMethodInvocation(原Dispatcher):

/**
 * @Author wmf
 * @Date 2022/1/17 18:05
 * @Description 调度员本身就是一个打包信息,所以继承了MethodInvocation,自己重新实现了proceed
 */
public class ReflectiveMethodInvocation implements MethodInvocation {

	private Object target;

	private Method method;

	private Object[] args;

	/**
	 * 拦截计划列表
	 */
	List<MethodInterceptor> chain;

	private int index = -1;

	public ReflectiveMethodInvocation(Object target, Method method, Object[] args, List<MethodInterceptor> chain) {
		this.target = target;
		this.method = method;
		this.args = args;
		this.chain = chain;
	}

	@Override
	public Method getMethod() {
		return this.method;
	}

	@Override
	public Object[] getArguments() {
		return args;
	}

	@Override
	public Object proceed() throws Throwable {
		Object re;
		if (index == chain.size()-1) {
			re =  method.invoke(target, args);
		}else{
			re = chain.get(++index).invoke(this);
		}
		return re;
	}

	@Override
	public Object getThis() {
		return target;
	}

	@Override
	public AccessibleObject getStaticPart() {
		return null;
	}
}

测试方法还是不变,输出结果页一样(写法干净了很多)

以上就是整个spring aop动态代理+方法拦截+责任链模式的整个思路,下面对比下源码

对比spring

ReflectiveMethodInvocation对比spring ReflectiveMethodInvocation:

image.png

ProxyCompany(使用的是jdk动态代理)对比spring的JdkDynamicAopProxy:

image.png

MethodInterceptor和MethodInvocation就是直接用的spring的,所以不用对比了。

over~

后期会再扩展一下,把整个aop都加上