SpringBoot中Filter bean是怎麼被添加到Servlet容器中的

来源:https://www.cnblogs.com/yagefuma/archive/2023/08/04/17605798.html
-Advertisement-
Play Games

@[TOC] # Scala的基本使用 ## 一、基礎語法 ### 1.1 變數 #### 1.1.1 var和val Scala中的變數分為兩種: 可變var:可以隨時修改var聲明的變數的值 不可變val:val聲明的變數,值不能被修改,否則會報錯:error: reassignment to ...


參考資料

對於Spring Boot的IOC容器——ServletWebServerApplicationContext,其中的Filter bean,每個Filter bean都會被獨立的註冊成為Servlet的Filter。大概的註冊過程分成2步:

  1. IOC容器——ServletWebServerApplicationContext將Filter介面的實現類封裝成FilterRegistrationBean,放到ServletContextInitializerBeans實例的成員變數initializers變數(LinkedMultiValueMap)中
  2. Spring 容器(ServletWebServerApplicationContext)從ServletContextInitializerBeans實例的成員變數initializers變數(LinkedMultiValueMap),中獲取所有的ServletContextInitializer實現類,調用它們的onStartUp函數,FilterRegistrationBean的onStartup函數就是在調用ServletContext的addFilter函數向Servlet添加Filter

1. IOC容器——ServletWebServerApplicationContext將Filter介面的實現類封裝成FilterRegistrationBean,放到ServletContextInitializerBeans實例的成員變數initializers變數(LinkedMultiValueMap)中

  1. Spring容器——ServletWebServerApplicationContext的refresh()函數被調用。
protected void onRefresh() {
    super.onRefresh();    
    try {
       createWebServer();    
       }
    catch (Throwable ex) {
       throw new ApplicationContextException("Unable to start web server", ex);   
    }
}
  1. ServletWebServerApplicationContext的createWebServer()函數,
  • 在函數中會調用getSelfInitializer()函數獲取所有的ServletContextInitializer實例,
  • 而獲取所有的ServletContextInitializer實例的過程中,會查找所有實現了Filter介面的bean,並註冊成FilterRegistrationBean(實現了ServletContextInitializer介面,所以也屬於ServletContextInitializer實例),
  • 然後把FilterRegistrationBean放到initializers變數(LinkedMultiValueMap)中
private void createWebServer() {
    WebServer webServer = this.webServer;    
    ServletContext servletContext = getServletContext();    
    if (webServer == null && servletContext == null) {
       StartupStep createWebServer = this.getApplicationStartup().start("spring.boot.webserver.create");       
       ServletWebServerFactory factory = getWebServerFactory();       
       createWebServer.tag("factory", factory.getClass().toString());  
       //  getSelfInitializer()函數中獲取所有ServletContextInitializer實例,調用onStartup方法完成註冊
       this.webServer = factory.getWebServer(getSelfInitializer());
       ......
}

private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
    return this::selfInitialize;
    }

private void selfInitialize(ServletContext servletContext) throws ServletException {
    prepareWebApplicationContext(servletContext);    
    registerApplicationScope(servletContext);    
    WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);  
    //   獲取所有ServletContextInitializer實例,調用onStartup方法
    for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
       beans.onStartup(servletContext);   
        }
}
// 傳入BeanFactory,new 一個ServletContextInitializerBeans對象,這個對象實現了Collection介面,是一個集合,
// 集合內的元素是需要內置在Servlet Context上的ServletContextInitializer bean
protected Collection<ServletContextInitializer> getServletContextInitializerBeans() {
    return new ServletContextInitializerBeans(getBeanFactory());
    }
  1. ServletContextInitializerBeans的構建
public ServletContextInitializerBeans(ListableBeanFactory beanFactory,       Class<? extends ServletContextInitializer>... initializerTypes) {
    this.initializers = new LinkedMultiValueMap<>();    
    this.initializerTypes = (initializerTypes.length != 0) ? Arrays.asList(initializerTypes)
          : Collections.singletonList(ServletContextInitializer.class);    
     // 找直接定義為ServletContextInitializer的bean(比如FilterRegistrationBean),DelegatingFilterProxyRegistrationBean等,
     // 添加到initializers中
    addServletContextInitializerBeans(beanFactory);    
     // 找實現了Filter介面的bean,將他們封裝成ServletContextInitializer bean(對於Filter的實現類也就是封裝成FilterRegistrationBean),
     // 添加到initializers中
    addAdaptableBeans(beanFactory);   
    //排序  
    List<ServletContextInitializer> sortedInitializers = this.initializers.values().stream()
          .flatMap((value) -> value.stream().sorted(AnnotationAwareOrderComparator.INSTANCE))
          .collect(Collectors.toList());    
          this.sortedList = Collections.unmodifiableList(sortedInitializers);    
          logMappings(this.initializers);
}

protected void addAdaptableBeans(ListableBeanFactory beanFactory) {
    MultipartConfigElement multipartConfig = getMultipartConfig(beanFactory);    
    // 獲取實現了Servlet介面的bean
    addAsRegistrationBean(beanFactory, Servlet.class, new ServletRegistrationBeanAdapter(multipartConfig)); 
    // 查找所有實現了Filter介面的bean,並註冊成FilterRegistrationBean,
    // 然後把FilterRegistrationBean放到initializers變數(LinkedMultiValueMap)中
    addAsRegistrationBean(beanFactory, Filter.class, new FilterRegistrationBeanAdapter());    
    for (Class<?> listenerType : ServletListenerRegistrationBean.getSupportedTypes()) {
       addAsRegistrationBean(beanFactory, EventListener.class, (Class<EventListener>) listenerType,             
                 new ServletListenerRegistrationBeanAdapter());    
       }
}

所以我們可以看到ServletContextInitializerBeans構造函數是怎麼構造一個包含
ServletContextInitializer bean集合的

  • 直接定義為ServletContextInitializer的bean(比如FilterRegistrationBean)
  • DelegatingFilterProxyRegistrationBean等,添加到initializers中
    找實現了Filter介面的bean,將他們封裝成ServletContextInitializer bean(對於Filter的實現類也就是封裝成FilterRegistrationBean),添加到initializers中
  • 排序然後一併返回

所以ServletContextInitializerBeans實例表示的是:一個從ListableBeanFactory bean容器中獲得的ServletContextInitializer實例的集合。這個集合中的每個元素來自容器中定義的每個如下類型的bean :

  • 實現了ServletContextInitializer介面的bean
    具體可能以ServletRegistrationBean/FilterRegistrationBean/EventListenerRegistrationBean的形式存在。這些 bean 設計的目的是用來註冊相應的 Servlet/Filter/EventListener bean 到 ServletContext
- **實現了Servlet/Filter/EventListener介面的 bean**

這些 bean直接以實現裸的Servlet/Filter/EventListener介面的 bean的形式存在,但是Springboot會將它們封裝成相應的 RegistrationBean(也是ServletContextInitializer ),然後也註冊到ServletContext。

也就是說,Spring幫我們將實現裸的Servlet/Filter/EventListener介面的 bean,封裝成ServletRegistrationBean/FilterRegistrationBean/EventListenerRegistrationBean。

2. Spring 容器(ServletWebServerApplicationContext)從ServletContextInitializerBeans實例的成員變數initializers變數(LinkedMultiValueMap),中獲取所有的ServletContextInitializer實現類,調用它們的startUp函數,FilterRegistrationBean的onStartup函數就是在調用ServletContext的addFilter函數向Servlet添加Filter

  1. 還是要回到上面1.2中的createWebServer()函數中,這次我們不看getSelfInitializer(),而是看factory.getWebServer(...)函數。
private void createWebServer() {
    WebServer webServer = this.webServer;    
    ServletContext servletContext = getServletContext();    
    if (webServer == null && servletContext == null) {
       StartupStep createWebServer = this.getApplicationStartup().start("spring.boot.webserver.create");       
       ServletWebServerFactory factory = getWebServerFactory();       
       createWebServer.tag("factory", factory.getClass().toString());  
       //  getSelfInitializer()函數中獲取所有ServletContextInitializer實例,調用onStartup方法完成註冊
       this.webServer = factory.getWebServer(getSelfInitializer());
       ......
}
  1. TomcatServletWebServerFactory的getWebServer(...)函數
public WebServer getWebServer(ServletContextInitializer... initializers) {
    if (this.disableMBeanRegistry) {
       Registry.disableRegistry();    
    }
    ......
    // 將前面獲取到的所有ServletContextInitializer實例作為參數,傳下去
    prepareContext(tomcat.getHost(), initializers);    
    return getTomcatWebServer(tomcat);
 }
  1. TomcatServletWebServerFactory的prepareContext(...)
protected void prepareContext(Host host, ServletContextInitializer[] initializers) {
    ......
    // 合併一下
    ServletContextInitializer[] initializersToUse = mergeInitializers(initializers);    
    host.addChild(context);   
    // 將前面獲取到的所有ServletContextInitializer實例作為參數,傳下去 
    configureContext(context, initializersToUse);    
    postProcessContext(context);}
  1. tomcatServletWebServerFactory的configureContext(...),創建了TomcatStarter,並將TomcatStarter綁定到Tomcat上,這樣Tomcat啟動時就會回調TomcatStarter的onStartup函數
protected void configureContext(Context context, ServletContextInitializer[] initializers) {
    // 將前面獲取到的所有ServletContextInitializer實例作為參數,傳下去 
    TomcatStarter starter = new TomcatStarter(initializers);
    
    // 下麵這一系列操作是將TomcatStarter綁定到Tomcat上,這樣TomcatStarter就會在Tomcat啟動時被回調
    if (context instanceof TomcatEmbeddedContext) {
    TomcatEmbeddedContext embeddedContext = (TomcatEmbeddedContext) context;    
    embeddedContext.setStarter(starter);    
    embeddedContext.setFailCtxIfServletStartFails(true);
    }
    context.addServletContainerInitializer(starter, NO_CLASSES);
    ......
}
  1. TomcatStarter的startUp函數被調用的時候,就會變數所有的ServletContextInitializer實例的onStartup函數,
public void onStartup(Set<Class<?>> classes, ServletContext servletContext) throws ServletException {
    try {
       for (ServletContextInitializer initializer : this.initializers) {
          initializer.onStartup(servletContext);       
          }
    }
    catch (Exception ex) {
       this.startUpException = ex;       
       // Prevent Tomcat from logging and re-throwing when we know we can       
       // deal with it in the main thread, but log for information here.      
       if (logger.isErrorEnabled()) {
          logger.error("Error starting Tomcat context. Exception: " + ex.getClass().getName() + ". Message: "                + ex.getMessage());       
          }
    }
}
  1. 回顧第一章:“Filter介面的實現類封裝成FilterRegistrationBean”,而FilterRegistrationBean又是ServletContextInitializer介面的實現類,所以上面第5步,也會回調FilterRegistrationBean的onStartup函數,
//FilterRegistrationBean
public final void onStartup(ServletContext servletContext) throws ServletException {
    String description = getDescription();    
    if (!isEnabled()) {
       logger.info(StringUtils.capitalize(description) + " was not registered (disabled)");       return;    
       }
    // 這裡
    register(description, servletContext);
}
  1. DynamicRegistrationBean.class
protected final void register(String description, ServletContext servletContext) {
    //這裡
    D registration = addRegistration(description, servletContext);    
    if (registration == null) {
       logger.info(StringUtils.capitalize(description) + " was not registered (possibly already registered?)");       
       return;    
       }
    configure(registration);
    }
  1. AbstractFilterRegistrationBean.class, 看到沒,最終還是用ServletContext的addFilter函數,向Servlet容器中添加Filter。
protected Dynamic addRegistration(String description, ServletContext servletContext) {
    // getFilter()在FilterRegistrationBean中被實現,返回的就是那個Filter介面的實現類。
    Filter filter = getFilter();   
    // 將Filter介面的實現類添加到Servlet容器中 
    return servletContext.addFilter(getOrDeduceName(filter), filter);
  }

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • ## 1. 問題復現 話不多說,先貼出問題代碼:這裡的`GetUserInfoByAccessToken`是我自定義的一個實體類。 ``` GetUserInfoByAccessToken getUserInfoByAccessTokenString = restTemplate.getForObj ...
  • 想要搭建一個強大的後臺管理系統?本文提供了詳細的 Webman-Admin 安裝指南,幫助您快速部署和配置這個功能豐富的 Web 開發工具。瞭解如何安裝 Webman-Admin,並利用其強大的功能來管理和監控您的應用程式。立即開始搭建您的後臺管理系統,提升工作效率和用戶體驗! ...
  • 選擇哪種分配方式由Java堆是否規整決定,而Java堆是否規整又由所採用的垃圾收集器是否帶有壓縮整理功能決定。因此,在使用Serial、ParNew等帶Compact過程的收集器時,系統採用的分配演算法是指針碰撞,而使用CMS這種基於Mark-Sweep演算法的收集器時,通常採用空閑列表。這兩種對象訪問... ...
  • 環境: centos7.9 tomcat9 jdk1.8 # 一.阿裡雲申請 [免費SSL](https://yundunnext.console.aliyun.com/?spm=5176.21213303.782131.4.304053c9wUb2BP&p=cas#/certExtend/free ...
  • python中的多線程其實並不是真正的多線程,如果想要充分地使用多核CPU資源,在python中大部分情況需要使用多進程。 python提供了非常好用的多進程包Multiprocessing,只需要定義一個函數,python會完成其它所有事情。藉助這個包,可以輕鬆完成從單進程到併發執行的轉換。 mu ...
  • Lucas定理: 主要是求$C_{n}^{m}$在模$p$情況下($mod \, p$)(一般$p$較小,而$n,m$較大的情況) 公式: $ C_{n}^{m} ≡ C_{n \, mod \, p}^{m \, mod \, p} \times C_{n/p}^{m/p} (mod \, p) ...
  • ## 測試 Spring提供了一組測試工具,可以輕鬆地測試Spring應用程式的各個組件,包括控制器、服務、存儲庫和其他組件。它具有豐富的測試註釋、實用程式類和其他功能,以幫助進行單元測試、集成測試等。 ### JPA測試 Spring JPA(Java Persistence API)是一個庫,它 ...
  • ## JAVA函數式編程 ### 函數式編程的背景和概念 維基百科:**函數式編程**,或稱**函數程式設計**、**泛函編程**(英語:Functional programming),是一種[編程範型](https://zh.wikipedia.org/wiki/編程範型),它將[電腦運算](ht ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...