“This is the 25th day of my participation in the First Challenge 2022. For details: First Challenge 2022.”

Chain of Responsibility model

Behavioral design patterns, unlike structural design patterns, focus on optimizing processes.

In real development, you can consider the chain of responsibility pattern when you encounter multiple objects that can handle a single request.

The chain of responsibility pattern is defined to avoid coupling between the sender and receiver of the request by giving multiple objects the opportunity to process the request, linking the object into a chain, and passing the request along the chain until one object processes it.

The standard responsibility chain model has the following characteristics:

  • Each object on the chain has a chance to process the request
  • Each object on the chain holds a reference to the next object to be processed
  • If an object on the chain cannot handle the current request, it passes the same request to the next object

A diagram shows the following architecture using the chain of responsibility pattern:

In other words, the chain of responsibility pattern satisfies the loose coupling between the request sender and the request handler, abstracts the non-core part, and processes the request object in the form of chain call.

Example scenario:

In a separately deployed algorithm system, a request comes in that wants to do something before calling the algorithm; Such as permission verification, logging, caching request parameters, the request must be in the specified order, the above operations can be executed after the algorithm logic.

Do not use the chain of responsibility model

Start by defining an action list object:

package NotChainResponsibility; Public class PreparationList {public class PreparationList {private Boolean authorization; /** * private Boolean byLog; /** * Whether to cache request parameters */ private Boolean toRedies; public boolean isAuthorization() { return authorization; } public void setAuthorization(boolean authorization) { this.authorization = authorization; } public boolean isByLog() { return byLog; } public void setByLog(boolean byLog) { this.byLog = byLog; } public boolean isToRedies() { return toRedies; } public void setToRedies(boolean toRedies) { this.toRedies = toRedies; }}Copy the code
package NotChainResponsibility; /** * algorithm prediction class; Public class Forecast {public void doForecast(PreparationList PreparationList) {if (preparationList isAuthorization ()) {System. Out. Println (" authentication "); } if (preparationList.isbylog ()) {system.out.println (" Save the log "); {} the if (preparationList isToRedies ()) System. The out. Println (" parameter in the cache "); } system.out.println (" call algorithm "); }}Copy the code

This example fulfils our requirements, but it is not elegant enough. Our main flow is to call algorithmic prediction, but to couple the actions of preparing to do in algorithmic prediction, there are two problems:

  • When you add something in the PreparationList, such as sending a request notifying a monitor count, or you want to remove cache parameters, you must modify the doForecast method to fit
  • When the order of these things needs to change and the Forecast method must be modified, such as logging first, the code must be switched

The worst way to write it is just to satisfy the feature. It violates the open and closed principle, that is, we need to modify the main flow when we extend the feature. We cannot close for change and open for extension.

Use the chain of responsibility model

Create a chain of responsibility

/** * Copyright(C),2015‐2021, Beijing QnENG Interconnection Technology Co., LTD. */ Package finalChainResponsibility; import finalChainResponsibility.filter.PrepareFilter; import java.util.ArrayList; import java.util.List; ** @author: [email protected] * @date: 2021/11/30 13:32 * @version: 1.0.0 */ public class FilterChain implements PrepareFilter {private int pos = 0; private Forecast forecast; private List<PrepareFilter> prepareFilterList; public FilterChain(Forecast forecast) { this.forecast = forecast; } public void addFilter(PrepareFilter prepareFilter) { if (prepareFilterList == null) { prepareFilterList = new ArrayList<PrepareFilter>(); } prepareFilterList.add(prepareFilter); } @Override public void doFilter(PreparationList thingList, FilterChain FilterChain) {if (pos == prepareFilterList.size()) {forecast.doforecast (); } else { prepareFilterList.get(pos++).doFilter(thingList, filterChain); }}}Copy the code
package finalChainResponsibility; Public class PreparationList {public class PreparationList {private Boolean authorization; /** * private Boolean byLog; /** * Whether to cache request parameters */ private Boolean toRedies; public boolean isAuthorization() { return authorization; } public void setAuthorization(boolean authorization) { this.authorization = authorization; } public boolean isByLog() { return byLog; } public void setByLog(boolean byLog) { this.byLog = byLog; } public boolean isToRedies() { return toRedies; } public void setToRedies(boolean toRedies) { this.toRedies = toRedies; }}Copy the code
package finalChainResponsibility; /** */ public class Forecast {public void doForecast() {system.out.println (" algorithm call logic "); }}Copy the code

Define the interceptor interface

package finalChainResponsibility.filter;


import finalChainResponsibility.PreparationList;
import finalChainResponsibility.FilterChain;

/**
 * Description:  <br>
 */
public interface PrepareFilter {

    void doFilter(PreparationList preparationList, FilterChain filterChain);

}
Copy the code
package finalChainResponsibility.filter; import finalChainResponsibility.PreparationList; import finalChainResponsibility.FilterChain; Public class AuthorizationFilter implements PrepareFilter {@override public void doFilter(PreparationList) PreparationList, FilterChain FilterChain) {if (preparationList isAuthorization ()) {System. Out. Println (" authentication "); } filterChain.doFilter(preparationList, filterChain); }}Copy the code
package finalChainResponsibility.filter; import finalChainResponsibility.PreparationList; import finalChainResponsibility.FilterChain; Public class ByLogFilter implements PrepareFilter {@override public void doFilter(PreparationList) PreparationList, FilterChain FilterChain) {if (PreparationList.isbylog ()) {system.out.println (" Log "); } filterChain.doFilter(preparationList, filterChain); }}Copy the code
package finalChainResponsibility.filter; import finalChainResponsibility.PreparationList; import finalChainResponsibility.FilterChain; Public class ToRediesFilter implements PrepareFilter {@override public void doFilter(PreparationList) PreparationList, FilterChain FilterChain) {if (preparationList isToRedies ()) {System. Out. Println (" cache request "); } filterChain.doFilter(preparationList, filterChain); }}Copy the code

Test pseudocode

package finalChainResponsibility; import finalChainResponsibility.filter.AuthorizationFilter; import finalChainResponsibility.filter.ByLogFilter; import finalChainResponsibility.filter.PrepareFilter; import finalChainResponsibility.filter.ToRediesFilter; import javax.security.*; public class Test { public static void main(String[] args) { PreparationList preparationList = new PreparationList(); preparationList.setAuthorization(true); preparationList.setByLog(true); preparationList.setToRedies(true); Forecast forecast = new Forecast(); PrepareFilter authorizationFilter = new AuthorizationFilter(); PrepareFilter byLogFilter = new ByLogFilter(); PrepareFilter toRediesFilter = new ToRediesFilter(); FilterChain chain = new FilterChain(forecast); chain.addFilter(authorizationFilter); chain.addFilter(byLogFilter); chain.addFilter(toRediesFilter); chain.doFilter(preparationList, chain); }}Copy the code

In the demo above, FilterChain can be made into a Spring Bean, and all Filter implementations are Spring beans. Check into PrepareFilterList. In this way, adding or modification operations does not need to change the calling code.

Such as:

<bean id="filterChain" class="xxx.xxx.xxx.FilterChain">
    <property name="PrepareFilterList">
        <list>
            <ref bean="authorizationFilter" />
            <ref bean="byLogFilter" />
            <ref bean="toRediesFilter" />
        </list>
    </property>
</bean>
Copy the code

In this way, you only need to modify the. XML file to add, reduce, or modify the Filter order. Not only the core logic conforms to the open and closed principle, but also the caller complies with the open and closed principle.

Practical application of responsibility chain model

The Servlet Filter

The chain of responsibility pattern is most typical of filters in servlets. The upgraded version of responsibility chain above is based on the application scenario of servlets.

Advantages of using the chain of responsibility pattern in servlets:

  • Make the filter is pluggable, we do not need a filter, directly delete will not affect the operation of the program.
  • A filter is not dependent on another resource
  • Low maintenance and easy maintenance

Spring Boot interceptor

Interceptors used in Spring Boot are also an implementation of the chain of responsibility pattern. The bottom layer is still using the Servelt Filter.

Register a custom interceptor with FilterRegistrationBean. For example, busbar system user login join login user ID into the session cache interceptor:

Defining interceptors

package com.tsintergy.buslf.web.base.common.interceptor; import com.tsieframework.cloud.security.serviceapi.system.api.SecurityService; import com.tsieframework.cloud.security.serviceapi.system.bean.TokenBean; import com.tsieframework.cloud.security.serviceapi.system.pojo.TsieMenuVO; import com.tsieframework.cloud.security.serviceapi.system.pojo.TsieUserVO; import com.tsieframework.cloud.security.web.common.security.TokenManager; import com.tsieframework.core.base.dao.hibernate.DBQueryParam; import com.tsieframework.core.base.dao.hibernate.DBQueryParamBuilder; import com.tsieframework.core.base.dao.hibernate.QueryOp; import java.io.IOException; import java.util.List; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang3.StringUtils; import org.jasig.cas.client.validation.Assertion; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Description: <br> */ public class LoginInitFilter implements Filter { private static final Logger logger = LoggerFactory.getLogger(LoginInitFilter.class); protected SecurityService securityService; protected TokenManager tokenManager; public LoginInitFilter(SecurityService securityService, TokenManager tokenManager) { this.securityService = securityService; this.tokenManager = tokenManager; } @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter( ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) servletRequest; HttpServletResponse res = (HttpServletResponse) servletResponse; logger.info("Cas request URL:" + req.getRequestURI()); String username = getUserName(req); Logger. info("request username:" + username); if(StringUtils.isBlank(username)) { filterChain.doFilter(req, res); }else { initUserInfo(filterChain, req, res, username); } } private void initUserInfo(FilterChain filterChain, HttpServletRequest req, HttpServletResponse res, String username) throws IOException, ServletException {/ / get the token request carried TokenBean bean. = tokenManager getTokenBeanFromCache (username); // Token does not exist, or token expires, If go backstage login method (bean = = null) {DBQueryParam param = DBQueryParamBuilder. The create (). The where (QueryOp StringEqualTo, "username", username).queryDataOnly().build(); Try {/ / user permissions initialization TsieUserVO user. = securityService queryTsieUserVo (param); String token = tokenManager.setCookieToken(user); List<TsieMenuVO> menuList = securityService.queryTsieMenuAllByUserId(user.getId(), null); TokenBean tokenBean = TokenManager.createTokenBean(token, user, menuList); tokenManager.addTokenBeanToCache(user.getUsername(), tokenBean); } catch (Exception e) {logger.error(" single sign-on failed to initialize user permissions and organizational structure information..." ); e.printStackTrace(); } filterChain.doFilter(req, res); } else {/ / set the session cache the req. GetSession (). The setAttribute (tokenManager. GetLoginTokenKey (), bean. The getToken ()); filterChain.doFilter(req, res); }} public String getUserName(HttpServletRequest Request) request.getSession().getAttribute("_const_cas_assertion_"); String username = null; if (object ! = null) { Assertion assertion = (Assertion) object; username = assertion.getPrincipal().getName(); } else { username = (String) request.getSession().getAttribute("edu.yale.its.tp.cas.client.filter.user"); } return username; } @Override public void destroy() { } }Copy the code

Add to the Spring context as a Spring object wrapped with FilterRegistrationBean:

@Bean public FilterRegistrationBean<LoginInitFilter> casLoginInitFilter(){ final FilterRegistrationBean registration = new FilterRegistrationBean(); registration.setFilter(new LoginInitFilter(securityService, tokenManager)); / / set the path of the matching registration. AddUrlPatterns (" / * "); // Set the loading order registration.setOrder(99); return registration; }Copy the code

At some point after Spring Boot is started, interceptors are retrieved from the FilterRegistrationBean, sorted according to the set order, placed in the context, and the logic is then executed through the FilterChain’s doFilter method.

Note:

The Filter and FilterChain objects in Spring Boot that do not conform to the example’s chain of responsibility pattern do not inherit from the same object.

In Spring Boot (i.e. servlet):

There are three parameters in Filter that contain the chain of responsibility. There are only two parameters in the FilterChain, no responsibility chain. This is different from demo. In the chain of responsibility model, the most prominent feature is the chain of responsibility chain call to achieve similar circular behavior.

The design consideration here may be to uniformly get a list of methode-ordered interceptors from the environment variable so that responsibility chain objects are not passed in the responsibility chain. After tracing the Spring source code, I found that it is so, interested partners can follow it.

What the chain of responsibility does is to string together all the processes. The demo shows that all flows are connected using filterchain-dofilter. In Demo, filterChain and filter origin need not be distinguished. In Spring Boot, filterChain and filter origin need not be distinguished.

Find any FilterChain implementation class: for example, ApplicationFilterChain

Call internalDoFilter from the context Filter Filter = filterconfig.getFilter (); Filter.dofilter (request, response, this); Enter the corresponding process.

Conclusion:

Spring Boot starts a node. 1 uses the FilterRegistrationBean to put the interceptor into the environment variable first.

Execute the doFilter() method of FilterChain on node 2, which will get the first Filter from the environment variable.

The doFilter() method of Filter is then called, passing the chain of responsibility object as a parameter. (There may be changes in the state of the chain of responsibility object such as counting +1 to determine whether the Filter is all executed, so the object will be passed).

Filterchain-dofilter (request, response) is called at the end of the doFilter() method of Filter; To pass the chain of responsibility to the next Filter. Method gets the second-order Filter from the environment variable… And so on.

Until all the filters have been executed, the final logic is executed.

Nodes 1 and 2 of Spring Boot involve the Spring startup process and context initialization sequence, which is not described here.

Satisfy your curiosity:

If filterchain-dofilter (request, response) is not written at the end of the custom Filter logic; Subsequent filters are not executed.

Similar questions:

It’s easy to understand the design patterns used for filters.