详述 Java 中过滤器 Filter 的工作原理及使用方法

1 简介

  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();  
}  

2 Filter 的工作原理

  Filter 接口中有一个doFilter 方法,当咱们编写好 Filter,并配置对哪个 web 资源进行拦截后,web 服务器每次在调用 web 资源的 service 方法之前,都会先调用一下 Filter 的 doFilter 方法,因此,在该方法内编写代码可达到如下的:

  • 调用目标资源之前,让一段代码执行;
  • 是否调用目标资源,即是否让用户访问 web 资源;
  • 调用目标资源之后,让一段代码执行。

web 服务器在调用 doFilter 方法时,会传递一个 filterChain 对象进来,filterChain 对象是 filter 接口中最重要的一个对象,它也提供了一个 doFilter 方法,开发人员可以根据需求决定是否调用此方法。如果调用该方法,则 web 服务器就会调用 web 资源的 service 方法,即 web 资源就会被访问;否则的话, web 资源就不会被访问。

3 Filter 开发流程

3.1 Filter 开发步骤

Filter 开发分为两步:

  • 编写 Java 类实现 Filter 接口,并实现其 doFilter 方法;
  • 在 web.xml 文件中使用filterfilter-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 中配置过滤器

<?xml version="1.0" encoding="UTF-8"?>  
<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.jsp</welcome-file>  
  </welcome-file-list>  
  <!--配置过滤器-->  
  <filter>  
      <filter-name>FilterTest</filter-name>  
      <filter-class>com.yangcq.filter.FilterTest</filter-class>  
  </filter>  
  <!--映射过滤器-->  
  <filter-mapping>  
      <filter-name>FilterTest</filter-name>  
      <!-- “/*” 表示拦截所有的请求 -->  
      <url-pattern>/*</url-pattern>  
  </filter-mapping>  
</web-app>  

3.2 Filter 链

  在一个 web 应用中,可以开发编写多个 Filter,这些 Filter 组合起来称之为一个 Filter 链。web 服务器根据 Filter 在 web.xml 文件中的注册顺序,决定先调用哪个 Filter,当第一个 Filter 的 doFilter 方法被调用时,web 服务器会创建一个代表 Filter 链的 filterChain 对象传递给该方法。在 doFilter 方法中,开发人员如果调用了 filterChain 对象的 doFilter 方法,则 web 服务器会检查 FilterChain 对象中是否还有 Filter,如果有,则调用第 2 个 Filter;如果没有,则调用目标资源。

4 Spring 框架中的过滤器配置

  如果项目中使用了 Spring 框架,那么,很多过滤器都不用咱们自己来写了,Spring 为咱们写好了一些常用的过滤器。下面咱们就以字符编码的过滤器CharacterEncodingFilter为例,来看看在 Spring 框架下,如何配置过滤器。

<filter>  
    <filter-name>encodingFilter</filter-name>  
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>  
    <init-param>  
        <param-name>encoding</param-name>  
        <param-value>UTF-8</param-value>  
    </init-param>  
    <init-param>  
        <param-name>forceEncoding</param-name>  
        <param-value>true</param-value>  
    </init-param>  
</filter>  

<filter-mapping>  
    <filter-name>encodingFilter</filter-name>  
    <url-pattern>/*</url-pattern>  
</filter-mapping>  

很简单吧?这样几行配置代码,就完成了从全局控制字符编码的功能。接下来,咱们看看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<String> 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>InvalidCharacterFilter</filter-name>  
    <filter-class>com.yangcq.filter.InvalidCharacterFilter</filter-class>  
</filter>  
<filter-mapping>  
    <filter-name>InvalidCharacterFilter</filter-name>  
    <url-pattern>/*</url-pattern>  
</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<String> 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>SelfDefineInvalidCharacterFilter</filter-name>  
    <filter-class>com.yangcq.filter.SelfDefineInvalidCharacterFilter</filter-class>  
</filter>  
<filter-mapping>  
    <filter-name>SelfDefineInvalidCharacterFilter</filter-name>  
    <url-pattern>/*</url-pattern>  
</filter-mapping> 

5 Filter 的生命周期

5.1 Filter 的创建

  Filter 的创建和销毁由 web 服务器负责。 web 应用程序启动时,web 服务器将创建 Filter 的实例对象,并调用其 init 方法,完成对象的初始化功能,从而为后续的用户请求作好拦截的准备工作,Filter 对象只会创建一次,init 方法也只会执行一次。通过 init 方法的参数,可获得代表当前 Filter 配置信息 FilterConfig 对象。

5.2 Filter 的销毁

  web 容器调用 destroy 方法销毁 Filter,destroy 方法在 Filter 的生命周期中仅执行一次。在 destroy 方法中,可以释放过滤器使用的资源。

5.3 FilterConfig 接口

  用户在配置 Filter 时,可以使用init-param为 Filter 配置一些初始化参数,当 web 容器实例化 Filter 对象,调用其 init 方法时,会把封装了 Filter 初始化参数的 FilterConfig 对象传递进来。因此开发人员在编写 Filter 时,通过 FilterConfig 对象的方法,就可获得:

  • String getFilterName():得到 Filter 的名称;
  • String getInitParameter(String name):返回在部署描述中指定名称的初始化参数的值,如果不存在,则返回 null;
  • Enumeration getInitParameterNames():返回过滤器的所有初始化参数的名字的枚举集合;
  • public ServletContext getServletContext():返回 Servlet 上下文对象的引用。

示例:利用 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("----过滤器初始化----");  
        /** 
         *  <filter> 
                  <filter-name>FilterTest</filter-name> 
                  <filter-class>com.yangcq.filter.FilterTest</filter-class> 
                  <!--配置FilterTest过滤器的初始化参数--> 
                  <init-param> 
                      <description>FilterTest</description> 
                      <param-name>name</param-name> 
                      <param-value>gacl</param-value> 
                  </init-param> 
                  <init-param> 
                      <description>配置FilterTest过滤器的初始化参数</description> 
                      <param-name>like</param-name> 
                      <param-value>java</param-value> 
                  </init-param> 
            </filter> 

             <filter-mapping> 
                  <filter-name>FilterDemo02</filter-name> 
                  <!--“/*”表示拦截所有的请求 --> 
                  <url-pattern>/*</url-pattern> 
             </filter-mapping> 
         */  
        //得到过滤器的名字  
        String filterName = filterConfig.getFilterName();  
        //得到在web.xml文件中配置的初始化参数  
        String initParam1 = filterConfig.getInitParameter("name");  
        String initParam2 = filterConfig.getInitParameter("like");  
        //返回过滤器的所有初始化参数的名字的枚举集合。  
        Enumeration<String> 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("----过滤器销毁----");  
    }  
} 

6 在部署 Filter 时一些参数的含义

Filter 的部署分为两个步骤:

  • 注册 Filter;
  • 映射 Filter。

6.1 注册 Filter

  开发好 Filter 之后,需要在 web.xml 文件中进行注册,这样才能够被 web 服务器调用。在 web.xml 文件中注册 Filter 的范例:

<filter>  
    <description>过滤器名称</description>  
    <filter-name>自定义的名字</filter-name>  
    <filter-class>com.yangcq.filter.FilterTest</filter-class>  
    <!--配置 FilterTest 过滤器的初始化参数-->  
    <init-param>  
        <description>配置过滤器的初始化参数</description>  
        <param-name>name</param-name>  
        <param-value>gacl</param-value>  
    </init-param>  
    <init-param>  
        <description>配置 FilterTest 过滤器的初始化参数</description>  
        <param-name>like</param-name>  
        <param-value>java</param-value>  
    </init-param>  
</filter>  
  • description:用于添加描述信息,该元素的内容可为空,description可以不配置;
  • filter-name:用于为过滤器指定一个名字,该元素的内容不能为空;
  • filter-class:元素用于指定过滤器的完整的限定类名;
  • init-param:元素用于为过滤器指定初始化参数,它的子元素param-name指定参数的名字,param-value指定参数的值。

在过滤器中,可以使用 FilterConfig 接口对象来访问初始化参数。如果过滤器不需要指定初始化参数,那么init-param元素可以不配置。

6.2 映射 Filter

  在 web.xml 文件中注册了 Filter 之后,还要在 web.xml 文件中映射 Filter,例如:

<!--映射过滤器-->
<filter-mapping>
      <filter-name>FilterTest</filter-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 容器调用的方式,可以是REQUESTINCLUDEFORWARDERROR之一,默认为REQUEST,用户可以设置多个dispatcher子元素用来指定 Filter 对资源的多种调用方式进行拦截。例如:
<filter-mapping>
    <filter-name>testFilter</filter-name>
    <url-pattern>/index.jsp</url-pattern>
    <dispatcher>REQUEST</dispatcher>
    <dispatcher>FORWARD</dispatcher>
</filter-mapping>

dispatcher子元素可以设置的值及其意义:

  • REQUEST:当用户直接访问页面时,web 容器将会调用过滤器,如果目标资源是通过 RequestDispatcher 的 include() 或 forward() 方法访问时,那么该过滤器就不会被调用;
  • INCLUDE:如果目标资源是通过 RequestDispatcher 的 include() 方法访问时,那么该过滤器将被调用,除此之外,该过滤器不会被调用;
  • FORWARD:如果目标资源是通过 RequestDispatcher 的 forward() 方法访问时,那么该过滤器将被调用,除此之外,该过滤器不会被调用;
  • ERROR:如果目标资源是通过声明式异常处理机制调用时,那么该过滤器将被调用,除此之外,该过滤器不会被调用。

转载声明:本文转自「春秋战国程序猿」的博客,Java三大器之过滤器(Filter)的工作原理和代码演示

  • 9
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值