Handling a thing in daily life often requires a series of processes, and the processes are direct and sequential. For example, when we go to the hospital to see a doctor, we first need to register, and then go to the queuing consultation. The doctor issues the specified drugs according to the patient's situation, and the interfacing patients pay according to the drug bill issued by the doctor. After the payment is completed, we go to the drug collection window to queue up for drugs. This series of links are in order, and the front and back processes are directly linked. It can't be said to complete an item before dealing with the unfinished items.
This scenario is often used in the process of software development. For an object processed by different processors (filters), the design pattern responsibility chain pattern to be talked about today is actually applied here.
definitionResponsibility chain is also called responsibility chain, which decouples the sending and receiving of requests, so that multiple receiving objects have the opportunity to process the request. String these receiving objects into a chain and pass the request along the chain until a receiving object in the chain can process it.
Structure and implementation of responsibility chain model- The structure of the mode responsibility chain mode mainly includes the following roles.
- Abstract Handler role: define an interface for processing requests, including abstract processing methods and a subsequent connection.
- Concrete Handler role: implement the processing method of the abstract handler to judge whether the request can be processed. If the request can be processed, process it. Otherwise, transfer the request to its successor.
- Client role: create a processing chain and submit a request to the specific handler object of the chain head. It does not care about the processing details and the transmission process of the request.
2. Structure diagram of responsibility chain mode
Case 1 gives an example of a scene often encountered at work. It is inevitable to encounter some things at work and need to ask for leave. The administrative department of the company often sets different levels of leadership approval according to the individual days of leave. Here, for example, we ask for leave of less than or equal to 1 day for supervisor approval, more than 1 less than or equal to 3 days for supervisor approval, and more than 3 days for Boos approval. Here, each role is defined as a specific handler in the responsibility chain pattern. See the code implementation for details.
- Abstract handler
//Abstract executor public abstract class HandlerChain { protected HandlerChain handlerChain; public HandlerChain getHandlerChain() { return handlerChain; } public void setHandlerChain(HandlerChain handlerChain) { this.handlerChain = handlerChain; } //Execution logic protected abstract void chain(Integer request); }
- Specific handler 1 Supervisor
public class DirectorApproveHandler extends HandlerChain{ @Override protected void chain(Integer request) { if (Objects.isNull(request)) { return; } if (request > 0 && request <=1) { System.out.println("Leave less than one day approved by supervisor"); } else { handlerChain.chain(request); } } }
- Specific handler 2 Manager
public class ManagerApproveHandler extends HandlerChain{ @Override protected void chain(Integer request) { if (Objects.isNull(request)) { return; } if (request > 1 && request <=3) { System.out.println("Leave 1~3 Day manager approval"); } else { handlerChain.chain(request); } } }
- Specific handler 3 boos
public class BossApproveHandler extends HandlerChain{ @Override protected void chain(Integer request) { if (Objects.isNull(request)) { return; } if (request > 3) { System.out.println("Leave more than 3 days Boos Examine and approve"); } else { handlerChain.chain(request); } } }
- client
public class Client { public static void main(String[] args) { HandlerChain directorHandlerChain = new DirectorApproveHandler(); HandlerChain managerHandlerChain = new ManagerApproveHandler(); HandlerChain bossHandlerChain = new BossApproveHandler(); //Set responsibility chain execution directorHandlerChain.setHandlerChain(managerHandlerChain); managerHandlerChain.setHandlerChain(bossHandlerChain); bossHandlerChain.setHandlerChain(directorHandlerChain); //call directorHandlerChain.chain(2); } }
The client sets the specific processor to be set as the processing chain, and then calls it from the initial node, and the satisfied node processes it.
Leave for 1 ~ 3 days approved by manager
The case above the responsibility chain mode is just a usage scenario. When the current node is executed, the next node of each processing node is also set. If it is satisfied, it is equivalent to no further execution. If we add another processor between two processors, in addition to the added processor settings, we also need to change the node settings of the previous processor, which is not conducive to expansion.
The following describes another variant, which maintains a batch of processors through collection, and the processors can be added according to our predetermined order. It is given in the form of source code below;
- Abstract handler
//Abstract executor public abstract class HandlerChain { //Execution logic protected abstract boolean chain(Integer request); }
- Specific handler Supervisor
//Supervisor approval public class DirectorApproveHandler extends HandlerChain{ @Override protected boolean chain(Integer request) { if (Objects.isNull(request)) { return false; } if (request > 0 && request <=1) { System.out.println("Leave less than one day approved by supervisor"); return false; } return true; } }
- Specific handler Manager
public class ManagerApproveHandler extends HandlerChain{ @Override protected boolean chain(Integer request) { if (Objects.isNull(request)) { return false; } if (request > 1 && request <=3) { System.out.println("Leave 1~3 Day manager approval"); return false; } return true; } }
- Specific handler boos
public class BossApproveHandler extends HandlerChain{ @Override protected boolean chain(Integer request) { if (Objects.isNull(request)) { return false; } if (request > 3) { System.out.println("Leave more than 3 days Boos Examine and approve"); return false; } return true; } }
- client
public class Client { public static void main(String[] args) { HandlerChain directorHandlerChain = new DirectorApproveHandler(); HandlerChain managerHandlerChain = new ManagerApproveHandler(); HandlerChain bossHandlerChain = new BossApproveHandler(); List<HandlerChain> list = new ArrayList<>(3); list.addAll(Arrays.asList(directorHandlerChain,managerHandlerChain,bossHandlerChain)); Integer request = 2; for (HandlerChain handlerChain : list) { if (!handlerChain.chain(request)) { break; } } } }
Leave for 1 ~ 3 days approved by manager
Case 2 structure diagram
It can be seen that the results obtained are the same, and the implementation details have changed. The client (user) executes the processor through collection traversal. According to the processor's results, it feels that it needs to continue to execute downward. The advantage over the first implementation method is that the next processor can be set between processors without attention. Instead, the order of execution is determined by the order in which it is added to the collection.
Advantages, disadvantages and application scenarios of responsibility chain model1. Advantages:
- Reduces the coupling between objects. This mode makes an object do not need to know which object handles its request and the structure of the chain, and the sender and receiver do not need to have each other's clear information.
- It enhances the scalability of the system. New request processing classes can be added as needed to meet the opening and closing principle.
-Increased flexibility in assigning responsibilities to objects. When the workflow changes, you can dynamically change the members in the chain or transfer their order, or dynamically add or delete responsibilities. - The chain of responsibility simplifies the connection between objects. Each object only needs to maintain a reference to its successor without maintaining the references of all other processors, which avoids the use of many if or if ·· else statements.
- Responsibility sharing. Each class only needs to deal with its own work that should be handled, and the work that should not be handled should be transferred to the next object for completion. The scope of responsibility of each class should be defined and in line with the principle of single responsibility of the class.
2. Disadvantages:
- There is no guarantee that every request will be processed. Since a request has no specific receiver, it cannot be guaranteed that it will be processed. The request may not be processed until it reaches the end of the chain.
- For a long responsibility chain, the processing of requests may involve multiple processing objects, and the system performance will be affected to some extent.
- A global context object runs through the whole processing link, and a processor can be abstracted into a processor with a single responsibility
- You can dynamically specify a set of objects to process requests, or add new processors.
You need to submit a request to one of multiple processors without explicitly specifying the request processor.
There are still many scenarios for using the responsibility chain in the source code, such as our common servlet filter or Interceptor interceptor in spring mvc.
1. Filter in ServletServlet Filter is a component defined in the Java Servlet specification. Translated into Chinese, it is a filter. It can filter HTTP requests, such as authentication, flow limiting, logging, verification parameters, etc. Because it is part of the servlet specification, so long as it is a Web container that supports servlets (for example, Tomcat, Jetty, etc.)
To facilitate understanding, draw a simple diagram.
If you have done web development, you should be very clear and easy to use. If you want to add a Filter, you only need to implement the Filter interface
Filter interface
public interface Filter { default void init(FilterConfig filterConfig) throws ServletException { } void doFilter(ServletRequest var1, ServletResponse var2, FilterChain var3) throws IOException, ServletException; default void destroy() { } }
Example
public class DemoFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { // Automatically called when creating a Filter, where filterConfig contains the configuration parameters of the Filter, such as name (read from the configuration file)} } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.println("Intercept requests from clients."); filterChain.doFilter(servletRequest, servletResponse); System.out.println("Intercept responses sent to clients."); } @Override public void destroy() { // Called automatically when the Filter is destroyed } }
Configure the added filter in web.xml
// Configure the following in the web.xml configuration file: <filter> <filter-name>DemoFilter</filter-name> <filter-class>com.edu.DemoFilter</filter-class> </filter> <filter-mapping> <filter-name>DemoFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
When an http request is initiated, the custom interceptor will play a corresponding role. And you can find it very convenient to add a filter without too many configuration changes. Meet our design principles.
How does Servlet implement the responsibility chain pattern? The implementation of responsibility chain pattern includes processor interface (IHandler) or abstract class (Handler) and processor chain (HandlerChain). Corresponding to Servlet Filter, javax.servlet.Filter is the processor interface and FilterChain is the processor chain. Next, let's focus on how FilterChain is implemented.
As mentioned earlier, Servlet is only a specification and does not contain specific implementation. Therefore, FilterChain in Servlet is only an interface definition. The specific implementation classes are provided by the group from the Web container of Servlet specification. For example, the ApplicationFilterChain class is the implementation class of FilterChain provided by Tomcat. The source code is as follows.
public final class ApplicationFilterChain implements FilterChain { private int pos = 0; //Which filter is currently executed private int n; //Number of filter s private ApplicationFilterConfig[] filters; private Servlet servlet; @Override public void doFilter(ServletRequest request, ServletResponse response) { if (pos < n) { ApplicationFilterConfig filterConfig = filters[pos++]; Filter filter = filterConfig.getFilter(); filter.doFilter(request, response, this); } else { // After all filter s are processed, execute servlet. Service (request, response);} } public void addFilter(ApplicationFilterConfig filterConfig) { for (ApplicationFilterConfig filter:filters) if (filter==filterConfig) return; if (n == filters.length) {//Capacity expansion ApplicationFilterConfig[] newFilters = new ApplicationFilterConfig[n + INCREMENT]; System.arraycopy(filters, 0, newFilters, 0, n); filters = newFilters; } filters[n++] = filterConfig; }}2. Interceptor in springmvc
Spring Interceptor, translated into Chinese, is an interceptor. Although English words and Chinese translations are different, they can basically be regarded as a concept, both of which are used to intercept HTTP requests.
Different from Servlet Filter, Servlet Filter is a part of Servlet specification, and its implementation depends on Web container. Spring Interceptor is a part of the Spring MVC framework and is implemented by the Spring MVC framework. The request sent by the client will first pass through the Servlet Filter, then through the Spring Interceptor, and finally reach the specific business code.
Here, a simple diagram is also used to show the execution flow of an interceptor
Similar to filters, we only need to implement the HandlerInterceptor interface
Example
public class DemoInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("Intercept requests from clients."); return true;//Continue } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("Intercept requests to customers"); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("It's always executed here"); } }
to configure
//Configure interceptors < MVC: interceptors > in the Spring MVC configuration file <mvc:interceptor> <mvc:mapping path="/*"/> <bean /> </mvc:interceptor> </mvc:interceptors>
The interceptor is also implemented based on the responsibility chain pattern. The HandlerExecutionChain class is the processor chain in the responsibility chain pattern. Compared with the ApplicationFilterChain in Tomcat, its implementation has clearer logic and does not need to use recursive implementation, mainly because it splits the interception of requests and responses into two functions. The source code of HandlerExecutionChain is as follows.
public class HandlerExecutionChain { private final Object handler; private HandlerInterceptor[] interceptors; public void addInterceptor(HandlerInterceptor interceptor) { initInterceptorList().add(interceptor); } 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; } } } return true; } 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); } } } 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); } } } }
In the Spring framework, the dispatcher servlet uses the doDispatch() method to distribute requests. Before and after the real business logic is executed, it executes the applyPreHandle() and applyPostHandle() functions in the HandlerExecutionChain to realize the function of interception.
✨✨ welcome 🔔 Subscribe to WeChat's official account for updates