netty(3)-译j2ee interceptingfilter

  • 2019 年 10 月 11 日
  • 筆記

上下文 表示层请求处理机制接收许多不同类型的请求,这需要各种类型的处理。有些请求只是简单地转发到适当的处理程序组件,而其他请求必须先进行修改,审核或解压缩,然后再进一步处理。

问题 客户端Web请求和响应的预处理和后处理是必需的。

当请求进入Web应用程序时,它通常必须在主处理阶段之前通过几个入口测试。例如,

客户端已通过身份验证吗?

客户端是否有有效的会话? 客户端的IP地址是否来自受信任的网络? 请求路径是否违反任何约束? 客户端用于发送数据的编码是什么? 我们是否支持客户端的浏览器类型? 其中一些检查是测试,结果为是或否,以决定是否继续处理。其他检查将进入的数据流操纵为适合处理的形式。

经典解决方案包括一系列条件检查,任何失败的检查都会中止请求。嵌套的if / else语句是一种标准策略,但是此解决方案会导致代码易碎,并且会产生编程的复制粘贴风格,因为过滤的流程和过滤器的操作已编译到应用程序中。

以灵活,简便的方式解决此问题的关键是要有一种简单的机制来添加和删除处理组件,其中每个组件都可以完成特定的过滤动作。

强制 常见处理(例如检查数据编码方案或记录有关每个请求的信息)将针对每个请求完成。

需要集中通用逻辑。 服务应该易于添加或删除,而且不会影响现有组件,因此可以将它们以多种组合使用,例如 记录和认证 调试和转换特定客户端的输出 输入的解压缩转换编码方案 解 创建可插拔过滤器以标准方式处理通用服务,而无需更改核心请求处理代码。筛选器拦截传入的请求和传出的响应,从而允许进行预处理和后处理。我们能够毫不干扰地添加和删除这些过滤器,而无需更改现有代码。

实际上,我们能够使用各种常用服务(例如安全性,日志记录,调试等)来修饰我们的主处理程序。这些过滤器是独立于主要应用程序代码的组件,可以声明性地添加或删除它们。例如,可以修改部署配置文件以设置过滤器链。同一配置文件可能包含特定URL到此过滤器链的映射。当客户端请求与该配置的URL映射匹配的资源时,将在调用请求的目标资源之前按顺序处理链中的筛选器。

结构体 图7.1表示了截取滤波器模式。

Figure 7.1 Intercepting Filter pattern class diagram

参加者和责任

Figure 7.2 represents the Intercepting Filter pattern.

Figure 7.2 Intercepting Filter sequence diagram

FilterManager FilterManager管理过滤器处理。它以正确的顺序创建具有适当过滤器的FilterChain,并启动处理。

FilterChain FilterChain是独立过滤器的有序集合。

过滤器一,过滤器二,过滤器三 这些是映射到目标的单个过滤器。 FilterChain协调其处理。

目标 目标是客户端请求的资源。

策略 自定义过滤策略 过滤器是通过开发人员定义的自定义策略实现的。与首选的标准过滤器策略相比,它的灵活性和功能不足,后者将在下一部分中介绍,并且仅在支持2.3 Servlet规范的容器中可用。自定义筛选器策略的功能较弱,因为它无法以标准且可移植的方式提供请求和响应对象的包装。此外,无法修改请求对象,如果要使用过滤器控制输出流,则必须引入某种缓冲机制。为了实现自定义过滤器策略,开发人员可以使用装饰器模式[GoF]将过滤器包装在核心请求处理逻辑周围。例如,可能有一个调试过滤器,它包装了身份验证过滤器。例7.1和例7.2显示了如何以编程方式创建此机制:

Example 7.1 Implementing a Filter - Debugging Filter    public class DebuggingFilter implements Processor {    private Processor target;      public DebuggingFilter(Processor myTarget) {      target = myTarget;    }      public void execute(ServletRequest req,    ServletResponse res) throws IOException,      ServletException    {      //Do some filter processing here, such as      // displaying request parameters      target.execute(req, res);    }  }      Example 7.2 Implementing a Filter - Core Processor    public class CoreProcessor implements Processor {    private Processor target;    public CoreProcessor()   {      this(null);    }      public CoreProcessor(Processor myTarget)   {      target = myTarget;    }      public void execute(ServletRequest req,        ServletResponse res) throws IOException,        ServletException   {      //Do core processing here    }  }

在Servlet控制器中,我们委托一个名为processRequest的方法来处理传入的请求,如示例7.3所示。

Example 7.3 Handling Requests    public void processRequest(ServletRequest req,    ServletResponse res)    throws IOException, ServletException {    Processor processors = new DebuggingFilter(      new AuthenticationFilter(new CoreProcessor()));    processors.execute(req, res);      //Then dispatch to next resource, which is probably    // the View to display    dispatcher.dispatch(req, res);  }

仅出于示例目的,假设每个处理组件在执行时都写入标准输出。 例7.4显示了可能的执行输出。

例7.4写入标准输出的消息

调试过滤器预处理已完成… 验证过滤器处理已完成… 核心处理完成… 调试过滤器后处理已完成… 处理器链按顺序执行。 除链中的最后一个处理器外,每个处理器均被视为过滤器。 最终处理器组件是我们封装要为每个请求完成的核心处理的地方。 给定这种设计,当我们想要修改处理请求的方式时,我们将需要更改CoreProcessor类以及任何过滤器类中的代码。

图7.3是描述使用示例7.1,示例7.2和示例7.3的过滤器代码时控制流程的顺序图。

图7.3自定义过滤器策略的序列图,装饰器实现

注意,当我们使用装饰器实现时,尽管使用通用接口,每个过滤器都会直接在下一个过滤器上调用。 或者,可以使用FilterManager和FilterChain实现此策略。 在这种情况下,这两个组件协调并管理过滤器处理,并且各个过滤器不直接相互通信。 尽管它仍然是一个自定义策略,但该设计近似于Servlet 2.3兼容的实现。 例7.5列出了创建FilterChain的FilterManager类,如例7.6所示。 FilterChain以适当的顺序将过滤器添加到链中(为简洁起见,这是在FilterChain构造函数中完成的,但通常会代替注释来完成),处理过滤器,最后处理目标资源。 图7.4是此代码的序列图。

Figure 7.4 Sequence diagram for Custom Filter Strategy, nondecorator implementation

Example 7.5 FilterManager - Custom Filter Strategy    public class FilterManager {    public void processFilter(Filter target,      javax.servlet.http.HttpServletRequest request,      javax.servlet.http.HttpServletResponse response)      throws javax.servlet.ServletException,        java.io.IOException       {      FilterChain filterChain = new FilterChain();        // The filter manager builds the filter chain here      // if necessary        // Pipe request through Filter Chain      filterChain.processFilter(request, response);        //process target resource      target.execute(request, response);    }  }      Example 7.6 FilterChain - Custom Filter Strategy    public class FilterChain {    // filter chain    private Vector myFilters = new Vector();      // Creates new FilterChain    public FilterChain()  {      // plug-in default filter services for example      // only. This would typically be done in the      // FilterManager, but is done here for example      // purposes      addFilter(new DebugFilter());      addFilter(new LoginFilter());      addFilter(new AuditFilter());    }      public void processFilter(      javax.servlet.http.HttpServletRequest request,      javax.servlet.http.HttpServletResponse response)    throws javax.servlet.ServletException,      java.io.IOException         {      Filter filter;        // apply filters      Iterator filters = myFilters.iterator();      while (filters.hasNext())      {        filter = (Filter)filters.next();        // pass request & response through various        // filters        filter.execute(request, response);      }    }      public void addFilter(Filter filter)  {      myFilters.add(filter);    }  }

此策略不允许我们创建所需的灵活或强大的过滤器。首先,以编程方式添加和删除过滤器。尽管我们可以编写一种专有的机制来通过配置文件处理添加和删除过滤器,但是我们仍然无法包装请求和响应对象。此外,如果没有复杂的缓冲机制,此策略将无法提供灵活的后处理。

标准过滤器策略利用2.3 Servlet规范的功能为这些问题提供了解决方案,该规范为过滤器困境提供了标准解决方案。

注意

撰写本文时,Servlet 2.3规范处于最终草案形式。

标准过滤器策略 过滤器使用部署描述符以声明方式进行控制,如Servlet规范版本2.3中所述,截至撰写本文时,该过滤器处于最终草案形式。 Servlet 2.3规范包括一个标准机制,用于构建过滤器链并从这些链中毫不干扰地添加和删除过滤器。筛选器围绕接口构建,并通过修改Web应用程序的部署描述符以声明方式添加或删除。

该策略的示例将是创建一个过滤器,该过滤器可预处理任何编码类型的请求,以便可以在我们的核心请求处理代码中以类似方式处理每个请求。为什么这有必要?包含文件上载的HTML表单使用的编码类型不同于大多数表单。因此,通过简单的getParameter()调用无法获得伴随上传的表单数据。因此,我们创建了两个过滤器来预处理请求,将所有编码类型转换为统一的格式。我们选择的格式是使所有表单数据都可以用作请求属性。

一个过滤器处理application / x-www-form-urlencoded类型的标准表单编码,另一个过滤器处理不太常见的编码类型multipart / form-data,该编码类型用于包含文件上传的表单。筛选器将所有表单数据转换为请求属性,因此核心请求处理机制可以以相同的方式处理每个请求,而不是使用特殊的大小写来表示不同的编码。

例7.8展示了一个过滤器,该过滤器使用通用的应用程序表单编码方案来转换请求。例7.9显示了过滤器,该过滤器处理使用多部分表单编码方案的请求的翻译。这些过滤器的代码基于Servlet规范2.3的最终草案。还使用了基本过滤器,这两个过滤器都从中继承(请参见“基本过滤器策略”一节)。如例7.7所示,基本过滤器为标准过滤器回调方法提供了默认行为。

Example 7.7 Base Filter - Standard Filter Strategy    public class BaseEncodeFilter implements        javax.servlet.Filter {    private javax.servlet.FilterConfig myFilterConfig;      public BaseEncodeFilter()     {  }      public void doFilter(      javax.servlet.ServletRequest servletRequest,      javax.servlet.ServletResponse servletResponse,      javax.servlet.FilterChain filterChain)    throws java.io.IOException,      javax.servlet.ServletException {      filterChain.doFilter(servletRequest,          servletResponse);    }      public javax.servlet.FilterConfig getFilterConfig()    {      return myFilterConfig;    }      public void setFilterConfig(      javax.servlet.FilterConfig filterConfig) {        myFilterConfig = filterConfig;    }  }      Example 7.8 StandardEncodeFilter - Standard Filter Strategy    public class StandardEncodeFilter    extends BaseEncodeFilter {    // Creates new StandardEncodeFilter    public StandardEncodeFilter()   {  }      public void doFilter(javax.servlet.ServletRequest      servletRequest,javax.servlet.ServletResponse      servletResponse,javax.servlet.FilterChain      filterChain)    throws java.io.IOException,      javax.servlet.ServletException {        String contentType =        servletRequest.getContentType();      if ((contentType == null) ||        contentType.equalsIgnoreCase(          "application/x-www-form-urlencoded"))     {        translateParamsToAttributes(servletRequest,          servletResponse);      }        filterChain.doFilter(servletRequest,        servletResponse);    }      private void translateParamsToAttributes(      ServletRequest request, ServletResponse response)    {      Enumeration paramNames =          request.getParameterNames();        while (paramNames.hasMoreElements())     {        String paramName = (String)            paramNames.nextElement();          String [] values;          values = request.getParameterValues(paramName);        System.err.println("paramName = " + paramName);        if (values.length == 1)          request.setAttribute(paramName, values[0]);        else          request.setAttribute(paramName, values);      }   }  }      Example 7.9 MultipartEncodeFilter - Standard Filter Strategy    public class MultipartEncodeFilter extends    BaseEncodeFilter {    public MultipartEncodeFilter() { }    public void doFilter(javax.servlet.ServletRequest      servletRequest, javax.servlet.ServletResponse      servletResponse,javax.servlet.FilterChain      filterChain)    throws java.io.IOException,      javax.servlet.ServletException {      String contentType =        servletRequest.getContentType();      // Only filter this request if it is multipart      // encoding      if (contentType.startsWith(                  "multipart/form-data")){        try {          String uploadFolder =            getFilterConfig().getInitParameter(                "UploadFolder");          if (uploadFolder == null) uploadFolder = ".";            /** The MultipartRequest class is:          * Copyright (C) 2001 by Jason Hunter          * <[email protected]>. All rights reserved.          **/          MultipartRequest multi = new            MultipartRequest(servletRequest,                             uploadFolder,                             1 * 1024 * 1024 );          Enumeration params =                   multi.getParameterNames();          while (params.hasMoreElements()) {            String name = (String)params.nextElement();            String value = multi.getParameter(name);            servletRequest.setAttribute(name, value);          }            Enumeration files = multi.getFileNames();          while (files.hasMoreElements()) {            String name = (String)files.nextElement();            String filename = multi.getFilesystemName(name);            String type = multi.getContentType(name);            File f = multi.getFile(name);            // At this point, do something with the            // file, as necessary          }        }        catch (IOException e)        {          LogManager.logMessage(            "error reading or saving file"+ e);        }      } // end if      filterChain.doFilter(servletRequest,                           servletResponse);    } // end method doFilter()  }

示例7.10中的以下摘录来自包含此示例的Web应用程序的部署描述符。 它显示了如何注册这两个过滤器,然后将它们映射到资源(在本例中为简单的测试servlet)。 另外,该示例的时序图如图7.5所示。

Example 7.10 Deployment Descriptor - Standard Filter Strategy    .  .  .  <filter>      <filter-name>StandardEncodeFilter</filter-name>      <display-name>StandardEncodeFilter</display-name>      <description></description>      <filter-class> corepatterns.filters.encodefilter.              StandardEncodeFilter</filter-class>    </filter>    <filter>      <filter-name>MultipartEncodeFilter</filter-name>      <display-name>MultipartEncodeFilter</display-name>      <description></description>      <filter-class>corepatterns.filters.encodefilter.              MultipartEncodeFilter</filter-class>      <init-param>        <param-name>UploadFolder</param-name>        <param-value>/home/files</param-value>      </init-param>   </filter>  .  .  .  <filter-mapping>      <filter-name>StandardEncodeFilter</filter-name>      <url-pattern>/EncodeTestServlet</url-pattern>    </filter-mapping>    <filter-mapping>      <filter-name>MultipartEncodeFilter</filter-name>      <url-pattern>/EncodeTestServlet</url-pattern>    </filter-mapping>  .  .  .

图7.5拦截过滤器,标准过滤器策略的序列图-编码转换示例

当客户端向控制器Servlet发出请求时,StandardEncodeFilter和MultiPartEncodeFilter会拦截控件。容器完成了过滤器管理器的角色,并通过调用它们的doFilter方法对这些过滤器进行矢量控制。完成处理后,每个过滤器将控制权传递到其包含的FilterChain,它指示执行下一个过滤器。一旦两个过滤器都已接收并随后放弃了控制,则接收控制的下一个组件是实际的目标资源,在这种情况下为控制器servlet。

Servlet规范的版本2.3中支持的过滤器还支持包装请求和响应对象。与使用“自定义过滤器策略”建议的自定义实现所构建的机制相比,此功能提供了更强大的机制。当然,结合两种策略的混合方法也可以定制构建,但是仍然缺少Servlet规范支持的标准过滤器策略的功能。

基本过滤策略 基本过滤器是所有过滤器的通用超类。通用功能可以封装在基本过滤器中,并在所有过滤器之间共享。例如,基本过滤器是在“声明的过滤器策略”中包括容器回调方法的默认行为的好地方。例7.11显示了如何做到这一点。

Example 7.11 Base Filter Strategy    public class BaseEncodeFilter implements    javax.servlet.Filter {    private javax.servlet.FilterConfig myFilterConfig;      public BaseEncodeFilter()             {       }      public void doFilter(javax.servlet.ServletRequest      servletRequest,javax.servlet.ServletResponse      servletResponse, javax.servlet.FilterChain      filterChain) throws java.io.IOException,      javax.servlet.ServletException {        filterChain.doFilter(servletRequest,        servletResponse);    }      public javax.servlet.FilterConfig getFilterConfig() {      return myFilterConfig;    }      public void    setFilterConfig(javax.servlet.FilterConfig      filterConfig) {      myFilterConfig = filterConfig;    }  }

模板过滤策略 使用其他所有继承的基本过滤器(请参阅本章中的“基本过滤器策略”)允许基类提供模板方法[Gof]功能。 在这种情况下,基本过滤器用于指示每个过滤器必须完成的常规步骤,而将如何完成该步骤的细节留给每个过滤器子类。 通常,这些方法是粗略定义的基本方法,这些方法只是在每个模板上强加了有限的结构。 该策略也可以与任何其他过滤器策略组合。 例7.12和例7.13中的清单显示了如何将此方法与“声明的过滤器策略”一起使用。

示例7.12显示了一个名为TemplateFilter的基本过滤器,如下所示。

Example 7.12 Using a Template Filter Strategy    public abstract class TemplateFilter implements    javax.servlet.Filter {    private FilterConfig filterConfig;      public void setFilterConfig(FilterConfig fc) {      filterConfig=fc;    }      public FilterConfig getFilterConfig()         {      return filterConfig;    }      public void doFilter(ServletRequest request,      ServletResponse response, FilterChain chain)      throws IOException, ServletException {      // Common processing for all filters can go here      doPreProcessing(request, response, chain);        // Common processing for all filters can go here      doMainProcessing(request, response, chain);        // Common processing for all filters can go here      doPostProcessing(request, response, chain);        // Common processing for all filters can go here        // Pass control to the next filter in the chain or      // to the target resource      chain.doFilter(request, response);    }    public void doPreProcessing(ServletRequest request,      ServletResponse response, FilterChain chain) {    }      public void doPostProcessing(ServletRequest request,      ServletResponse response, FilterChain chain) {    }      public abstract void doMainProcessing(ServletRequest     request, ServletResponse response, FilterChain     chain);  }

给定TemplateFilter的此类定义,每个过滤器均作为子类实现,该子类仅必须实现doMainProcessing方法。 但是,如果需要,这些子类可以选择实现所有三种方法。 Example 7.13是一个过滤器子类的示例,该子类实现一个强制方法(由模板过滤器决定)和可选的预处理方法。 另外,使用该策略的时序图如图7.6所示。

Example 7.13 Debugging Filter    public class DebuggingFilter extends TemplateFilter {    public void doPreProcessing(ServletRequest req,      ServletResponse res, FilterChain chain) {      //do some preprocessing here    }      public void doMainProcessing(ServletRequest req,      ServletResponse res, FilterChain chain) {      //do the main processing;    }  }

Figure 7.6 Intercepting Filter, Template Filter Strategy sequence diagram

在图7.6的序列图中,过滤器子类(例如DebuggingFilter)通过覆盖抽象的doMainProcessing方法以及可选的doPreProcessing和doPostProcessing来定义特定的处理。因此,模板过滤器为每个过滤器的处理施加了结构,并提供了一个封装每个过滤器通用的代码的位置。

后果 通过松散耦合的处理程序集中控制 过滤器和控制器一样,为处理多个请求的处理提供了一个中心位置。筛选器更适合按摩请求和响应,以最终由目标资源(例如控制器)进行处理。另外,控制器通常将许多不相关的公共服务(例如身份验证,日志记录,加密等)的管理联系在一起,而过滤允许松散耦合的处理程序,这些处理程序可以组合成各种组合。

提高可重用性 筛选器可促进更清洁的应用程序分区并鼓励重用。这些可插入拦截器是透明地添加到现有代码中或从现有代码中删除的,并且由于它们的标准接口,它们可以以任何组合使用,并且可用于各种表示形式。 声明式和灵活的配置 无需单一重新编译核心代码库,即可将许多服务组合成各种排列。 信息共享效率低下 在过滤器之间共享信息可能效率很低,因为根据定义,每个过滤器都是松散耦合的。如果必须在过滤器之间共享大量信息,则此方法可能证明是昂贵的。 相关模式 前控制器 该控制器解决了一些类似的问题,但更适合处理核心处理。

装饰器[GoF] 拦截过滤器模式与装饰器模式有关,该装饰器模式提供可动态插入的包装器。 模板方法[GoF] 模板方法模式用于实现模板过滤器策略。 拦截器[POSA2] 拦截过滤器模式与拦截器模式有关,后者允许透明地添加服务并自动触发。 管道和过滤器[POSA1] 拦截过滤器模式与“管道和过滤器”模式有关。

原文连接:https://www.oracle.com/technetwork/java/interceptingfilter-142169.html