spring event-listener模型

在讨论spring event-listener之前,我们先来看一个设计模式-订阅者设计模式。这个模式在编程世界很常见,比如tomcat的event-listener,spring event-listener等都是实际案例。

下面给一个简单版本的demo

package com.jt.ec.salesorder.domain;

import java.util.ArrayList;
import java.util.List;

public class EventListenerDemo {
	
	/**
	 * 定义一个报刊
	 */
	public static abstract class News{  
		public News(){
			
		}
		
		/**
		 * 订阅的内容
		 */
		public abstract void readNew(String content);
	}
	
	/**
	 * 报刊管理中心
	 */
	public static class NewsManager{
		private List buyers;
		
		public NewsManager(){
			People p = new People("luohong");
			People p1 = new People("luohong1");
			People p2 = new People("luohong2");
			addBuyer(p);
			addBuyer(p1);
			addBuyer(p2);
		}
		
		public List getBuyers() {
			return buyers;
		}

		/**
		 * 添加多个订阅者
		 */
		public void addBuyers(List buyers) {
			this.buyers = buyers;
		}
		
		/**
		 * 添加一个订阅者
		 */
		public void addBuyer(People buyer) {
			if(this.buyers == null){
				this.buyers = new ArrayList<>();
			}
			this.buyers.add(buyer);
		}
		
		/**
		 * 通知订阅的读者
		 */
		public void sendToBuyers(){
			for(People buyer: buyers){
				buyer.readNew("<哈哈,欢迎您的订阅,我是骆宏...>");
			}
		}
	}
	
	/**
	 * 读者
	 */
	public static class People extends News{
		private String name;
		
		public People(String name){
			this.name = name;
		}

		public String getName() {
			return name;
		}

		public void setName(String name) {
			this.name = name;
		}

		@Override
		public void readNew(String content) {
			System.out.println(name + "阅读了" + content);
		}
	}
	
	public static void main(String[] args) {
		NewsManager manager = new NewsManager();
		manager.sendToBuyers();
	}
	/*
	 * 输出内容 
	 * luohong阅读了<哈哈,欢迎您的订阅,我是骆宏...>
	 * luohong1阅读了<哈哈,欢迎您的订阅,我是骆宏...>
         * luohong2阅读了<哈哈,欢迎您的订阅,我是骆宏...>
	 */
}
        这里面,News代表一个主题,People是该主题的具体关注着,NewsManager起到一个管理的作用,在代码中,我们可以发现,People只需要注册感兴趣的News,具体什么时候阅读,有NewsManager来完成调用。这种模型的代码,能够很好的实现解耦。

具体到spring中,我们该怎么使用event-listener呢?由于spring已经将抽象出了event,listener了,所以我们只需要按照spring的约定,然后将listener注册为一个bean,即可在抛出事件时,listener完成调用。这次的主题,不在于spring的实现细节,而是讨论,什么情境下该使用event-listener。我们看一个实际的代码案列,如下所示

if(CollectionUtils.isNotEmpty(financeBillPos)){
	//3循环所有的财务单据,把完成和审核通过的财务单据的金额进行统计
	for(FinanceBillPo financeBillPo : financeBillPos){
		if(financeBillPo.getStatus().equals(FinanceBillStatus.done.name())){
			if(financeBillPo.getType().equals(FinanceBillType.receipt.name())){
				//3.1收款单,退款的金额=+已收款的金额
				totalAmt=totalAmt.add(financeBillPo.getTotal());
				cpAccount=financeBillPo.getPayAccount();
			}else if(financeBillPo.getType().equals(FinanceBillType.pay.name()) || financeBillPo.getType().equals(FinanceBillType.refund.name())){
				//3.2付款单/退款单,退款的金额=-已付款/已退款的金额
				totalAmt=totalAmt.subtract(financeBillPo.getTotal());
			}
		}
	}
}else{
	//绿色通道主订单的生成退款单
	totalAmt = soEntityPo.getPayedAmount();
}
//4金额大于零则生成退款单
if(totalAmt.compareTo(BigDecimal.ZERO)==1){
	String summary="订单["+soEntityPo.getSoNo()+"]"+soEntityPo.getCashTypeEnum().desc()+";退款金额:";
	FinanceBillResult  financeBillResult =financeBillService.addRefundBill(FinanceBillEntityType.salesorder.name(), soEntityPo.getSoNo(), totalAmt,  summary+totalAmt.toString(), soEntityPo.getPayType(), cpAccount);
	SoBillEvent soBillEvent = new SoBillEvent(financeBillResult.getFinanceBillPo(),soEntityPo.getId(),true);
	SpringHelper.publishEvent(soBillEvent);
}
我们忽略其中的业务代码,关注最后面的一部分,这里面抛出了一个SoBillEvent,然后这个Event的业务,该开发者也已经备注好了,就是生成一个退款单。但是从这个代码来看,我们发现压根看不懂,如果删除4金额大于零则生成退款单的备注,那么我们就更加难懂这块代码了。

综合上面所述,我们可以明显的看到,event-listener的模型,硬生生将一个业务,然后拆分为了多个业务,并且多个业务是未知的,你不知道会有多少个listener来监听该event,更加不知道每个listener的业务意义了。假设你是一个维护代码的开发者,看到event时,你不得不去查看该event的所有listener,然后逐一找出来,然后逐个观察里面的业务。event-listener模型将一个线性的代码块,拆分为了一个tree结构的代码块,所以这种是属于典型的误用event-listener编程模型的例子。

那我们以更加抽象的方式来观察下event-listener模型,见下面的伪代码

try{
	saveOrderItem();
	saveShipingInfo();
	saveSoLog();
	placeOrder();
	operOrderProfix();  //创建业绩
}

try{
	saveOrderItem();
	saveShipingInfo();
	saveSoLog();
	placeOrder();
	var event = createOrderEvent();   //跑出一个事件,在listener中创建业绩
	publishEvent(event);
}
我们可以对比两种代码的差异点,第一个方案,直接在逻辑代码中,调用创建业绩的代码,第二个方案,却新建了两个类,然后将线性代码拆分为了tree模型。

那么什么时候合适使用event-listener呢?根据我个人的实践,推荐在如下几种模型中,可以考虑使用event-listener

1.执行无关业务代码,比如:记录日志等

2.需要异步执行的代码,比如:下载文件

3.跨系统调用,比如集成第三方的api等


总结

使用event-listener模型时,需要非常的谨慎,避免误用。一般我个人在使用时,会做如下几点的思考

        1.考核业务的连续性
        2.考核添加event-listener后,维护代码的同事能否一眼看出event,listener的业务意义
        3.如果是需要使用异步编程模型,可以考虑使用

4.如果使用了event-listener,那么遵循:one event one listener,并且不允许在listener中继续抛出其他类型event




你可能感兴趣的:(开发规范,框架开发,spring)