Filter
是Servlet
提供支持的,用于Web
环境,并不属于Spring
,所以Sping
需要对Filter
做一些处理,使之成为受Spring
管理的Bean
,来融入IoC 容器
中。
引入 spring-web
依赖:
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webartifactId>
<version>*.*.*version>
dependency>
Spring
(使用版本:5.3.7
) 中对 Filter
的实现在 org.springframework.web.filter
包中:
可以看到两个主要的抽象类 GenericFilterBean
和 OncePerRequestFilter
,以及一些常见的 Filter
如:CorsFilter
、CharacterEncodingFilter
等
GenericFilterBean
是一个抽象类,用于提供 Filter
在 Spring
IoC 容器
中接入的通用处理。
public abstract class GenericFilterBean implements Filter, BeanNameAware, EnvironmentAware,
EnvironmentCapable, ServletContextAware, InitializingBean, DisposableBean {
...
@Override
public void destroy() {
}
@Override
public final void init(FilterConfig filterConfig) throws ServletException {
Assert.notNull(filterConfig, "FilterConfig must not be null");
this.filterConfig = filterConfig;
// Set bean properties from init parameters.
PropertyValues pvs = new FilterConfigPropertyValues(filterConfig, this.requiredProperties);
if (!pvs.isEmpty()) {
try {
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(filterConfig.getServletContext());
Environment env = this.environment;
if (env == null) {
env = new StandardServletEnvironment();
}
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, (PropertyResolver)env));
this.initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
} catch (BeansException var6) {
...
}
}
// Let subclasses do whatever initialization they like.
initFilterBean();
...
}
protected void initFilterBean() throws ServletException {
}
...
}
从源码中可以看到:GenericFilterBean
实现了 Filter
的 init()
和 destroy()
方法,doFilter()
方法交给子类实现;其中 init()
方法主要是将用户自定义的一些配置(FilterConfig
:用来配置 Filter
的 name
和 param
等)映射到 BeanWrapper
中,然后调用 initFilterBean()
方法,这个方法也交给子类实现,目前实现这个方法的只有 DelegatingFilterProxy
。
接下来说一下继承自
GenericFilterBean
的两个重要的类OncePerRequestFilter
和DelegatingFilterProxy
OncePerRequestFilter
public abstract class OncePerRequestFilter extends GenericFilterBean {
public static final String ALREADY_FILTERED_SUFFIX = ".FILTERED";
public OncePerRequestFilter() {
}
public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException {
if (request instanceof HttpServletRequest && response instanceof HttpServletResponse) {
HttpServletRequest httpRequest = (HttpServletRequest)request;
HttpServletResponse httpResponse = (HttpServletResponse)response;
String alreadyFilteredAttributeName = this.getAlreadyFilteredAttributeName();
boolean hasAlreadyFilteredAttribute = request.getAttribute(alreadyFilteredAttributeName) != null;
if (!this.skipDispatch(httpRequest) && !this.shouldNotFilter(httpRequest)) {
if (hasAlreadyFilteredAttribute) {
if (DispatcherType.ERROR.equals(request.getDispatcherType())) {
this.doFilterNestedErrorDispatch(httpRequest, httpResponse, filterChain);
return;
}
filterChain.doFilter(request, response);
} else {
request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);
try {
this.doFilterInternal(httpRequest, httpResponse, filterChain);
} finally {
request.removeAttribute(alreadyFilteredAttributeName);
}
}
} else {
filterChain.doFilter(request, response);
}
} else {
throw new ServletException("OncePerRequestFilter just supports HTTP requests");
}
}
protected abstract void doFilterInternal(HttpServletRequest var1, HttpServletResponse var2, FilterChain var3) throws ServletException, IOException;
}
源码注释中有这么一段话:
OncePerRequestFilter
是一个 Filter
基类,保证了每个请求只执行一次!!!
,防止内部请求进行转发或重定向时再次执行这个过滤器(在 SpringSecurity
过滤器链中用的比较多)。
DelegatingFilterProxy
我们知道 Filter
是 Servlet
提供的组件,可以脱离 Spring
单独使用,但是我们一般是在 SpringMVC
环境下进行 Web
开发,所以 Spring
提供了 DelegatingFilterProxy
类将 Web
体系中的 Filter
的 doFilter()
指向一个从 spring
上下文获取的 bean
,最终调用的是该 bean
的 doFilter()
,以后用的都是这个 bean
而不是原生 Web
体系的 Filter
,也正是因为是一个 bean
,所以才可以使用 @AutoWired
注入 spring
bean
。将我们自定义的 Filter
创建到 Spring
的上下文中,又能集成到 web
容器的 filterChain
上。
public class DelegatingFilterProxy extends GenericFilterBean {
...
@Nullable
private WebApplicationContext webApplicationContext;
@Nullable
private String targetBeanName;
...
public DelegatingFilterProxy(String targetBeanName) {
this(targetBeanName, (WebApplicationContext)null);
}
...
protected void initFilterBean() throws ServletException {
synchronized(this.delegateMonitor) {
if (this.delegate == null) {
if (this.targetBeanName == null) {
this.targetBeanName = this.getFilterName();
}
WebApplicationContext wac = this.findWebApplicationContext();
if (wac != null) {
this.delegate = this.initDelegate(wac);
}
}
}
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException {
Filter delegateToUse = this.delegate;
if (delegateToUse == null) {
synchronized(this.delegateMonitor) {
delegateToUse = this.delegate;
if (delegateToUse == null) {
WebApplicationContext wac = this.findWebApplicationContext();
if (wac == null) {
throw new IllegalStateException("No WebApplicationContext found: no ContextLoaderListener or DispatcherServlet registered?");
}
delegateToUse = this.initDelegate(wac);
}
this.delegate = delegateToUse;
}
}
// 执行实际的 Filter 的 doFilter() 方法
this.invokeDelegate(delegateToUse, request, response, filterChain);
}
@Nullable
protected WebApplicationContext findWebApplicationContext() {
// 获取 WebApplicationContext
...
}
// 从 Spring IoC 容器中获取名称为 targetBeanName,类型为 Filter 的 Bean
protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
String targetBeanName = this.getTargetBeanName();
Assert.state(targetBeanName != null, "No target bean name set");
Filter delegate = (Filter)wac.getBean(targetBeanName, Filter.class);
if (this.isTargetFilterLifecycle()) {
// 这里的 targetFilterLifecycle 默认是 false,如果是 true,这里会执行 Filter 的 init 方法,如果我们自定义的 Filter 是实现的顶层接口 Filter,那么必须实现 init 和 destroy
// 如果是继承了 GenericFilterBean,那么就不用,因为 GenericFilterBean 实现了 init 和 destroy
delegate.init(this.getFilterConfig());
}
return delegate;
}
...
}
具体使用方法见下面
拓展
中实际场景
在 SpringMVC
环境下配置 Filter
有两种方式:
关于
Servlet 3.0
,如果不了解可以先阅读 Java - servlet 3.0 这篇文章
1、使用 Servlet 3.0
之后提供的 @WebFilter
注解(Servlet 3.0
之前也可以通过 web.xml
配置的方式)
@WebFilter(filterName = "vloggerFilter", urlPatterns = "/*")
public class LoggerFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("init loggerFilter...");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("loggerFilter before doFilter");
chain.doFilter(request, response);
System.out.println("loggerFilter after doFilter");
}
@Override
public void destroy() {
System.out.println("destroy loggerFilter...");
}
}
servlet
容器启动之后,会扫描类路径下标注了 @WebFilter
注解的 Filter
并初始化。执行请求时,在匹配到指定的路径规则后执行 doFilter
方法
2、使用 Servlet 3.0
之后提供的插件化能力:定义一个配置类实现 WebApplicationInitializer
获取到 ServletContext
来注册 Filter
、定义一个配置类继承 AbstractAnnotationConfigDispatcherServletInitializer
实现层级上下文
关于
SpringMVC
集成Servlet 3.0
,如果不了解可以先阅读 SpringMVC - 以 Servlet 3.0 的方式搭建 SSM 框架 这篇文章
首先定义一个 LoggerFilter
:
public class LoggerFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
}
}
WebApplicationInitializer
,重写 onStartUp()
方法,使用 servletContext
中 FilterRegistration
来注册 Filter
:@Configuration
public class MyConfig implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
FilterRegistration.Dynamic myFilter = servletContext.addFilter("myFilter", LoggerFilter.class);
myFilter.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*");
}
}
AbstractAnnotationConfigDispatcherServletInitializer
后重写 getServletFilters()
方法来注册 Filter
:public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{SpringConfig.class};
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{WebMvcConfig.class};
}
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
@Override
protected Filter[] getServletFilters() {
LoggerFilter loggerFilter = new LoggerFilter();
return new Filter[]{
loggerFilter
};
}
}
SpringMVC
环境下在自定义 Filter
中无法注入 Bean
的问题,因为 Filter
的初始化是在 Spring
IoC 容器
初始化之前创建的,比如上面的 loggerFilter
中注入一个 Bean
:
@WebFilter(filterName = "loggerFilter", urlPatterns = "/")
public class LoggerFilter implements Filter {
@Autowired
private JdbcConsts jdbcConsts; // null
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("init loggerFilter...");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("loggerFilter before doFilter");
System.out.println(jdbcConsts);
chain.doFilter(request, response);
System.out.println("loggerFilter after doFilter");
}
@Override
public void destroy() {
System.out.println("destroy loggerFilter...");
}
}
使用 @WebFilter
配置的方式无法解决这个问题,所以在 SpringMVC
环境中如果要注入 Bean
,就只能使用上面第二种方式:
// 必须加入到 IoC 容器中
@Component
// 不能使用 @WebFilter 注解
//@WebFilter(filterName = "loggerFilter", urlPatterns = "/")
public class LoggerFilter implements Filter {
@Autowired
private JdbcConsts jdbcConsts;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("init loggerFilter...");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("loggerFilter before doFilter");
System.out.println(jdbcConsts);
chain.doFilter(request, response);
System.out.println("loggerFilter after doFilter");
}
@Override
public void destroy() {
System.out.println("destroy loggerFilter...");
}
}
使用 DelegatingFilterProxy
解决问题:
@Override
protected Filter[] getServletFilters() {
DelegatingFilterProxy delegatingFilterProxy = new DelegatingFilterProxy();
// 这里的 targerBeanName 就是上面 Filter 在 Ioc 容器中的 名称
delegatingFilterProxy.setTargetBeanName("loggerFilter");
delegatingFilterProxy.setTargetFilterLifecycle(true);
return new Filter[]{
delegatingFilterProxy
};
}