1、Filter基础:

-  Filter是Servlet规范的三大组件之一。顾名思义,就是过滤。可以在请求到达目标资源之前先对请求进行拦截过滤,即对请求进行一些处理;也可以在响应到达客户端之前先对响应进行拦截过滤,即对响应进行一些处理。

7e07d61a59ac2eda617c8d5d8a179764.png


2、Filter的生命周期:

a、Filter的生命周期与Servlet的生命周期类似,其主要生命周期阶段有四个:Filter对象的创建、Filter对象的初始化、Filter执行doFilter()方法以及最终Filter对象被销毁。

-   Filter的整个生命周期过程的执行,均由Web服务器负责管理。即Filter从创建到销毁的整个过程中方法的调用,都是由Web服务器负责调用执行的,程序员无法控制其执行流程。

d2aa463a4c67054411058cf4d0205555.png


-  过滤器生命周期示例:

-  自定义的Filter类需要实现Filter接口(javax.servlet.Filter),并实现其接口中定义的方法:

package com.geeklicreed.filters;

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;

public class SomeFilter implements Filter {

    public SomeFilter() {
        System.out.println("SomeFilter被创建");
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("SomeFilter被初始化");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {
        // 请求拦截过滤时可以编写的代码部分
        System.out.println("执行SomeFilter -- before --");
        chain.doFilter(request, response);
        // 响应拦截过滤时可以编写的代码部分
        System.out.println("执行SomeFilter -- after --");
    }

    @Override
    public void destroy() {
        System.out.println("SomeFilter被销毁");
    }

}


-  自定义Servlet类:

package com.geeklicreed.servlet;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class SomeServlet extends HttpServlet {
    private static final long serialVersionUID = 2884109743938532642L;

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        System.out.println("执行SomeServlet");
    }
}


-  需要注意的是,需要在web.xml中注册filter:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://java.sun.com/xml/ns/javaee"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
    id="WebApp_ID" version="3.0">
    <!-- 注册Filter -->
    <filter>
        <filter-name>some-Filter</filter-name>
        <filter-class>com.geeklicreed.filters.SomeFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>some-Filter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    <servlet>
        <servlet-name>some-servlet</servlet-name>
        <servlet-class>com.geeklicreed.servlet.SomeServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>some-servlet</servlet-name>
        <url-pattern>/some</url-pattern>
    </servlet-mapping>
</web-app>


-  启动服务器,访问项目中的/some路径资源,在控制台中显示的结果为其生命周期:

cf3bafd5a683179e82b0b6e7a1f92715.png

bb74e1cbea2210e38a4b374b22f85370.png


-  上例中的接口以及方法的解释:

-  javax.servlet.Filter接口:过滤器是用于过滤请求资源(servlet、静态资源)或者是从资源响应客户端的过滤任务:(或者是两者兼而有之)

433145c565c29c5dbc2d7c66a3db4081.png

-  其接口中三个定义方法:

9f177840846c7347c74d798909b66edc.png


- javax.servlet.FilterChain接口:FilterChain接口类对象是被用于servlet容器提供给开发者从视图到资源过滤请求的调用链的对象。过滤器使用过滤器链来调用链中的下一个过滤器,如果正在调用的过滤器是链中的最后一个,则会直接调用链末尾端的资源。

04a57b3f4eecb8d59cb029ea8d8c41b7.png

-  该接口中只定义一个方法:调用过滤器链中的下一个过滤器,或者如果正在调用的过滤器是链中的最后一个,则会直接调用链末尾端的资源。

d9a7f40d6652237f5e154c426a6dacba.png


b、Filter的特征:

-  Filter是单例多线程的。

-  Filter是在应用被加载时创建并初始化的,这是与Servlet不同的地方。Servlet是在该Servlet被第一次访问时创建的。Filter与Servlet的共同点是,其无参构造器与init()方法只会执行一次。

-  用户每提交一次该Filter可以过滤的请求,服务器就会执行一次doFilter()方法,即doFilter()方法可以被多次执行。

-  当应用被停止时执行destroy()方法,Filter被销毁,即destroy()方法只会执行一次。

-  由于Filter是单例多线程的,所以为了保证其线程安全性,一般情况下是不为Filter类定义可修改的成员变量。因为每个线程均可修改这个成员变量,会出现线程安全问题。


3、javax.servlet.FilterConfig接口:通常指代filter在web.xml中的注册信息:

66cec9c88e202f1e230370fbff68be53.png

-  FilterConfig接口中的方法可以获取关于当前Filter在web.xml中的信息:(ServletContext、filterName、initParameter等):

9bfbce43e2aef2b7ae0b732649292d1d.png


-  FilterConfig接口中的方法在filter类中使用的示例:

-  可以在自定义Filter类中定义私有化的成员变量(并提供getFilterConfig()方法),在init(FilterConfig filterConfig)方法接收。在doFilter方法中使用这个filterConfig成员变量:

package com.geeklicreed.filters;

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 SomeFilter implements Filter {
    private FilterConfig filterConfig;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        this.filterConfig = filterConfig;
    }

    public FilterConfig getFilterConfig() {
        return filterConfig;
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {
        System.out.println(filterConfig.getFilterName());
        System.out.println(filterConfig.getServletContext());
        System.out.println(filterConfig.getInitParameter("username"));

        Enumeration<String> initParameterNames = filterConfig
                .getInitParameterNames();

        while (initParameterNames.hasMoreElements()) {
            String initParameterName = initParameterNames.nextElement();
            System.out.println(initParameterName + " --> "
                    + filterConfig.getInitParameter(initParameterName));
        }
        chain.doFilter(request, response);
    }

    @Override
    public void destroy() {
    }

}


-  需要注意的是,在web.xml中的<filter>标签中,可以提供<init-param>标签,以提供初始化参数:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://java.sun.com/xml/ns/javaee"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
    id="WebApp_ID" version="3.0">
    <!-- 注册Filter -->
    <filter>
        <filter-name>Some-Filter</filter-name>
        <filter-class>com.geeklicreed.filters.SomeFilter</filter-class>
        <init-param>
            <param-name>username</param-name>
            <param-value>geeklicreed</param-value>
        </init-param>
        <init-param>
            <param-name>age</param-name>
            <param-value>21</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>Some-Filter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
</web-app>

-  当访问一个index.jsp页面时,控制台中的打印如下:

b59046e93d3f9f7281f7774e878a33da.png


4、<dispatcher>标签的四种取值:

-  在<filter-mapping>中还有一个子标签<dispatcher>,用于设置过滤器的请求类型。其有四种取值,REQUEST、FORWARD、INCLUDE、ERROR。

-  FORWARD:若请求是由一个Servlet通过RequestDsipatcher的forward()方法所转发的,那么这个请求将被<dispatcher>值为FORWARD的Filter所拦截。即当前Filter只会拦截由RequestDispatcher的forward()方法所转发的请求。其他请求均

不拦截。

8d3d164072a475b6e285a39b62e494ad.png


-  INCLUDE:当前Fillter只会拦截由RequestDsipatcher的include()方法所转发的请求。其他请求均不拦截。

f53472dd914181ea9126dfd8e8949711.png


-  REQUEST:表示当前过滤其只会拦截普通请求,但是对forward和include的跳转不进行拦截。(默认值)

-  ERROR:表示当跳转到指定的错误处理页面时,这个跳转请求会被当前过滤器拦截。


-  <dispatcher>标签的ERROR取值示例:

-  在web.xml中填写如下代码,使得当跳转到指定的错误处理页面时,这个跳转请求会被当前过滤器拦截。

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://java.sun.com/xml/ns/javaee"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
    id="WebApp_ID" version="3.0">
    <!-- 注册Filter -->
    <filter>
        <filter-name>Some-Filter</filter-name>
        <filter-class>com.geeklicreed.filters.SomeFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>Some-Filter</filter-name>
        <url-pattern>/*</url-pattern>
        <dispatcher>ERROR</dispatcher>
    </filter-mapping>
    <!-- 错误页面 -->
    <error-page>
        <error-code>404</error-code>
        <location>/error.jsp</location>
    </error-page>
</web-app>


5、多个Filter的执行顺序问题:

114f495daa566a17123406916578e9c1.png

-  当应用中存在多个Filter时,其执行顺序与注册顺序一致。(如上图所示,先执行OneFilter的请求过滤拦截,再是TwoFilter的请求过滤拦截,后是SomeServlet的执行,后是TwoFilter的响应过滤拦截,最后是TwoFilter的请求过滤拦截)


6、Filter的执行原理:

-  当某资源的请求到达Web容器时,会先对请求进行解析,使用解析出来的URI作为比较对象,从Map(Map的key为<url-pattern>的值,value为Filter实例对象的引用)中查找是否存在相匹配的key。若存在,那么读取其value,即Filter对象的引用,将该引用存入到数组中。然后继续向后查找,直到将该Map查找完毕。这样在数组中就存在按照查找顺序排好序的Filter引用。

-  数组初始化完毕后,开始按照数组元素顺序进行执行。所以数组中的Filter全部执行完毕之后,再跳转到请求的目标资源。(数组中存放着与请求相匹配的所有Filter)


7、附加说明:

a、若Filter为全路径匹配方式,那么url-pattern只能写为/*,而不能够写为/。(写为/,无论是静态资源,还是动态资源都不起作用)

-  <filter-mapping>标签中可以不使用<url-pattern>,但需要指定<servlet-name>,即当前过滤器拦截的是对指定Servlet的请求。


b、filter过滤器的应用:

-  可以在自定义的过滤器(MyCharactorEncodingFilter类)中解决POST提交中文乱码问题。

package com.geeklicreed.filters;

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;

public class MyCharactorEncodingFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {
        //解决请求参数中文乱码问题
        request.setCharacterEncoding("UTF-8");
        //解决响应中文乱码问题
        response.setContentType("text/html;charset=UTF-8");
        chain.doFilter(request, response);
    }

    @Override
    public void destroy() {
    }

}