SpringBoot之Filter

上篇分析了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)


獲取信息的區(qū)別

Filter與Interceptor聯(lián)系與區(qū)別

  1. 攔截器是基于java的反射機(jī)制,使用代理模式,而過濾器是基于函數(shù)回調(diào)。
  2. 攔截器不依賴servlet容器,過濾器依賴于servlet容器。
  3. 攔截器只能對action起作用,而過濾器可以對幾乎所有的請求起作用(可以保護(hù)資源)。
  4. 攔截器可以訪問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ù)見下表


@WebFilter參數(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。


FilterRegistrationBean的類繼承圖

從類繼承圖也可以看出來,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對象。

從onRefresh到調(diào)用每個ServletContextInitializer的onStartup方法

(先去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方法。

從onStartup到調(diào)用ServletContext的addFilter方法將Filter注冊到Servlet容器

其中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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • IOC 控制反轉(zhuǎn)容器控制程序?qū)ο笾g的關(guān)系,而不是傳統(tǒng)實(shí)現(xiàn)中,有程序代碼之間控制,又名依賴注入。All 類的創(chuàng)建,...
    irckwk1閱讀 1,101評論 0 0
  • Filters是Java組件,它們在從請求到資源及從資源到響應(yīng)上允許有效負(fù)荷與頭部信息的傳遞。 本章描述了Java...
    Lucky_Micky閱讀 945評論 0 0
  • 監(jiān)聽器(listener) 監(jiān)聽器簡介 :監(jiān)聽器就是一個實(shí)現(xiàn)特定接口的普通java程序,這個程序?qū)iT用于監(jiān)聽另一個...
    奮斗的老王閱讀 2,694評論 0 53
  • 我一直覺得感恩節(jié)是人們最應(yīng)該過的節(jié)日,是很有有意義節(jié)日特別是我們這個年齡過的節(jié)日。無需大張旗鼓,不是肆意狂歡,只是...
    久不久閱讀 318評論 0 0
  • 最近因?yàn)檠b修的原因,對家居美學(xué)有了興趣,尤其覺得,墻壁上有了裝飾畫的放置,整個空間頓時生動飽滿起來。所以,趁這段時...
    小寒不冷閱讀 326評論 1 0

友情鏈接更多精彩內(nèi)容