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

63 | Responsibility Chain Mode (Part 2): How are the filters and interceptors commonly used in the framework implemented?

In the last class, we learned the principle and implementation of the chain of responsibility model, and demonstrated the design intent of the chain of responsibility model through an example of a sensitive word filtering framework. In essence, like most design patterns, it is to decouple the code, deal with the complexity of the code, make the code satisfy the principle of opening and closing, and improve the scalability of the code.

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

Without further ado, let's officially start today's study!

Servlet Filter

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

insert image description here

In actual projects, how do we use Servlet Filter? I wrote a simple sample code as 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 go 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, without modifying any code, just define a class that implements javax. So how does Servlet Filter achieve such good scalability? I think you should have guessed that it uses the Chain of Responsibility pattern. Now, let's take a closer look at how it is implemented at the bottom by analyzing its source code.

In the previous lesson, we mentioned that the implementation of the Chain of Responsibility pattern includes a handler interface (IHandler) or an abstract class (Handler), and a handler 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.

However, as we have said before, Servlet is just a specification and does not contain a specific implementation. Therefore, FilterChain in Servlet is just an interface definition. The specific implementation class is provided by the Web container that complies with the Servlet specification. For example, the ApplicationFilterChain class is the implementation class of FilterChain provided by Tomcat. The source code is as follows.

In order to make the code easier to understand, I simplified the code and only kept the code fragments related to the design idea. You can check the complete code by yourself in Tomcat.

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 quite tricky, and it is actually a recursive call. You can use the doFilter() code of each Filter (such as LogFilter) to directly replace the 12th line of code in ApplicationFilterChain, and you can see at a glance that it is a recursive call. 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 not only intercept the request sent by the client, but also intercept the response sent to the client. You can combine the example of LogFilter and compare it later. To the Spring Interceptor, come to understand for yourself. However, the two implementation methods we gave in the last class cannot add processing codes before and after the execution of business logic.

Spring Interceptor

I just talked about Servlet Filter, now let's talk about something very similar to it 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, both of which are used to intercept HTTP requests.

The difference between them is that Servlet Filter is part of the Servlet specification, and its implementation depends on the Web container. Spring Interceptor is 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. I drew a picture to illustrate the processing flow of a request, as shown below.

insert image description here

In the project, how do we use Spring Interceptor? I wrote a simple sample code as below. The function implemented by LogInterceptor is exactly the same as that of LogFilter just now, but the implementation method is slightly different. LogFilter's interception of requests and responses is implemented in a function of doFilter(), while LogInterceptor's interception of requests is implemented in preHandle(), and the interception of responses is implemented 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 bottom layer of Spring Interceptor is implemented.

Of course, it is also implemented based on the Chain of Responsibility pattern. Among them, the HandlerExecutionChain class is the processor chain in the responsibility chain mode. Compared with the ApplicationFilterChain in Tomcat, its implementation has a clearer logic and does not need to use recursion to implement it, mainly because it splits the interception of requests and responses into two functions. The source code of HandlerExecutionChain is shown below. Similarly, I have simplified the code and kept only 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, the doDispatch() method of DispatcherServlet distributes requests. It executes the applyPreHandle() and applyPostHandle() functions in HandlerExecutionChain before and after the real business logic is executed to implement the interception function. The specific code implementation is very simple, you should be able to figure it out by yourself, so I won’t list it here. If you are interested, you can check it yourself.

key review

Well, that's all for today's content. Let's summarize and review together, what you need to focus on.

The Chain of Responsibility mode is often used in framework development to implement the filter and interceptor functions of the framework, allowing framework users to add new filtering and interception functions without modifying the source code of the framework. This also reflects the design principle of being open to extension and closed to modification mentioned earlier.

Today, through two practical examples of Servlet Filter and Spring Interceptor, we show you how the Chain of Responsibility pattern is applied in framework development. From the source code, we can also find that although we have given the classic code implementation of the chain of responsibility model in the previous lesson, in actual development, we still need to deal with specific issues, and the code implementation will vary according to different needs. changed. In fact, this applies to all design patterns.

class disscussion

  1. When we talked about the proxy mode earlier, we mentioned that Spring AOP is implemented based on the proxy mode. In actual project development, we can use AOP to implement access control functions, such as authentication, current limiting, logging, etc. Today we said that Servlet Filter and Spring Interceptor can also be used to implement access control. In project development, which of the three (AOP, Servlet Filter, Spring Interceptor) should we choose to implement access control functions like permissions? Is there any reference standard?
  2. In addition to the Servlet Filter and Spring Interceptor we mentioned, Dubbo Filter and Netty ChannelPipeline are also practical application cases of the responsibility chain mode. Can you find a framework that you are familiar with and use the responsibility chain mode, and analyze it like me What about the underlying implementation?

Welcome to leave a message and share your thoughts with me. If you gain something, you are welcome to share this article with your friends.

Guess you like

Origin blog.csdn.net/fegus/article/details/130519181