转载

责任链模式多条链路时 Spring 单例造成的深坑

(一)线上事故:

催收系统每日自动分配案件时一直正常,突然某一天(2018-3-27)以后 案件分配不均匀,一系列追踪下查到原因 是责任链 有一环 未被执行(kibana上当天2018-3-27 以后 未查看相应的日志记录) ,很奇怪 ,重启服务后 第二天 结果恢复正常

(二)分析问题:

分析 具体的催收分案业务 , 有手动分案(责任链模式)和每日自动分案(责任链模式)两种情形,未被执行的一环正好是 手动分案 相比 自动分案 缺少的一环

(三)问题猜想 :

查看 日志发现 当天 确实 存在手动分案的操作记录 ,猜想是手动分案 影响了 自动分案的功能

(四)查看代码,分析问题


 

@Service

public class DistributionHandlerHolder implements IDistributionHandlerHolder {


/**

* 自动分案,逻辑最完备

*/

private AbstractHandler autoDistribution;


/**

* 手动分案,不记录上次催收人以及下次催收人等

*/

private AbstractHandler manualDistribution;


@Override

public AbstractHandler getAutoExecuteHandler() {

if(autoDistribution == null) {

synchronized(this) {

if(autoDistribution == null) {

createAutoExecuteHandler();

}

}

}

return autoDistribution;

}


@Override

public AbstractHandler getManualExecuteHandler() {

if(manualDistribution == null) {

synchronized(this) {

if(manualDistribution == null) {

createManualExecuteHandler();

}

}

}

return manualDistribution;

}



private void createAutoExecuteHandler() {

AbstractHandler autoStart = null;

AbstractHandler autoPre = null;

for(AutoDistributionEnum distributionEnum : AutoDistributionEnum.values()) {

AbstractHandler handler = ApplicationContextHolder.getBean(distributionEnum.getBeanName(), AbstractHandler.class);

if(autoPre != null) {

autoPre.setNextHandler(handler);

}

if(autoStart == null) {

autoStart = handler;

}

autoPre = handler;

}

autoDistribution = autoStart;

}


private void createManualExecuteHandler() {

AbstractHandler manualStart = null;

AbstractHandler manualPre = null;

for(ManualDistributionEnum distributionEnum : ManualDistributionEnum.values()) {

AbstractHandler handler = ApplicationContextHolder.getBean(distributionEnum.getBeanName(), AbstractHandler.class);

if(manualPre != null) {

manualPre.setNextHandler(handler);

}

if(manualStart == null) {

manualStart = handler;

}

manualPre = handler;

}

manualDistribution = manualStart;

}

}


 

@Slf4j

public abstract class AbstractHandler {



protected AbstractHandler nextHandler;


public void setNextHandler(AbstractHandler nextHandler) {

this.nextHandler = nextHandler;

}

}


 

@Component("distributionUserSortHandler")

@Slf4j

public class UserSortHandler extends AbstractHandler {


@Autowired

private IOrderService orderService;


@Override

public void execute(DistributionRequest request, DistributionResponse response) {

nextHandler.execute(request, response);

}

}

1、大体上看没有问题,

手动分案和自动分案是两条完全不同

责任链创建过程

2、仔细观察是每个责任链 是单例模式 ,没有标记@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) 注解 ,和大脑印象中代码不符合

3、仔细分析 : (1)自动分案 走 1-》2-》3-》4

(2)手动分案走 1-》2-》4

(3)先自动分案 ,后手动分案, 造成 自动分案 

被替换成   1-》2-》4,造成 3链缺失

(四) 解决方案:

每个责任链上加上@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)注解,造成

AbstractHandler handler =

ApplicationContextHolder.getBean(distributionEnum.getBeanName(), AbstractHandler.class);获取时获取到不同的对象 ,而不是spring单例对象

(五) 归纳总结:

spring 单例 最好无状态,责任链 的每一环必有状态(指向下一环),因为存在多条链路(多种业务情况)

所以每一环应该定义成多例(最好多例),否则就造成如上的线程间不安全的问题。

责任链模式多条链路时 Spring 单例造成的深坑

原文  https://mp.weixin.qq.com/s/ipXPMtPawDdESMJYrYthcQ
正文到此结束
Loading...