专注Java教育14年 全国咨询/投诉热线:444-1124-454
星辉LOGO图
始于2009,口口相传的Java黄埔军校
首页 hot资讯 Servlet过滤器的本质

Servlet过滤器的本质

更新时间:2022-01-04 09:52:45 来源:星辉 浏览566次

Java Servlet 规范 2.3 版引入了一种新的组件类型,称为过滤器。甲滤波器动态地拦截请求和响应,以变换或使用包含在请求或响应中的信息。过滤器本身通常不创建响应,而是提供可以“附加”到任何类型的 servlet 或 JSP 页面的通用功能。

出于多种原因,过滤器很重要。首先,它们提供了将重复性任务封装在可重用单元中的能力。有组织的开发人员一直在寻找模块化代码的方法。模块化代码更易于管理和记录,更易于调试,如果做得好,可以在其他设置中重用。

其次,过滤器可用于转换来自 servlet 或 JSP 页面的响应。Web 应用程序的一个常见任务是格式化发送回客户端的数据。客户端越来越需要 HTML 以外的格式(例如 WML)。为了适应这些客户,在功能齐全的 Web 应用程序中通常有一个强大的转换或过滤组件。许多 servlet 和 JSP 容器都引入了专有过滤器机制,这为部署在该容器上的开发人员带来了收益,但降低了此类代码的可重用性。随着过滤器作为 Java Servlet 规范的一部分的引入,开发人员现在有机会编写可跨容器移植的可重用转换组件。

过滤器可以执行许多不同类型的功能。我们将讨论本文中斜体项目的示例:

基于用户身份的身份验证阻止请求。

日志记录和审计 -跟踪 Web 应用程序的用户。

图像转换-缩放贴图等。

数据压缩 -使下载更小。

本地化 -针对特定语言环境的请求和响应。

XML 内容的 XSL/T 转换 - 将Web 应用程序响应定位到不止一种类型的客户端。

这些只是过滤器的一些应用。还有更多,例如加密、标记化、触发资源访问事件、mime 类型链接和缓存。

在本文中,我们将首先讨论如何编写过滤器来执行以下类型的任务:

查询请求并采取相应措施

阻止请求和响应对进一步传递。

修改请求头和数据。您可以通过提供请求的自定义版本来完成此操作。

修改响应头和数据。您可以通过提供自定义版本的响应来完成此操作。

我们将概述过滤器 API,并描述如何开发定制的请求和响应。

对过滤器进行编程只是使用过滤器的一半工作——当应用程序部署在 Web 容器中时,您还需要配置它们如何映射到 servlet。编程和配置的这种分离是过滤机制的主要好处:

您无需重新编译任何内容即可更改 Web 应用程序的输入或输出。您只需编辑文本文件或使用工具来更改配置。例如,向 PDF 下载添加压缩只是将压缩过滤器映射到下载 servlet 的问题。

您可以轻松地试验过滤器,因为它们非常易于配置。

本文的最后一部分展示了如何使用非常灵活的过滤器配置机制。阅读本文后,您将掌握实现自己的过滤器的知识,并掌握一些基于一些常见过滤器类型的实用技巧。

编程过滤器

该过滤器API由定义Filter,FilterChain和FilterConfig在所述接口javax.servlet包。您可以通过实现Filter 接口来定义过滤器 。由容器传递给过滤器的过滤器链提供了调用一系列过滤器的机制。过滤器配置包含初始化数据。

Filter接口中最重要的方法是doFilter方法,它是过滤器的核心。此方法通常执行以下一些操作:

检查请求标头

如果希望修改请求标头或数据或完全阻止请求,则自定义请求对象

如果希望修改响应头或数据,则自定义响应对象

调用过滤器链中的下一个实体。如果当前过滤器是链中以目标 servlet 结尾的最后一个过滤器,则下一个实体是链末尾的资源;否则,它是在 WAR 中配置的下一个过滤器。它通过调用doFilter链对象上的方法来调用下一个实体(传入调用它的请求和响应,或者它可能创建的包装版本)。或者,它可以选择通过不调用下一个实体来阻止请求。在后一种情况下,过滤器负责填写响应。

在调用链中的下一个过滤器后检查响应头

抛出异常以指示处理中的错误

除了doFilter,您还必须实现init和destroy方法。it容器在实例化过滤器时调用in方法。如果您希望将初始化参数传递给过滤器,您可以从FilterConfig传递给的对象中检索它们init。

示例:记录 Servlet 访问

现在您已经知道过滤器 API 的主要元素是什么,让我们来看看一个非常简单的过滤器,它不会阻止请求、转换响应或任何花哨的东西——这是开始学习 API 基本概念的好地方。

考虑跟踪用户数量的网站。要将此功能添加到现有 Web 应用程序而不更改任何 servlet,您可以使用日志过滤器。

HitCounterFilter在访问 servlet 时递增并记录计数器的值。在该doFilter方法中,HitCounterFilter首先从过滤器配置对象中检索 servlet 上下文,以便它可以访问存储为上下文属性的计数器。在过滤器检索、递增并将计数器写入日志后,它会调用doFilter传递给原始doFilter方法的过滤器链对象。省略的代码在编程定制的请求和响应中讨论 。

public final class HitCounterFilter implements Filter {
private FilterConfig filterConfig = null;
public void init(FilterConfig filterConfig) 
throws ServletException {
this.filterConfig = filterConfig;
}
public void destroy() {
this.filterConfig = null;
}
public void doFilter(ServletRequest request,
ServletResponse response, FilterChain chain) 
throws IOException, ServletException {
if (filterConfig == null)
return;
StringWriter sw = new StringWriter();
PrintWriter writer = new PrintWriter(sw);
Counter counter = (Counter)filterConfig.
getServletContext().
getAttribute("hitCounter");
writer.println();
writer.println("===============");
writer.println("The number of hits is: " +
counter.incCounter());
writer.println("===============");
// Log the resulting string
writer.flush();
filterConfig.getServletContext().
log(sw.getBuffer().toString());
...
chain.doFilter(request, wrapper);
...
}
}

示例:修改请求字符编码

目前,很多浏览器在 HTTP 请求的 Content-Type 头中不发送字符编码信息。如果客户端请求未指定编码,则容器使用默认编码来解析请求参数。如果客户端没有设置字符编码并且请求参数的编码与默认编码不同,则参数将被错误解析。您可以使用该方法setCharacterEncoding在ServletRequest接口设置编码。由于必须在解析任何发布数据或从请求中读取任何输入之前调用此方法,因此此函数是过滤器的主要应用程序。

这种过滤器包含在随Tomcat 4.0 Web 容器一起分发的示例 中。过滤器从过滤器初始化参数设置字符编码。这个过滤器可以很容易地扩展到根据传入请求的特征设置编码,例如 Accept-Language 和 User-Agent 标头的值,或者保存在当前用户会话中的值。

public void doFilter(ServletRequest request, 
ServletResponse response, FilterChain chain) throws
IOException, ServletException {
String encoding = selectEncoding(request);
if (encoding != null)
request.setCharacterEncoding(encoding);
chain.doFilter(request, response);
}
public void init(FilterConfig filterConfig) throws
ServletException {
this.filterConfig = filterConfig;
this.encoding = filterConfig.getInitParameter("encoding");
}
protected String selectEncoding(ServletRequest request) {
return (this.encoding);
}

编程自定义请求和响应

到目前为止,我们已经看过一些简单的例子。现在让我们更复杂一点,看看一个过滤器,它修改来自客户端的请求或响应返回给客户端。过滤器可以通过多种方式修改请求或响应。例如,过滤器可以向请求添加一个属性,或者它可以在响应中插入数据或以其他方式转换响应。

修改响应的过滤器通常必须在响应返回给客户端之前捕获响应。这样做的方法是将生成响应的 servlet 传递给一个替代流。替代流可防止 servlet 在完成时关闭原始响应流,并允许过滤器修改 servlet 的响应。

为了将此替代流传递给 servlet,过滤器创建了一个响应“包装器”,该响应“包装器”覆盖getWriterorgetOutputStream方法以返回此替代流。包装器被传递给doFilter过滤器链的方法。包装方法默认调用包装的请求或响应对象。这种方法遵循设计模式,可重用面向对象软件的元素中描述的著名的包装器或装饰器模式。以下部分描述了前面描述的命中计数器过滤器和其他类型的过滤器如何使用包装器。

要覆盖请求方法,请将请求包装在一个扩展 或的对象中。要覆盖响应方法,请将响应包装在一个扩展 或 的对象中 。 ServletRequestWrapperHttpServletRequestWrapperServletResponseWrapperHttpServletResponseWrapper编程过滤器中描述的命中计数器过滤 器将计数器的值插入到响应中。省略的代码HitCounterFilter是:

PrintWriter out = response.getWriter();
CharResponseWrapper wrapper = new CharResponseWrapper(
    (HttpServletResponse)response);
chain.doFilter(request, wrapper);
if(wrapper.getContentType().equals("text/html")) {
   CharArrayWriter caw = new CharArrayWriter();
   caw.write(wrapper.toString().substring(0,
       wrapper.toString().indexOf("</body>")-1));
   caw.write("<p>\nYou are visitor number 
   <font color='red'>" + counter.getCounter() + "</font>");
   caw.write("\n</body></html>");
   response.setContentLength(caw.toString().length());
   out.write(caw.toString());
} else 
   out.write(wrapper.toString());
out.close();

HitCounterFilter将响应包装在一个CharResponseWrapper. CharResponseWrapper覆盖该getWriter方法以返回一个替代流,过滤器链末尾的 servlet 将其响应写入该替代流。当chain.doFilter返回时,HitCounterFilter检索来自servlet的响应PrintWriter,并将其写入缓冲区,如果它是一个HTML的响应。过滤器将计数器的值插入缓冲区,重置响应的内容长度头,最后将缓冲区的内容写入响应流。

public class CharResponseWrapper extends
   HttpServletResponseWrapper {
   private CharArrayWriter output;
   public String toString() {
       return output.toString();
  }
   public CharResponseWrapper(HttpServletResponse response){
       super(response);
       output = new CharArrayWriter();
  }
   public PrintWriter getWriter(){
       return new PrintWriter(output);
  }
}

示例:压缩响应

修改响应的过滤器的另一个示例是随 Tomcat servlet 引擎分发的示例中包含的压缩过滤器。尽管高速 Internet 连接变得越来越普遍,但仍然需要有效地使用带宽。压缩过滤器很方便,因为您可以将它附加到任何 servlet 以减小响应的大小。

与命中计数器过滤器一样,压缩过滤器创建一个替代流,在本例中CompressionResponseStream,用于 servlet 写入并包装传递给 servlet 的响应。

仅当客户端可以接受压缩响应时,过滤器才会创建包装器和替代流。servlet 将其响应写入它从包装器检索的压缩流。一旦数据大于作为初始化参数传递给过滤器的阈值,则CompressionResponseStream覆盖将write响应数据写入 a的方法GZIPOutputStream:

public void write(int b) throws IOException {
   ...
   if ((bufferCount >= buffer.length) || 
      (count>=compressionThreshold)) {
      compressionThresholdReached = true;
   }
   if (compressionThresholdReached) {
      writeToGZip(b);
   } else {
      buffer[bufferCount++] = (byte) b;
      count++;
   }
}

示例:转换响应

我们将讨论的最后一个过滤器是 XSLT 过滤器。XSLT 是一种用于转换 XML 数据的语言。您可以使用 XSLT 将 XML 文档转换为面向最终用户的格式,例如 HTML 或 PDF,或另一种 XML 格式。一些示例应用包括:

将一家公司要求的格式的 XML 文档转换为另一家公司要求的格式。

根据用户偏好自定义网页的外观。

通过查看 User-Agent 标头并选择样式表,使同一 Web 应用程序能够响应不同类型的客户端,例如 WML 手机和 cHTML 手机。

考虑一个响应产品库存请求的 Web 服务。以下 XML 文档是此类响应的示例:

<book>
<isbn>123</isbn>
<title>Web Servers for Fun and Profit</title>
<quantity>10</quantity>
<price>$17.95</price>
</book>

以下 XSL 样式表将此 XML 文档呈现为 HTML 格式的面向用户的库存描述和 XML 格式的面向机器的版本。

<?xml version="1.0" ?> 
<xsl:stylesheet version="1.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" omit-xml-declaration="yes"/>
<xsl:template match="/">
      <xsl:apply-templates/>
</xsl:template>
         <xsl:template match="book"> <html>
<body>There are <xsl:value-of select="quantity"/> copies of 
<i><xsl:value-of select="title"/></i> available.
</body>
</html>
</xsl:template>
</xsl:stylesheet>
<?xml version="1.0" ?> 
<xsl:stylesheet version="1.0" 
     xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" omit-xml-declaration="no"/>
<xsl:template match="/">
     <xsl:apply-templates/>
</xsl:template>
<xsl:template match="book">
<xsl:element name="book">
<xsl:attribute name="isbn"><xsl:value-of select="isbn"/></
xsl:attribute>
<xsl:element name="quantity"><xsl:value-of select="quantity"/
></xsl:element>
</xsl:element>
</xsl:template>
</xsl:stylesheet>

以下 XSLT 过滤器使用样式表根据请求参数的值转换响应。过滤器根据请求参数设置响应的内容类型。然后将响应包装在 a 中CharResponseWrapper并传递给doFilter过滤器链的方法。过滤器链中的最后一个元素是一个 servlet,它返回前面描述的库存响应。当doFilter返回时,过滤器检索来自包装器和转换所述响应数据它使用样式表。

public void doFilter(ServletRequest request, 
   ServletResponse response, FilterChain chain)
   throws IOException, ServletException {
   String contentType;
   String styleSheet;
   String type = request.getParameter("type");
   if (type == null || type.equals("")) {
      contentType = "text/html";
      styleSheet = "/xml/html.xsl";
   } else {
      if (type.equals("xml")) {
         contentType = "text/plain";
         styleSheet = "/xml/xml.xsl";
      } else {
         contentType = "text/html";
         styleSheet = "/xml/html.xsl";
      }
   }
   response.setContentType(contentType);
   String stylepath=filterConfig.getServletContext().
      getRealPath(styleSheet);
   Source styleSource = new StreamSource(stylePath);
   PrintWriter out = response.getWriter();
   CharResponseWrapper responseWrapper = 
      new CharResponseWrapper(
         (HttpServletResponse)response);
   chain.doFilter(request, wrapper);
   // Get response from servlet
   StringReader sr = new StringReader(
      new String(wrapper.getData()));
   Source xmlSource = new StreamSource((Reader)sr);
   try {
      TransformerFactory transformerFactory =
         TransformerFactory.newInstance();
      Transformer transformer = transformerFactory.
         newTransformer(styleSource);
      CharArrayWriter caw = new CharArrayWriter();
      StreamResult result  = new StreamResult(caw);
      transformer.transform(xmlSource, result);
      response.setContentLength(caw.toString().length());
      out.write(caw.toString());
   } catch(Exception ex) {
      out.println(ex.toString());
      out.write(wrapper.toString());
   }
}

指定过滤器配置

现在我们已经了解了如何编写过滤器,最后一步是指定如何将其应用于一个 Web 组件或一组 Web 组件。要将过滤器映射到 servlet,您:

使用<filter>Web 应用程序部署描述符中的元素声明过滤器。此元素为过滤器创建名称并声明过滤器的实现类和初始化参数。

通过<filter-mapping>在部署描述符中定义一个元素,将过滤器映射到 servlet 。此元素通过名称或 URL 模式将过滤器名称映射到 servlet。

以下元素显示了如何指定压缩过滤器所需的元素。要定义压缩过滤器,您需要为过滤器提供一个名称、实现过滤器的类以及阈值初始化参数的名称和值。

<filter>
   <filter-name>Compression Filter</filter-name>
   <filter-class>CompressionFilter</filter-class>
   <init-param>
     <param-name>compressionThreshold</param-name>
     <param-value>10</param-value>
  </init-param>
</filter>

该filter-mapping元素将压缩过滤器映射到 servlet CompressionTest。映射还可以指定 URL 模式/CompressionTest。请注意filter,filter-mapping、servlet、 和servlet-mapping元素必须按此顺序出现在 Web 应用程序部署描述符中。

<filter-mapping>
   <filter-name>Compression Filter</filter-name>
   <servlet-name>CompressionTest</servlet-name>
</filter-mapping>
<servlet>
  <servlet-name>CompressionTest</servlet-name>
  <servlet-class>CompressionTest</servlet-class>
</servlet>
<servlet-mapping>
  <servlet-name>CompressionTest</servlet-name>
  <url-pattern>/CompressionTest</url-pattern>
</servlet-mapping>

请注意,此映射会导致过滤器被调用以处理对CompressionTestservlet和映射到 URL 模式的任何 servlet JSP 或静态内容的所有请求/CompressionTest。

如果您想记录对 Web 应用程序的每个请求,您可以将命中计数器过滤器映射到 URL 模式/*。这是与示例一起分发的部署描述符:

?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web 
Application 2.3//EN" "http://bit.ly/15eRp2z">
<web-app>
<filter>
  <filter-name>XSLTFilter</filter-name>
  <filter-class>XSLTFilter</filter-class>
</filter>
<filter>
  <filter-name>HitCounterFilter</filter-name>
  <filter-class>HitCounterFilter</filter-class>
</filter>
<filter-mapping>
  <filter-name>HitCounterFilter</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>  
<filter-mapping>
  <filter-name>XSLTFilter</filter-name>
  <servlet-name>FilteredFileServlet</servlet-name>
</filter-mapping>  
<servlet>
  <servlet-name>FilteredFileServlet</servlet-name>
  <servlet-class>FileServlet</servlet-class>
</servlet>
<servlet-mapping>
  <servlet-name>FilteredFileServlet</servlet-name>
  <url-pattern>/ffs</url-pattern>
</servlet-mapping>
</web-app>

如您所见,您可以将一个过滤器映射到一个或多个 servlet,也可以将多个过滤器映射到一个 servlet。这在图 1中进行了说明,其中过滤器 F1 映射到 servlet S1、S2 和 S3,过滤器 F2 映射到 servlet S2,过滤器 F3 映射到 servlet S1 和 S2。

提交申请后,顾问老师会电话与您沟通安排学习

免费课程推荐 >>
技术文档推荐 >>