Main contents of this paper:

  • This section describes the responsibility chain mode
  • Leave process Example
  • Summary of responsibility chain model
  • Source code analysis of Tomcat Filter responsibility chain pattern

For more information, please visit my personal blog: Laijianfeng.org


Chain of Responsibility model

It is a common scenario that an event needs to be processed by multiple objects, such as procurement approval process, leave process, anomaly processing process in software development, Web request processing process and various other processes, which can be realized by using the responsibility chain mode.

Take the leave process as an example, the leave process of ordinary employees in general companies is simplified as follows:

Ordinary employees initiate a leave application, and only need to get approval from the supervisor when the leave is less than 3 days. If the number of leave days is more than 3 days, the supervisor shall submit it to the manager for approval. If the number of leave days is more than 7 days, the supervisor shall submit it to the general manager for approval.

Use if-else to simplify the leave process as follows:

public class LeaveApproval {
    public boolean process(String request, int number) {
        boolean result = handleByDirector(request); // Supervisor processing
        if (result == false) {  // Supervisor does not approve
            return false;
        } else if (number < 3) {    // Supervisor approves with less than 3 days
            return true;
        }

        result = handleByManager(request); // If the number of days is greater than or equal to 3, submit it to the manager
        if (result == false) {   // The manager did not approve
            return false;
        } else if (number < 7) { // Approved by the manager for less than 7 days
            return true;
        }

        result = handleByTopManager(request);   If the number of days is greater than or equal to 7, submit it to the general manager for processing
        if (result == false) { // General manager does not approve
            return false;
        }
        return true;    // General manager gives final approval
    }

    public boolean handleByDirector(String request) {
        // Supervisor processes the leave application
    }

    public boolean handleByManager(String request) {
        // The manager processes the request for leave
    }

    public boolean handleByTopManager(String request) {
        // The general manager processes the request for leave}}Copy the code

The problem seems simple enough, but there are several problems with this solution:

  1. The LeaveApproval class is relatively large, and the approval methods of all superiors are concentrated in this class, which violates the “single responsibility principle” and is difficult to test and maintain

  2. When the leave process needs to be modified, for example, if the number of additional days is more than 30 days, it needs to be submitted to the chairman for processing. The source code of this class must be modified (and rigorously tested again), which violates the “on/off principle”.

  3. The process is inflexible; once the process is determined, it cannot be modified (unless the source code is modified), and the client cannot customize the process

The responsibility chain model can solve the above problems.

define

Chain of Responsibility Pattern: Avoid coupling the sender of a request with the receiver, make it possible for multiple objects to receive a request, connect these objects into a Chain, and pass the request along the Chain until an object handles it. The responsibility chain pattern is an object behavior pattern.

role

Handler (Abstract Handler) : It defines an interface for handling requests. It is generally designed as an abstract class, in which abstract request handling methods are defined because different concrete handlers handle requests in different ways. Because the next parent of each handler is also a handler, an object of type abstract handler is defined in the abstract handler as its reference to the next parent. With this reference, the handler can be linked into a chain.

ConcreteHandler: It is a subclass of abstract handler, which can process user requests. In the concrete handler class, the abstract request processing method defined in the abstract handler is realized. Before processing the request, we need to judge whether there is a corresponding processing permission. The next object in the chain can be accessed in the concrete handler for forwarding the request.

The class diagram is as follows:

Pure and impure liability chain models

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:

  • Allows a request to be partially processed by a specific handler before being passed down
  • Or after a specific handler has processed a request, subsequent handlers can continue processing the request
  • And a request can end up not being received by any handler object

The sample

Refactoring the leave process using the chain of responsibility model (impure)

Leave information, including the name and days of leave

@Data
@AllArgsConstructor
public class LeaveRequest {
    private String name;    // The name of the person asking for leave
    private int numOfDays;  // Days of absence
}
Copy the code

The abstract Handler class Handler maintains a nextHandler property that is a reference to the nextHandler of the current Handler; The abstract method process is declared

@Data
public abstract class Handler {
    protected String name; // Handler name
    protected Handler nextHandler;  // Next handler

    public Handler(String name) {
        this.name = name;
    }

    public abstract boolean process(LeaveRequest leaveRequest); // Handle leave
}
Copy the code

Three concrete processing classes implement the process method of the abstract processing class

// The handler in charge
public class Director extends Handler {
    public Director(String name) {
        super(name);
    }

    @Override
    public boolean process(LeaveRequest leaveRequest) {
        boolean result = (new Random().nextInt(10)) > 3; // If the number is greater than 3, it is approved; otherwise, it is not approved
        String log = "Supervisor <%s> approves the leave application of <%s>, the number of days of leave: <%d>, and the approval result: <%s>";
        System.out.println(String.format(log, this.name, leaveRequest.getName(), leaveRequest.getNumOfDays(), result == true ? "Approval" : "Not approved"));

        if (result == false) {  / / not approved
            return false;
        } else if (leaveRequest.getNumOfDays() < 3) { Return true if the number of days approved is less than 3
            return true;
        }
        return nextHandler.process(leaveRequest);   // If the number of days is greater than or equal to 3, submit it to the next processor}}/ / manager
public class Manager extends Handler {
    public Manager(String name) {
        super(name);
    }

    @Override
    public boolean process(LeaveRequest leaveRequest) {
        boolean result = (new Random().nextInt(10)) > 3; // If the number is greater than 3, it is approved; otherwise, it is not approved
        String log = "The manager <%s> approves the leave application of <%s>, the number of days of leave: <%d>, and the approval result: <%s>";
        System.out.println(String.format(log, this.name, leaveRequest.getName(), leaveRequest.getNumOfDays(), result == true ? "Approval" : "Not approved"));

        if (result == false) {  / / not approved
            return false;
        } else if (leaveRequest.getNumOfDays() < 7) { // The number of approved days is less than 7
            return true;
        }
        return nextHandler.process(leaveRequest);   // If the number of days is greater than or equal to 7, submit it to the next processor}}/ / general manager
public class TopManager extends Handler {
    public TopManager(String name) {
        super(name);
    }

    @Override
    public boolean process(LeaveRequest leaveRequest) {
        boolean result = (new Random().nextInt(10)) > 3; // If the number is greater than 3, it is approved; otherwise, it is not approved
        String log = "General manager <%s> approves leave application of <%s>, leave days: <%d>, approval result: <%s>";
        System.out.println(String.format(log, this.name, leaveRequest.getName(), leaveRequest.getNumOfDays(), result == true ? "Approval" : "Not approved"));

        if (result == false) { // General manager does not approve
            return false;
        }
        return true;    // General manager gives final approval}}Copy the code

Client testing

public class Client {
    public static void main(String[] args) {
        Handler zhangsan = new Director("Zhang");
        Handler lisi = new Manager("Bill");
        Handler wangwu = new TopManager("Fifty");

        // Create a chain of responsibility
        zhangsan.setNextHandler(lisi);
        lisi.setNextHandler(wangwu);

        // Initiate an application for leave
        boolean result1 = zhangsan.process(new LeaveRequest("Little whirl".1));
        System.out.println("Final result:" + result1 + "\n");

        boolean result2 = zhangsan.process(new LeaveRequest("Little whirl".4));
        System.out.println("Final result:" + result2 + "\n");

        boolean result3 = zhangsan.process(new LeaveRequest("Little whirl".8));
        System.out.println("Final result:" + result3 + "\n"); }}Copy the code

Possible results are as follows :(since approval is simulated by random numbers, your results may differ from mine)

Supervisor < Zhang SAN > approved the leave application of < Xiao Xuan Feng >, the number of days of leave: <1>, the approval result: < approval > Final result:trueSupervisor < Zhang SAN > approved the leave application of < Xiao Xuan Feng >, the number of days of leave: <4>, the approval result: < not approved > Final result:falseSupervisor < Zhang SAN > approve the application for leave of < xiao Xuan Feng >, leave days: <8>, approval result: < approval > Manager < Li Si > approve the application for leave of < Xiao Xuan Feng >, leave days: <8>, approval result: < approval > General manager < Wang Wu > approve the application for leave of < Xiao Xuan Feng >, leave days: <8>, approval result: < approval > Final result:true
Copy the code

The class diagram is shown below

conclusion

Key advantages of the chain of responsibility model

  • Objects only need to know that the request will be processed, and objects in the chain do not need to know the structure of the chain, the client is responsible for the creation of the chain, reducing the coupling degree of the system

  • The request-processing object maintains only one reference to its successors, rather than all its references to candidate handlers, simplifying the interconnections of objects

  • A responsibility chain gives us more flexibility in assigning responsibilities to objects, adding, deleting and changing the chain dynamically at run time to change the responsibility for handling a request

  • When you add a new concrete request handler, you don’t need to modify the original code, you just need to create a new chain on the client side, in accordance with the “open close principle”.

Major disadvantages of the chain of responsibility model

  • A request may not be processed because the chain of responsibility is not configured correctly

  • For a long chain of responsibilities, the processing of the request may involve multiple processing objects, which will affect the system performance and make it inconvenient to debug

  • The system may fall into an infinite loop due to improper creation of the responsibility chain

Applicable scenario

  • There are multiple objects that can handle the same request, and which object will handle the request will be determined at run-time. The client just needs to submit the request to the chain and doesn’t care who the request is handled by or how it is handled

  • Submit a request to one of multiple objects without explicitly specifying the recipient

  • You can dynamically specify a set of objects to handle requests, and clients can dynamically create chains of responsibilities to handle requests and change the order of the handlers in the chain

Typical application of the chain of responsibility model

Responsibility chain pattern in the Tomcat filter

Servlet filters are Java classes that can be used for Servlet programming to: intercept client requests before they access back-end resources; The server’s responses are processed before they are sent back to the client.

The Servlet defines Filter interface and Filter link interface FilterChain

public interface Filter {
    public void init(FilterConfig filterConfig) throws ServletException;
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException;
    public void destroy(a);
}

public interface FilterChain {
    void doFilter(ServletRequest var1, ServletResponse var2) throws IOException, ServletException;
}
Copy the code

Our steps for customizing a filter are:

1) Write a Filter class that implements the Javax.servlet. Filter interface, as shown below

public class MyFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        // Do some customization....
        System.out.println("Before executing the doFilter() method...");
        chain.doFilter(request, response);              // Pass the request to the next filter
        System.out.println("After the doFilter() method...");
    }

    @Override
    public void destroy(a) {}@Override
    public void init(FilterConfig filterConfig) throws ServletException {}}Copy the code

2) Add the filter configuration to the web.xml file, such as the following to block all requests

<filter>  
        <filter-name>MyFilter</filter-name>  
        <filter-class>com.whirly.filter.MyFilter</filter-class>  
</filter>
  
<filter-mapping>  
        <filter-name>MyFilter</filter-name>  
        <url-pattern>/ *</url-pattern>  
</filter-mapping>
Copy the code

Our filter comes into play when Tomcat is started. So how does the filter work?

Tomcat has Pipeline Valve mechanism, also using the chain of responsibility mode, a request will flow in Pipeline, Pipeline will call the corresponding Valve to complete the specific logic processing; One of the basic valves is the StandardWrapperValve, which calls the ApplicationFilterFactory to generate a Filter chain in the Invoke method

The filter needs to be loaded and initialized before running the filter, and the filter chain needs to be generated based on configuration information:

  1. To load a filter, load the information about filter and filterMap respectively in the configureContext method of the ContextConfig class and save the information in the context

  2. Initialization of the filter is done in the startInternal method of the StandardContext class, stored in filterConfigs and stored in the context

  3. When requests flow to StandardWrapperValve, the invoke method creates an ApplicationFilterChain for each request, which contains the target Servlet and the corresponding filter chain, based on the filter mapping configuration information. And calls the doFilter method of the filter chain to execute the filter

The key code StandardWrapperValve calls ApplicationFilterFactory to create and invoke the filter chain for the request is as follows:

final class StandardWrapperValve extends ValveBase {
    public final void invoke(Request request, Response response) throws IOException, ServletException {
        // omit other logic processing...
        / / call ApplicationFilterChain. CreateFilterChain () to create a filter chain
        ApplicationFilterChain filterChain = ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);
        
        if(servlet ! =null&& filterChain ! =null) {
            / / to omit
        } else if (request.isAsyncDispatching()) {
            request.getAsyncContextInternal().doInternalDispatch();
        } else if (comet) {
            filterChain.doFilterEvent(request.getEvent());
        } else {
            // Call the doFilter method of the filter chain to start filtering
            filterChain.doFilter(request.getRequest(), response.getResponse());
        }
Copy the code

The key code for the ApplicationFilterChain, which is an array of ApplicationFilterConfig, is as follows

final class ApplicationFilterChain implements FilterChain.CometFilterChain {
    private ApplicationFilterConfig[] filters = new ApplicationFilterConfig[0]; // Filter chain
    private Servlet servlet = null; / / target
    // ...
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
        if( Globals.IS_SECURITY_ENABLED ) {
            // ...
        } else {
            internalDoFilter(request,response); // Call internalDoFilter}}private void internalDoFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
        // Call the next filter if there is one
        if (pos < n) {
            // Fetches the current filter configuration from the filter array, then increments the subscript by 1
            ApplicationFilterConfig filterConfig = filters[pos++];
            Filter filter = null;
            try {
                filter = filterConfig.getFilter();  // Retrieves the filter object from the filter configuration

                if( Globals.IS_SECURITY_ENABLED ) {
                    final ServletRequest req = request;
                    final ServletResponse res = response;
                    Principal principal = ((HttpServletRequest) req).getUserPrincipal();

                    Object[] args = new Object[]{req, res, this};
                    SecurityUtil.doAsPrivilege("doFilter", filter, classType, args, principal);
                } else {
                    // Call the filter doFilter to complete the filtering function of a filter
                    filter.doFilter(request, response, this);
                }
            return;  // It is important not to repeat servlet.service(request, response)
        }
        
        // After all the filters in the filter chain are executed, the Servlet's service is called to complete the processing of the request
        if ((request instanceof HttpServletRequest) && (response instanceof HttpServletResponse)) {
            if( Globals.IS_SECURITY_ENABLED ) {
                
            } else{ servlet.service(request, response); }}else{ servlet.service(request, response); }}/ / to omit...
}
Copy the code

The filter

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("Before executing the doFilter() method...");
        chain.doFilter(request, response);              // Pass the request to the next filter
        System.out.println("After the doFilter() method...");
    }
Copy the code

When the subscript is less than the length of the filter array n, it indicates that the FilterChain is not completed. Therefore, the current filter is extracted from the array, and the doFilter method of the filter is called to complete the filtering process. In the doFilter of the filter, the doFilter of the FilterChain is also called. Back to the ApplicationFilterChain, we continue to determine if the filter chain is complete based on whether the subscript is less than the length of the array. If not, we continue to fetch the filter from the array and call the doFilter method.

, when all filter has been completed. The last time to enter ApplicationFilterChain doFilter method when the pos < n to false, do not enter the if (pos < n), but behind the execution of code, Request instanceof HttpServletRequest && (Response Instanceof HttpServletResponse), Servlet. service(request, response) is called for HTTP requests; To process the request.

If (pos < n) = if (pos < n); if (pos < n) = if (pos < n); That ensures that only the last enters ApplicationFilterChain. DoFilter method calls can perform the back of the servlet. Service (request, response) method

Draw a brief call stack as follows:

The ApplicationFilterChain class plays the role of abstract handler, which is played by individual filters

Other typical applications of the chain of responsibility model

The application of other responsibility chain models is basically similar

Here are a few examples:

  1. In the NettyPipelineChannelHandlerOrganize code logic through the chain of responsibility design pattern
  2. Spring Security uses the responsibility chain pattern to dynamically add or remove responsibilities (processing request requests)
  3. Spring AOP manages advisors through the chain of responsibility pattern
  4. Dubbo Filter chain also uses the responsibility chain mode (linked list), which can do some filtering for method calls, such as TimeoutFilter, ExceptionFilter, TokenFilter, etc
  5. Plugin mechanism in Mybatis uses the responsibility chain mode to configure various official or custom plugins, similar to Filter, which can perform some operations when executing Sql statements

Debug mode + Memory analysis Responsibility chain design mode (filter, interceptor)

Afterword.

Welcome to comment, forward, share, your support is my biggest motivation

Recommended reading

Design patterns and typical application of design patterns | | simple factory pattern factory method pattern and a typical application of design patterns | the abstract factory pattern and a typical application of design pattern model and typical application design | | builders prototype model and typical application design model and typical application design | | appearance Decorator pattern and a typical application of design pattern model and typical application design | | adapter the flyweight pattern and a typical application of design patterns | | portfolio model and typical application design pattern template method pattern and a typical application of design patterns | the iterator pattern and a typical application of design patterns | strategy pattern and typical applications Design patterns | observer pattern and a typical application of design pattern model and typical application design | | memo mediator pattern and typical applications