上篇分析了Interceptor(攔截器),今天繼續(xù)對Filter(過濾器)做一個分析。
何為過濾器
Filter是J2EE中來的,可以看做是Servlet的一種“加強(qiáng)版”,它主要用于對用戶請求進(jìn)行預(yù)處理和后處理,擁有一個典型的處理鏈。Filter也可以對用戶請求生成響應(yīng),這一點(diǎn)與Servlet相同,但實(shí)際上很少會使用Filter向用戶請求生成響應(yīng)。使用Filter完整的流程是:Filter對用戶請求進(jìn)行預(yù)處理,接著將請求交給Servlet進(jìn)行預(yù)處理并生成響應(yīng),最后Filter再對服務(wù)器響應(yīng)進(jìn)行后處理。
通過Filter技術(shù),開發(fā)人員可以實(shí)現(xiàn)用戶在訪問某個目標(biāo)資源之前,對訪問的請求和響應(yīng)進(jìn)行攔截過濾。

過濾器作用
在JavaDoc中給出了幾種過濾器的作用:
* Examples that have been identified for this design are
* 1) Authentication Filters, 即用戶訪問權(quán)限過濾
* 2) Logging and Auditing Filters, 日志過濾,可以記錄特殊用戶的特殊請求的記錄等
* 3) Image conversion Filters,圖像轉(zhuǎn)換過濾器
* 4) Data compression Filters ,數(shù)據(jù)轉(zhuǎn)換
* 5) Encryption Filters ,安全加密
* 6) Tokenizing Filters ,詞法分析
* 7) Filters that trigger resource access events ,資源訪問事件觸發(fā)過濾器
* 8) XSL/T filters
* 9) Mime-type chain Filter ,文件類型鏈過濾器
過濾器的生命周期
Filter的生命周期:
Filter的創(chuàng)建:
Filter的創(chuàng)建和銷毀由WEB服務(wù)器負(fù)責(zé)。 web 應(yīng)用程序啟動時,web 服務(wù)器將創(chuàng)建Filter 的實(shí)例對象,并調(diào)用其init方法,完成對象的初始化功能,從而為后續(xù)的用戶請求作好攔截的準(zhǔn)備工作,filter對象只會創(chuàng)建一次,init方法也只會執(zhí)行一次。通過init方法的參數(shù),可獲得代表當(dāng)前filter配置信息的FilterConfig對象。
Filter的銷毀:
Web容器調(diào)用destroy方法銷毀Filter。destroy方法在Filter的生命周期中僅執(zhí)行一次。在destroy方法中,可以釋放過濾器使用的資源。
FilterConfig接口:
用戶在配置filter時,可以使用<init-param>為filter配置一些初始化參數(shù),當(dāng)web容器實(shí)例化Filter對象,調(diào)用其init方法時,會把封裝了filter初始化參數(shù)的filterConfig對象傳遞進(jìn)來。因此開發(fā)人員在編寫filter時,通過filterConfig對象的方法,就可獲得:
String getFilterName():得到filter的名稱。
String getInitParameter(String name): 返回在部署描述中指定名稱的初始化參數(shù)的值。如果不存在返回null.
Enumeration getInitParameterNames():返回過濾器的所有初始化參數(shù)的名字的枚舉集合。
public ServletContext getServletContext():返回Servlet上下文對象的引用
過濾器、攔截器和切面
Filter過濾器:攔截web訪問url地址。依賴于servlet容器。在實(shí)現(xiàn)上基于函數(shù)回調(diào),可以對幾乎所有請求進(jìn)行過濾,但是缺點(diǎn)是一個過濾器實(shí)例只能在容器初始化時調(diào)用一次。使用過濾器的目的是用來做一些過濾操作,獲取我們想要獲取的數(shù)據(jù)。比如:在過濾器中修改字符編碼;在過濾器中修改HttpServletRequest的一些參數(shù),包括:過濾低俗文字、危險字符等。
Interceptor攔截器:攔截以 .action結(jié)尾的url,攔截Action的訪問。依賴于web框架,在SpringMVC中就是依賴于SpringMVC框架。在實(shí)現(xiàn)上基于Java的反射機(jī)制,屬于面向切面編程(AOP)的一種運(yùn)用。由于攔截器是基于Web框架的調(diào)用。因此可以使用spring的依賴注入(DI)進(jìn)行一些業(yè)務(wù)操作,同時一個攔截器實(shí)例在一個controller生命周期之內(nèi)可以多次調(diào)用。但是缺點(diǎn)是只能對controller請求進(jìn)行攔截,對其他的一些比如直接訪問靜態(tài)資源的請求則沒辦法進(jìn)行攔截處理。
Spring AOP攔截器:只能攔截Spring管理Bean的訪問(業(yè)務(wù)層Service)

Filter與Interceptor聯(lián)系與區(qū)別
- 攔截器是基于java的反射機(jī)制,使用代理模式,而過濾器是基于函數(shù)回調(diào)。
- 攔截器不依賴servlet容器,過濾器依賴于servlet容器。
- 攔截器只能對action起作用,而過濾器可以對幾乎所有的請求起作用(可以保護(hù)資源)。
- 攔截器可以訪問action上下文,堆棧里面的對象,而過濾器不可以。
調(diào)用順序:

如上圖,展示了三者的調(diào)用順序Filter->Interceptor->Aspect->Controller。相反的是,當(dāng)Controller拋出的異常的處理順序則是從內(nèi)到外的。因此我們總是定義一個注解@ControllerAdvice去統(tǒng)一處理控制器拋出的異常。如果一旦異常被@ControllerAdvice處理了,則調(diào)用攔截器的afterCompletion方法的參數(shù)Exception ex就為空了。

使用過濾器
根據(jù) Filter 注冊方式的不同,有注解、配置兩種使用方式。若使用的是 Servlet3.0+版本,則兩種方式均可使用;若使用的是 Servlet2.5版本,則只能使用配置類方式。
自定義的過濾器都必須實(shí)現(xiàn)javax.Servlet.Filter接口,并重寫接口中定義的三個方法:
void init(FilterConfig config):用于完成Filter的初始化。
void destory():用于Filter銷毀前,完成某些資源的回收。
void doFilter(ServletRequest request,ServletResponse response,FilterChain chain):實(shí)現(xiàn)過濾功能,即對每個請求及響應(yīng)增加的額外的預(yù)處理和后處理。執(zhí)行該方法之前,即對用戶請求進(jìn)行預(yù)處理;執(zhí)行該方法之后,即對服務(wù)器響應(yīng)進(jìn)行后處理。值得注意的是,chain.doFilter()方法執(zhí)行之前為預(yù)處理階段,該方法執(zhí)行結(jié)束即代表用戶的請求已經(jīng)得到控制器處理。因此,如果在doFilter中忘記調(diào)用chain.doFilter()方法,則用戶的請求將得不到處理。
1、配置方式實(shí)現(xiàn)過濾器:
先自定義filter實(shí)現(xiàn)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<AccesLogFilter> registration() {
//創(chuàng)建filter
AccesLogFilter accesLogFilter = new AccesLogFilter();
//注冊過濾器
FilterRegistrationBean<AccesLogFilter> registration = new FilterRegistrationBean<>(accesLogFilter);
//添加條件
registration.addUrlPatterns("/*");
registration.setOrder(1);
return registration;
}
}
2、注解方式實(shí)現(xiàn)過濾器
自定義filter實(shí)現(xiàn)Filter接口。
@Order(1):表示過濾器的順序,假設(shè)我們有多個過濾器,你如何確定過濾器的執(zhí)行順序?這個注解就是規(guī)定過濾器的順序。
@WebFilter:表示這個class是過濾器。里面的參數(shù),filterName 為過濾器名字,urlPatterns 為過濾器的范圍,initParams 為過濾器初始化參數(shù)。更多具體參數(shù)見下表

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注解標(biāo)注為組件自動注入bean。只要當(dāng)前類在@ComponentScan的掃描范圍內(nèi),就會自動注入此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 進(jìn)行處理的一個對象。
* Examples that have been identified for this design are
* 1) Authentication Filters, 即用戶訪問權(quán)限過濾
* 2) Logging and Auditing Filters, 日志過濾,可以記錄特殊用戶的特殊請求的記錄等
* 3) Image conversion Filters,圖像轉(zhuǎn)換過濾器
* 4) Data compression Filters ,數(shù)據(jù)轉(zhuǎn)換
* 5) Encryption Filters ,安全加密
* 6) Tokenizing Filters ,詞法分析
* 7) Filters that trigger resource access events ,資源訪問事件觸發(fā)過濾器
* 8) XSL/T filters
* 9) Mime-type chain Filter ,文件類型鏈過濾器
*/
public interface Filter {
/**
*當(dāng)過濾器啟動服務(wù)的時候,比如應(yīng)用程序啟動時,servlet容器就會調(diào)用init方法。換句話說,
*不用等到調(diào)用與被過濾器相關(guān)的資源之后,才調(diào)用init方法。這個方法只調(diào)用一次,并且應(yīng)該
*包含該過濾器的初始化代碼。
*init方法的簽名如下:
*void init(FilterConfig filterConfig)
*注意:servlet容器給init方法傳遞了一個FilterConfig。
*/
public void init(FilterConfig filterConfig) throws ServletException;
/**
* 每次調(diào)用與過濾器相關(guān)的資源時,servlet容器都會調(diào)用Filter實(shí)例的doFilter方法。該方法會
*收到一個ServletRequest、ServletResponse和FilterChain。
*doFilter方法的簽名如下:
*public void doFilter(ServletRequest arg0, ServletResponse arg1, FilterChain arg2)
*doFilter的實(shí)現(xiàn)可以訪問ServletRequest 和 ServletResponse。因此,可以在
*ServletRequest中添加屬性,或者在ServletResponse中添加一個標(biāo)頭,甚至可以對
*ServletRequest 或 ServletResponse進(jìn)行修復(fù),改變它們的行為。
*
*doFilter方法實(shí)現(xiàn)中最后一行代碼應(yīng)該是調(diào)用FilterChain中的doFilter方法,
*FilterChain.doFilter方法簽名為
*public void doFilter(ServletRequest request, ServletResponse response)
*一個資源可以與多個過濾器關(guān)聯(lián),F(xiàn)ilterChain.doFilter( ) 通常會引發(fā)調(diào)用鏈中的下一個過濾
*器被調(diào)用。在鏈中的最后一個過濾器中調(diào)用FilterChain.doFilter( )會引發(fā)資源本身被調(diào)用。
*如果你沒有在 Filter.doFilter( )方法實(shí)現(xiàn)代碼的最后調(diào)用FilterChain.doFilter( )方法,那么程
*序的處理將會在這里停止,并且不會調(diào)用請求。
*注意,doFilter方法是FilterChain接口中唯一的方法,它與Filter中的doFilter方法不同。在
*FilterChain中,doFilter只有兩個參數(shù),而不是三個。
*/
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain)
throws IOException, ServletException;
/**
* Filter中最后一個生命周期方法是destory,其方法簽名為
* destroy()
*這個方法在過濾器即將終止服務(wù)之前,由Servlet調(diào)用,銷毀過濾器對象,一般發(fā)生在應(yīng)用程序停止的時候。
*/
public void destroy();
}
由翻譯來的源碼注釋可見,在應(yīng)用程序啟動時,servlet容器就會調(diào)用init方法。然后每次調(diào)用與過濾器相關(guān)的資源時,servlet容器都會調(diào)用doFilter方法,若是doFilter方法調(diào)用了FilterChain.doFilter方法,那么會引發(fā)調(diào)用鏈中的下一個過濾器被調(diào)用,一直到最后。當(dāng)應(yīng)用程序停止的時候,servlet會調(diào)用destroy(),銷毀過濾器對象。
Filter的注冊原理
servlet可以通過ServeltContext來注冊Filter(當(dāng)然還包括Servlet、Listener)到Servlet容器;至于注冊到Servlet容器后,容器內(nèi)部如何處理Filter以后再寫?,F(xiàn)在先這樣認(rèn)為:通過ServletContext注冊Filter到容器,那么Filter就能起到過濾作用了。那么問題來了,springboot是如何將Filter注冊到容器的?
在SpringBoot應(yīng)用來說,是自身啟動了一個Servlet引擎,并且需要創(chuàng)建一個與應(yīng)用關(guān)聯(lián)ServletContext對象綁定到Servlet引擎,從而使得Servlet引擎接收到請求可以分發(fā)到該應(yīng)用來處理。
ServletContext內(nèi)部通常會包含Servlet規(guī)范中的Servlet,F(xiàn)ilter,Listener等組件,而將這些組件注冊到ServletContext,在SpringBoot中主要通過三步來完成,分別是:
1、在應(yīng)用代碼定義和配置組件;
2、應(yīng)用啟動,獲取這些組件,并生成對應(yīng)的BeanDefinition注冊到Spring容器;
3、從Spring容器取出這些組件Bean(取出過程中完成有BeanFactory調(diào)用getBean方法,基于BeanDefinition完成Bean對象的創(chuàng)建)并綁定到該ServletContext中。
RegistrationBean是SpringBoot提供的一個抽象類,是ServletContextInitializer接口的實(shí)現(xiàn)類,故在應(yīng)用啟動創(chuàng)建應(yīng)用對應(yīng)的內(nèi)嵌的ServletContext時,會從Spring容器獲取已經(jīng)加載好的ServletContextInitializer接口實(shí)現(xiàn)類對象,然后對ServletContext進(jìn)行初始化。
注意:
對于兩種實(shí)現(xiàn)方式(配置類和注解類)來說,其實(shí)底層的實(shí)現(xiàn)都是一樣的,都是基于RegistrationBean實(shí)現(xiàn)的,只是注解這種方式是SpringBoot在內(nèi)部完成封裝,而配置類方式是在應(yīng)用代碼顯示使用FilterRegistrationBean(實(shí)現(xiàn)了RegistrationBean接口)的實(shí)現(xiàn)類進(jìn)行操作。接下來分別看下兩種方式:
先來看FilterRegistrationBean配置類方式:
FilterRegistrationBean:相當(dāng)于Servlet 3.0+的ServletContext#addFilter(String, Filter)方法,主要用于自定義Filter過濾器來添加到當(dāng)前的ServletContext,對請求進(jìn)行過濾,源碼如下:
/**
* 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.
* <p>
* 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 <T> 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<T extends Filter>
extends AbstractFilterRegistrationBean<T> {
/**
* 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,F(xiàn)ilter)注冊功能,但具有Spring Bean特性的友好設(shè)計。相當(dāng)于對ServletContext#addFilter進(jìn)行了spring bean的友好性適配,本質(zhì)還是ServletContext#addFilter。

從類繼承圖也可以看出來,F(xiàn)ilterRegistrationBean是實(shí)現(xiàn)類RegistrationBean的。
再來看看注解實(shí)現(xiàn)方式:
這種方式用@WebFilter和注解掃描@ServletComponentScan實(shí)現(xiàn)。
可以使用@WebServlet,@WebFilter,@WebListener注解運(yùn)用在對應(yīng)的組件類上面,注意組件類自身實(shí)現(xiàn)基于Servlet規(guī)范,如實(shí)現(xiàn)Filter接口,ServletListener接口等。然后需要在@Configuration注解的配置類中,加上@ServletComponentScan注解,用于掃描@WebServlet,@WebFilter,@WebListener這些注解的類來注冊到Spring容器中。
其中創(chuàng)建BeanDefinition注冊到Spring容器時,也是使用RegistrationBean的實(shí)現(xiàn)類,即ServletRegistrationBean,F(xiàn)ilterRegistrationBean,ServletListenerRegistrationBean,對組件進(jìn)行封裝的,故也是基于RegistrationBean實(shí)現(xiàn)的,只是SpringBoot在內(nèi)部完成封裝,而不需要像上面一種方式一樣在應(yīng)用代碼顯示使用以上三個RegistrationBean的實(shí)現(xiàn)類進(jìn)行操作,使用@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();
}
...
}
}
依次按順序執(zhí)行:
1:obtainFreshBeanFactory:創(chuàng)建BeanFactory和加載BeanDefintion;
2:invokeBeanFactoryPostProcessors:調(diào)用BeanFactoryPostProcessor,即BeanFactory后置處理器。ComponentScan進(jìn)行相關(guān)類掃描是在這里完成的。以上兩種方法創(chuàng)建Servlet,F(xiàn)ilter和Listener,對應(yīng)的BeanDefinition的創(chuàng)建并注冊到BeanFactory是在這步完成的;
3:onRefresh:完成有特殊功能的bean實(shí)例的創(chuàng)建。從BeanFactory獲取Servlet,F(xiàn)ilter和Listener對應(yīng)的BeanDefinition并創(chuàng)建Bean對象實(shí)例,然后綁定到ServletContext是在這步完成的。
Filter(包括Servlet和Listener)對應(yīng)的BeanDefinition的創(chuàng)建并注冊到BeanFactory這一步詳細(xì)源碼要分兩種實(shí)現(xiàn)方式(配置法和注解法)來討論,回頭我會另起一篇來說。(鏈接:待定)
那么在注冊后,接下來ApplicationContext的refresh方法會調(diào)用onRefresh方法,在這個方法中注冊具有特殊含義的bean對象。

(先去spring的beanFactory中獲取ServletContextInitializer的全部實(shí)例,并將其放入到ServletContextInitializerBeans的initializers中,然后遍歷initializers,調(diào)用每個ServletContextInitializer的onStartup方法)
創(chuàng)建和啟動應(yīng)用內(nèi)嵌的Servlet引擎WebServer,創(chuàng)建內(nèi)嵌的ServletContext對象綁定到WebServer,創(chuàng)建Servlet,F(xiàn)ilter和Listener對應(yīng)的bean對象綁定到ServletContext就是在ServletWebServerApplicationContext類的onRefresh方法實(shí)現(xiàn)的。onRefresh方法的實(shí)現(xiàn)如下:
@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();
// 創(chuàng)建webServer,ServletContext,
// 以及獲取ServletContext的ServletContextInitializer
// 并執(zhí)行其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調(diào)用該方法
// 從BeanFactory獲取ServletContextInitializer接口的實(shí)現(xiàn)類
private void selfInitialize(ServletContext servletContext) throws ServletException {
prepareWebApplicationContext(servletContext);
registerApplicationScope(servletContext);
WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(),
servletContext);
// 在getServletContextInitializerBeans方法內(nèi)部實(shí)現(xiàn):
// 從BeanDefiniton創(chuàng)建bean對象實(shí)例,具體為調(diào)用了BeanFactory的getBean
for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
beans.onStartup(servletContext);
}
}
從以上源碼可知,在selfInitialize方法中,調(diào)用getServletContextInitializerBeans方法來從BeanFactory獲取ServletContextInitializer接口的實(shí)現(xiàn)類,創(chuàng)建bean對象實(shí)例并執(zhí)行onStartup方法。

其中RegistrationBean就實(shí)現(xiàn)了 ServletContextInitializer接口。RegistrationBean的onStartup方法實(shí)現(xiàn)如下:具體由子類實(shí)現(xiàn)register方法完成業(yè)務(wù)邏輯。對Servlet規(guī)范相關(guān)的Servlet,F(xiàn)ilter,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;
}
// 將當(dāng)前bean對象注冊到servletContext
register(description, servletContext);
}
// 抽象方法,由子類實(shí)現(xiàn)
/**
* 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);
...
}
最終調(diào)用ServletContext的addFilter方法將Filter注冊到Servlet容器,以下以ServletListenerRegistrationBean的register方法實(shí)現(xiàn)為例看看子類來調(diào)用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類)對應(yīng)的BeanDefinition注冊到beanFactory后,然后從beanFactory中獲取全部的ServletContextInitializer,遍歷它們并調(diào)用他們的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