Chain of Responsibility model
Introduction to Baidu Encyclopedia:
Chain of responsibility mode is a design mode. In the chain of responsibility pattern, many objects are connected in a chain by each object’s reference to its next parent. Requests pass along the chain until one of the objects on the chain decides to process the request. The client making the request does not know which object on the chain ultimately handles the request, allowing the system to dynamically reorganize and assign responsibilities without affecting the client.
Introduction to Wikipedia:
The chain of responsibility pattern is a software design pattern in object-oriented programming, which contains some command objects and a series of processing objects. Each processing object determines which command objects it can process, and it knows how to pass command objects that it cannot process to the next processing object in the chain. The pattern also describes adding new processing objects to the end of the processing chain.
A central role
The essence of responsibility chain mode is to decouple request from processing so that the request can be transmitted and processed in the processing chain. Understanding the chain of responsibility model should understand its pattern, not its implementation. The uniqueness of the chain of responsibility mode is that it combines its node handlers into a chain structure and allows the node itself to decide whether to process or forward the request, which is equivalent to letting the request flow.
structure
The responsibility chain mode mainly includes the following roles.
- The role of Abstract Handler: Defines an interface to process a request, including abstract processing methods and a subsequent connection.
- The role of Concrete Handler: Implements the processing method of the abstract Handler, determines whether the request can be processed, if it can be processed, and otherwise passes the request to its successor.
- The Client role: Creates a processing chain and submits a request to a specific handler object in the header. It does not care about processing details or the delivery of the request.
Classification of chains of responsibility
Pure chain of responsibility model:
- A specific handler can only choose one of the two behaviors: either assume full responsibility or pass the responsibility to the next handler. It is not allowed for a specific handler to pass the responsibility down after assuming part or all of the responsibility
- A request must be received by a handler object, and no request can be processed by any handler object
Impure chain of responsibility model:
- Allow a request to be partially processed by a specific handler before being passed down, or allow a specific handler to process a request after it has been processed by a subsequent handler
- A request can end up not being received by any handler object
End of introduction, class dismissed! Oh, no, no, no, the text is just beginning!Copy the code
“Chain” construction
Related blog articles have introduced the construction of this “chain” of responsibility chain mode, but most of them are simply adding arrays. In addition to the responsibility chain model itself, this paper will also discuss a different way of constructing “chain” from the conventional scheme.
This implementation is purely learning the sequela of Spring Security source code, for the chain node order configuration can be directly set up the order index to achieve, you see the officer do not like not to spray key. Make the wheel, look at the source code, two mistakes.
FilterChain construction in Spring Security
In Spring Security (based on Spring Security 5.4+), the Security check filter chain is constructed by HttpSecurity addFilterBefore, AddFilterAfter to implement the construction of the filter chain (omitting extraneous code) :
@Override
public HttpSecurity addFilterAfter(Filter filter, Class<? extends Filter> afterFilter) {
this.comparator.registerAfter(filter.getClass(), afterFilter);
return addFilter(filter);
}
@Override
public HttpSecurity addFilterBefore(Filter filter, Class<? extends Filter> beforeFilter) {
this.comparator.registerBefore(filter.getClass(), beforeFilter);
return addFilter(filter);
}
// this.filters are simply a List
@Override
public HttpSecurity addFilter(Filter filter) {
// ...
this.filters.add(filter);
return this;
}
Copy the code
The core of this is a List<Filter> object that holds the filters added to the builder. When the HttpSecurity#performBuild() method is called, the configured filters are regrouped in a certain order. Finally, a sequence filter chain is constructed.
The question is: how to determine the order of filters?
Look at this paragraph:
# addFilterAfter
this.comparator.registerAfter(filter.getClass(), afterFilter);
# addFilterBefore
this.comparator.registerBefore(filter.getClass(), beforeFilter);
Copy the code
Who is the comparator?
Okay, so if I click on it, and I see that it’s the FilterComparator, it’s defined like this:
final class FilterComparator implements Comparator<Filter>, Serializable
Copy the code
The Spring Security team defines it like this:
An internal use only “Comparator” that sorts the Security “Filter” instances to ensure they are in the correct order.
The FilterComparator is used to determine the order of each Filter instance to be configured in the chain.
It is called in HttpSecurity#performBuild() like this:
@Override
protected DefaultSecurityFilterChain performBuild(a) {
this.filters.sort(this.comparator);
return new DefaultSecurityFilterChain(this.requestMatcher, this.filters);
}
Copy the code
This.filters are sorted by this.filters using this.parator so that the order of the filter chains can be determined for further action.
Reference to ideas
When it comes to reference, first of all, you must have a basic understanding of the object being used for reference.
In FilterComparator, registerAfter(Class<T> targer, Class<T> after) RegisterBefore (Class<T> targer, Class<T> befroe) to set:
void registerAfter(Class<? extends Filter> filter, Class<? extends Filter> afterFilter) {
Integer position = getOrder(afterFilter);
// ...
put(filter, position + 1);
}
void registerBefore(Class<? extends Filter> filter, Class<? extends Filter> beforeFilter) {
Integer position = getOrder(beforeFilter);
// ...
put(filter, position - 1);
}
// Set the order to the cache object filterToOrder
private void put(Class<? extends Filter> filter, int position) {
String className = filter.getName();
this.filterToOrder.put(className, position);
}
/** * Gets the order of a particular Filter class taking into consideration * superclasses. */
private Integer getOrder(Class
clazz) {
while(clazz ! =null) {
// When the type order is obtained directly, there is no configuration for the type, get the index of the parent type, loop operation
Integer result = this.filterToOrder.get(clazz.getName());
if(result ! =null) {
return result;
}
clazz = clazz.getSuperclass();
}
return null;
}
Copy the code
At its core, the filterToOrder object holds the actual class name of the filter and the index value of the filter in the chain.
Finally, the core implementation method of the FilterComparator (called when sorting the configuration filter collection) :
@Override
public int compare(Filter lhs, Filter rhs) {
Integer left = getOrder(lhs.getClass());
Integer right = getOrder(rhs.getClass());
return left - right;
}
// invoking:
this.filters.sort(this.comparator);
Copy the code
Through these and other methods, Spring Security implements the sorting of Security filters and implements Security filtering related functions.
Implement responsibility chain tools
Expected operating
// Build the chain through Builder, masking specific processor setup logic
ChainBuilder<String> builder = ChainManager.builder();
AddHandler, addHandlerBefore, addHandlerAfter to set the processor order
builder.addHandler(new A())
.addHandlerBefore(new B(), A.class)
.addHandlerBefore(new C(), A.class);
ChainManager<String> manager = builder.build();
// Handle the actual content
manager.handle("A");
Copy the code
Design of three related test classes:
static class A implements Handler<String> {
@Override
public void handle(String arg, Chain<String> chain) {
System.out.println("A---");
if (!"A".equals(arg)) {
chain.handle(arg);
return; } System.out.println(arg); }}static class B implements Handler<String> {
@Override
public void handle(String arg, Chain<String> chain) {
System.out.println("B---");
if (!"B".equals(arg)) {
chain.handle(arg);
return; } System.out.println(arg); }}static class C implements Handler<String> {
@Override
public void handle(String arg, Chain<String> chain) {
System.out.println("C---");
if (!"C".equals(arg)) {
chain.handle(arg);
return; } System.out.println(arg); }}Copy the code
Desired output
B---
C---
A---
A
Copy the code
Description:
- The constructed chain is: B–>C–>A
- It actually calls processor A
Define relevant interfaces:
// Chain.java
public interface Chain<T> {
void handle(T arg);
}
// Handler.java
public interface Handler<T> {
void handle(T arg, Chain<T> chain);
}
Copy the code
Description:
- Chain. Java: defines the basic interface for callers to operate the responsibility Chain. Clients directly operate the Chain interface to achieve service functions
- Handler.java: Defines the interface to the chain operation node, the basic unit that implements the direct operation logic in the chain
- Generic
: Type qualifiers for input parameters
Chain container:
// ChainManager.java
public class ChainManager<T> {
private final Step step = new Step();
private final Chain<T> delegateChain;
// The client cannot create objects directly
ChainManager(List<Handler<T>> list) {
this.delegateChain = new DelegateHandlerChain<>(this.step, list);
}
// Provide the build Builder object
public static <T> ChainBuilder<T> builder(a) {
return new ChainBuilder<>();
}
// The manager only provides processing methods
public void handle(T arg) {
// Call the processing chain to process the business
this.delegateChain.handle(arg);
// Reset the pedometer after a round of chain processing
this.step.reset();
}
// The implementation of the proxy Chain interface, the actual management of the Chain of each processor access, call, etc
private static class DelegateHandlerChain<T> implements Chain<T> {
// A collection of sorted processors
private final List<Handler<T>> handlers;
// The number of processors in the current chain
private final int handlersSize;
// The node pedometer of the current chain
private final Step step;
DelegateHandlerChain(Step step, List<Handler<T>> handlers) {
this.step = step;
this.handlers = handlers;
handlersSize = handlers.size();
}
// Call the dry goods logic of the next processor
// Call chain.handle(arg) in specific processor logic;
@Override
public void handle(T arg) {
// Get the index of the current processor
int current = this.step.currentStep();
if (current < handlersSize) {
Handler<T> handler = this.handlers.get(current);
// The pedometer advances by one unit
this.step.next();
handler.handle(arg, this); }}}}// ChainBuilder.java
public class ChainBuilder<T> {
private final OrderComparator<Handler<T>> comparator = new OrderComparator<>();
Constructor, the collection of processors to be configured
private final List<Handler<T>> handlers = new LinkedList<>();
// Objects cannot be created by external calls
ChainBuilder() {
}
// Add handlers
public ChainBuilder<T> addHandler(Handler<T> handler) {
Class<? extends Handler<T>> handlerClass
= (Class<? extends Handler<T>>) handler.getClass();
this.comparator.register(handlerClass);
this.handlers.add(handler);
return this;
}
// Add handlers after specifying the constructor type
public ChainBuilder<T> addHandlerAfter(Handler<T> handler, Class<? extends Handler<T>> afterHandler) {
Class<? extends Handler<T>> handlerClass
= (Class<? extends Handler<T>>) handler.getClass();
this.comparator.registerAfter(handlerClass, afterHandler);
this.handlers.add(handler);
return this;
}
// Add the handler before specifying the constructor type
public ChainBuilder<T> addHandlerBefore(Handler<T> handler, Class<? extends Handler<T>> beforeHandler) {
Class<? extends Handler<T>> handlerClass
= (Class<? extends Handler<T>>) handler.getClass();
this.comparator.registerBefore(handlerClass, beforeHandler);
this.handlers.add(handler);
return this;
}
// Build the ChainManager object
public ChainManager<T> build(a) {
// Adjust the order
this.handlers.sort(this.comparator);
List<Handler<T>> list = Collections.unmodifiableList(this.handlers);
return newChainManager<>(list); }}Copy the code
Description:
- A Step pedometer is a Step counting object, an object of a tool class, and the index number of the current processor
- OrderComparator object, which implements functionality similar to the FilterComparator in Spring Security and is used in the chain builder
test
Test string A
After B, C, A processor, is captured and processed by A processor
Test string B
After passing through the B processor, it is captured and processed by the B processor
Test string C
After B, C processor, is captured and processed by C processor
Test string D
Description: After B, C, A processor, no appropriate processor is called
Attached: OrderComparator, Step implementation
public class OrderComparator<T> implements Comparator<T> {
private final Map<Class<? extends T>, Integer> sortedMap = new LinkedHashMap<>();
public void register(Class<? extends T> type, int index) {
if (!this.sortedMap.isEmpty()) {
throw new IllegalArgumentException("Cannot add object if current sorted context is not empty");
}
this.sortedMap.put(type, index);
}
public void register(Class<? extends T> type) {
register(type, 0);
}
public void registerBefore(Class<? extends T> target, Class<? extends T> before) {
if (this.sortedMap.isEmpty()) {
register(target);
return;
}
Integer index = getIndex(before);
this.sortedMap.put(target, index - 1);
}
public void registerAfter(Class<? extends T> target, Class<? extends T> after) {
if (this.sortedMap.isEmpty()) {
register(target);
return;
}
Integer index = getIndex(after);
this.sortedMap.put(target, index + 1);
}
private Integer getIndex(Class<? extends T> target) {
for (val entry : this.sortedMap.entrySet()) {
if (entry.getKey().equals(target)) {
returnentry.getValue(); }}throw new IllegalArgumentException("Type[" + target + "] is not in current sorted context");
}
@SuppressWarnings("unchecked")
@Override
public int compare(T o1, T o2) {
Integer left = getIndex((Class<T>) o1.getClass());
Integer right = getIndex((Class<T>) o2.getClass());
returnleft - right; }}public final class Step {
private final static int DEFAULT_SIZE = 1;
private final static int INITIAL_VALUE = 0;
private final int size;
private int current = INITIAL_VALUE;
public Step(int size) {
this.size = size;
}
public Step(int initial, int size) {
this.current = initial;
this.size = size;
}
public Step(a) {
this(DEFAULT_SIZE);
}
public void next(a) {
this.current += this.size;
}
public int currentStep(a) {
return this.current;
}
public void reset(a) {
this.current = INITIAL_VALUE; }}Copy the code
conclusion
Examples of application
- The onion Model in the Express framework
- Filters in the Servlet API specification
- Interceptor chains in Spring Seccurity
- Plugin mechanism in Mybatis
Understand the chain of responsibility model
Chain of responsibility pattern, which is for an input, the category of the input has a similar appearance but in fact, a sequence of different task logic together, become a workflow: each node only CARES about its concern into the part, to the information processing, after completion of either end, or transfer to the next node processing.
To truly understand the chain of responsibility model, we need to analyze it from different perspectives:
- From the caller’s point of view, the caller only cares about the construction of the chain and the invocation of the processing method
According to the business needs, the caller designs the corresponding processing logic, and arranges the logical order according to the need, and performs the processing method call of the data
- Each node needs to know the input information and the execution interface of the next node at the actual point of view of the chain node.
Each node checks and verifies the input parameter to determine whether the parameter is processed by itself. If it is not a parameter that it cares about, it needs to notify the manager of the chain to call the next node for processing
The purpose of the chain of responsibility model can be summarized in the following aspects:
- Intercept illegal Input
- Filter, convert, and format parameters
- Pre – and post-processing of actual business logic
The chain of responsibility model is not perfect, and there are a lot of weird problems when used improperly:
- Increase system complexity
The original if-else can solve the business, add a lot of useless code out of thin air, in the process of a few logic, it is a dead end