Servlet过滤器是从Servlet2.3规范开始新增的功能,并在Servlet2.4规范中得到增强。Servlet过滤器主要用于对客户端(浏览器)的请求进行过滤处理,先将过滤后的请求转交给下一资源,它在Java Web开发中具有十分重要的作用。
1、过滤器的介绍
Servlet过滤器与Servlet十分相似,但它具有拦截客户端(浏览器)请求的功能,Servlet过滤器可以改变请求中的内容,来满足实际开发中的需要。对于程序开发人员而言,过滤器实质就是在Web应用服务器上的一个Web应用组件,用于拦截客户端(浏览器)与目标资源的请求,并对这些请求进行一定过滤处理再发送给目标资源,过滤器的处理方式如下图所示。
在Web服务器中部署了过滤器以后,不仅客户端发送的请求会经过过滤器的处理,而且请求在发送到目标资源处理以后,请求的回应信息也同样要经历过滤器。如果一个Web应用中使用一个过滤器不能解决实际中的业务需要,那么可以部署多个过滤器对业务请求进行多次处理,这样做就组成了一个过滤器链。Web服务器在处理过滤器链时,将按过滤器的先后顺序对请求进行处理。
2、过滤器核心对象
过滤器对象放置在javax.servlet包中,其名称为Filter,它是一个接口。除这个接口外,与过滤器相关的对象还有FilterConfig对象与FilterChain对象,这个两个对象也同样是接口对象,位于javax.servlet包中,分别为过滤器的配置对象与过滤器的传递工具。
在实际开发中,定义过滤器对象只需要直接或间接地实现Filter接口即可。如上图所示中的MyFilter1过滤器与MyFilter2过滤器,而FilterConfig对象与FilterChain对象用于对过滤器的相关操作。
2.1 Filter接口
每一个过滤器对象都要直接或间接地实现Filter接口,在Filter接口中定义了3个方法,其方法声明及说明如下表:
Filter接口的方法声明及说明:
方法声明 | 说明 |
---|---|
public default void init(FilterConfig filterConfig) throws ServletException | 过滤器初始化方法,该方法在过滤器初始化时调用。 |
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException | 对请求进行过滤处理。 |
public default void destroy() | 销毁方法,以便释放资源。 |
2.2 FilterConfig接口
FilterConfig接口由Servlet容器进行实现,主要用于获取过滤器中的配置信息,其方法声明及说明如下表:
FilterConfig接口的方法声明及说明:
方法声明 | 说明 |
---|---|
public String getFilterName() | 用于获取过滤器的名字。 |
public ServletContext getServletContext() | 获取Servlet上下文。 |
public String getInitParameter(String name) | 获取过滤器的初始化参数值。 |
public Enumeration<String> getInitParameterNames() | 获取过滤器的所有初始化参数。 |
2.3 FilterChain对象
FilterChain对象仍然有Servlet容器进行实现,在这个接口中只有一个方法,其方法声明如下:
public void doFilter(ServletRequest request, ServletResponse response)
throws IOException, ServletException;
该方法用于将过滤后的请求传递给下一个过滤器,如果此过滤器已经是过滤器链中的最后一个过滤器,那么,请求将传送给目标资源。
3、过滤器创建与配置
示例:创建一个过滤器,实现网站访问计数器的功能,并在 web.xml 配置中将网站访问量的初始值设置为5000。
(1)创建名称为CountFilter的类,该类实现 javax.servlet.Filter 接口,同时实现Filter接口的三个方法,是一个过滤器对象,通过该过滤器实现统计网站访问人数功能。
package com.pjb.filter;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
/**
* 计数器过滤器
*
* @author pan_junbiao
*
*/
public class CountFilter implements Filter
{
// 来访数量
private int count;
// 初始化方法
@Override
public void init(FilterConfig filterConfig) throws ServletException
{
String param = filterConfig.getInitParameter("count"); // 获取初始化参数
count = Integer.valueOf(param); // 将字符串转换为int类型
}
// 过滤处理方法
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException
{
count++;
// 将ServletResponse对象转换成HttpServletRequest
HttpServletRequest req = (HttpServletRequest) request;
// 获取ServletContext
ServletContext context = req.getServletContext();
// 将来访数量保存到ServletContext对象中
context.setAttribute("count", count);
// 向下传递过滤器
chain.doFilter(request, response);
}
// 销毁方法
@Override
public void destroy()
{
// 释放资源
}
}
(2)过滤器的配置主要分为两个步骤,分别为:声明过滤器对象、创建过滤器映射。
配置已创建的CountFilter对象,此操作通过配置web.xml文件进行实现。
<!-- 过滤器声明 -->
<filter>
<!-- 过滤器的名称 -->
<filter-name>CountFilter</filter-name>
<!-- 过滤器的完整类名 -->
<filter-class>com.pjb.filter.CountFilter</filter-class>
<!-- 设置初始化参数 -->
<init-param>
<!-- 参数名 -->
<param-name>count</param-name>
<!-- 参数值 -->
<param-value>5000</param-value>
</init-param>
</filter>
<!-- 过滤器映射 -->
<filter-mapping>
<!-- 过滤器名称 -->
<filter-name>CountFilter</filter-name>
<!-- 过滤器URL映射 -->
<url-pattern>/index.jsp</url-pattern>
</filter-mapping>
<filter>标签用于声明过滤器对象,在这个标签中必须配置两个子元素,分别为过滤器的名称与过滤器完整类名,其中:
<filter-name>子标签:用于定义过滤器的名称。
<filter-class>子标签:用于指定过滤器的完整类名。
<init-param>子标签:用于指定过滤器的初始化参数,非必填项。
<filter-mapping>标签用于创建过滤器的映射,它的主要作用就是指定Web应用中,哪些URL应用哪一个过滤器进行处理。在<filter-mapping>标签中需要指定过滤器的名称与过滤器的URL映射,其中:
<filter-name>子标签:用于定义过滤器的名称,它需要与<filter>标签中的<filter-name>一一对应。
<url-pattern>子标签:用于指定过滤器应用的URL。
问题:如何匹配所有页面的请求?
答:使用 “/*” 来匹配所有页面的请求。在 web.xml 文件中配置过滤器,其过滤器的URL映射可以使用正则表达进行配置。
(3)创建程序中的首页 index.jsp ,在该页面中通过JSP内置对象 Application 获取计数器的值。
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>人数统计</title>
</head>
<body>
<h2>欢迎访问 pan_junbiao的博客</h2>
您是本站的第【
<%= application.getAttribute("count") %>
】位访客!
</body>
</html>
执行结果:
由于在 web.xml 文件中将计数器的初始值设置为5000,所以实例运行后,计数器的数值变为大于5000的数,在多次刷新页面后,计数器的数值会跟着变大。
4、Servlet3.0新特性
4.1 @WebFilter注释
在Servlet3.0中新增了@WebFilter注释,通过使用该注释就无需在web.xml文件中对过滤器进行配置。@WebFilter注释用于声明过滤器,该注释将会在部署时被容器处理,容器将根据具体的属性配置将相应的类部署为过滤器。
@WebFilter注释主要属性列表:
属性名 | 类型 | 描述 |
---|---|---|
filterName | String | 指定过滤器的name属性,等价于<filter-name>标签。 |
value | String[] | 该属性等价于urlPatterns属性,但是两者不应该同时使用。 |
urlPatterns | String[] | 指定一组过滤器的URL匹配模式,等价于<url-pattern>标签。 |
servletNames | String[] | 指定过滤器将应用于哪些Servlet,是@WebServlet中的name属性的取值,或者是web.xml文件中的<servlet-name>标签的取值。 |
initParams | WebInitParam[] | 指定一组过滤器初始化参数,等价于<init-param>标签。 |
asyncSupported | boolean | 声明过滤器是否支持异步操作模式,等价于<async-supported>标签。 |
description | String | 该过滤器的描述信息,等价于<description>标签。 |
displayName | String | 该过滤器的显示名,通常配合工具使用,等价于<display-name>标签。 |
dispatcherTypes | DispatcherType[] | 指定过滤器的转发模式。具体取值包括:ASYNC、ERROR、FORWARD、INCLUDE 和 REQUEST。 |
4.2 @WebInitParam注释
@WebInitParam注释等价于web.xml文件中的<servlet>和<filter>的<init-param>子标签,该注释通常不单独使用,而是配合@WebServlet或者@WebFilter使用。
@WebInitParam注释主要属性列表:
属性名 | 类型 | 描述 |
---|---|---|
name | String | 指定参数的名字,等价于<param-name>标签,必填项。 |
value | String | 指定参数的值,等价于<param-value>标签,必填项。 |
description | String | 关于参数的描述,等价于<description>标签,非必填项。 |
示例:创建过滤器,使用@WebFilter注释和@WebInitParam注释进行配置。
/**
* 计数器过滤器
*
* @author pan_junbiao
*
*/
@WebFilter(filterName = "CountFilter", urlPatterns = "/index.jsp",
initParams = { @WebInitParam(name = "count", value = "5000") })
public class CountFilter implements Filter
{
// 省略了过滤器中间的代码
}
如此配置之后,就不需要在 web.xml 文件中配置相应的<filter>、<filter-mapping>和<init-param>元素了,容器会在部署时根据指定的属性将该类发布为过滤器。使用@WebFilter注释,等价于在 web.xml 文件中进行如下配置:
<filter>
<filter-name>CountFilter</filter-name>
<filter-class>com.pjb.filter.CountFilter</filter-class>
<init-param>
<param-name>count</param-name>
<param-value>5000</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CountFilter</filter-name>
<url-pattern>/index.jsp</url-pattern>
</filter-mapping>
5、字符编码过滤器
在Java Web程序开发中,由于Web容器内部所使用编码格式并不支持中文字符集,所以,处理浏览器请求中的中文数据,就会出现乱码现象。如下图:
由于Web容器使用了ISO-8859-1的编码格式,所以在Web应用的业务处理中也会使用ISO-8859-1的编码格式。虽然浏览器提交的使用的是中文编码格式UTF-8,但经过业务处理中的ISO-8859-1编码,仍然会出现中文乱码现象。解决此问题的方法非常简单,在业务处理中重新指定中文字符集进行编码即可解决。在实际开发过程中,如果通过每一个业务处理指定中文字符集编码,则操作过于烦琐,而且容易遗漏某一个业务中的字符编码设置;如果通过过滤器来处理字符编码,就可以做到简单又万无一失。
示例:实现图书信息的添加功能,并创建字符编码过滤器,避免中文乱码现象的产生。
(1)创建字符编码过滤器对象,其名称为CharactorFilter类。该类实现了 javax.servlet.Filter 接口,并在doFilter()方法中对请求中的字符编码进行设置。
package com.pjb.filter;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.annotation.WebInitParam;
/**
* 字符编码过滤器
*
* @author pan_junbiao
*/
@WebFilter(filterName = "CharactorFilter", urlPatterns = "/*",
initParams = { @WebInitParam(name = "encoding", value = "UTF-8") })
public class CharactorFilter implements Filter
{
// 字符编码
String encoding = null;
// 初始化方法
@Override
public void init(FilterConfig filterConfig) throws ServletException
{
// 获取初始化参数
encoding = filterConfig.getInitParameter("encoding");
}
// 过滤处理方法
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException
{
// 判断字符编码是否为空
if (encoding != null)
{
// 设置request的编码格式
request.setCharacterEncoding(encoding);
// 设置response字符编码
response.setContentType("text/html; charset=" + encoding);
}
// 传递给下一过滤器
chain.doFilter(request, response);
}
// 销毁方法
@Override
public void destroy()
{
encoding = null;
}
}
(2)创建名称为AddServlet的类,该类继承 HttpServlet,是处理添加图书信息请求的Servlet对象。
package com.pjb.servlet;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 添加图书信息请求的Servlet类
*
* @author pan_junbiao
*
*/
@WebServlet(name = "AddServlet", urlPatterns = "/servlet/AddServlet")
public class AddServlet extends HttpServlet
{
private static final long serialVersionUID = 1L;
// 处理GET请求
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
doPost(request, response);
}
// 处理POST请求
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
// 获取 PrintWriter
PrintWriter out = response.getWriter();
// 获取图书编号
String id = request.getParameter("id");
// 获取名称
String name = request.getParameter("name");
// 获取作者
String author = request.getParameter("author");
// 获取价格
String price = request.getParameter("price");
// 输出图书信息
out.print("<h2>图书信息添加成功</h2><hr>");
out.print("图书编号:" + id + "<br>");
out.print("图书名称:" + name + "<br>");
out.print("作者:" + author + "<br>");
out.print("价格:" + price + "<br>");
// 刷新流
out.flush();
// 关闭流
out.close();
}
}
(3)创建名称为AddBook.jsp的页面,该页面主要用于放置添加图书信息的表单。
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>添加图书信息</title>
</head>
<body>
<form action="servlet/AddServlet" method="post">
<table align="center" border="1" width="350">
<tr>
<td class="2" align="center" colspan="2">
<h2>添加图书信息</h2>
</td>
</tr>
<tr>
<td align="right">图书编号:</td>
<td>
<input type="text" name="id">
</td>
</tr>
<tr>
<td align="right">图书名称:</td>
<td>
<input type="text" name="name">
</td>
</tr>
<tr>
<td align="right">作 者:</td>
<td>
<input type="text" name="author">
</td>
</tr>
<tr>
<td align="right">价 格:</td>
<td>
<input type="text" name="price">
</td>
</tr>
<tr>
<td class="2" align="center" colspan="2">
<input type="submit" value="添 加">
</td>
</tr>
</table>
</form>
</body>
</html>
执行结果:
(1)添加图书信息:
(2)显示图示信息: