La belleza de los patrones de diseño 63-Patrón de cadena de responsabilidad (parte 2): ¿Cómo se implementan los filtros e interceptores comúnmente utilizados en el marco?

63 | Modo de cadena de responsabilidad (Parte 2): ¿Cómo se implementan los filtros e interceptores comúnmente utilizados en el marco?

En la última clase, aprendimos el principio y la implementación del modelo de cadena de responsabilidad y demostramos la intención del diseño del modelo de cadena de responsabilidad a través de un ejemplo de un marco de filtrado de palabras confidenciales. En esencia, como la mayoría de los patrones de diseño, es desacoplar el código, lidiar con la complejidad del código, hacer que el código satisfaga 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 de marcos para proporcionar puntos de extensión para el marco, lo que permite a los usuarios del marco agregar nuevas funciones basadas en puntos de extensión sin modificar el código fuente del marco. De hecho, más específicamente, el patrón Cadena de responsabilidad se usa con mayor frecuencia para desarrollar filtros e interceptores de marco. Hoy hablaremos sobre su aplicación en el desarrollo de marcos a través de Servlet Filter y Spring Interceptor, dos componentes comúnmente utilizados en el desarrollo de Java.

Sin más preámbulos, ¡comencemos oficialmente el estudio de hoy!

Filtro de servlets

Servlet Filter es un componente definido en la especificación Java Servlet. Se puede traducir al chino como un filtro. Puede filtrar 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, siempre que sea un contenedor web compatible con Servlet (como Tomcat, Jetty, etc.), admite la función de filtro. Para ayudarlo a comprender, dibujé un diagrama 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 inicie 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>

Del código de muestra de ahora, encontramos que es muy conveniente agregar un filtro, sin modificar ningún código, simplemente defina una clase que implemente javax. Entonces, ¿cómo logra Servlet Filter una escalabilidad tan buena? Creo que deberías haber adivinado que usa el patrón Cadena de responsabilidad. Ahora, echemos un vistazo más de cerca a cómo se implementa en la parte inferior analizando su código fuente.

En la lección anterior, mencionamos que la implementación del patrón Chain of Responsibility incluye una interfaz de controlador (IHandler) o una clase abstracta (Handler) y una cadena de controlador (HandlerChain). En correspondencia con Servlet Filter, javax.servlet.Filter es la interfaz del procesador y FilterChain es la cadena del procesador. A continuación, centrémonos en cómo se implementa FilterChain.

Sin embargo, como hemos dicho antes, Servlet es solo una especificación y no contiene una implementación específica, por lo tanto, FilterChain en Servlet es solo una definición de interfaz. La clase de implementación específica la proporciona el contenedor web que cumple con la especificación Servlet. Por ejemplo, la clase ApplicationFilterChain es la clase de implementación de FilterChain proporcionada por Tomcat. El código fuente es el siguiente.

Para que el código sea más fácil de entender, lo simplifiqué y solo conservé los fragmentos de código relacionados con la idea de diseño. Puede verificar el código completo usted mismo en 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;
  }
}

La implementación del código de la función doFilter() en ApplicationFilterChain es bastante complicada y en realidad es una llamada recursiva. Puede usar el código doFilter() de cada filtro (como LogFilter) para reemplazar directamente la línea 12 de código en ApplicationFilterChain, y puede ver de un vistazo que es una llamada recursiva. 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 no solo puede interceptar la solicitud enviada por el cliente, sino también interceptar la respuesta enviada al cliente. Puede combinar el ejemplo de LogFilter y compararlo más tarde. Para el Spring Interceptor, ven a entender por ti mismo. Sin embargo, los dos métodos de implementación que dimos en la última clase no pueden agregar códigos de procesamiento antes y después de la ejecución de la lógica empresarial.

Interceptor de resorte

Acabo de hablar sobre Servlet Filter, ahora hablemos 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 pueden considerarse básicamente 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 Servlet y su implementación depende del contenedor web. Spring Interceptor es parte del marco Spring MVC y está implementado por el marco Spring MVC. La solicitud enviada por el cliente pasará primero por el Servlet Filter, luego por el Spring Interceptor y finalmente llegará al código comercial específico. Dibujé una imagen 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 usamos 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 la de LogFilter, pero el método de implementación es ligeramente diferente. La interceptación de solicitudes y respuestas de LogFilter se implementa en una función de doFilter(), mientras que la interceptación de solicitudes de LogInterceptor se implementa en preHandle(), y la interceptación de respuestas se implementa 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 la capa inferior de Spring Interceptor.

Por supuesto, también se implementa en base al patrón Cadena de Responsabilidad. Entre ellos, la clase HandlerExecutionChain es la cadena de procesadores en el modo de cadena de responsabilidad. En comparación con ApplicationFilterChain en Tomcat, su implementación tiene una lógica más clara y no necesita usar la recursividad para implementarla, principalmente porque divide la intercepción de solicitudes y respuestas en dos funciones. El código fuente de HandlerExecutionChain se muestra a continuación. De manera similar, simplifiqué el código y conservé solo 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 Spring, el método doDispatch() de DispatcherServlet distribuye las solicitudes. Ejecuta las funciones applyPreHandle() y applyPostHandle() en HandlerExecutionChain antes y después de ejecutar la lógica comercial real para implementar la función de intercepción. La implementación del código específico es muy simple, debería poder resolverlo usted mismo, por lo que no lo enumeraré aquí. Si te interesa, puedes comprobarlo tú mismo.

revisión clave

Bueno, eso es todo por el contenido de hoy. Resumamos y revisemos juntos, en qué necesitas concentrarte.

El modo Cadena de responsabilidad se usa a menudo en el desarrollo de marcos para implementar las funciones de filtro e interceptor del marco, lo que permite a los usuarios del marco agregar nuevas funciones de filtrado e intercepción sin modificar el código fuente del marco. Esto también refleja el principio de diseño de estar abierto a la extensión y cerrado a la modificación mencionado anteriormente.

Hoy, a través de dos ejemplos prácticos de Servlet Filter y Spring Interceptor, le mostramos cómo se aplica el patrón Chain of Responsibility en el desarrollo del marco. A partir del código fuente, también podemos encontrar que, aunque en la lección anterior hemos dado la implementación de código clásica del modelo de cadena de responsabilidad, en el desarrollo real, todavía tenemos que lidiar con problemas específicos, y la implementación del código variará de acuerdo con diferentes necesidades cambiadas. De hecho, esto se aplica a todos los patrones de diseño.

discusión en clase

  1. Cuando hablamos sobre el modo proxy anteriormente, mencionamos que Spring AOP se implementa en función del modo proxy. En el desarrollo de proyectos reales, podemos usar AOP para implementar funciones de control de acceso, como autenticación, limitación de corriente, registro, etc. Hoy dijimos que Servlet Filter y Spring Interceptor también se pueden usar para implementar el control de acceso. En el desarrollo de proyectos, ¿cuál de los tres (AOP, Servlet Filter, Spring Interceptor) deberíamos elegir para implementar funciones de control de acceso como permisos? ¿Existe algún estándar de referencia?
  2. Además del Servlet Filter y Spring Interceptor que mencionamos, Dubbo Filter y Netty ChannelPipeline también son casos prácticos de aplicación del modo de cadena de responsabilidad. ¿Puede encontrar un marco con el que esté familiarizado y usar el modo de cadena de responsabilidad, y analizarlo como yo? ¿Qué pasa con la implementación subyacente?

Bienvenido a dejar un mensaje y compartir sus pensamientos conmigo. Si obtienes algo, puedes compartir este artículo con tus amigos.

Supongo que te gusta

Origin blog.csdn.net/fegus/article/details/130519181
Recomendado
Clasificación