Geek Time-The Beauty of Design Patterns. Chain of Responsibility Pattern (Part 2): How are the filters and interceptors commonly used in the framework implemented?

We learned the principle and implementation of the chain of responsibility pattern before, and demonstrated the design intent of the chain of responsibility pattern through an example of a sensitive word filtering framework. In essence, it, like most design patterns, is to decouple the code, cope with the complexity of the code, make the code meet the open and close principle, and improve the scalability of the code .

In addition, we also mentioned that the chain of responsibility model is often used in framework development to provide extension points for the framework, allowing users of the framework to add new functions based on the extension points without modifying the framework source code. In fact, to be more specific, the chain of responsibility model is most commonly used to develop filters and interceptors for frameworks. Today, we will use Servlet Filter and Spring Interceptor, two commonly used components in Java development, to specifically talk about its application in framework development.

Servlet Filter

Servlet Filter is a component defined in the Java Servlet specification. Translated into Chinese, it is a filter. It can implement filtering functions for HTTP requests, such as authentication, current limiting, logging, verification parameters, and so on. Because it is part of the Servlet specification, any Web container that supports Servlet (for example, Tomcat, Jetty, etc.) supports the filter function. To help you understand, I drew a schematic diagram to explain how it works, as shown below.

Insert picture description here
In actual projects, how do we use Servlet Filter? I wrote a simple sample code as shown below. To add a filter, we only need to define a filter class that implements the javax.servlet.Filter interface and configure it in the web.xml configuration file. When the web container starts, it will read the configuration in web.xml and create a filter object. When a request comes, it will pass through the filter before being processed by the servlet.


public class LogFilter implements Filter {
    
    
  @Override
  public void init(FilterConfig filterConfig) throws ServletException {
    
    
    // 在创建Filter时自动调用,
    // 其中filterConfig包含这个Filter的配置参数,比如name之类的(从配置文件中读取的)
  }

  @Override
  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    
    
    System.out.println("拦截客户端发送来的请求.");
    chain.doFilter(request, response);
    System.out.println("拦截发送给客户端的响应.");
  }

  @Override
  public void destroy() {
    
    
    // 在销毁Filter时自动调用
  }
}

// 在web.xml配置文件中如下配置:
<filter>
  <filter-name>logFilter</filter-name>
  <filter-class>com.xzg.cd.LogFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>logFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

From the sample code just now, we found that it is very convenient to add a filter. You don't need to modify any code. Define a class that implements javax.servlet.Filter, and then change the configuration. It is completely in line with the principle of opening and closing. So how does Servlet Filter achieve such good scalability? I think you might have guessed it, it uses the chain of responsibility model. Now, by analyzing its source code, we have a detailed look at how it is implemented at the bottom.

In the previous lesson, we mentioned that the realization of the responsibility chain model includes the processor interface (IHandler) or abstract class (Handler), and the processor chain (HandlerChain). Corresponding to Servlet Filter, javax.servlet.Filter is the processor interface, and FilterChain is the processor chain. Next, we will focus on how FilterChain is implemented.

However, as we have mentioned before, Servlet is only a specification and does not contain specific implementations. Therefore, FilterChain in Servlet is just an interface definition. The specific implementation class is provided by a Web container that conforms to the Servlet specification. For example, the ApplicationFilterChain class is the implementation class of FilterChain provided by Tomcat. The source code is shown below.

In order to make the code easier to read, I simplified the code and only kept the code snippets related to the design ideas. You can go to Tomcat to view the complete code.


public final class ApplicationFilterChain implements FilterChain {
    
    
  private int pos = 0; //当前执行到了哪个filter
  private int n; //filter的个数
  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 {
    
    
      // filter都处理完毕后,执行servlet
      servlet.service(request, response);
    }
  }
  
  public void addFilter(ApplicationFilterConfig filterConfig) {
    
    
    for (ApplicationFilterConfig filter:filters)
      if (filter==filterConfig)
         return;

    if (n == filters.length) {
    
    //扩容
      ApplicationFilterConfig[] newFilters = new ApplicationFilterConfig[n + INCREMENT];
      System.arraycopy(filters, 0, newFilters, 0, n);
      filters = newFilters;
    }
    filters[n++] = filterConfig;
  }
}

The code implementation of the doFilter() function in ApplicationFilterChain is more skillful, it is actually a recursive call. You can use the code of doFilter() of each Filter (such as LogFilter) to directly replace the 12th line of the ApplicationFilterChain code, and you can see that it is a recursive call at a glance. I replaced it, as shown below.


  @Override
  public void doFilter(ServletRequest request, ServletResponse response) {
    
    
    if (pos < n) {
    
    
      ApplicationFilterConfig filterConfig = filters[pos++];
      Filter filter = filterConfig.getFilter();
      //filter.doFilter(request, response, this);
      //把filter.doFilter的代码实现展开替换到这里
      System.out.println("拦截客户端发送来的请求.");
      chain.doFilter(request, response); // chain就是this
      System.out.println("拦截发送给客户端的响应.")
    } else {
    
    
      // filter都处理完毕后,执行servlet
      servlet.service(request, response);
    }
  }

This implementation is mainly to support two-way interception in a doFilter() method, which can intercept both the request sent by the client and the response sent to the client. You can combine the LogFilter example and compare it later. Come to understand the Spring Interceptor. However, the two implementations we gave in the previous lesson cannot add processing code before and after the execution of business logic.

Spring Interceptor

Just talked about Servlet Filter, now we are talking about something very similar in function, Spring Interceptor, translated into Chinese is an interceptor. Although English words and Chinese translations are different, the two can basically be regarded as a concept, and both are used to intercept HTTP requests.

The difference between them is that Servlet Filter is a part of the Servlet specification, and the implementation depends on the Web container. Spring Interceptor is a part of Spring MVC framework, and implementation is provided by Spring MVC framework. The request sent by the client will first go through the Servlet Filter, then through the Spring Interceptor, and finally reach the specific business code. I drew a picture to illustrate the processing flow of a request, as shown below.

Insert picture description here
In the project, how should we use Spring Interceptor? I wrote a simple sample code as shown below. The function implemented by LogInterceptor is exactly the same as the LogFilter just now, but the implementation is slightly different. LogFilter intercepts requests and responses in a function doFilter(), while LogInterceptor intercepts requests in preHandle(), and intercepts responses in postHandle().


public class LogInterceptor implements HandlerInterceptor {
    
    

  @Override
  public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    
    
    System.out.println("拦截客户端发送来的请求.");
    return true; // 继续后续的处理
  }

  @Override
  public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    
    
    System.out.println("拦截发送给客户端的响应.");
  }

  @Override
  public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    
    
    System.out.println("这里总是被执行.");
  }
}

//在Spring MVC配置文件中配置interceptors
<mvc:interceptors>
   <mvc:interceptor>
       <mvc:mapping path="/*"/>
       <bean class="com.xzg.cd.LogInterceptor" />
   </mvc:interceptor>
</mvc:interceptors>

Similarly, let's analyze how the underlying Spring Interceptor is implemented.

Of course, it is also implemented based on the chain of responsibility model. Among them, the HandlerExecutionChain class is the processor chain in the chain of responsibility model. Compared with the ApplicationFilterChain in Tomcat, its implementation has clearer logic and does not need to be implemented with recursion, mainly because it splits the request and response interception work into two functions. The source code of HandlerExecutionChain is shown below. Similarly, I have simplified the code and only retained the key code.


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, DispatcherServlet's doDispatch() method is used to distribute requests. It executes the applyPreHandle() and applyPostHandle() functions in HandlerExecutionChain before and after the execution of the real business logic to implement the interception function. The specific code implementation is very simple, you should be able to make it up by yourself, so I won't list it here. If you are interested, you can check it yourself.

Guess you like

Origin blog.csdn.net/zhujiangtaotaise/article/details/110486470