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>
从浏览器进入到添加用户的界面
点击”添加”打印到页面上都是乱码了。
控制台打印输出是正常正文显示
现在添加字符编码过滤器
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(“”);
然后再次请求,就能正常显示中文了。
进行到这一步,对这个过滤器的实现有了一定的了解。这是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
此时控制台打印显示经过了过滤器,但是没有进行过滤操作。
输入账号信息登录
跳转到首页index.jsp
这次跳转其实进行了一个操作,控制器里有个添加请求的session的操作
request.getSession().setAttribute(“user”, user);
将这个账号信息保存到了session里,这个session 能够保持到浏览器关闭之前。
比如我这时候请求一个进行过滤操作的uri
此时控制台打印消息:
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, ", "));
}
}
}
}