Geek Time: la belleza de los patrones de diseño Patrón de cadena de responsabilidad (Parte 2): ¿Cómo se implementan los filtros e interceptores comúnmente usados en el marco?

Aprendimos el principio y la implementación del patrón de cadena de responsabilidad anteriormente, y demostramos la intención de diseño del patrón de cadena de responsabilidad a través de un ejemplo de un marco de filtrado de palabras sensibles. En esencia, como la mayoría de los patrones de diseño, consiste en desacoplar el código, hacer frente a la complejidad del código, hacer que el código cumpla con el principio de apertura y cierre y mejorar la escalabilidad del código .

Además, también mencionamos que el modelo de cadena de responsabilidad se usa a menudo en el desarrollo del marco para proporcionar puntos de extensión para el marco, lo que permite a los usuarios del marco agregar nuevas funciones basadas en los puntos de extensión sin modificar el código fuente del marco. De hecho, para ser más específicos, el modelo de cadena de responsabilidad se usa más comúnmente para desarrollar filtros e interceptores para marcos. Hoy, usaremos Servlet Filter y Spring Interceptor, dos componentes comúnmente usados ​​en el desarrollo de Java, para hablar específicamente sobre su aplicación en el desarrollo de frameworks.

Filtro de servlet

Servlet Filter es un componente definido en la especificación Java Servlet. Traducido al chino, es un filtro. Puede implementar funciones de filtrado para solicitudes HTTP, como autenticación, limitación de corriente, registro, parámetros de verificación, etc. Debido a que es parte de la especificación de Servlet, cualquier contenedor web que admita Servlet (por ejemplo, Tomcat, Jetty, etc.) admite la función de filtro. Para ayudarlo a comprender, dibujé un diagrama esquemático para explicar cómo funciona, como se muestra a continuación.

Inserte la descripción de la imagen aquí
En proyectos reales, ¿cómo usamos Servlet Filter? Escribí un código de muestra simple como se muestra a continuación. Para agregar un filtro, solo necesitamos definir una clase de filtro que implemente la interfaz javax.servlet.Filter y configurarla en el archivo de configuración web.xml. Cuando se inicia el contenedor web, leerá la configuración en web.xml y creará un objeto de filtro. Cuando llega una solicitud, pasará por el filtro antes de ser procesada por el 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>

A partir del código de muestra de hace un momento, descubrimos que es muy conveniente agregar un filtro. No es necesario modificar ningún código. Defina una clase que implemente javax.servlet.Filter y luego cambie la configuración. Está completamente en línea con el principio de apertura y cierre. Entonces, ¿cómo logra Servlet Filter una escalabilidad tan buena? Creo que lo habrás adivinado, utiliza el modelo de cadena de responsabilidad. Ahora, al analizar su código fuente, tenemos una vista detallada de cómo se implementa en la parte inferior.

En la lección anterior, mencionamos que la realización del modelo de cadena de responsabilidad incluye la interfaz de procesador (IHandler) o clase abstracta (Handler) y la cadena de procesador (HandlerChain). Correspondiente a Servlet Filter, javax.servlet.Filter es la interfaz del procesador y FilterChain es la cadena del procesador. A continuación, nos centraremos en cómo se implementa FilterChain.

Sin embargo, como hemos mencionado anteriormente, Servlet es solo una especificación y no contiene implementaciones específicas, por lo tanto, FilterChain en Servlet es solo una definición de interfaz. La clase de implementación específica es proporcionada por un contenedor Web que cumple con la especificación de Servlet. Por ejemplo, la clase ApplicationFilterChain es la clase de implementación de FilterChain proporcionada por Tomcat. El código fuente se muestra a continuación.

Para facilitar la lectura del código, simplifiqué el código y solo mantuve los fragmentos de código relacionados con las ideas de diseño. Puede ir a Tomcat para ver el código completo.


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;
  }
}

La implementación de código de la función doFilter () en ApplicationFilterChain es más hábil, en realidad es una llamada recursiva. Puede usar el código de doFilter () de cada filtro (como LogFilter) para reemplazar directamente la línea 12 del código ApplicationFilterChain, y puede ver que es una llamada recursiva de un vistazo. Lo reemplacé, como se muestra a continuación.


  @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);
    }
  }

Esta implementación es principalmente para admitir la interceptación bidireccional en un método doFilter (), que puede interceptar tanto la solicitud enviada por el cliente como la respuesta enviada al cliente. Puede combinar el ejemplo de LogFilter y compararlo más tarde. Llegue a comprender el Spring Interceptor. Sin embargo, las dos implementaciones que dimos en la lección anterior no pueden agregar código de procesamiento antes y después de la ejecución de la lógica empresarial.

Interceptor de resorte

Acabo de hablar de Servlet Filter, ahora estamos hablando de algo muy similar en función, Spring Interceptor, traducido al chino es un interceptor. Aunque las palabras en inglés y las traducciones al chino son diferentes, las dos básicamente se pueden considerar como un concepto, y ambas se utilizan para interceptar solicitudes HTTP.

La diferencia entre ellos es que Servlet Filter es parte de la especificación de Servlet y la implementación depende del contenedor web. Spring Interceptor es parte del marco Spring MVC, y el marco Spring MVC proporciona la implementación. La solicitud enviada por el cliente pasará primero por el filtro de servlet, luego por el Spring Interceptor y finalmente llegará al código de negocio específico. Hice un dibujo para ilustrar el flujo de procesamiento de una solicitud, como se muestra a continuación.

Inserte la descripción de la imagen aquí
En el proyecto, ¿cómo deberíamos usar Spring Interceptor? Escribí un código de muestra simple como se muestra a continuación. La función implementada por LogInterceptor es exactamente la misma que LogFilter en este momento, pero la implementación es ligeramente diferente. LogFilter intercepta solicitudes y respuestas en una función doFilter (), mientras que LogInterceptor intercepta solicitudes en preHandle () e intercepta respuestas en 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>

De manera similar, analicemos cómo se implementa el Spring Interceptor subyacente.

Por supuesto, también se implementa en base al modelo de cadena de responsabilidad. Entre ellos, la clase HandlerExecutionChain es la cadena de procesadores en el modelo de cadena de responsabilidad. En comparación con ApplicationFilterChain en Tomcat, su implementación tiene una lógica más clara y no necesita implementarse con recursividad, principalmente porque divide el trabajo de interceptación de solicitudes y respuestas en dos funciones. El código fuente de HandlerExecutionChain se muestra a continuación. De manera similar, he simplificado el código y solo conservé el código clave.


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);
    }
   }
  }
 }
}

En el marco de Spring, el método doDispatch () de DispatcherServlet se utiliza para distribuir solicitudes. Ejecuta las funciones applyPreHandle () y applyPostHandle () en HandlerExecutionChain antes y después de la ejecución de la lógica empresarial real para implementar la función de intercepción. La implementación del código específico es muy simple, debería poder inventarlo usted mismo, por lo que no lo enumeraré aquí. Si está interesado, puede comprobarlo usted mismo.

Supongo que te gusta

Origin blog.csdn.net/zhujiangtaotaise/article/details/110486470
Recomendado
Clasificación