[design mode from introduction to mastery] 22 - responsibility chain mode

Posted by b on Thu, 20 Jan 2022 11:55:48 +0100

Note source: Shang Silicon Valley Java design pattern (illustration + framework source code analysis)

Responsibility chain model

1. Purchase approval items of OA system

For the purchase approval project of school OA system, the requirements are

  • 1) Purchaser purchases teaching equipment
  • 2) If the amount is less than or equal to 5000, it shall be approved by the teaching Director (0 < x ≤ 5000)
  • 3) If the amount is less than or equal to 10000, it shall be approved by the president (5000 < x ≤ 10000)
  • 4) If the amount is less than or equal to 30000, it shall be approved by the vice president (10000 < x ≤ 30000)
  • 5) If the amount exceeds 30000, it shall be approved by the president (30000 < x)

Please design procedures to complete the procurement approval items

Traditional solution OA system approval, traditional design scheme (class diagram)

Analysis of traditional solutions to OA system approval problems

  • 1) The traditional method is to call the corresponding Approver to complete the approval according to the purchase amount after receiving a purchase request
  • 2) Traditional problem analysis: the client will use branch judgment (such as switch) to process different purchase requests, so there are the following problems
    • (1) If the approved amount of personnel at all levels changes, the amount at the client also needs to change
    • (2) The client must clearly know how many approval levels and access levels there are
  • 3) In this way, there is a strong coupling relationship between the processing of a purchase request and the Approver, which is not conducive to code expansion and maintenance
  • 4) Solution = responsibility chain model

2. Basic introduction of responsibility chain mode

  • 1) The Chain of Responsibility Pattern, also known as the responsibility chain pattern, creates a chain of receiver objects for requests (simple diagram).

    This mode decouples the sender and receiver of the request

  • 2) In the responsibility chain pattern, each recipient usually contains a reference to another recipient. If an object cannot process the request, it passes the same request to the next recipient, and so on

  • 3) This type of design pattern belongs to behavioral pattern

Schematic class diagram

The responsibility chain pattern enables multiple objects to process requests, thus avoiding the coupling between the sender and receiver of the request

Connect the object into a chain and pass the request along the chain until an object processes it

  • Handler Abstract handler: defines a method for processing a request and contains another handler
  • ConcreteHandler specific handler: handles the request for which you are responsible and can access its successor (i.e. the next handler); If the request can be processed, it will be processed, otherwise it will be handed over to the successor to form a responsibility chain
  • Request contains many attributes that represent a request

3. Responsibility chain mode to solve OA system procurement approval projects

UML class diagram

Core code

Request class

/**
 * Purchase requisition
 */
public class PurchaseRequest {
    private Integer id;
    private Float price;

    public PurchaseRequest(Integer id, Float price) {
        this.id = id;
        this.price = price;
    }

    public Integer getId() {
        return id;
    }

    public Float getPrice() {
        return price;
    }
}

Abstract approver object

/**
 * Abstract approver object
 */
public abstract class Approver {
    protected Approver nextApprover;
    protected String name;

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

    /**
     * Set successor
     *
     * @param nextApprover
     */
    public void setNextApprover(Approver nextApprover) {
        this.nextApprover = nextApprover;
    }

    /**
     * Method of processing request
     */
    public abstract void processRequest(PurchaseRequest purchaseRequest);
}

Specific approver object

/**
 * Teaching director approver
 */
public class TeachDirectorApprover extends Approver {
    public TeachDirectorApprover(String name) {
        super(name);
    }

    @Override
    public void processRequest(PurchaseRequest purchaseRequest) {
        if (purchaseRequest.getPrice() <= 5000) {
            System.out.println("Request No.:" + purchaseRequest.getId() + ",Handled by:" + this.name);
        } else {
            nextApprover.processRequest(purchaseRequest);
        }
    }
}
/**
 * President approver
 */
public class DepartmentHeadApprover extends Approver {
    public DepartmentHeadApprover(String name) {
        super(name);
    }

    @Override
    public void processRequest(PurchaseRequest purchaseRequest) {
        if (purchaseRequest.getPrice() > 5000 && purchaseRequest.getPrice() <= 10000) {
            System.out.println("Request No.:" + purchaseRequest.getId() + ",Handled by:" + this.name);
        } else {
            nextApprover.processRequest(purchaseRequest);
        }
    }
}
/**
 * Vice President approver
 */
public class ViceChancellorApprover extends Approver {
    public ViceChancellorApprover(String name) {
        super(name);
    }

    @Override
    public void processRequest(PurchaseRequest purchaseRequest) {
        if (purchaseRequest.getPrice() > 10000 && purchaseRequest.getPrice() <= 30000) {
            System.out.println("Request No.:" + purchaseRequest.getId() + ",Handled by:" + this.name);
        } else {
            nextApprover.processRequest(purchaseRequest);
        }
    }
}
/**
 * Vice President approver
 */
public class ChancellorApprover extends Approver {
    public ChancellorApprover(String name) {
        super(name);
    }

    @Override
    public void processRequest(PurchaseRequest purchaseRequest) {
        if (purchaseRequest.getPrice() > 30000) {
            System.out.println("Request No.:" + purchaseRequest.getId() + ",Handled by:" + this.name);
        } else {
            nextApprover.processRequest(purchaseRequest);
        }
    }
}

Test code

//Create a request
PurchaseRequest purchaseRequest = new PurchaseRequest(1, 31000.0f);

//Create relevant approvers
TeachDirectorApprover teachDirectorApprover = new TeachDirectorApprover("Director Tong");
DepartmentHeadApprover departmentHeadApprover = new DepartmentHeadApprover("President Wang");
ViceChancellorApprover viceChancellorApprover = new ViceChancellorApprover("Vice President Qian");
ChancellorApprover chancellorApprover = new ChancellorApprover("Principal Zheng");

//Set successor (processor forms a ring)
teachDirectorApprover.setNextApprover(departmentHeadApprover);
departmentHeadApprover.setNextApprover(viceChancellorApprover);
viceChancellorApprover.setNextApprover(chancellorApprover);
chancellorApprover.setNextApprover(teachDirectorApprover);

//Initiate a request
teachDirectorApprover.processRequest(purchaseRequest); //Request No.: 1, processed by: President Zheng

4. Source code analysis of application of responsibility chain pattern in spring MVC framework

The HandlerExecutionChain class in spring MVC uses the responsibility chain pattern

First, you need to understand the basic request process of spring MVC, as shown in the following figure

First, when the user initiates a request to the background, the request will first pass through the DispatcherServlet. The DispatcherServlet object will first traverse the received HandlerMapping set, then find the corresponding HandlerMapping set and get the HandlerExecutionChain object. The HandlerExecutionChain object contains some interceptors. After getting the HandlerInterceptor interceptor, there are the following operations

  • The preHandle() method in HandlerInterceptor is called first
  • The postHandle() method in the HandlerInterceptor is then called
  • Finally, the afterCompletion() method in HandlerInterceptor is called

Now to analyze the source code of spring MVC, we need to introduce spring MVC related dependencies

<properties>
    <maven.compiler.source>8</maven.compiler.source>
    <maven.compiler.target>8</maven.compiler.target>
    <org.springframework.version>4.3.7.RELEASE</org.springframework.version>
</properties>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>${org.springframework.version}</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-beans</artifactId>
    <version>${org.springframework.version}</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>${org.springframework.version}</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
    <version>${org.springframework.version}</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>${org.springframework.version}</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-expression</artifactId>
    <version>${org.springframework.version}</version>
</dependency>
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>servlet-api</artifactId>
    <version>2.5</version>
    <scope>provided</scope>
</dependency>

Next, let's explore how to call these three methods in the HandlerInterceptor interceptor

1) Find the doDispatch() method in the DispatcherServlet and find that a HandlerExecutionChain object is defined in the method

In subsequent code logic, the getHandler() method is invoked, and a processedRequest request object is taken as a parameter to get the initialized HandlerExecutionChain object.

Directly highlight the mappedHandler object, so that we can more intuitively see how mappedHandler calls the three methods mentioned above: preHandle(), postHandle(), and afterCompletion()

[the external chain image transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-0y8h983u-1642509863528) (C: / users / Archives / appdata / roaming / typora / typora user images / image-20220118202141860. PNG)]

Finally, we found two pieces of code, much like the two methods of preHandle() and postHandle() mentioned above

if (!mappedHandler.applyPreHandle(processedRequest, response)) {
    return;
}

mappedHandler.applyPostHandle(processedRequest, response, mv);

First, the applyPreHandle() method of mappedHandler will be executed: if the return is false, the judgment is true and subsequent codes will not be executed; Otherwise, continue to execute and call the applyPostHandle() method of mappedHandler

We trace the source code of the applyPreHandle() and applyPostHandle() methods

2) First look at the source code of the applyPreHandle() method

boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
    HandlerInterceptor[] interceptors = getInterceptors();
    if (!ObjectUtils.isEmpty(interceptors)) {
        for (int i = 0; i < interceptors.length; i++) {
            HandlerInterceptor interceptor = interceptors[i];
            if (!interceptor.preHandle(request, response, this.handler)) {
                triggerAfterCompletion(request, response, null);
                return false;
            }
            this.interceptorIndex = i;
        }
    }
    return true;
}

It can be found that a group of interceptors interceptors will be obtained inside the applyPreHandle method. When the interceptor array is not empty, the following processing will be carried out:

  • Firstly, the for loop of interceptors interceptors is traversed to get each specific interceptor interceptor
  • Then call the preHandle() method of the interceptor. If false is returned, execute the triggerAfterCompletion() method and return. This method ends; Otherwise, continue the relevant processing

3) Next, look at the source code of the triggerAfterCompletion() method

void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, Exception ex)
    throws Exception {

    HandlerInterceptor[] interceptors = getInterceptors();
    if (!ObjectUtils.isEmpty(interceptors)) {
        for (int i = this.interceptorIndex; i >= 0; i--) {
            HandlerInterceptor interceptor = interceptors[i];
            try {
                interceptor.afterCompletion(request, response, this.handler, ex);
            }
            catch (Throwable ex2) {
                logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
            }
        }
    }
}

It can be found that the logic is very similar to the applyPreHandle() method: first traverse a group of interceptors interceptors, and then execute the afterCompletion() method of a single interceptor

4) Finally, take a look at the source code of the applyPostHandle() method

void applyPostHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView mv) throws Exception {
    HandlerInterceptor[] interceptors = getInterceptors();
    if (!ObjectUtils.isEmpty(interceptors)) {
        for (int i = interceptors.length - 1; i >= 0; i--) {
            HandlerInterceptor interceptor = interceptors[i];
            interceptor.postHandle(request, response, this.handler, mv);
        }
    }
}

It can also be found that the logic is basically the same as the above methods: first traverse a group of interceptors interceptors, and then execute the postHandle() method of a single interceptor

Through the above spring MVC source code analysis, here is a simple combing and summary

  • In the flow chart of spring MVC request, interceptor related methods are executed, such as interceptor preHandler()
  • When processing spring MVC requests, we use the responsibility chain pattern and adapter pattern
  • HandlerExecutionChain: it is mainly responsible for the execution and request processing of the request interceptor, but it does not process the request itself, but only assigns the request to the registered processor on the chain for execution. This is the implementation of the responsibility chain, which reduces the coupling between the responsibility chain itself and the processing logic, and standardizes the processing flow
  • HandlerExecutionChain: maintains a collection of handlerlnterceptors to which corresponding interceptors can be registered

5. Precautions and details of responsibility chain model

  • 1) Separate the request and processing to realize decoupling and improve the flexibility of the system
  • 2) The object is simplified so that the object does not need to know the structure of the chain
  • 3) The performance will be affected, especially when the chain is relatively long. Therefore, it is necessary to control the maximum number of nodes in the chain. Generally, set a maximum number of nodes in the Handler and judge whether the threshold has been exceeded in the setNext() method. If the threshold is exceeded, the chain is not allowed to be established to avoid the unintentional damage to the system performance caused by the super long chain
  • 4) Inconvenient debugging. Similar to recursion, the logic may be complex during debugging
  • 5) Best application scenario: when there are multiple objects that can process the same request, such as multi-level request, leave / salary increase approval process, Tomcat Encoding processing in Java Web, interceptor

Topics: Design Pattern