責(zé)任鏈模式(Chain of Responsibility Pattern),簡而言之,就是把具有能力處理某種任務(wù)的類組合成這個鏈,這個鏈一般是有頭有尾的,在此我們稱鏈中的每一個類為節(jié)點(diǎn),當(dāng)需要處理的請求到達(dá)鏈頭時,就會執(zhí)行節(jié)點(diǎn)的處理邏輯,每個節(jié)點(diǎn)的處理情況可分為三種:
- 當(dāng)前節(jié)點(diǎn)處理完并直接返回(一般是到尾節(jié)點(diǎn))
- 當(dāng)前節(jié)點(diǎn)不作任何處理傳給下一個節(jié)點(diǎn)
- 當(dāng)前節(jié)點(diǎn)進(jìn)行相關(guān)處理后再傳給下一個節(jié)點(diǎn)
最近在閱讀開源工作流項目flowable的源碼時發(fā)現(xiàn)大量應(yīng)用了責(zé)任鏈模式,本文先模擬日志級別處理來介紹一個簡單的責(zé)任鏈模式,然后再看flowable源碼中的實(shí)現(xiàn)。
1. 責(zé)任鏈模式之日志級別處理
先以我們比較熟悉的日志系統(tǒng)為例,假如現(xiàn)在我們要實(shí)現(xiàn)一個有Debug, Info, Warn, Error四個級別的日志系統(tǒng),需要滿足以下條件:
a. 系統(tǒng)啟動時可以配置日志級別,打印日志時可以設(shè)置日志的級別。
b. 日志級別從小到大依次為Debug, Info, Warn, Error, 且只有當(dāng)配置日志級別小于或等于某類日志級別時,才會打印相應(yīng)級別的日志,否則不打印。
備注: 為了方便判斷,我們分別用1,2,3,4來表示這四個日志級別。
用責(zé)任鏈實(shí)現(xiàn)的日志系統(tǒng)類圖如下:
首先定義一個接口,如下:
public interface Logger {
/**
* 打印日志的方法
* @param message 具體內(nèi)容
* @param msgLevel 日志級別
* @throws Exception 當(dāng)打印消息的日志級別不在范圍內(nèi)時拋出異常
*/
void excute(String message, int msgLevel) throws Exception;
/**
* 獲取日志類的下一個節(jié)點(diǎn)
* @return
*/
Logger getNext();
/**
* 設(shè)置日志類的下一個節(jié)點(diǎn),以形成責(zé)任鏈
* @param logger
* @throws Exception
*/
void setNext(Logger logger) throws Exception;
}
然后定義一個實(shí)現(xiàn)上面接口的抽象類:
public abstract class AbstractLogger implements Logger{
/**
* 日志類的下一個節(jié)點(diǎn)
*/
protected Logger next;
/**
* 系統(tǒng)的日志級別
*/
protected int configLevel;
public AbstractLogger(int configLevel) throws Exception {
if(configLevel < 1 || configLevel > 4){
throw new Exception("Unsupported logger config level, only 1 to 4 is allowed!");
}
this.configLevel = configLevel;
}
@Override
public Logger getNext() {
return next;
}
@Override
public void setNext(Logger logger) throws Exception {
this.next = logger;
}
/**
* 提供給子類用的公共方法
* @param message
*/
protected void print(String message){
System.out.println(message);
}
}
然后是四個級別對應(yīng)的日志實(shí)現(xiàn)類:
首先是責(zé)任鏈中第一個節(jié)點(diǎn):
public class ErrorLogger extends AbstractLogger {
public static final int level = 4;
public ErrorLogger(int configLevel) throws Exception {
super(configLevel);
}
@Override
public void excute(String message, int msgLevel) throws Exception {
if(msgLevel == level){
print("Logger::Error: " + message);
}else if(msgLevel >= configLevel){
next.excute(message, msgLevel);
}
}
}
第二個節(jié)點(diǎn):
public class InfoLogger extends AbstractLogger {
public static final int level = 2;
public InfoLogger(int configLevel) throws Exception {
super(configLevel);
}
@Override
public void excute(String message, int msgLevel) throws Exception {
if(msgLevel == level){
print("Logger::Info: " + message);
}else if(msgLevel >= configLevel){
next.excute(message, msgLevel);
}
}
}
第三個節(jié)點(diǎn):
public class WarnLogger extends AbstractLogger {
public static final int level = 3;
public WarnLogger(int configLevel) throws Exception {
super(configLevel);
}
@Override
public void excute(String message, int msgLevel) throws Exception {
if(msgLevel == level){
print("Logger::Warn: " + message);
}else if(msgLevel >= configLevel){
next.excute(message, msgLevel);
}
}
}
最后一個節(jié)點(diǎn):
public class DebugLogger extends AbstractLogger {
public static final int level = 1;
public DebugLogger(int configLevel) throws Exception {
super(configLevel);
}
@Override
public void excute(String message, int msgLevel) throws Exception {
if(msgLevel == level){
print("Logger::Debug: " + message);
}
}
/**
* Debug為最低的級別,在責(zé)任鏈中沒有下一個節(jié)點(diǎn)
* @return
*/
@Override
public Logger getNext() {
return null;
}
/**
* 當(dāng)嘗試給Debug日志類設(shè)置下一個節(jié)點(diǎn)時拋出異常
* @param logger
* @throws Exception
*/
@Override
public void setNext(Logger logger) throws Exception {
throw new Exception("Next Logger in not Allowed in here!");
}
}
測試類的代碼如下:
public class ChainPatternDemo {
/**
* 設(shè)置的系統(tǒng)的日志級別,組合日志處理責(zé)任鏈,返回責(zé)任鏈的首個節(jié)點(diǎn)
* @param level 系統(tǒng)的日志級別
* @return
* @throws Exception
*/
public static Logger initLogConfig(int level) throws Exception {
try{
Logger root = new ErrorLogger(level);
Logger warnLogger = new WarnLogger(level);
Logger infoLogger = new InfoLogger(level);
Logger debugLogger = new DebugLogger(level);
root.setNext(warnLogger);
warnLogger.setNext(infoLogger);
infoLogger.setNext(debugLogger);
return root;
}catch (Exception e){
throw e;
}
}
public static void main(String[] args) {
try{
Logger logger = initLogConfig(4);
logger.excute("error message", 4);
logger.excute("warn message", 3);
logger.excute("info message", 2);
logger.excute("debug message", 1);
}catch (Exception e){
System.out.println(e.getMessage());
}
}
}
通過Logger logger = initLogConfig(4)
中的數(shù)字設(shè)置日志級別,然后嘗試打印所有級別的日志, 當(dāng)設(shè)置為1即最低時,輸出如下, 打印所有日志,符合預(yù)期
Logger::Error: error message
Logger::Warn: warn message
Logger::Info: info message
Logger::Debug: debug message
當(dāng)設(shè)置日志級別為2時,輸出如下,只打印info及以上級別的日志,符合預(yù)期:
Logger::Error: error message
Logger::Warn: warn message
Logger::Info: info message
當(dāng)設(shè)置日志級別為4即最高時,輸出如下,只打印Error級別的日志,符合預(yù)期:
Logger::Error: error message
所有的日志處理都會通過定義的責(zé)任鏈進(jìn)行處理,每個責(zé)任鏈中的節(jié)點(diǎn)只有在需要時才進(jìn)行處理或者傳遞給下一個節(jié)點(diǎn)處理。
2. flowable中的責(zé)任鏈模式
如下,以RuntimeServiceImpl為例, 在flowable源碼中很多這樣的調(diào)用,這就是典型的責(zé)任鏈模式的應(yīng)用。
此處關(guān)鍵在于實(shí)現(xiàn)了ServiceImpl這個類,如下所示,flowable的幾大主要類都實(shí)現(xiàn)了該接口。
相關(guān)的類結(jié)構(gòu)圖如下:
?ServiceImpl類有CommandExecutor類型的成員變量,而CommandExecutor的實(shí)現(xiàn)類又有CommandInterceptor類型的成員變量,實(shí)現(xiàn)CommandInterceptor的抽象類中的成員變量next也是一個CommandInterceptor類,通過next既可以實(shí)現(xiàn)責(zé)任鏈模式,AbstractCommandInterceptor的如下實(shí)現(xiàn)類將作為責(zé)任鏈中的節(jié)點(diǎn)。
那么責(zé)任鏈的前后關(guān)系又是怎樣的呢,這里我們就要查看ProcessEngineConfigurationImpl的源碼了,這個類的initInterceptorChain方法即為責(zé)任鏈的初始處理邏輯,如下:
public CommandInterceptor initInterceptorChain(List<CommandInterceptor> chain) {
if (chain == null || chain.isEmpty()) {
throw new FlowableException("invalid command interceptor chain configuration: " + chain);
}
for (int i = 0; i < chain.size() - 1; i++) {
chain.get(i).setNext(chain.get(i + 1));
}
return chain.get(0);
}
可以看到責(zé)任鏈的前后關(guān)系是按照列表中的順序的,所以關(guān)鍵點(diǎn)在于傳參,找到調(diào)用這個方法的地方:
public void initCommandExecutor() {
if (commandExecutor == null) {
CommandInterceptor first = initInterceptorChain(commandInterceptors);
commandExecutor = new CommandExecutorImpl(getDefaultCommandConfig(), first);
}
}
參數(shù)commandInterceptors是ProcessEngineConfigurationImpl類的一個成員變量,所以接下來要看看哪里初始化這個成員變量,如下:
public void initCommandInterceptors() {
if (commandInterceptors == null) {
commandInterceptors = new ArrayList<CommandInterceptor>();
if (customPreCommandInterceptors != null) {
commandInterceptors.addAll(customPreCommandInterceptors);
}
commandInterceptors.addAll(getDefaultCommandInterceptors());
if (customPostCommandInterceptors != null) {
commandInterceptors.addAll(customPostCommandInterceptors);
}
commandInterceptors.add(commandInvoker);
}
}
public Collection<? extends CommandInterceptor> getDefaultCommandInterceptors() {
List<CommandInterceptor> interceptors = new ArrayList<CommandInterceptor>();
interceptors.add(new LogInterceptor());
CommandInterceptor transactionInterceptor = createTransactionInterceptor();
if (transactionInterceptor != null) {
interceptors.add(transactionInterceptor);
}
if (commandContextFactory != null) {
interceptors.add(new CommandContextInterceptor(commandContextFactory, this));
}
if (transactionContextFactory != null) {
interceptors.add(new TransactionContextInterceptor(transactionContextFactory));
}
return interceptors;
}
先添加自定義前置節(jié)點(diǎn),再添加默認(rèn)節(jié)點(diǎn)(先是LogInterceptor, 然后createTransactionInterceptor(),接著CommandContextInterceptor,還有一個成員變量transactionContextFactory),再添加自定義后置節(jié)點(diǎn),最后還加加上CommandInvoker。所以我們?nèi)绻麤]有特殊配置的話這條鏈依次會有LogInterceptor, TransactionContextInterceptor(和使用環(huán)境有關(guān),在Spring下為SpringTransactionInterceptor), CommandContextInterceptor,TransactionContextInterceptor, CommandInvoker這五個節(jié)點(diǎn)。通過調(diào)試源碼可以驗證我們的分析,如下:
接著runtimeService也會進(jìn)行相關(guān)初始化,其中責(zé)任鏈結(jié)構(gòu)如下:
查看前四個節(jié)點(diǎn),如下以LogInterceptor為例,肯定會有next.execute(config, command);
, 這也是保證責(zé)任鏈能夠往下走的決定因素。
接下來再看看最后一個節(jié)點(diǎn)CommandInvoker的源碼,關(guān)鍵是執(zhí)行了command的execute方法,沒有next.execute了。
同時可以發(fā)現(xiàn)getNext()返回為null, 即獲取不到下一個節(jié)點(diǎn),并且在嘗試設(shè)置下一個節(jié)點(diǎn)時會拋出異常,這也正是責(zé)任鏈鏈尾節(jié)點(diǎn)應(yīng)有的特征。
由此也可以看出所有業(yè)務(wù)邏輯的處理最終會通過comand.execute來完成,所以接下來就是要搞清楚Command的處理邏輯,具體請待下一篇分析。