Filter 也称之为过滤器,它是 Servlet 技术中最激动人心的技术之一,WEB 开发人员通过 Filter 技术,对 web 服务器管理的所有 web 资源:例如 JSP、Servlet,、静态图片文件或静态 HTML 文件等进行拦截,从而实现一些特殊的功能。例如实现 URL 级别的权限访问控制、过滤敏感词汇、压缩响应信息等一些高级功能。
Servlet API 中提供了一个 Filter 接口,开发 web 应用时,如果编写的 Java 类实现了这个接口,则把这个 Java 类称之为过滤器 Filter. 通过 Filter 技术,开发人员可以实现用户在访问某个目标资源之前,对访问的请求和响应进行拦截,Filter 接口源代码:
public abstract interface Filter{
public abstract void init(FilterConfig paramFilterConfig) throws ServletException;
public abstract void doFilter(ServletRequest paramServletRequest, ServletResponse paramServletResponse, FilterChain paramFilterChain) throws IOException, ServletException;
public abstract void destroy();
}
Filter 接口中有一个doFilter 方法,当咱们编写好 Filter,并配置对哪个 web 资源进行拦截后,web 服务器每次在调用 web 资源的 service 方法之前,都会先调用一下 Filter 的 doFilter 方法,因此,在该方法内编写代码可达到如下的:
web 服务器在调用 doFilter 方法时,会传递一个 filterChain 对象进来,filterChain 对象是 filter 接口中最重要的一个对象,它也提供了一个 doFilter 方法,开发人员可以根据需求决定是否调用此方法。如果调用该方法,则 web 服务器就会调用 web 资源的 service 方法,即 web 资源就会被访问;否则的话, web 资源就不会被访问。
Filter 开发分为两步:
filter
和filter-mapping
元素对编写的 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;
/**
* 过滤器 Filter 的工作原理
*/
public class FilterTest implements Filter{
public void destroy() {
System.out.println("----Filter销毁----");
}
public void doFilter(ServletRequest request, ServletResponse response,FilterChain filterChain) throws IOException, ServletException {
// 对 request、response 进行一些预处理
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
System.out.println("----调用service之前执行一段代码----");
// 执行目标资源,放行
filterChain.doFilter(request, response);
System.out.println("----调用service之后执行一段代码----");
}
public void init(FilterConfig arg0) throws ServletException {
System.out.println("----Filter初始化----");
}
}
第二步:在 web. xml 中配置过滤器
<web-app version="3.0"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
<display-name>display-name>
<welcome-file-list>
<welcome-file>index.jspwelcome-file>
welcome-file-list>
<filter>
<filter-name>FilterTestfilter-name>
<filter-class>com.yangcq.filter.FilterTestfilter-class>
filter>
<filter-mapping>
<filter-name>FilterTestfilter-name>
<url-pattern>/*url-pattern>
filter-mapping>
web-app>
在一个 web 应用中,可以开发编写多个 Filter,这些 Filter 组合起来称之为一个 Filter 链。web 服务器根据 Filter 在 web.xml 文件中的注册顺序,决定先调用哪个 Filter,当第一个 Filter 的 doFilter 方法被调用时,web 服务器会创建一个代表 Filter 链的 filterChain 对象传递给该方法。在 doFilter 方法中,开发人员如果调用了 filterChain 对象的 doFilter 方法,则 web 服务器会检查 FilterChain 对象中是否还有 Filter,如果有,则调用第 2 个 Filter;如果没有,则调用目标资源。
如果项目中使用了 Spring 框架,那么,很多过滤器都不用咱们自己来写了,Spring 为咱们写好了一些常用的过滤器。下面咱们就以字符编码的过滤器CharacterEncodingFilter
为例,来看看在 Spring 框架下,如何配置过滤器。
<filter>
<filter-name>encodingFilterfilter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilterfilter-class>
param>
<param-name>encodingparam-name>
<param-value>UTF-8param-value>
param>
param>
<param-name>forceEncodingparam-name>
<param-value>trueparam-value>
param>
filter>
<filter-mapping>
<filter-name>encodingFilterfilter-name>
/*
很简单吧?这样几行配置代码,就完成了从全局控制字符编码的功能。接下来,咱们看看CharacterEncodingFilter
这个过滤器的代码,感受一下大师的风采,如果咱们要写过滤器的话,就可以以此为范例。
package org.springframework.web.filter;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.util.ClassUtils;
public class CharacterEncodingFilter extends OncePerRequestFilter{
private static final boolean responseSetCharacterEncodingAvailable = ClassUtils.hasMethod(
class$javax$servlet$http$HttpServletResponse, "setCharacterEncoding",
new Class[] { String.class });
// 需要设置的编码方式,为了支持可配置,Spring 把编码方式设置成了一个变量
private String encoding;
// 是否强制使用统一编码,也是为了支持可配置
private boolean forceEncoding;
// 构造器,在这里,Spring 把 forceEncoding 的值默认设置为 false
public CharacterEncodingFilter(){
this.forceEncoding = false;
}
// encoding/forceEncoding 的 setter 方法
public void setEncoding(String encoding){
this.encoding = encoding;
}
public void setForceEncoding(boolean forceEncoding){
this.forceEncoding = forceEncoding;
}
// Spring 通过 GenericFilterBean 抽象类,对 Filter 接口进行了整合,
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException{
if ((this.encoding != null) && (((this.forceEncoding) || (request.getCharacterEncoding() == null)))) {
request.setCharacterEncoding(this.encoding);
if ((this.forceEncoding) && (responseSetCharacterEncodingAvailable)) {
response.setCharacterEncoding(this.encoding);
}
}
filterChain.doFilter(request, response);
}
}
还没有过瘾?那咱们就再看一个项目中使用过的一个过滤器:InvilidCharacterFilter
(防止脚本攻击的过滤器)
import java.io.IOException;
import java.util.Enumeration;
import javax.servlet.FilterChain;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang.StringUtils;
import org.springframework.web.filter.CharacterEncodingFilter;
/*
* InvalidCharacterFilter:过滤 request 请求中的非法字符,防止脚本攻击
* InvalidCharacterFilter 继承了 Spring 框架的 CharacterEncodingFilter 过滤器,
* 当然,咱们也可以自己实现这样一个过滤器
*/
public class InvalidCharacterFilter extends CharacterEncodingFilter{
// 需要过滤的非法字符
private static String[] invalidCharacter = new String[]{
"script","select","insert","document","window","function",
"delete","update","prompt","alert","create","alter",
"drop","iframe","link","where","replace","function","onabort",
"onactivate","onafterprint","onafterupdate","onbeforeactivate",
"onbeforecopy","onbeforecut","onbeforedeactivateonfocus",
"onkeydown","onkeypress","onkeyup","onload",
"expression","applet","layer","ilayeditfocus","onbeforepaste",
"onbeforeprint","onbeforeunload","onbeforeupdate",
"onblur","onbounce","oncellchange","oncontextmenu",
"oncontrolselect","oncopy","oncut","ondataavailable",
"ondatasetchanged","ondatasetcomplete","ondeactivate",
"ondrag","ondrop","onerror","onfilterchange","onfinish","onhelp",
"onlayoutcomplete","onlosecapture","onmouse","ote",
"onpropertychange","onreadystatechange","onreset","onresize",
"onresizeend","onresizestart","onrow","onscroll",
"onselect","onstaronsubmit","onunload","IMgsrc","infarction"
};
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException{
String parameterName = null;
String parameterValue = null;
// 获取请求的参数
@SuppressWarnings("unchecked")
Enumeration allParameter = request.getParameterNames();
while(allParameter.hasMoreElements()){
parameterName = allParameter.nextElement();
parameterValue = request.getParameter(parameterName);
if(null != parameterValue){
for(String str : invalidCharacter){
if (StringUtils.containsIgnoreCase(parameterValue, str)){
request.setAttribute("errorMessage", "非法字符:" + str);
RequestDispatcher requestDispatcher = request.getRequestDispatcher("/error.jsp");
requestDispatcher.forward(request, response);
return;
}
}
}
}
super.doFilterInternal(request, response, filterChain);
}
}
接下来,需要在 web.xml 中进行配置:
<filter>
<filter-name>InvalidCharacterFilterfilter-name>
<filter-class>com.yangcq.filter.InvalidCharacterFilterfilter-class>
filter>
<filter-mapping>
<filter-name>InvalidCharacterFilterfilter-name>
/*
filter-mapping>
此外,如果咱们不使用 Spring 的 CharacterEncodingFilter 类的话,可以自己来写。
import java.io.IOException;
import java.util.Enumeration;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.apache.commons.lang.StringUtils;
/**
* SelfDefineInvalidCharacterFilter:过滤 request 请求中的非法字符,防止脚本攻击
*/
public class SelfDefineInvalidCharacterFilter implements Filter{
public void destroy() {
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
String parameterName = null;
String parameterValue = null;
// 获取请求的参数
@SuppressWarnings("unchecked")
Enumeration allParameter = request.getParameterNames();
while(allParameter.hasMoreElements()){
parameterName = allParameter.nextElement();
parameterValue = request.getParameter(parameterName);
if(null != parameterValue){
for(String str : invalidCharacter){
if (StringUtils.containsIgnoreCase(parameterValue, str)){
request.setAttribute("errorMessage", "非法字符:" + str);
RequestDispatcher requestDispatcher = request.getRequestDispatcher("/error.jsp");
requestDispatcher.forward(request, response);
return;
}
}
}
}
// 执行目标资源,放行
filterChain.doFilter(request, response);
}
public void init(FilterConfig filterConfig) throws ServletException {
}
// 需要过滤的非法字符
private static String[] invalidCharacter = new String[]{
"script","select","insert","document","window","function",
"delete","update","prompt","alert","create","alter",
"drop","iframe","link","where","replace","function","onabort",
"onactivate","onafterprint","onafterupdate","onbeforeactivate",
"onbeforecopy","onbeforecut","onbeforedeactivateonfocus",
"onkeydown","onkeypress","onkeyup","onload",
"expression","applet","layer","ilayeditfocus","onbeforepaste",
"onbeforeprint","onbeforeunload","onbeforeupdate",
"onblur","onbounce","oncellchange","oncontextmenu",
"oncontrolselect","oncopy","oncut","ondataavailable",
"ondatasetchanged","ondatasetcomplete","ondeactivate",
"ondrag","ondrop","onerror","onfilterchange","onfinish","onhelp",
"onlayoutcomplete","onlosecapture","onmouse","ote",
"onpropertychange","onreadystatechange","onreset","onresize",
"onresizeend","onresizestart","onrow","onscroll",
"onselect","onstaronsubmit","onunload","IMgsrc","infarction"
};
}
仍然需要在 web.xml 中进行配置:
<filter>
<filter-name>SelfDefineInvalidCharacterFilterfilter-name>
<filter-class>com.yangcq.filter.SelfDefineInvalidCharacterFilterfilter-class>
filter>
<filter-mapping>
<filter-name>SelfDefineInvalidCharacterFilterfilter-name>
/*
filter-mapping>
Filter 的创建和销毁由 web 服务器负责。 web 应用程序启动时,web 服务器将创建 Filter 的实例对象,并调用其 init 方法,完成对象的初始化功能,从而为后续的用户请求作好拦截的准备工作,Filter 对象只会创建一次,init 方法也只会执行一次。通过 init 方法的参数,可获得代表当前 Filter 配置信息 FilterConfig 对象。
web 容器调用 destroy 方法销毁 Filter,destroy 方法在 Filter 的生命周期中仅执行一次。在 destroy 方法中,可以释放过滤器使用的资源。
用户在配置 Filter 时,可以使用init-param
为 Filter 配置一些初始化参数,当 web 容器实例化 Filter 对象,调用其 init 方法时,会把封装了 Filter 初始化参数的 FilterConfig 对象传递进来。因此开发人员在编写 Filter 时,通过 FilterConfig 对象的方法,就可获得:
示例:利用 FilterConfig 得到 Filter 配置信息
import java.io.IOException;
import java.util.Enumeration;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
public class FilterTest implements Filter {
/* 过滤器初始化
* @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
*/
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("----过滤器初始化----");
/**
*
FilterTest
com.yangcq.filter.FilterTest
FilterTest
name
gacl
配置FilterTest过滤器的初始化参数
like
java
FilterDemo02
/*
*/
//得到过滤器的名字
String filterName = filterConfig.getFilterName();
//得到在web.xml文件中配置的初始化参数
String initParam1 = filterConfig.getInitParameter("name");
String initParam2 = filterConfig.getInitParameter("like");
//返回过滤器的所有初始化参数的名字的枚举集合。
Enumeration initParameterNames = filterConfig.getInitParameterNames();
System.out.println(filterName);
System.out.println(initParam1);
System.out.println(initParam2);
while (initParameterNames.hasMoreElements()) {
String paramName = (String) initParameterNames.nextElement();
System.out.println(paramName);
}
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
System.out.println("FilterDemo 执行前!!!");
chain.doFilter(request, response); //让目标资源执行,放行
System.out.println("FilterDemo 执行后!!!");
}
@Override
public void destroy() {
System.out.println("----过滤器销毁----");
}
}
Filter 的部署分为两个步骤:
开发好 Filter 之后,需要在 web.xml 文件中进行注册,这样才能够被 web 服务器调用。在 web.xml 文件中注册 Filter 的范例:
<filter>
<description>过滤器名称description>
<filter-name>自定义的名字filter-name>
<filter-class>com.yangcq.filter.FilterTestfilter-class>
<init-param>
<description>配置过滤器的初始化参数description>
<param-name>nameparam-name>
<param-value>gaclparam-value>
init-param>
<init-param>
<description>配置 FilterTest 过滤器的初始化参数description>
<param-name>likeparam-name>
<param-value>javaparam-value>
init-param>
filter>
description
:用于添加描述信息,该元素的内容可为空,description
可以不配置;filter-name
:用于为过滤器指定一个名字,该元素的内容不能为空;filter-class
:元素用于指定过滤器的完整的限定类名;init-param
:元素用于为过滤器指定初始化参数,它的子元素param-name
指定参数的名字,param-value
指定参数的值。在过滤器中,可以使用 FilterConfig 接口对象来访问初始化参数。如果过滤器不需要指定初始化参数,那么init-param
元素可以不配置。
在 web.xml 文件中注册了 Filter 之后,还要在 web.xml 文件中映射 Filter,例如:
<filter-mapping>
<filter-name>FilterTestfilter-name>
<url-pattern>/*url-pattern>
filter-mapping>
filter-mapping
:元素用于设置一个 Filter 所负责拦截的资源,Filter 拦截的资源可通过两种方式来指定,即 Servlet 名称和资源访问的请求路径;filter-name
:子元素用于设置 Filter 的注册名称,该值必须是在filter
元素中声明过的过滤器的名字;url-pattern
:设置 Filter 所拦截的请求路径(过滤器关联的 URL 样式);servlet-name
:指定过滤器所拦截的 Servlet 名称;dispatcher
:指定过滤器所拦截的资源被 Servlet 容器调用的方式,可以是REQUEST
、INCLUDE
、FORWARD
和ERROR
之一,默认为REQUEST
,用户可以设置多个dispatcher
子元素用来指定 Filter 对资源的多种调用方式进行拦截。例如:<filter-mapping>
<filter-name>testFilterfilter-name>
<url-pattern>/index.jspurl-pattern>
<dispatcher>REQUESTdispatcher>
<dispatcher>FORWARDdispatcher>
filter-mapping>
dispatcher
子元素可以设置的值及其意义:
REQUEST
:当用户直接访问页面时,web 容器将会调用过滤器,如果目标资源是通过 RequestDispatcher 的 include() 或 forward() 方法访问时,那么该过滤器就不会被调用;INCLUDE
:如果目标资源是通过 RequestDispatcher 的 include() 方法访问时,那么该过滤器将被调用,除此之外,该过滤器不会被调用;FORWARD
:如果目标资源是通过 RequestDispatcher 的 forward() 方法访问时,那么该过滤器将被调用,除此之外,该过滤器不会被调用;ERROR
:如果目标资源是通过声明式异常处理机制调用时,那么该过滤器将被调用,除此之外,该过滤器不会被调用。转载声明:本文转自「春秋战国程序猿」的博客,Java三大器之过滤器(Filter)的工作原理和代码演示。