原創/朱季謙
本文需要一定責任鏈模式的基礎與Activiti工作流知識,主要分成三部分講解:
一、簡單理解責任鏈模式概念
二、Activiti工作流里責任鏈模式的建立
三、Activiti工作流里責任鏈模式的應用
一、簡單理解責任鏈模式概念
網上關於責任鏈模式的介紹很多,菜鳥教程上是這樣說的:責任鏈模式(Chain of Responsibility Pattern)為請求創建了一個接收者對象的鏈。在這種模式中,通常每個接收者都包含對另一個接收者的引用。如果一個對象不能處理該請求,那么它會把相同的請求傳給下一個接收者,依此類推。
這個概念術語比較抽象。
我曾經在 深入理解Spring Security授權機制原理 一文中提到Spring Security在授權過程中有使用到過濾器的概念,過濾器鏈就像一條鐵鏈,中間的每個過濾器都包含對另一個過濾器的引用,從而把相關的過濾器鏈接起來,就像一條鏈的樣子。這時請求線程如螞蟻一樣,會沿着這條鏈一直爬過去-----即,通過各過濾器調用另一個過濾器引用方法chain.doFilter(request, response),實現一層嵌套一層地將請求傳遞下去,當該請求傳遞到能被處理的過濾器時,就會被處理,處理完成后轉發返回。通過過濾器鏈,可實現在不同的過濾器當中對請求request做攔截增加,且過濾器之間彼此互不干擾。
整個流程大致如下:
這個過濾器鏈的概念,其實就是責任鏈設計模式在Spring Security中的體現。
摘錄一段網上關於職責鏈模式介紹,其主要包含以下角色:
- 抽象處理者(Handler)角色:定義一個處理請求的接口,包含抽象處理方法和一個后繼連接。
- 具體處理者(Concrete Handler)角色:實現抽象處理者的處理方法,判斷能否處理本次請求,如果可以處理請求則處理,否則將該請求轉給它的后繼者。
- 客戶類(Client)角色:創建處理鏈,並向鏈頭的具體處理者對象提交請求,它不關心處理細節和請求的傳遞過程。
二、Activiti工作流里責任鏈模式的創建
最近在研究Activiti工作流框架,發現其所有實現都是采用命令模式實現,而命令模式當中的Invoker角色又是采用攔截器鏈式模式,即類似上面提到的過濾器鏈,即設計模式里的責任鏈模式。
這里的Activiti工作流版本是6.0。
CommandInterceptor是一個攔截器接口,包含三個方法:
- setNext()方法是在初始化時,設置每個攔截器對象中包含了下一個攔截器對象,最后形成一條攔截器鏈;
- getNext()可在每個攔截器對象中調用下一個攔截器對象;
- execute()是每個攔截器對請求的處理。若在上一個攔截器鏈式里不能處理該請求話,就會通過next.execute(CommandConfig var1, Command
var2)將請求傳遞到下一個攔截器做處理,類似上面過濾器里調用下一個過濾器的chain.doFilter(request, response)方法,將請求進行傳遞;
public interface CommandInterceptor {
<T> T execute(CommandConfig var1, Command<T> var2);
CommandInterceptor getNext();
void setNext(CommandInterceptor var1);
}
抽象類AbstractCommandInterceptor實現了CommandInterceptor攔截器接口,在責任鏈模式當中充當抽象處理者(Handler)角色。該類最主要的屬性是 protected CommandInterceptor next,在同一包下,直接通過next即可調用下一個攔截器對象。
public abstract class AbstractCommandInterceptor implements CommandInterceptor {
protected CommandInterceptor next;
public AbstractCommandInterceptor() {
}
public CommandInterceptor getNext() {
return this.next;
}
public void setNext(CommandInterceptor next) {
this.next = next;
}
}
接下來,將會分析攔截器鏈是如何初始化與工作的。
SpringBoot集成Activiti配置如下:
@Configuration
public class SpringBootActivitiConfig {
@Bean
public ProcessEngine processEngine() {
ProcessEngineConfiguration pro = ProcessEngineConfiguration.createStandaloneProcessEngineConfiguration();
pro.setJdbcDriver("com.mysql.jdbc.Driver");
pro.setJdbcUrl("xxxx");
pro.setJdbcUsername("xxxx");
pro.setJdbcPassword("xxx");
//避免發布的圖片和xml中文出現亂碼
pro.setActivityFontName("宋體");
pro.setLabelFontName("宋體");
pro.setAnnotationFontName("宋體");
//數據庫更更新策略
pro.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);
return pro.buildProcessEngine();
}
}
這時,啟動項目后,pro.buildProcessEngine()這行代碼會初始化Activiti框架,進入里面,會發現它有三種實現,默認是第二種,即ProcessEngineConfigurationImpl。
點進去,Activiti框架具體構建buildProcessEngine方法如下,其中 this.init()的作用是環境初始化,包括配置設置、JDBC連接、bean裝載等的:
public ProcessEngine buildProcessEngine() {
this.init();
ProcessEngineImpl processEngine = new ProcessEngineImpl(this);
if (this.isActiviti5CompatibilityEnabled && this.activiti5CompatibilityHandler != null) {
Context.setProcessEngineConfiguration(processEngine.getProcessEngineConfiguration());
this.activiti5CompatibilityHandler.getRawProcessEngine();
}
this.postProcessEngineInitialisation();
return processEngine;
}
在this.init()方法里,涉及到責任鏈模式初始化的方法是this.initCommandExecutors(),里面詳情如下:
public void initCommandExecutors() {
this.initDefaultCommandConfig();
this.initSchemaCommandConfig();
//初始化命令調用器
this.initCommandInvoker();
//List存放進涉及到的攔截器
this.initCommandInterceptors();
//初始化命令執行器
this.initCommandExecutor();
}
這里只需要關注最后三個方法——
-
this.initCommandInvoker()
initCommandInvoker()初始化構建了一個CommandInvoker攔截器,它繼承上邊提到的攔截器抽象類AbstractCommandInterceptor。這個攔截器在整條過濾器鏈中是最重要和關鍵,它排在了整條鏈的最后,其實,它才是最終執行請求的,前邊幾個攔截器都是傳遞請求而已。
public void initCommandInvoker() { if (this.commandInvoker == null) { if (this.enableVerboseExecutionTreeLogging) { this.commandInvoker = new DebugCommandInvoker(); } else { //初始化執行該行代碼 this.commandInvoker = new CommandInvoker(); } } }
這里 new CommandInvoker()一個對象,然后將地址復制給this.commandInvoker對象引用,注意,該引用將會用在接下來的initCommandInterceptors()方法里——
-
this.initCommandInterceptors();
initCommandInterceptors方法主要作用是創建一個List集合,然后將需要用到的攔截器都保存到該List集合里——
public void initCommandInterceptors() { if (this.commandInterceptors == null) { this.commandInterceptors = new ArrayList(); if (this.customPreCommandInterceptors != null) { //用戶自定義前置攔截器 this.commandInterceptors.addAll(this.customPreCommandInterceptors); } //框架自帶默認的攔截器 this.commandInterceptors.addAll(this.getDefaultCommandInterceptors()); if (this.customPostCommandInterceptors != null) { this.commandInterceptors.addAll(this.customPostCommandInterceptors); } //命令調用器,在攔截器鏈最后一個 this.commandInterceptors.add(this.commandInvoker); } }
this.getDefaultCommandInterceptors()的代碼如下:
public Collection<? extends CommandInterceptor> getDefaultCommandInterceptors() { List<CommandInterceptor> interceptors = new ArrayList(); //日志攔截器 interceptors.add(new LogInterceptor()); CommandInterceptor transactionInterceptor = this.createTransactionInterceptor(); if (transactionInterceptor != null) { interceptors.add(transactionInterceptor); } // if (this.commandContextFactory != null) { interceptors.add(new CommandContextInterceptor(this.commandContextFactory, this)); } //事務攔截器 if (this.transactionContextFactory != null) { interceptors.add(new TransactionContextInterceptor(this.transactionContextFactory)); } return interceptors; }
可見,方法里的 this.commandInterceptors 就是一個專門儲存攔截器對象的List集合——
protected List<CommandInterceptor> commandInterceptors;
這里只需要重點關注this.commandInterceptors.add(this.commandInvoker)這行代碼,就是將上邊創建的CommandInvoker攔截器對象存儲到List里,它是放在initCommandInterceptors()方法最后,某種程度也就意味着,這個攔截器在整條鏈當中處在最后面的位置。
執行完該this.initCommandInterceptors()方法后,就可獲取到所有的攔截器對象,到這一步時,各攔截器還是互相獨立的,仍無法通過next()來進行調用傳遞,那么,究竟是如何將它們串起來形成一條鏈呢?
接下來的this.initCommandExecutor()方法,就是實現將各攔截器串起來形成一條長鏈。
-
this.initCommandExecutor();
該方法有兩個作用,一個是生成Interceptor攔截器鏈,一個是創建命令執行器commandExecutor。
public void initCommandExecutor() {
if (this.commandExecutor == null) {
CommandInterceptor first = this.initInterceptorChain(this.commandInterceptors);
this.commandExecutor = new CommandExecutorImpl(this.getDefaultCommandConfig(), first);
}
}
this.initInterceptorChain(this.commandInterceptors)是將集合里的攔截器初始化生成一條攔截器鏈,先循環獲取List集合里的攔截器對象chain.get(i),然后通過setNext()方法在該攔截器對象chain.get(i)里設置下一個攔截器引用,這樣,就可實現責任鏈里所謂每個接收者都包含對另一個接收者的引用的功能。
public CommandInterceptor initInterceptorChain(List<CommandInterceptor> chain) {
if (chain != null && !chain.isEmpty()) {
for(int i = 0; i < chain.size() - 1; ++i) {
((CommandInterceptor)chain.get(i)).setNext((CommandInterceptor)chain.get(i + 1));
}
return (CommandInterceptor)chain.get(0);
} else {
throw new ActivitiException("invalid command interceptor chain configuration: " + chain);
}
}
那么,這條攔截器鏈當中,都有哪些攔截器呢?
直接debug到這里,可以看到,總共有4個攔截器對象,按照順序排,包括LogInterceptor,CommandContextInterceptor,TransactionContextInterceptor,CommandInvoker(在命令模式里,該類相當Invoker角色)。這四個攔截器對象在責任鏈模式當中充當了具體處理者(Concrete Handler)角色。
責任鏈模式里剩余客戶類(Client)角色應該是命令執行器this.commandExecutor。
因此,工作流引擎當中的責任鏈模式結構圖如下:
組成一條攔截器鏈如下圖所示——
生成攔截器鏈后,會返回一個(CommandInterceptor)chain.get(0),即攔截器LogInterceptor,為什么只返回第一個攔截器呢,這是一個很巧妙的地方,因為該攔截器里已經一層一層地嵌套進其他攔截器了,因此,只需要返回第一個攔截器,賦值給first即可。
接下來,就會創建命令執行器——
this.commandExecutor = new CommandExecutorImpl(this.getDefaultCommandConfig(), first);
這個命令執行器是整個引擎的底層靈魂,通過它,可以實現責任鏈模式與命令模式——
攔截器鏈初始化介紹完成后,接下來開始介紹攔截器鏈在引擎里的應用方式。
三、Activiti工作流里責任鏈模式的應用
Activiti引擎的各操作方法其底層基本都是以命令模式來實現的,即調用上面創建的命令執行器this.commandExecutor的execute方法來實現的,例如自動生成28張數據庫表的方法,就是通過命令模式去做具體實現的——
this.commandExecutor.execute(processEngineConfiguration.getSchemaCommandConfig(), new SchemaOperationsProcessEngineBuild());
進入到commandExecutor方法里,會發現前邊new CommandExecutorImpl(this.getDefaultCommandConfig(), first)建立命令執行器時,已將配置對象和嵌套其他攔截器的LogInterceptor攔截器對象,通過構造器CommandExecutorImpl(CommandConfig defaultConfig, CommandInterceptor first)生成對象時,傳參賦值給了相應的對象屬性,其中first引用指向LogInterceptor,即攔截器鏈上的第一個攔截器——
public class CommandExecutorImpl implements CommandExecutor {
protected CommandConfig defaultConfig;
protected CommandInterceptor first;
public CommandExecutorImpl(CommandConfig defaultConfig, CommandInterceptor first) {
this.defaultConfig = defaultConfig;
this.first = first;
}
public CommandInterceptor getFirst() {
return this.first;
}
public void setFirst(CommandInterceptor commandInterceptor) {
this.first = commandInterceptor;
}
public CommandConfig getDefaultConfig() {
return this.defaultConfig;
}
public <T> T execute(Command<T> command) {
return this.execute(this.defaultConfig, command);
}
public <T> T execute(CommandConfig config, Command<T> command) {
return this.first.execute(config, command);
}
}
當引擎執行this.commandExecutor.execute(xxx,xxx))類似方法時,其實是執行了this.first.execute(config, command)方法,這里的this.first在構建命令執行器時是通過LogInterceptor傳進來的,因此,執行代碼其實是調用了LogInterceptor內部的execute()方法,也就是說,開始攔截器鏈上的第一個LogInterceptor攔截器傳遞方法execute()請求——
進入到攔截器鏈上的第一個攔截器LogInterceptor。
根據其內部代碼可以看出,這是一個跟日志有關的攔截器,內部並沒有多少增強功能,只是做了一個判斷是否需要debug日志打印。若需要,則進行debug打印,若不需要,直接進入到 if (!log.isDebugEnabled()) 為true的作用域內部,進而執行this.next.execute(config, command)用以將請求傳遞給下一個攔截器做處理。
public class LogInterceptor extends AbstractCommandInterceptor {
private static Logger log = LoggerFactory.getLogger(LogInterceptor.class);
public LogInterceptor() {
}
public <T> T execute(CommandConfig config, Command<T> command) {
if (!log.isDebugEnabled()) {
return this.next.execute(config, command);
} else {
log.debug("\n");
log.debug("--- starting {} --------------------------------------------------------", command.getClass().getSimpleName());
Object var3;
try {
var3 = this.next.execute(config, command);
} finally {
log.debug("--- {} finished --------------------------------------------------------", command.getClass().getSimpleName());
log.debug("\n");
}
return var3;
}
}
}
這里有一個小地方值得稍微打斷說下,就這個 if (!log.isDebugEnabled())判斷。眾生周知,若集成第三方日志插件如logback之類,若其配置里去除debug的打印,即時代碼里 存在log.debug("xxxxx")也不會打印到控制台,那么,這里增加一個判斷 if (!log.isDebugEnabled())是否多次一舉呢?
事實上,這里並非多此一舉,增加這個判斷,是可以提升代碼執行效率的。因為log.debug("xxxxx")里的字符串拼接早於log.debug("xxxxx")方法執行的,也就是說,即使該log.debug("xxxxx")不會打印,但其內部的字符串仍然會進行拼接,而拼接,是需要時間的,雖然很細微,但同樣屬於影響性能范疇內的。因此,增加一個if判斷,若無需要打印debug日志時,那么就無需讓其內部的字符串進行自動拼接。
這是一個很小的知識點,但面試過程中其實是有可能會遇到這類與日志相關的面試題的。
接下來,讓我們繼續回到攔截器鏈的傳遞上來。
LogInterceptor攔截器調用this.next.execute(config, command),意味着將請求傳遞到下一個攔截器上進行處理,根據前邊分析,可知下一個攔截器是CommandContextInterceptor,根據代碼大概可知,這個攔截器內主要是獲取上下文配置對象和信息相關的,這些都是在工作流引擎初始化時生成的,它們被保存在Stack棧里,具體都保存了哪些信息暫不展開分析——
public class CommandContextInterceptor extends AbstractCommandInterceptor {
......
public <T> T execute(CommandConfig config, Command<T> command) {
CommandContext context = Context.getCommandContext();
boolean contextReused = false;
if (config.isContextReusePossible() && context != null && context.getException() == null) {
contextReused = true;
context.setReused(true);
} else {
context = this.commandContextFactory.createCommandContext(command);
}
try {
Context.setCommandContext(context);
Context.setProcessEngineConfiguration(this.processEngineConfiguration);
if (this.processEngineConfiguration.getActiviti5CompatibilityHandler() != null) {
Context.setActiviti5CompatibilityHandler(this.processEngineConfiguration.getActiviti5CompatibilityHandler());
}
//繼續將命令請求傳遞到下一個攔截器
Object var5 = this.next.execute(config, command);
return var5;
} catch (Exception var31) {
context.exception(var31);
} finally {
......
}
return null;
}
}
CommandContextInterceptor攔截器沒有對命令請求做處理,它繼續將請求傳遞到下一個攔截器TransactionContextInterceptor,根據名字就大概可以猜到,這個攔截器主要是增加與事務有關的功能——
public <T> T execute(CommandConfig config, Command<T> command) {
CommandContext commandContext = Context.getCommandContext();
boolean isReused = commandContext.isReused();
Object var9;
try {
if (this.transactionContextFactory != null && !isReused) {
TransactionContext transactionContext = this.transactionContextFactory.openTransactionContext(commandContext);
Context.setTransactionContext(transactionContext);
commandContext.addCloseListener(new TransactionCommandContextCloseListener(transactionContext));
}
var9 = this.next.execute(config, command);
} finally {
......
}
return var9;
}
TransactionContextInterceptor攔截器同樣沒有對命令請求做處理,而是繼續傳遞到下一個攔截器,也就是最后一個攔截器CommandInvoker,根據名字可以大概得知,這是一個與命令請求有關的攔截器,傳遞過來的請求將會在這個攔截器里處理——
public class CommandInvoker extends AbstractCommandInterceptor {
......
public <T> T execute(CommandConfig config, final Command<T> command) {
final CommandContext commandContext = Context.getCommandContext();
commandContext.getAgenda().planOperation(new Runnable() {
public void run() {
commandContext.setResult(command.execute(commandContext));
}
});
this.executeOperations(commandContext);
if (commandContext.hasInvolvedExecutions()) {
Context.getAgenda().planExecuteInactiveBehaviorsOperation();
this.executeOperations(commandContext);
}
return commandContext.getResult();
}
}
進入到其內部,可以發現,這里沒有再繼續調用this.next.execute(config, command)這樣的請求進行傳遞,而是直接執行command.execute(commandContext),然后將返回值進行返回,其中,command是請求參數當中的第二個參數,讓我們回過頭看下該請求案例最開始的調用——
this.commandExecutor.execute(processEngineConfiguration.getSchemaCommandConfig(), new SchemaOperationsProcessEngineBuild());
這里的第二個參數是new SchemaOperationsProcessEngineBuild(),不妨進入到SchemaOperationsProcessEngineBuild類中,是吧,其內部同樣有一個execute方法——
public final class SchemaOperationsProcessEngineBuild implements Command<Object> {
public SchemaOperationsProcessEngineBuild() {
}
public Object execute(CommandContext commandContext) {
DbSqlSession dbSqlSession = commandContext.getDbSqlSession();
if (dbSqlSession != null) {
dbSqlSession.performSchemaOperationsProcessEngineBuild();
}
return null;
}
}
可見,CommandInvoker攔截器內部執行command.execute(commandContext),就相當於執行了new SchemaOperationsProcessEngineBuild().execute(commandContext),也就是——
public Object execute(CommandContext commandContext) {
DbSqlSession dbSqlSession = commandContext.getDbSqlSession();
if (dbSqlSession != null) {
dbSqlSession.performSchemaOperationsProcessEngineBuild();
}
return null;
}
這是一種命令模式的實現。
本文主要是分析責任鏈模式在Activiti框架中的實踐,故暫不展開分析框架中的其他設計模式,有興趣的童鞋可以自行深入研究,在Activiti框架當中,其操作功能底層基本都是以命令模式來實現的。
至此,就大概分析完了責任鏈模式在Activiti框架的創建和應用,學習完這塊內容,我對責任鏈模式有了更好理解,相對於看網上那些簡單以小例子來介紹設計模式的方法,我更喜歡去深入框架當中學習其設計模式,這更能讓我明白,這種設計模式在什么場景下適合應用,同時,能潛移默化地影響到我,讓我在設計系統架構時,能明白各設計模式的落地場景具體都是怎樣的。