Error message here!

Hide Error message here!

忘记密码?

Error message here!

请输入正确邮箱

Hide Error message here!

密码丢失?请输入您的电子邮件地址。您将收到一个重设密码链接。

Error message here!

返回登录

Close

Activity workflow learning notes (4) -- the establishment and application principle of responsibility chain mode in workflow engine

Zhu Jiqian 2021-04-09 00:39:51 阅读数:3 评论数:0 点赞数:0 收藏数:0

original / Zhu Jiqian

This paper needs the basis of the responsibility chain model , It is mainly divided into three parts :

One 、 Simply understand the concept of chain of responsibility model
Two 、Activiti The establishment of responsibility chain model in workflow
3、 ... and 、Activiti The application of responsibility chain model in workflow


One 、 Simply understand the concept of chain of responsibility model

There are many introductions about the responsibility chain mode on the Internet , This is what the rookie tutorial says : The chain of responsibility model (Chain of Responsibility Pattern) A chain of receiver objects is created for the request . In this mode , Usually each recipient contains a reference to another recipient . If an object cannot process the request , Then it will send the same request to the next recipient , And so on .

This concept term is more abstract .

I have been in In depth understanding of Spring Security Principle of authorization mechanism Mentioned in the article Spring Security There is the concept of using filters in the authorization process , The filter chain is like an iron chain , Each filter in the middle contains a reference to another filter , To link the relevant filters , Like a chain . The request thread is like an ant , It's going to climb all the way down this chain ----- namely , Call another filter reference method through each filter chain.doFilter(request, response), Implement layer by layer nesting to pass the request down , When the request is passed to a filter that can be processed , Will be dealt with , When the processing is finished, forward and return . Through the filter chain , Can be implemented in different filters for requests request Do the processing , And the filters don't interfere with each other .

The whole process is roughly as follows :

image

The concept of this filter chain , In fact, that is Responsibility chain design pattern stay Spring Security Embodiment in .

Excerpt an online introduction to the responsibility chain model , It mainly includes the following roles :

  1. Abstract processor (Handler) role : Define an interface to process requests , Contains abstract processing methods and a subsequent connection .
  2. Specific handler (Concrete Handler) role : Implement the processing methods of the abstract processor , Determine whether the request can be processed , If the request can be processed, process , Otherwise, forward the request to its successor .
  3. Customer class (Client) role : Create a processing chain , And submit the request to the specific handler object of the chain head , It doesn't care about processing details and the delivery of requests .

Two 、Activiti The creation of responsibility chain pattern in workflow

Recent research Activiti Workflow framework , It is found that all its implementations are implemented in command mode , And in command mode Invoker The role uses blocker chain mode again , That is similar to the filter chain mentioned above , That is, the responsibility chain model in the design pattern .

there Activiti The workflow version is 6.0.

CommandInterceptor It's an interceptor interface , There are three methods :

  • setNext() The method is at initialization time , Set each interceptor object to include the next interceptor object , Finally, a chain of interceptors is formed ;
  • getNext() The next interceptor object can be called in every interceptor object ;
  • execute() It's the processing of requests by each interceptor . If the request cannot be processed in the previous blocker chain , Would pass next.execute(CommandConfig var1, Command var2) Pass the request to the next interceptor for processing , Similar to calling the next filter in the above filter chain.doFilter(request, response) Method , Deliver the request ;
public interface CommandInterceptor {
<T> T execute(CommandConfig var1, Command<T> var2);
CommandInterceptor getNext();
void setNext(CommandInterceptor var1);
}

abstract class AbstractCommandInterceptor Realized CommandInterceptor Interceptor interface , Acting as an abstract processor in the chain of responsibility model (Handler) role . The most important attribute of this class is protected CommandInterceptor next, Under the same package , Directly through next The next interceptor object is called .

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;
}
}

Next , We will analyze how the interceptor chain initializes and works .

SpringBoot Integrate Activiti The configuration is as follows :

 @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");
// Avoid posting pictures and xml There is a garbled code in Chinese
pro.setActivityFontName(" Song style ");
pro.setLabelFontName(" Song style ");
pro.setAnnotationFontName(" Song style ");
// Database update strategy
pro.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);
return pro.buildProcessEngine();
}
}

At this time , After starting the project ,pro.buildProcessEngine() This line of code initializes Activiti frame , Go inside , You'll find that there are three implementations , The default is the second , namely ProcessEngineConfigurationImpl.

image

Click in ,Activiti The concrete construction of the framework buildProcessEngine The method is as follows , among this.init() The function of is environment initialization , Including configuration settings 、JDBC Connect 、bean Loading, etc :

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;
}

stay this.init() In the method , The way to initialize the chain of responsibility pattern is this.initCommandExecutors(), The details are as follows :

public void initCommandExecutors() {
this.initDefaultCommandConfig();
this.initSchemaCommandConfig();
// Initialize the command caller
this.initCommandInvoker();
//List Put in the interceptor involved
this.initCommandInterceptors();
// Initialize the command executor
this.initCommandExecutor();
}

Just focus on the last three methods ——

  1. this.initCommandInvoker()

    initCommandInvoker() Initialization builds a CommandInvoker Interceptor , It inherits the interceptor abstract class mentioned above AbstractCommandInterceptor. This interceptor is the most important and key in the whole filter chain , It's at the end of the chain , Actually , It's the one that finally executes the request , The previous interceptors are just passing requests .

    public void initCommandInvoker() {
    if (this.commandInvoker == null) {
    if (this.enableVerboseExecutionTreeLogging) {
    this.commandInvoker = new DebugCommandInvoker();
    } else {
    // Initialize the execution of this line of code
    this.commandInvoker = new CommandInvoker();
    }
    }
    }
    

    here new CommandInvoker() An object , Then copy the address to this.commandInvoker Object reference , Be careful , This reference will be used in the following initCommandInterceptors() In the method ——

  2. this.initCommandInterceptors();

    initCommandInterceptors The main purpose of the method is to create a List aggregate , Then save all the interceptors you need to use to the List In the assembly ——

    public void initCommandInterceptors() {
    if (this.commandInterceptors == null) {
    this.commandInterceptors = new ArrayList();
    if (this.customPreCommandInterceptors != null) {
    // User defined pre interceptor
    this.commandInterceptors.addAll(this.customPreCommandInterceptors);
    }
    // The framework comes with a default interceptor
    this.commandInterceptors.addAll(this.getDefaultCommandInterceptors());
    if (this.customPostCommandInterceptors != null) {
    this.commandInterceptors.addAll(this.customPostCommandInterceptors);
    }
    // Command invoker , At the end of the interceptor chain
    this.commandInterceptors.add(this.commandInvoker);
    }
    }
    

    this.getDefaultCommandInterceptors() The code for is as follows :

    public Collection<? extends CommandInterceptor> getDefaultCommandInterceptors() {
    List<CommandInterceptor> interceptors = new ArrayList();
    // Log interceptor
    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));
    }
    // Transaction interceptor
    if (this.transactionContextFactory != null) {
    interceptors.add(new TransactionContextInterceptor(this.transactionContextFactory));
    }
    return interceptors;
    }
    

    so , In the method this.commandInterceptors It's a special one for storing interceptor objects List aggregate ——

    protected List<CommandInterceptor> commandInterceptors;
    

    Just focus on this.commandInterceptors.add(this.commandInvoker) This line of code , That's what we created above CommandInvoker Interceptor objects are stored in List in , It is placed in the initCommandInterceptors() Method finally , To some extent, it means , This interceptor is on the last side of the chain .

    Finish executing this this.initCommandInterceptors() After the method , You can get all the interceptor objects , At this point , The interceptors are still independent of each other , Still can't pass next() For call passing , that , How on earth are they strung together to form a chain ?

    Next this.initCommandExecutor() Method , Is to achieve the interceptor string together to form a long chain .

  3. this.initCommandExecutor();

This method has two functions , One is generation Interceptor The interceptor chain , One is to create command executors 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) Is to initialize the interceptors in the collection to generate an interceptor chain , First loop to get List Interceptor objects in the collection chain.get(i), And then through setNext() Method in the interceptor object chain.get(i) Set the next interceptor reference in , such , It can realize the function that each receiver in the responsibility chain contains a reference to another receiver .

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);
}
}

that , In this blocker chain , What interceptors are there ?

direct debug Come here , You can see , All in all 4 Two interceptor objects , In order , Include LogInterceptor,CommandContextInterceptor,TransactionContextInterceptor,CommandInvoker( In command mode , This class is quite similar Invoker role ). These four interceptor objects act as specific processors in the chain of responsibility pattern (Concrete Handler) role .

image

The remaining customers in the responsibility chain model (Client) The role should be the command executor this.commandExecutor.

therefore , The structure of responsibility chain mode in workflow engine is as follows :

image

Form an interceptor chain as shown in the figure below ——

image

After generating the interceptor chain , Will return a (CommandInterceptor)chain.get(0), The interceptor LogInterceptor, Why only return to the first interceptor , It's a very clever place , Because the interceptor has been nested into other interceptors layer by layer , therefore , Just return to the first interceptor , Assign a value to first that will do .

Next , The command executor is created ——

this.commandExecutor = new CommandExecutorImpl(this.getDefaultCommandConfig(), first);

The command executor is the bottom soul of the whole engine , Through it , It can realize responsibility chain mode and command mode ——

After the introduction of interceptor chain initialization , Next, we will introduce the application of interceptor chain in the engine .


3、 ... and 、Activiti The application of responsibility chain model in workflow

Activiti The basic operation methods of the engine are implemented in command mode , Call the command executor created above this.commandExecutor Of execute Method to achieve , For example, automatic generation 28 How to create a database table , It's realized through the command mode ——

this.commandExecutor.execute(processEngineConfiguration.getSchemaCommandConfig(), new SchemaOperationsProcessEngineBuild());

Enter into commandExecutor In the method , You'll find the front new CommandExecutorImpl(this.getDefaultCommandConfig(), first) When setting up a command executor , The configuration object and the LogInterceptor Interceptor object , By constructor CommandExecutorImpl(CommandConfig defaultConfig, CommandInterceptor first) When generating objects , Parameters are assigned to the corresponding object properties , among first Reference point LogInterceptor, The first interceptor in the interceptor chain ——

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);
}
}

When the engine performs this.commandExecutor.execute(xxx,xxx)) In a similar way , In fact, it was implemented this.first.execute(config, command) Method , there this.first When building Command executors, it's through LogInterceptor incoming , therefore , Executing code actually calls LogInterceptor Inside execute() Method , in other words , Start the first one on the interceptor chain LogInterceptor Interceptor delivery method execute() request ——

image

The first interceptor in the interceptor chain LogInterceptor.

According to its internal code, it can be seen that , This is a log related interceptor , There are not many enhancements inside , I just made a judgment on whether it was necessary to debug Log printing . If you need , Is to debug Print , If you don't need , Direct access to if (!log.isDebugEnabled()) by true Within the scope of , And then perform this.next.execute(config, command) To pass the request to the next interceptor for processing .

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;
}
}
}

Here's a little bit to cut in , Just this if (!log.isDebugEnabled()) Judge . All living beings know that , If the third-party log plug-in is integrated, such as logback And so on , If it is removed from the configuration debug Printing of , In real time code There is log.debug("xxxxx") And it won't print to the console , that , Add a judgment here if (!log.isDebugEnabled()) Is it repeated ?

in fact , It's not unnecessary here , Add this judgment , It can improve the efficiency of code execution . because log.debug("xxxxx") String stitching in is earlier than log.debug("xxxxx") Method execution , in other words , Even if it's time to log.debug("xxxxx") Don't print , But the strings inside are still spliced , And splicing , It takes time , Although it's very subtle , But it's also within the scope of performance . therefore , Add one more if Judge , If you don't need to print debug When the log , Then there is no need for automatic splicing of strings within it .

This is a small knowledge point , But in the process of interview, it is possible to encounter such interview questions related to diary .

Next , Let's go back to interceptor chain delivery .

LogInterceptor Interceptor call this.next.execute(config, command), It means passing the request on to the next interceptor for processing , According to the previous analysis , So the next interceptor is CommandContextInterceptor, According to the code, we can see that , This interceptor is mainly used to obtain context configuration objects and information related to , These are generated when the workflow engine is initialized , They are kept in Stack In the stack , What information has been saved will not be analyzed ——

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());
}
// Continue passing command requests to the next interceptor
Object var5 = this.next.execute(config, command);
return var5;
} catch (Exception var31) {
context.exception(var31);
} finally {
......
}
return null;
}
}

CommandContextInterceptor The interceptor did not process the command request , It continues to pass the request to the next interceptor TransactionContextInterceptor, You can probably guess from the name , This interceptor mainly adds transaction related functions ——

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 Interceptors also don't process command requests , Instead, it goes on to the next interceptor , The last interceptor CommandInvoker, You can get a rough idea of it by name , This is an interceptor related to command requests , The incoming request will be processed in this interceptor ——

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();
}
}

Into its interior , You can find , There's no more calling this.next.execute(config, command) Such requests are delivered , I'm going to do it directly command.execute(commandContext), Then the return value is returned , among ,command Is the second of the request parameters , Let's go back to the first call of the request case ——

this.commandExecutor.execute(processEngineConfiguration.getSchemaCommandConfig(), new SchemaOperationsProcessEngineBuild());

The second parameter here is new SchemaOperationsProcessEngineBuild(), You might as well enter SchemaOperationsProcessEngineBuild Class , Is that so? , There's also one inside execute Method ——

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;
}
}

so ,CommandInvoker Interceptor internal execution command.execute(commandContext), It's equivalent to executing new SchemaOperationsProcessEngineBuild().execute(commandContext), That is to say ——

 public Object execute(CommandContext commandContext) {
DbSqlSession dbSqlSession = commandContext.getDbSqlSession();
if (dbSqlSession != null) {
dbSqlSession.performSchemaOperationsProcessEngineBuild();
}
return null;
}

This is an implementation of command mode .

This paper mainly analyzes the mode of responsibility chain in Activiti Practice in the framework , Therefore, other design patterns in the analysis framework will not be expanded , If you are interested in children's shoes, you can do in-depth research by yourself , stay Activiti In the frame , Its operation functions are basically implemented in command mode .

thus , It's about the end of the analysis. The mode of responsibility chain is Activiti The creation and application of framework , After learning this content , I have a better understanding of the chain of responsibility model , Compared with the simple ways to introduce design patterns with small examples on the Internet , I prefer to go deep into the framework and learn its design patterns , It makes me understand more , In what scenarios is this design pattern suitable for application , meanwhile , Can influence me imperceptibly , Let me design the system architecture , Can understand the landing scene of each design pattern .

Copyright statement
In this paper,the author:[Zhu Jiqian],Reprint please bring the original link, thank you

编程之旅,人生之路,不止于编程,还有诗和远方。
阅代码原理,看框架知识,学企业实践;
赏诗词,读日记,踏人生之路,观世界之行;