上篇分析了Interceptor(拦截器),今天继续对Filter(过滤器)做一个分析。
何为过滤器
Filter是J2EE中来的,可以看做是Servlet的一种“加强版”,它主要用于对用户请求进行预处理和后处理,拥有一个典型的处理链。Filter也可以对用户请求生成响应,这一点与Servlet相同,但实际上很少会使用Filter向用户请求生成响应。使用Filter完整的流程是:Filter对用户请求进行预处理,接着将请求交给Servlet进行预处理并生成响应,最后Filter再对服务器响应进行后处理。
通过Filter技术,开发人员可以实现用户在访问某个目标资源之前,对访问的请求和响应进行拦截过滤。
过滤器作用
在JavaDoc中给出了几种过滤器的作用:
* Examples that have been identified for this design are
* 1) Authentication Filters, 即用户访问权限过滤
* 2) Logging and Auditing Filters, 日志过滤,可以记录特殊用户的特殊请求的记录等
* 3) Image conversion Filters,图像转换过滤器
* 4) Data compression Filters ,数据转换
* 5) Encryption Filters ,安全加密
* 6) Tokenizing Filters ,词法分析
* 7) Filters that trigger resource access events ,资源访问事件触发过滤器
* 8) XSL/T filters
* 9) Mime-type chain Filter ,文件类型链过滤器
过滤器的生命周期
Filter的生命周期:
Filter的创建:
Filter的创建和销毁由WEB服务器负责。 web 应用程序启动时,web 服务器将创建Filter 的实例对象,并调用其init方法,完成对象的初始化功能,从而为后续的用户请求作好拦截的准备工作,filter对象只会创建一次,init方法也只会执行一次。通过init方法的参数,可获得代表当前filter配置信息的FilterConfig对象。
Filter的销毁:
Web容器调用destroy方法销毁Filter。destroy方法在Filter的生命周期中仅执行一次。在destroy方法中,可以释放过滤器使用的资源。
FilterConfig接口:
用户在配置filter时,可以使用
String getFilterName():得到filter的名称。
String getInitParameter(String name): 返回在部署描述中指定名称的初始化参数的值。如果不存在返回null.
Enumeration getInitParameterNames():返回过滤器的所有初始化参数的名字的枚举集合。
public ServletContext getServletContext():返回Servlet上下文对象的引用
过滤器、拦截器和切面
Filter过滤器:拦截web访问url地址。依赖于servlet容器。在实现上基于函数回调,可以对几乎所有请求进行过滤,但是缺点是一个过滤器实例只能在容器初始化时调用一次。使用过滤器的目的是用来做一些过滤操作,获取我们想要获取的数据。比如:在过滤器中修改字符编码;在过滤器中修改HttpServletRequest的一些参数,包括:过滤低俗文字、危险字符等。
Interceptor拦截器:拦截以 .action结尾的url,拦截Action的访问。依赖于web框架,在SpringMVC中就是依赖于SpringMVC框架。在实现上基于Java的反射机制,属于面向切面编程(AOP)的一种运用。由于拦截器是基于Web框架的调用。因此可以使用spring的依赖注入(DI)进行一些业务操作,同时一个拦截器实例在一个controller生命周期之内可以多次调用。但是缺点是只能对controller请求进行拦截,对其他的一些比如直接访问静态资源的请求则没办法进行拦截处理。
Spring AOP拦截器:只能拦截Spring管理Bean的访问(业务层Service)
Filter与Interceptor联系与区别
- 拦截器是基于java的反射机制,使用代理模式,而过滤器是基于函数回调。
- 拦截器不依赖servlet容器,过滤器依赖于servlet容器。
- 拦截器只能对action起作用,而过滤器可以对几乎所有的请求起作用(可以保护资源)。
- 拦截器可以访问action上下文,堆栈里面的对象,而过滤器不可以。
调用顺序:
如上图,展示了三者的调用顺序Filter->Interceptor->Aspect->Controller。相反的是,当Controller抛出的异常的处理顺序则是从内到外的。因此我们总是定义一个注解@ControllerAdvice去统一处理控制器抛出的异常。如果一旦异常被@ControllerAdvice处理了,则调用拦截器的afterCompletion方法的参数Exception ex就为空了。
使用过滤器
根据 Filter 注册方式的不同,有注解、配置两种使用方式。若使用的是 Servlet3.0+版本,则两种方式均可使用;若使用的是 Servlet2.5版本,则只能使用配置类方式。
自定义的过滤器都必须实现javax.Servlet.Filter接口,并重写接口中定义的三个方法:
void init(FilterConfig config):用于完成Filter的初始化。
void destory():用于Filter销毁前,完成某些资源的回收。
void doFilter(ServletRequest request,ServletResponse response,FilterChain chain):实现过滤功能,即对每个请求及响应增加的额外的预处理和后处理。执行该方法之前,即对用户请求进行预处理;执行该方法之后,即对服务器响应进行后处理。值得注意的是,chain.doFilter()方法执行之前为预处理阶段,该方法执行结束即代表用户的请求已经得到控制器处理。因此,如果在doFilter中忘记调用chain.doFilter()方法,则用户的请求将得不到处理。
1、配置方式实现过滤器:
先自定义filter实现Filter接口
import lombok.extern.slf4j.Slf4j;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Slf4j
public class AccesLogFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
log.info("AccesLogFilter init");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
String requesturi = request.getRequestURI();
log.info("Request URI:{}",requesturi);
filterChain.doFilter(request,response);
}
@Override
public void destroy() {
log.info("AccesLogFilter destroy");
}
}
再修改配置类
import com.example.demo.filter.AccesLogFilter;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@Bean
public FilterRegistrationBean registration() {
//创建filter
AccesLogFilter accesLogFilter = new AccesLogFilter();
//注册过滤器
FilterRegistrationBean registration = new FilterRegistrationBean<>(accesLogFilter);
//添加条件
registration.addUrlPatterns("/*");
registration.setOrder(1);
return registration;
}
}
2、注解方式实现过滤器
自定义filter实现Filter接口。
@Order(1):表示过滤器的顺序,假设我们有多个过滤器,你如何确定过滤器的执行顺序?这个注解就是规定过滤器的顺序。
@WebFilter:表示这个class是过滤器。里面的参数,filterName 为过滤器名字,urlPatterns 为过滤器的范围,initParams 为过滤器初始化参数。更多具体参数见下表
import lombok.extern.slf4j.Slf4j;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Order(1)
@WebFilter(filterName = "AccessLogFilter", urlPatterns = "/*" , initParams = {
@WebInitParam(name = "URL", value = "http://localhost:8080")})
@Slf4j
public class AccessLogFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
log.info("AccesLogFilter init");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
String requesturi = request.getRequestURI();
log.info("Request URI:{}",requesturi);
filterChain.doFilter(request,response);
}
@Override
public void destroy() {
log.info("AccesLogFilter destroy");
}
}
修改springboot启动入口:
在SpringBootApplication上使用@ServletComponentScan注解后,Servlet、Filter、Listener可以直接通过@WebServlet、@WebFilter、@WebListener注解自动注册。
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;
@SpringBootApplication
@ServletComponentScan(basePackages="com.example.demo.filter")
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
还可以通过@Component注解标注为组件自动注入bean。只要当前类在@ComponentScan的扫描范围内,就会自动注入此Filter,拦截路径为/*,拦截所有。
@Component
public class AccessLogFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
log.info("AccesLogFilter init");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
String requesturi = request.getRequestURI();
log.info("Request URI:{}",requesturi);
filterChain.doFilter(request,response);
}
@Override
public void destroy() {
log.info("AccesLogFilter destroy");
}
}
Filter源码
直接上翻译后的注释
package javax.servlet;
import java.io.IOException;
/**
* 过滤器是指拦截请求,并对传给被请求资源的ServletRequest 或 ServletResponse 进行处理的一个对象。
* Examples that have been identified for this design are
* 1) Authentication Filters, 即用户访问权限过滤
* 2) Logging and Auditing Filters, 日志过滤,可以记录特殊用户的特殊请求的记录等
* 3) Image conversion Filters,图像转换过滤器
* 4) Data compression Filters ,数据转换
* 5) Encryption Filters ,安全加密
* 6) Tokenizing Filters ,词法分析
* 7) Filters that trigger resource access events ,资源访问事件触发过滤器
* 8) XSL/T filters
* 9) Mime-type chain Filter ,文件类型链过滤器
*/
public interface Filter {
/**
*当过滤器启动服务的时候,比如应用程序启动时,servlet容器就会调用init方法。换句话说,
*不用等到调用与被过滤器相关的资源之后,才调用init方法。这个方法只调用一次,并且应该
*包含该过滤器的初始化代码。
*init方法的签名如下:
*void init(FilterConfig filterConfig)
*注意:servlet容器给init方法传递了一个FilterConfig。
*/
public void init(FilterConfig filterConfig) throws ServletException;
/**
* 每次调用与过滤器相关的资源时,servlet容器都会调用Filter实例的doFilter方法。该方法会
*收到一个ServletRequest、ServletResponse和FilterChain。
*doFilter方法的签名如下:
*public void doFilter(ServletRequest arg0, ServletResponse arg1, FilterChain arg2)
*doFilter的实现可以访问ServletRequest 和 ServletResponse。因此,可以在
*ServletRequest中添加属性,或者在ServletResponse中添加一个标头,甚至可以对
*ServletRequest 或 ServletResponse进行修复,改变它们的行为。
*
*doFilter方法实现中最后一行代码应该是调用FilterChain中的doFilter方法,
*FilterChain.doFilter方法签名为
*public void doFilter(ServletRequest request, ServletResponse response)
*一个资源可以与多个过滤器关联,FilterChain.doFilter( ) 通常会引发调用链中的下一个过滤
*器被调用。在链中的最后一个过滤器中调用FilterChain.doFilter( )会引发资源本身被调用。
*如果你没有在 Filter.doFilter( )方法实现代码的最后调用FilterChain.doFilter( )方法,那么程
*序的处理将会在这里停止,并且不会调用请求。
*注意,doFilter方法是FilterChain接口中唯一的方法,它与Filter中的doFilter方法不同。在
*FilterChain中,doFilter只有两个参数,而不是三个。
*/
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain)
throws IOException, ServletException;
/**
* Filter中最后一个生命周期方法是destory,其方法签名为
* destroy()
*这个方法在过滤器即将终止服务之前,由Servlet调用,销毁过滤器对象,一般发生在应用程序停止的时候。
*/
public void destroy();
}
由翻译来的源码注释可见,在应用程序启动时,servlet容器就会调用init方法。然后每次调用与过滤器相关的资源时,servlet容器都会调用doFilter方法,若是doFilter方法调用了FilterChain.doFilter方法,那么会引发调用链中的下一个过滤器被调用,一直到最后。当应用程序停止的时候,servlet会调用destroy(),销毁过滤器对象。
Filter的注册原理
servlet可以通过ServeltContext来注册Filter(当然还包括Servlet、Listener)到Servlet容器;至于注册到Servlet容器后,容器内部如何处理Filter以后再写。现在先这样认为:通过ServletContext注册Filter到容器,那么Filter就能起到过滤作用了。那么问题来了,springboot是如何将Filter注册到容器的?
在SpringBoot应用来说,是自身启动了一个Servlet引擎,并且需要创建一个与应用关联ServletContext对象绑定到Servlet引擎,从而使得Servlet引擎接收到请求可以分发到该应用来处理。
ServletContext内部通常会包含Servlet规范中的Servlet,Filter,Listener等组件,而将这些组件注册到ServletContext,在SpringBoot中主要通过三步来完成,分别是:
1、在应用代码定义和配置组件;
2、应用启动,获取这些组件,并生成对应的BeanDefinition注册到Spring容器;
3、从Spring容器取出这些组件Bean(取出过程中完成有BeanFactory调用getBean方法,基于BeanDefinition完成Bean对象的创建)并绑定到该ServletContext中。
RegistrationBean是SpringBoot提供的一个抽象类,是ServletContextInitializer接口的实现类,故在应用启动创建应用对应的内嵌的ServletContext时,会从Spring容器获取已经加载好的ServletContextInitializer接口实现类对象,然后对ServletContext进行初始化。
注意:
对于两种实现方式(配置类和注解类)来说,其实底层的实现都是一样的,都是基于RegistrationBean实现的,只是注解这种方式是SpringBoot在内部完成封装,而配置类方式是在应用代码显示使用FilterRegistrationBean(实现了RegistrationBean接口)的实现类进行操作。接下来分别看下两种方式:
先来看FilterRegistrationBean配置类方式:
FilterRegistrationBean:相当于Servlet 3.0+的ServletContext#addFilter(String, Filter)方法,主要用于自定义Filter过滤器来添加到当前的ServletContext,对请求进行过滤,源码如下:
/**
* A {@link ServletContextInitializer} to register {@link Filter}s in a Servlet 3.0+
* container. Similar to the {@link ServletContext#addFilter(String, Filter) registration}
* features provided by {@link ServletContext} but with a Spring Bean friendly design.
*
* The {@link #setFilter(Filter) Filter} must be specified before calling
* {@link #onStartup(ServletContext)}. Registrations can be associated with
* {@link #setUrlPatterns URL patterns} and/or servlets (either by {@link #setServletNames
* name} or via a {@link #setServletRegistrationBeans ServletRegistrationBean}s. When no
* URL pattern or servlets are specified the filter will be associated to '/*'. The filter
* name will be deduced if not specified.
*
* @param the type of {@link Filter} to register
* @author Phillip Webb
* @since 1.4.0
* @see ServletContextInitializer
* @see ServletContext#addFilter(String, Filter)
* @see DelegatingFilterProxyRegistrationBean
*/
public class FilterRegistrationBean
extends AbstractFilterRegistrationBean {
/**
* Filters that wrap the servlet request should be ordered less than or equal to this.
* @deprecated since 2.1.0 in favor of
* {@code OrderedFilter.REQUEST_WRAPPER_FILTER_MAX_ORDER}
*/
@Deprecated
public static final int REQUEST_WRAPPER_FILTER_MAX_ORDER = AbstractFilterRegistrationBean.REQUEST_WRAPPER_FILTER_MAX_ORDER;
private T filter;
...
}
FilterRegistrationBean的类描述:一个用于向Servlet 3.0+容器注册Filter的ServletContextInitializer,类似于ServletContext提供的ServletContext#addFilter(String,Filter)注册功能,但具有Spring Bean特性的友好设计。相当于对ServletContext#addFilter进行了spring bean的友好性适配,本质还是ServletContext#addFilter。
从类继承图也可以看出来,FilterRegistrationBean是实现类RegistrationBean的。
再来看看注解实现方式:
这种方式用@WebFilter和注解扫描@ServletComponentScan实现。
可以使用@WebServlet,@WebFilter,@WebListener注解运用在对应的组件类上面,注意组件类自身实现基于Servlet规范,如实现Filter接口,ServletListener接口等。然后需要在@Configuration注解的配置类中,加上@ServletComponentScan注解,用于扫描@WebServlet,@WebFilter,@WebListener这些注解的类来注册到Spring容器中。
其中创建BeanDefinition注册到Spring容器时,也是使用RegistrationBean的实现类,即ServletRegistrationBean,FilterRegistrationBean,ServletListenerRegistrationBean,对组件进行封装的,故也是基于RegistrationBean实现的,只是SpringBoot在内部完成封装,而不需要像上面一种方式一样在应用代码显示使用以上三个RegistrationBean的实现类进行操作,使用@WebServlet,@WebFilter,@WebListener注解定义,使用@ServletComponentScan来扫描即可。
接下来正式走注册流程:
Spring容器是通过ApplicationContext的refresh方法来定义启动步骤的。
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
...
}
}
依次按顺序执行:
1:obtainFreshBeanFactory:创建BeanFactory和加载BeanDefintion;
2:invokeBeanFactoryPostProcessors:调用BeanFactoryPostProcessor,即BeanFactory后置处理器。ComponentScan进行相关类扫描是在这里完成的。以上两种方法创建Servlet,Filter和Listener,对应的BeanDefinition的创建并注册到BeanFactory是在这步完成的;
3:onRefresh:完成有特殊功能的bean实例的创建。从BeanFactory获取Servlet,Filter和Listener对应的BeanDefinition并创建Bean对象实例,然后绑定到ServletContext是在这步完成的。
Filter(包括Servlet和Listener)对应的BeanDefinition的创建并注册到BeanFactory这一步详细源码要分两种实现方式(配置法和注解法)来讨论,回头我会另起一篇来说。(链接:待定)
那么在注册后,接下来ApplicationContext的refresh方法会调用onRefresh方法,在这个方法中注册具有特殊含义的bean对象。
(先去spring的beanFactory中获取ServletContextInitializer的全部实例,并将其放入到ServletContextInitializerBeans的initializers中,然后遍历initializers,调用每个ServletContextInitializer的onStartup方法)
创建和启动应用内嵌的Servlet引擎WebServer,创建内嵌的ServletContext对象绑定到WebServer,创建Servlet,Filter和Listener对应的bean对象绑定到ServletContext就是在ServletWebServerApplicationContext类的onRefresh方法实现的。onRefresh方法的实现如下:
@Override
protected void onRefresh() {
super.onRefresh();
try {
createWebServer();
}
catch (Throwable ex) {
throw new ApplicationContextException("Unable to start web server", ex);
}
}
@Override
protected void finishRefresh() {
super.finishRefresh();
WebServer webServer = startWebServer();
if (webServer != null) {
publishEvent(new ServletWebServerInitializedEvent(webServer, this));
}
}
private void createWebServer() {
WebServer webServer = this.webServer;
ServletContext servletContext = getServletContext();
if (webServer == null && servletContext == null) {
ServletWebServerFactory factory = getWebServerFactory();
// 创建webServer,ServletContext,
// 以及获取ServletContext的ServletContextInitializer
// 并执行其onStartup方法
this.webServer = factory.getWebServer(getSelfInitializer());
}
else if (servletContext != null) {
try {
getSelfInitializer().onStartup(servletContext);
}
catch (ServletException ex) {
throw new ApplicationContextException("Cannot initialize servlet context",
ex);
}
}
initPropertySources();
}
// getSelfInitializer调用该方法
// 从BeanFactory获取ServletContextInitializer接口的实现类
private void selfInitialize(ServletContext servletContext) throws ServletException {
prepareWebApplicationContext(servletContext);
registerApplicationScope(servletContext);
WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(),
servletContext);
// 在getServletContextInitializerBeans方法内部实现:
// 从BeanDefiniton创建bean对象实例,具体为调用了BeanFactory的getBean
for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
beans.onStartup(servletContext);
}
}
从以上源码可知,在selfInitialize方法中,调用getServletContextInitializerBeans方法来从BeanFactory获取ServletContextInitializer接口的实现类,创建bean对象实例并执行onStartup方法。
其中RegistrationBean就实现了 ServletContextInitializer接口。RegistrationBean的onStartup方法实现如下:具体由子类实现register方法完成业务逻辑。对Servlet规范相关的Servlet,Filter,Listener,则是绑定到ServletContext。
public abstract class RegistrationBean implements ServletContextInitializer, Ordered {
private boolean enabled = true;
@Override
public final void onStartup(ServletContext servletContext) throws ServletException {
String description = getDescription();
if (!isEnabled()) {
logger.info(StringUtils.capitalize(description)
+ " was not registered (disabled)");
return;
}
// 将当前bean对象注册到servletContext
register(description, servletContext);
}
// 抽象方法,由子类实现
/**
* Register this bean with the servlet context.
* @param description a description of the item being registered
* @param servletContext the servlet context
*/
protected abstract void register(String description, ServletContext servletContext);
...
}
最终调用ServletContext的addFilter方法将Filter注册到Servlet容器,以下以ServletListenerRegistrationBean的register方法实现为例看看子类来调用servletContext.addListener完成绑定:
@Override
protected void register(String description, ServletContext servletContext) {
try {
servletContext.addListener(this.listener);
}
catch (RuntimeException ex) {
throw new IllegalStateException(
"Failed to add listener '" + this.listener + "' to servlet context",
ex);
}
}
综上,spring将(@Bean修饰的RegistrationBean)/(@WebFilter修饰的Fliter类)对应的BeanDefinition注册到beanFactory后,然后从beanFactory中获取全部的ServletContextInitializer,遍历它们并调用他们的onStartup方法将RegistrationBean中的bean注册到servlet容器。
完事!
参考博客:
https://www.cnblogs.com/youzhibing/p/9866690.html#_label2_0
https://blog.csdn.net/u010013573/article/details/86707091?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task