SpringMVC——过滤器实现和理解

一、先提出几个问题

?过滤器是什么?
?过滤器是如何实现的?
?过滤器和拦截器的区别?
?过滤器的核心原理是什么(阅读源码)?

之前我学东西总是不够深入,现在决定换个思路来学习。
每次学一个东西,都先提出问题,让自己带着兴趣去实践和学习。
1、先问是什么?
2、然后怎么做?实践的过程能更加熟练并提出更深入的问题
3、最后为什么?这时候再看看源码,刚才实践过程中遇到了哪些问题,这时候能找出答案。

二、过滤器是什么

依赖于servlet容器。在实现上基于函数回调,可以对几乎所有请求进行过滤,但是缺点是一个过滤器实例只能在容器初始化时调用一次。使用过滤器的目的是用来做一些过滤操作,获取我们想要获取的数据,比如:在过滤器中修改字符编码;在过滤器中修改HttpServletRequest的一些参数,包括:过滤低俗文字、危险字符等

我的理解,就是对所有请求进行过滤操作,获取请求携带的数据或者修改请求的某些参数。

三、过滤器的实现

1、Spring自带的字符编码过滤器

过滤器常用的场景:解决中文乱码问题。

我搭建好了一个Spring+SpringMVC的简单项目框架,项目运行之后你能从浏览器请求到页面。

字符编码过滤器主要是过滤reponse响应,我用了一个添加用户信息和页面显示,来直观地看到响应被处理的过程。

Controller控制器

@RequestMapping(value="/add",method=RequestMethod.GET)
    public String add(){
        return "add";
    }

    @RequestMapping(value="/add",method=RequestMethod.POST)
    public void addPost(HttpServletRequest request,HttpServletResponse response,Model model) throws IOException{

        //姓名
        String name=request.getParameter("name");  
        System.out.println("name>>>"+name);
        //国家
        String country=request.getParameter("country"); 
        System.out.println("country>>>"+country);
        //性别
        String sex = request.getParameter("sex"); 
        System.out.println("sex>>>"+sex);

        //获取PrintWriter  
        PrintWriter out = response.getWriter(); 
        out.print("name : "+name+"|");  
        out.print("country : "+country+"|");  
        out.print("sex : "+sex);  
        out.flush();//刷新流  
        out.close();//关闭流 
    }

页面add.jsp

<%@ page language="java" contentType="text/html; charset=utf-8"
    pageEncoding="utf-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Insert title here</title>
</head>
<body>
 <form action="./add" 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="name">  
            </td>  
        </tr>  
        <tr>  
            <td align="right">国家:</td>  
            <td>  
                <input type="text" name="country">  
            </td>  
        </tr>  
        <tr>  
            <td align="right">性别:</td>  
            <td>  
                <input type="text" name="sex">  
            </td>  
        </tr>  
        <tr>  
            <td class="2" align="center" colspan="2">  
                <input type="submit" value="添 加">  
            </td>  
        </tr>  
    </table>  
</form>  

</body>
</html>

从浏览器进入到添加用户的界面

springmvc过滤器 方法详解 springmvc实现过滤器_System

点击”添加”打印到页面上都是乱码了。

springmvc过滤器 方法详解 springmvc实现过滤器_System_02

控制台打印输出是正常正文显示

springmvc过滤器 方法详解 springmvc实现过滤器_System_03

现在添加字符编码过滤器
web.xml

<!-- 字符集过滤器 -->
    <filter>
        <description>字符集过滤器</description>  
        <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>

注意,这里少了forceEncoding的参数也不行。

forceEncoding是强制字符集
true处理请求和响应,相当于:
request.setCharacterEncoding(“”);
response.setCharacterEncoding(“”);
false只处理请求,相当于
request.setCharacterEncoding(“”);

然后再次请求,就能正常显示中文了。

springmvc过滤器 方法详解 springmvc实现过滤器_System_04

进行到这一步,对这个过滤器的实现有了一定的了解。这是Spring自带的字符编码过滤器,用起来太简单了,只需要在web.xml中配置。

如果我们自定义过滤器的话,又该怎么配置呢?

2、自定义过滤器(权限过滤器,实现uri过滤)

这里写一个uri过滤器,同时因为兴趣原因,我也用cookie实现了自动登录的功能。

主要还是对过滤器进行说明,cookie自动登录只是写了点皮毛,也不值得拿出来说了。

uri过滤器主要实现的功能是:除了/login的uri,都要进行过滤处理,获取session值,看session里是否有用户信息,没有用户信息,就跳到登录页面。

(1)写个过滤器的类SessionFilter.class

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.http.HttpStatus;
import org.springframework.web.filter.OncePerRequestFilter;

import com.mocha.model.User;

public class SessionFilter extends OncePerRequestFilter{

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {

        //不过滤的uri
        String[] notFilter = new String[]{"/login"};

        //请求的uri
        String uri = request.getRequestURI();
        System.out.println("filter>>>uri>>>"+uri);

        //是否过滤
        boolean doFilter = true;
        for(String s : notFilter){
            if(uri.indexOf(s) != -1){
                //uri中包含不过滤uri,则不进行过滤
                doFilter = false;
                break;
            }
        }

        if(doFilter){
            System.out.println("doFilter>>>");
            //过滤操作
            //从session中获取登陆者实体
            Object obj = request.getSession().getAttribute("user");
            if(obj==null){
                System.out.println("doFilter>>>obj is null");
                boolean isAjaxRequest = isAjaxRequest(request);
                if(isAjaxRequest){
                    response.setCharacterEncoding("UTF-8");
                    response.sendError(HttpStatus.UNAUTHORIZED.value(),"您已经太长时间没有操作,请刷新页面");

                    System.out.println("doFilter>>>ajax request");

                    return ;
                }else{
                    System.out.println("doFilter>>>http request");
                    response.sendRedirect("./login");
                    //跳转到登录页面
                    return ;
                }
            }else{
                User user = (User) obj;
                System.out.println("doFilter>>>username>>"+user.getUsername());
                  // 如果session中存在登录者实体,则继续  
                filterChain.doFilter(request, response);
            }
        }else{
            System.out.println("no Filter>>>");
            //不执行过滤操作
            filterChain.doFilter(request, response);
        }
    }
    /**
     * is Ajax request
     * @param request
     * @return
     */
    private boolean isAjaxRequest(HttpServletRequest request) {
        String header = request.getHeader("X-Requested-With");
        if(header != null && "XMLHttpRequest".equals(header)){
            //ajax request
            return true;
        }else{
            //traditional sync http request
            return false;
        }
    }

}

(2)web.xml

<filter>
        <description>session过滤器</description>
        <filter-name>sessionFilter</filter-name>
        <filter-class>com.mocha.filter.SessionFilter</filter-class>
    </filter>

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

(3)控制器

@RequestMapping(value="/login",method=RequestMethod.GET)
    public String login(HttpServletRequest request,HttpServletResponse response){
        //获取Cookie
        Cookie[] cookies = request.getCookies();

        for(Cookie cookie : cookies){

            System.out.println("cookie>>"+cookie.getValue());

            //从数据库获取保存的cookie
            Session session = iSessionDAO.getSession(cookie.getValue());
            if(session!=null){
                //如果存在,就跳转到首页
                return "index";
            }
        }

        return "login";

    }
    @RequestMapping(value="/login",method=RequestMethod.POST)
    public String loginPOST(HttpServletRequest request, HttpServletResponse response,Model model){

        //用户名
        String username=request.getParameter("username");  
        System.out.println("username>>>"+username);
        //密码
        String password=request.getParameter("password"); 
        System.out.println("password>>>"+password);


        //先从数据库查找该账号信息
        User user = null;
        try {
            user = iUserDAO.queryForUser(username);
        } catch (NullPointerException e) {
            e.printStackTrace();
            model.addAttribute("message", "No account");
        }

        if(user==null){
            model.addAttribute("message", "No account");
        }else{

            // 匹配密码
            if (user.getPassword().equals(password)) {
                //登录成功,保存session
                request.getSession().setAttribute("user", user);

                // 保存cookie
                Cookie[] cookies = request.getCookies();
                Cookie cookie = cookies[0];//获得最新的那个cookie

                Session isSession = iSessionDAO.getSessionByUserId(user.getId());
                //没有session,就添加
                if(isSession==null){
                    Session session = new Session();
                    session.setId(UUID.randomUUID().toString());
                    session.setSession(cookie.getValue());
                    session.setUser_id(user.getId());
                    System.out.println("cookie>>" + cookie.getValue());

                    iSessionDAO.save(session);
                    System.out.println("==添加session==");
                }else{
                    //如果已经有session,就更新
                    isSession.setSession(cookie.getValue());
                    iSessionDAO.update(isSession);
                    System.out.println("==更新session==");
                }

                model.addAttribute("message", user.getUsername());
                return "index";
            }else{
                model.addAttribute("message", "Wrong password");
            }
        }
        return "login";
    }

    @RequestMapping(value="/sessionTest",method=RequestMethod.GET)
    public String sessionTest(HttpServletRequest request,HttpServletResponse response,Model model){
        System.out.println(">>>sessionTest");
        model.addAttribute("message", "sessionTest");
        return "index";

    }

这时候请求控制器,进入login.jsp

springmvc过滤器 方法详解 springmvc实现过滤器_java_05

此时控制台打印显示经过了过滤器,但是没有进行过滤操作。

springmvc过滤器 方法详解 springmvc实现过滤器_过滤器_06

输入账号信息登录

springmvc过滤器 方法详解 springmvc实现过滤器_springmvc过滤器 方法详解_07

跳转到首页index.jsp

springmvc过滤器 方法详解 springmvc实现过滤器_springmvc_08

这次跳转其实进行了一个操作,控制器里有个添加请求的session的操作

request.getSession().setAttribute(“user”, user);

将这个账号信息保存到了session里,这个session 能够保持到浏览器关闭之前。

比如我这时候请求一个进行过滤操作的uri

springmvc过滤器 方法详解 springmvc实现过滤器_java_09

此时控制台打印消息:
filter>>>uri>>>/spring-basic/sessionTest
doFilter>>>
doFilter>>>username>>kim
也就是说,经过了过滤处理,并且拿到了session里user的值。

当我打开一个新的浏览器进行请求的时候,由于没有session,就会跳转到登录页。

到这里,有了实际操作,我对过滤器有了一定的了解。

四、过滤器和拦截器的区别

什么是拦截器

依赖于web框架,在SpringMVC中就是依赖于SpringMVC框架。在实现上基于Java的反射机制,属于面向切面编程(AOP)的一种运用。由于拦截器是基于web框架的调用,因此可以使用Spring的依赖注入(DI)进行一些业务操作,同时一个拦截器实例在一个controller生命周期之内可以多次调用。但是缺点是只能对controller请求进行拦截,对其他的一些比如直接访问静态资源的请求则没办法进行拦截处理

多个过滤器与拦截器的代码执行顺序

  • 执行顺序跟在SpringMVC的配置文件中定义的先后顺序有关

五、过滤器原理

终于到了琢磨源码的时刻。
有了初步的实践,现在再来看源码,就不会太过陌生了。

session过滤器里继承了OncePerRequestFilter,可以阅读一下这个过滤器的源码

package org.springframework.web.filter;

import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.web.context.request.async.WebAsyncManager;
import org.springframework.web.context.request.async.WebAsyncUtils;
import org.springframework.web.util.WebUtils;

/**
 * Filter base class that aims to guarantee a single execution per request
 * dispatch, on any servlet container. 
 * It provides a {@link #doFilterInternal}
 * method with HttpServletRequest and HttpServletResponse arguments.
 *
 * <p>As of Servlet 3.0, a filter may be invoked as part of a
 * {@link javax.servlet.DispatcherType#REQUEST REQUEST} or
 * {@link javax.servlet.DispatcherType#ASYNC ASYNC} dispatches that occur in
 * separate threads. A filter can be configured in {@code web.xml} whether it
 * should be involved in async dispatches.
 * However, in some cases servlet containers assume different default configuration.
 *  (但是,在某些情况下,servlet容器会采用不同的默认配置。)
 * Therefore sub-classes can override the method {@link #shouldNotFilterAsyncDispatch()} 
 * to declare statically if they should indeed be invoked, once, during both types
 * of dispatches in order to provide thread initialization, logging, security,
 * and so on. This mechanism complements and does not replace the need to
 * configure a filter in {@code web.xml} with dispatcher types.
 * 为了提供线程初始化、日志记录、安全性等等,在这两种类型的调度期间,子类可以重写该方法以静态声明它们是否应该被调用。
 * 这个机制补充不能取代web.xml中对过滤器的配置。
 * <p>Subclasses may use {@link #isAsyncDispatch(HttpServletRequest)} to
 * determine when a filter is invoked as part of an async dispatch, and use
 * {@link #isAsyncStarted(HttpServletRequest)} to determine when the request
 * has been placed in async mode and therefore the current dispatch won't be
 * the last one for the given request.
 * 子类可以使用isAsyncDispatch方法来确定何时将过滤器作为异步调度的一部分进行调用,并使用此方法来确定请求何时处于异步模式,因此当前调度不会是给定请求的最后一个。
 * <p>Yet another dispatch type that also occurs in its own thread is
 * {@link javax.servlet.DispatcherType#ERROR ERROR}. Subclasses can override
 * {@link #shouldNotFilterErrorDispatch()} if they wish to declare statically
 * if they should be invoked <em>once</em> during error dispatches.
 *
 * <p>The {@link #getAlreadyFilteredAttributeName} method determines how to
 * identify that a request is already filtered. The default implementation is
 * based on the configured name of the concrete filter instance.
 *
 * @author Juergen Hoeller
 * @author Rossen Stoyanchev
 * @since 06.12.2003
 */
public abstract class OncePerRequestFilter extends GenericFilterBean {

    /**
     * Suffix that gets appended to the filter name for the
     * "already filtered" request attribute.
     * @see #getAlreadyFilteredAttributeName
     */
    public static final String ALREADY_FILTERED_SUFFIX = ".FILTERED";


    /**
     * This {@code doFilter} implementation stores a request attribute for
     * "already filtered", proceeding without filtering again if the
     * attribute is already there.
     * @see #getAlreadyFilteredAttributeName
     * @see #shouldNotFilter
     * @see #doFilterInternal
     */
    @Override
    public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {

        if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) {
            throw new ServletException("OncePerRequestFilter just supports HTTP requests");
        }
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;

        String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName();
        boolean hasAlreadyFilteredAttribute = request.getAttribute(alreadyFilteredAttributeName) != null;

        if (hasAlreadyFilteredAttribute || skipDispatch(httpRequest) || shouldNotFilter(httpRequest)) {

            // Proceed without invoking this filter...
            filterChain.doFilter(request, response);
        }
        else {
            // Do invoke this filter...
            request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);
            try {
                doFilterInternal(httpRequest, httpResponse, filterChain);
            }
            finally {
                // Remove the "already filtered" request attribute for this request.
                request.removeAttribute(alreadyFilteredAttributeName);
            }
        }
    }


    private boolean skipDispatch(HttpServletRequest request) {
        if (isAsyncDispatch(request) && shouldNotFilterAsyncDispatch()) {
            return true;
        }
        if (request.getAttribute(WebUtils.ERROR_REQUEST_URI_ATTRIBUTE) != null && shouldNotFilterErrorDispatch()) {
            return true;
        }
        return false;
    }

    /**
     * The dispatcher type {@code javax.servlet.DispatcherType.ASYNC} introduced
     * in Servlet 3.0 means a filter can be invoked in more than one thread over
     * the course of a single request. This method returns {@code true} if the
     * filter is currently executing within an asynchronous dispatch.
     * @param request the current request
     * @since 3.2
     * @see WebAsyncManager#hasConcurrentResult()
     */
    protected boolean isAsyncDispatch(HttpServletRequest request) {
        return WebAsyncUtils.getAsyncManager(request).hasConcurrentResult();
    }

    /**
     * Whether request processing is in asynchronous mode meaning that the
     * response will not be committed after the current thread is exited.
     * @param request the current request
     * @since 3.2
     * @see WebAsyncManager#isConcurrentHandlingStarted()
     */
    protected boolean isAsyncStarted(HttpServletRequest request) {
        return WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted();
    }

    /**
     * Return the name of the request attribute that identifies that a request
     * is already filtered.
     * <p>The default implementation takes the configured name of the concrete filter
     * instance and appends ".FILTERED". If the filter is not fully initialized,
     * it falls back to its class name.
     * @see #getFilterName
     * @see #ALREADY_FILTERED_SUFFIX
     */
    protected String getAlreadyFilteredAttributeName() {
        String name = getFilterName();
        if (name == null) {
            name = getClass().getName();
        }
        return name + ALREADY_FILTERED_SUFFIX;
    }

    /**
     * Can be overridden in subclasses for custom filtering control,
     * returning {@code true} to avoid filtering of the given request.
     * <p>The default implementation always returns {@code false}.
     * @param request current HTTP request
     * @return whether the given request should <i>not</i> be filtered
     * @throws ServletException in case of errors
     */
    protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
        return false;
    }

    /**
     * The dispatcher type {@code javax.servlet.DispatcherType.ASYNC} introduced
     * in Servlet 3.0 means a filter can be invoked in more than one thread
     * over the course of a single request. 
     * ASYNC类型在Servlet 3.0中意味着一个过滤器可以在一个请求的情况下中被多个线程调用
     * Some filters only need to filter the initial thread (e.g. request wrapping)
     *  while others may need
     * to be invoked at least once in each additional thread for example for
     * setting up thread locals or to perform final processing at the very end.
     * 一些过滤器只需要过滤初始线程(例如request wrapping),而其他过滤器可能需要在每个附加线程中至少调用一次,
     * 例如用于设置线程本地或在最后执行最终处理。
     * <p>Note that although a filter can be mapped to handle specific dispatcher
     * types via {@code web.xml} or in Java through the {@code ServletContext},
     * servlet containers may enforce different defaults with regards to
     * dispatcher types. 
     * 请注意,虽然过滤器可以通过{code web.xml}或Java中的通过{@code ServletContext}映射
     * 来处理特定的调度类型,但servlet容器可能会针对调度类型强制执行不同的默认值。
     * This flag enforces the design intent of the filter.
     * 该标志强制执行过滤器的设计意图。
     * <p>The default return value is "true", which means the filter will not be
     * invoked during subsequent async dispatches. 
     * 默认返回值是“true”,这意味着在随后的异步调度期间不会调用过滤器。
     * If "false", the filter will
     * be invoked during async dispatches with the same guarantees of being
     * invoked only once during a request within a single thread.
     * 如果为“false”,则将在异步调度期间调用过滤器,并保证在单个线程的请求期间仅调用一次该过滤器。
     * @since 3.2
     */
    protected boolean shouldNotFilterAsyncDispatch() {
        return true;
    }

    /**
     * Whether to filter error dispatches such as when the servlet container
     * processes and error mapped in {@code web.xml}. 
     * 是否过滤错误调度,例如,当servlet容器处理和错误映射到{web.xml}时。
     * The default return value is "true", which means the filter will not be 
     * invoked in case of an error dispatch.
     * 默认返回值是true,意味着如果发生错误,过滤器将不被调用。
     * @since 3.2
     */
    protected boolean shouldNotFilterErrorDispatch() {
        return true;
    }


    /**
     * Same contract as for {@code doFilter}, but guaranteed to be
     * just invoked once per request within a single request thread.
     * 与doFilter相同的协定,但是确保了在单个请求线程里每次请求只调用了一次。
     * See {@link #shouldNotFilterAsyncDispatch()} for details.
     * <p>Provides HttpServletRequest and HttpServletResponse arguments instead of the
     * default ServletRequest and ServletResponse ones.
     * 
     */
    protected abstract void doFilterInternal(
            HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException;

}

另外还有父类GenericFilterBean
这个父类主要是获取web.xml的配置参数对过滤器进行配置的。

package org.springframework.web.filter;

import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
import javax.servlet.Filter;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeansException;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyAccessorFactory;
import org.springframework.beans.PropertyValue;
import org.springframework.beans.PropertyValues;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.env.Environment;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceEditor;
import org.springframework.core.io.ResourceLoader;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.context.ServletContextAware;
import org.springframework.web.context.support.ServletContextResourceLoader;
import org.springframework.web.context.support.StandardServletEnvironment;
import org.springframework.web.util.NestedServletException;

/**
 * Simple base implementation of {@link javax.servlet.Filter} which treats
 * its config parameters (<code>init-param</code> entries within the
 * <code>filter</code> tag in <code>web.xml</code>) as bean properties.
 * 
 * 接口GenericFilterBean是javax.servlet.Filter接口的一个基本的实现类,特别的是,
 * 接口GenericFilterBean将web.xml中filter标签中的配置参数-init-param项作为bean的属性。
 * 
 * <p>A handy superclass for any type of filter. Type conversion of config
 * parameters is automatic, with the corresponding setter method getting
 * invoked with the converted value. It is also possible for subclasses to
 * specify required properties. Parameters without matching bean property
 * setter will simply be ignored.
 * 
 * 接口GenericFilterBean可以简单地成为任何类型的filter的父类。配置参数的值的类型转换通过调用
 * 相应的setter 方法来自动进行的。接口GenericFilterBean的子类可以自定义一些自己需要的属性。但是,
 * 配置文件中没有对应bean属性setter方法的参数会被忽略。
 * 
 * <p>This filter leaves actual filtering to subclasses, which have to
 * implement the {@link javax.servlet.Filter#doFilter} method.
 * 这个filter,接口GenericFilterBean,将实际的过滤工作留给他的子类来完成,这
 * 就导致了他的子类不得不实现doFilter方法。
 * 
 * <p>This generic filter base class has no dependency on the Spring
 * {@link org.springframework.context.ApplicationContext} concept.
 * Filters usually don't load their own context but rather access service
 * beans from the Spring root application context, accessible via the
 * filter's {@link #getServletContext() ServletContext} (see
 * {@link org.springframework.web.context.support.WebApplicationContextUtils}).
 * 接口GenericFilterBean不依赖于Spring的ApplicationContext。Filters通常不会直接读取他们
 * 的容器信息(ApplicationContext concept)而是通过访问spring容器(Spring root application context)
 * 中的service beans来获取,通常是通过调用filter里面的getServletContext() 方法来获取
 * 
 * @author Juergen Hoeller
 * @since 06.12.2003
 * @see #addRequiredProperty
 * @see #initFilterBean
 * @see #doFilter
 */
public abstract class GenericFilterBean implements
        Filter, BeanNameAware, EnvironmentAware, ServletContextAware, InitializingBean, DisposableBean {

    /** Logger available to subclasses */
    protected final Log logger = LogFactory.getLog(getClass());

    /**
     * Set of required properties (Strings) that must be supplied as
     * config parameters to this filter.
     */
    private final Set<String> requiredProperties = new HashSet<String>();

    private FilterConfig filterConfig;

    private String beanName;

    private Environment environment = new StandardServletEnvironment();

    private ServletContext servletContext;


    /**
     * Stores the bean name as defined in the Spring bean factory.
     * <p>Only relevant in case of initialization as bean, to have a name as
     * fallback to the filter name usually provided by a FilterConfig instance.
     * 存储Spring bean工厂中定义的bean名称。
     * 只有在初始化为bean的情况下,才有一个名称作为回退到通常由FilterConfig实例提供的过滤器名称。
     * @see org.springframework.beans.factory.BeanNameAware
     * @see #getFilterName()
     */
    @Override
    public final void setBeanName(String beanName) {
        this.beanName = beanName;
    }

    /**
     * {@inheritDoc}
     * <p>Any environment set here overrides the {@link StandardServletEnvironment}
     * provided by default.
     * <p>This {@code Environment} object is used only for resolving placeholders in
     * resource paths passed into init-parameters for this filter. If no init-params are
     * used, this {@code Environment} can be essentially ignored.
     * 此处设置的任何环境都会覆盖默认提供的{@link StandardServletEnvironment}。
     * 此Environment对象仅用于解析传递给此过滤器的init参数的资源路径中的占位符。 如果没有使用init-params,
     * 则可以基本忽略该代码。
     * 也就说这个方法是解析web.xml中<init-param>的配置的。
     * @see #init(FilterConfig)
     */
    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }

    /**
     * Stores the ServletContext that the bean factory runs in.
     * 存储bean factory运行的ServletContext
     * <p>Only relevant in case of initialization as bean, to have a ServletContext
     * as fallback to the context usually provided by a FilterConfig instance.
     * @see org.springframework.web.context.ServletContextAware
     * @see #getServletContext()
     */
    @Override
    public final void setServletContext(ServletContext servletContext) {
        this.servletContext = servletContext;
    }

    /**
     * Calls the {@code initFilterBean()} method that might
     * contain custom initialization of a subclass.
     * 调用可能包含子类自定义初始化的initFilterBean方法
     * <p>Only relevant in case of initialization as bean, where the
     * standard {@code init(FilterConfig)} method won't be called.
     * 只有在初始化为bean的情况下才调用,标准初始化init方法将不会被调用。
     * @see #initFilterBean()
     * @see #init(javax.servlet.FilterConfig)
     */
    @Override
    public void afterPropertiesSet() throws ServletException {
        initFilterBean();
    }


    /**
     * Subclasses can invoke this method to specify that this property
     * (which must match a JavaBean property they expose) is mandatory,
     * and must be supplied as a config parameter. This should be called
     * from the constructor of a subclass.
     * 子类可以调用这个方法来指定这个属性是强制性的,并且必须提供配置参数。这个必须从子类的构造器调用。
     * <p>This method is only relevant in case of traditional initialization
     * driven by a FilterConfig instance.
     * 此方法仅与由FilterConfig实例驱动的传统初始化相关。
     * @param property name of the required property
     */
    protected final void addRequiredProperty(String property) {
        this.requiredProperties.add(property);
    }

    /**
     * Standard way of initializing this filter.初始化该过滤器的标准方法
     * Map config parameters onto bean properties of this filter, and
     * invoke subclass initialization.
     * 映射配置参数到这个过滤器bean的属性里,并且调用子类的初始化方法。
     * @param filterConfig the configuration for this filter
     * @throws ServletException if bean properties are invalid (or required
     * properties are missing), or if subclass initialization fails.
     * @see #initFilterBean
     */
    @Override
    public final void init(FilterConfig filterConfig) throws ServletException {
        Assert.notNull(filterConfig, "FilterConfig must not be null");
        if (logger.isDebugEnabled()) {
            logger.debug("Initializing filter '" + filterConfig.getFilterName() + "'");
        }

        this.filterConfig = filterConfig;

        // Set bean properties from init parameters.
        try {
            PropertyValues pvs = new FilterConfigPropertyValues(filterConfig, this.requiredProperties);
            BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
            ResourceLoader resourceLoader = new ServletContextResourceLoader(filterConfig.getServletContext());
            bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, this.environment));
            initBeanWrapper(bw);
            bw.setPropertyValues(pvs, true);
        }
        catch (BeansException ex) {
            String msg = "Failed to set bean properties on filter '" +
                filterConfig.getFilterName() + "': " + ex.getMessage();
            logger.error(msg, ex);
            throw new NestedServletException(msg, ex);
        }

        // Let subclasses do whatever initialization they like.
        initFilterBean();

        if (logger.isDebugEnabled()) {
            logger.debug("Filter '" + filterConfig.getFilterName() + "' configured successfully");
        }
    }

    /**
     * Initialize the BeanWrapper for this GenericFilterBean,
     * possibly with custom editors.
     * <p>This default implementation is empty.
     * @param bw the BeanWrapper to initialize
     * @throws BeansException if thrown by BeanWrapper methods
     * @see org.springframework.beans.BeanWrapper#registerCustomEditor
     */
    protected void initBeanWrapper(BeanWrapper bw) throws BeansException {
    }


    /**
     * Make the FilterConfig of this filter available, if any.
     * Analogous to GenericServlet's {@code getServletConfig()}.
     * <p>Public to resemble the {@code getFilterConfig()} method
     * of the Servlet Filter version that shipped with WebLogic 6.1.
     * @return the FilterConfig instance, or {@code null} if none available
     * @see javax.servlet.GenericServlet#getServletConfig()
     */
    public final FilterConfig getFilterConfig() {
        return this.filterConfig;
    }

    /**
     * Make the name of this filter available to subclasses.
     * Analogous to GenericServlet's {@code getServletName()}.
     * <p>Takes the FilterConfig's filter name by default.
     * If initialized as bean in a Spring application context,
     * it falls back to the bean name as defined in the bean factory.
     * @return the filter name, or {@code null} if none available
     * @see javax.servlet.GenericServlet#getServletName()
     * @see javax.servlet.FilterConfig#getFilterName()
     * @see #setBeanName
     */
    protected final String getFilterName() {
        return (this.filterConfig != null ? this.filterConfig.getFilterName() : this.beanName);
    }

    /**
     * Make the ServletContext of this filter available to subclasses.
     * Analogous to GenericServlet's {@code getServletContext()}.
     * <p>Takes the FilterConfig's ServletContext by default.
     * If initialized as bean in a Spring application context,
     * it falls back to the ServletContext that the bean factory runs in.
     * @return the ServletContext instance, or {@code null} if none available
     * @see javax.servlet.GenericServlet#getServletContext()
     * @see javax.servlet.FilterConfig#getServletContext()
     * @see #setServletContext
     */
    protected final ServletContext getServletContext() {
        return (this.filterConfig != null ? this.filterConfig.getServletContext() : this.servletContext);
    }


    /**
     * Subclasses may override this to perform custom initialization.
     * All bean properties of this filter will have been set before this
     * method is invoked.
     * <p>Note: This method will be called from standard filter initialization
     * as well as filter bean initialization in a Spring application context.
     * Filter name and ServletContext will be available in both cases.
     * <p>This default implementation is empty.
     * @throws ServletException if subclass initialization fails
     * @see #getFilterName()
     * @see #getServletContext()
     */
    protected void initFilterBean() throws ServletException {
    }

    /**
     * Subclasses may override this to perform custom filter shutdown.
     * <p>Note: This method will be called from standard filter destruction
     * as well as filter bean destruction in a Spring application context.
     * <p>This default implementation is empty.
     */
    @Override
    public void destroy() {
    }


    /**
     * PropertyValues implementation created from FilterConfig init parameters.
     * 从FilterConfig 初始化参数创建的PropertyValues实现。
     */
    @SuppressWarnings("serial")
    private static class FilterConfigPropertyValues extends MutablePropertyValues {

        /**
         * Create new FilterConfigPropertyValues.
         * @param config FilterConfig we'll use to take PropertyValues from
         * @param requiredProperties set of property names we need, where
         * we can't accept default values
         * @throws ServletException if any required properties are missing
         */
        public FilterConfigPropertyValues(FilterConfig config, Set<String> requiredProperties)
            throws ServletException {

            Set<String> missingProps = (requiredProperties != null && !requiredProperties.isEmpty()) ?
                    new HashSet<String>(requiredProperties) : null;

            Enumeration<?> en = config.getInitParameterNames();
            while (en.hasMoreElements()) {
                String property = (String) en.nextElement();
                Object value = config.getInitParameter(property);
                addPropertyValue(new PropertyValue(property, value));
                if (missingProps != null) {
                    missingProps.remove(property);
                }
            }

            // Fail if we are still missing properties.
            if (missingProps != null && missingProps.size() > 0) {
                throw new ServletException(
                    "Initialization from FilterConfig for filter '" + config.getFilterName() +
                    "' failed; the following required properties were missing: " +
                    StringUtils.collectionToDelimitedString(missingProps, ", "));
            }
        }
    }

}