一直以来没有仔细研究过Filter和Intercepter,意识到过滤器的拦截器的认识很模糊。所以仔细学习了一下。其实还是很简单的。本来想把Filter和Interceptor写在一起,文章太长了还是分两篇写吧~
区别
过滤器和拦截器最大的区别在于:过滤器是servlet的规范规定的,只能用于过滤请求,而interceptor是Spring里面基于切面编程的一种实现。
过滤器作用于请求到达servlet之前,在spring中也就是在dispacherServlet之前。而拦截器最早只能作用于请求到达servlet之后。
基于这个特点,过滤器经常被用于处理请求的编码格式,session,日志等。
业务逻辑大多实现在拦截器中。
Filter
Spring的过滤器在Spring-web的包里。
现有的filter类结构图如下,这是直接从网上找的图,懒得自己再画了。
通常实现拦截器的方式:
- 实现
javax.servlet.Filter
接口,Filter
是servlet包的定义的接口,现有的这种实现方法有CompositeFilter
- 继承抽象类
GenericFilterBean
这个类实现了Filter接口。现有的实现有DelegatingFilterProxy
- 继承抽象类
OncePerRequestFilter
这个类是GenericFilterBean
的子类。现有的实现类有:CharacterEncodingFilter
、HiddenHttpMethodFilter
、HttpPutFormContentFilter
、RequestContextFilter
和ShallowEtagHeaderFilter
- 继承抽象类
AbstractRequestLoggingFilter
,该类为OncePerRequestFilter
的直接子类,这一类过滤器包括CommonsRequestLoggingFilter
、Log4jNestedDiagnosticContextFilter
和ServletContextRequestLoggingFilter
。
GenericFilterBean
从类结构图可以看到,这个类是Spring中直接实现了servlet的Filter的抽象类。所有的Filter都是基于这个类来实现的。
类的定义如下(Spring4.3):
public abstract class GenericFilterBean implements Filter, BeanNameAware, EnvironmentAware, EnvironmentCapable, ServletContextAware, InitializingBean, DisposableBean {...}
实现的接口有:
- Filter:过滤器的实现
- BeanNameAware:setBeanName方法,便于Bean管理器生成Bean
- EnvironmentAware:用于指明Bean的运行环境
- EnvironmentCapable:用于获取bean的运行环境
- ServletContextAware:实现该接口的setServletContextAware方法,指明ServletContext
- InitializingBean:实现该接口的afterPropertiesSet方法,在Bean初始化时设置属性
- DisposableBean :实现该接口的destroy方法,用于回收资源。
先后执行的方法为:init-doFilter-destroy。
filter可以在request执行之前进行处理,也可以在request执行之后进行处理。只需要在调用filterChain的doFilter方法之前和之后加入自己的处理逻辑就可以了。
没有实现Filter接口的doFilter方法,是在子类OncePerRequestFilter
和DelegatingFilterProxy
中实现的。接下来看它的两个子类。
OncePerRequestFilter
OncePerRequestFilter
依然是一个抽象类。实现了doFilter方法。但是真正要实现的逻辑在doFilterInternal()
方法中,这个方法由子类自己实现。所以如果要通过实现OncePerRequestFilter
类来实现过滤器,需要实现doFilterInternal()
方法
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 (!hasAlreadyFilteredAttribute && !this.skipDispatch(httpRequest) && !this.shouldNotFilter(httpRequest)) {
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");
}
}
DelegatingFilterProxy
这个类是具体的实现类,不是抽象的。
因为Filter是servlet容器规范的,会在servlet启动的时候就初始化,这个时候Spring的context还没有被初始化。当我们需要在Filter里面初始化一些spring的bean或者spring的特性时默认的Filter实现就不行。
比如我们如果要在Filter里注入一个用于鉴权的Service,因为在Filter初始化时spring容器还没有启动,因此这样不能实现我们的需求,这种情况下DelegatingFilterProxy可以实现我们的需求,它可以作为Filter在servlet容器中被启动,在使用的时候再去spring容器中获取被代理的对象。这样被代理对象的初始化就可以和普通spring的bean一样使用一些复杂的依赖注入等。
从类名可以看出,是一个委派过滤器代理。它作为一个过滤器没有实现任何逻辑功能。只是执行它所代理的类的方法。
看一下类的申明:
public class DelegatingFilterProxy extends GenericFilterBean {
private String contextAttribute;
private WebApplicationContext webApplicationContext;
private String targetBeanName;
private boolean targetFilterLifecycle;
private volatile Filter delegate;
private final Object delegateMonitor;
...
}
在使用时,传入targetBeanName,它会通过这个beanName找到对用的bean,然后在doFilter中调用这个bean的doFilter方法。
具体:
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException {
Filter delegateToUse = this.delegate;
if (delegateToUse == null) {
Object var5 = this.delegateMonitor;
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?");
}
//从spring容器中获取bean
delegateToUse = this.initDelegate(wac);
}
this.delegate = delegateToUse;
}
}
//调用被代理类的doFilter方法
this.invokeDelegate(delegateToUse, request, response, filterChain);
}
可以看出,如果要通过DelegatingFilterProxy
来实现过滤器,可以自己定义一个类实现Filter接口,把这个类的beanName注入到DelegatingFilterProxy的申明中或者filter-name和bean的name相同:
<filter>
<filter-name>myFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>myFilter</filter-name>
<url-pattern>*.htm</url-pattern>
</filter-mapping>
或者
<filter>
<filter-name>myFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<param-name>targetBeanName</param-name>
<param-value>myFilter</param-value>
</init-param>
</filter>
filter的生命周期的方法Filter.init和Filter.destory是由servlet容器来执行的,对于这个代理过滤器,init和destroy 方法默认是不会调用被代理对象来执行的。如果希望spring容器来管理被代理对象的这些生命周期,需要把targetFilterLifecycle参数设置为true(默认是false),这样当servlet容器执行init和destory方法时DelegatingFilterProxy也会代理给被代理对象。
spring对targetFilterLifecycle参数的解释:
Default is “false”; target beans usually rely on the Spring application context for managing their lifecycle. Setting this flag to “true” means that the servlet container will control the lifecycle of the target Filter, with this proxy delegating the corresponding calls.
Spring官方的解释:
Proxy for a standard Servlet Filter, delegating to a Spring-managed bean that implements the Filter interface. Supports a “targetBeanName” filter init-param in web.xml, specifying the name of the target bean in the Spring application context.
web.xml will usually contain a DelegatingFilterProxy definition, with the specified filter-name corresponding to a bean name in Spring’s root application context. All calls to the filter proxy will then be delegated to that bean in the Spring context, which is required to implement the standard Servlet Filter interface.
This approach is particularly useful for Filter implementation with complex setup needs, allowing to apply the full Spring bean definition machinery to Filter instances. Alternatively, consider standard Filter setup in combination with looking up service beans from the Spring root application context.
NOTE: The lifecycle methods defined by the Servlet Filter interface will by default not be delegated to the target bean, relying on the Spring application context to manage the lifecycle of that bean. Specifying the “targetFilterLifecycle” filter init-param as “true” will enforce invocation of the Filter.init and Filter.destroy lifecycle methods on the target bean, letting the servlet container manage the filter lifecycle.
As of Spring 3.1, DelegatingFilterProxy has been updated to optionally accept constructor parameters when using Servlet 3.0’s instance-based filter registration methods, usually in conjunction with Spring 3.1’s WebApplicationInitializer SPI. These constructors allow for providing the delegate Filter bean directly, or providing the application context and bean name to fetch, avoiding the need to look up the application context from the ServletContext.