SpringBoot嵌入式Tomcat的自動配置原理

来源:https://www.cnblogs.com/zhixiang-org-cn/archive/2019/10/08/11638485.html

在讀本篇文章之前如果你讀過這篇文章 "SpringBoot自動裝配原理解析" 應該會更加輕鬆 準備工作 我們知道SpringBoot的自動裝配的秘密在 包下的 文件中,而嵌入Tomcat的原理就在這個文件中載入的一個配置類: 首先看一下上方的幾個註解 1. 這個註解是決定配置類的載入順序的,當註解里 ...


在讀本篇文章之前如果你讀過這篇文章SpringBoot自動裝配原理解析應該會更加輕鬆

準備工作

我們知道SpringBoot的自動裝配的秘密在org.springframework.boot.autoconfigure包下的spring.factories文件中,而嵌入Tomcat的原理就在這個文件中載入的一個配置類:org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration

@Configuration
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(ServletRequest.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ServerProperties.class)
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
        ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
        ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
        ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration {

    @Bean
    public ServletWebServerFactoryCustomizer servletWebServerFactoryCustomizer(
            ServerProperties serverProperties) {
        return new ServletWebServerFactoryCustomizer(serverProperties);
    }

    @Bean
    @ConditionalOnClass(name = "org.apache.catalina.startup.Tomcat")
    public TomcatServletWebServerFactoryCustomizer tomcatServletWebServerFactoryCustomizer(
            ServerProperties serverProperties) {
        return new TomcatServletWebServerFactoryCustomizer(serverProperties);
    }

    /**
     * Registers a {@link WebServerFactoryCustomizerBeanPostProcessor}. Registered via
     * {@link ImportBeanDefinitionRegistrar} for early registration.
     */
    public static class BeanPostProcessorsRegistrar
            implements ImportBeanDefinitionRegistrar, BeanFactoryAware {

        private ConfigurableListableBeanFactory beanFactory;

        @Override
        public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
            if (beanFactory instanceof ConfigurableListableBeanFactory) {
                this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
            }
        }

        @Override
        public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
                BeanDefinitionRegistry registry) {
            if (this.beanFactory == null) {
                return;
            }
            registerSyntheticBeanIfMissing(registry,
                    "webServerFactoryCustomizerBeanPostProcessor",
                    WebServerFactoryCustomizerBeanPostProcessor.class);
            registerSyntheticBeanIfMissing(registry,
                    "errorPageRegistrarBeanPostProcessor",
                    ErrorPageRegistrarBeanPostProcessor.class);
        }

        private void registerSyntheticBeanIfMissing(BeanDefinitionRegistry registry,
                String name, Class<?> beanClass) {
            if (ObjectUtils.isEmpty(
                    this.beanFactory.getBeanNamesForType(beanClass, true, false))) {
                RootBeanDefinition beanDefinition = new RootBeanDefinition(beanClass);
                beanDefinition.setSynthetic(true);
                registry.registerBeanDefinition(name, beanDefinition);
            }
        }

    }

}

首先看一下上方的幾個註解

  1. @AutoConfigureOrder這個註解是決定配置類的載入順序的,當註解里的值越小越先載入,而Ordered.HIGHEST_PRECEDENCE的值是Integer.MIN_VALUE也就是說這個類肯定是最先載入的那一批
  2. @ConditionalOnXXX在之前的文章中已經無數次提到了,就不再闡述了
  3. @EnableConfigurationProperties開啟ServerProperties類的屬性值配置。而這個類裡面包含的就是Web服務的配置
@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties {

    private Integer port;

    private InetAddress address;

    @NestedConfigurationProperty
    private final ErrorProperties error = new ErrorProperties();

    private Boolean useForwardHeaders;

    private String serverHeader;

    private int maxHttpHeaderSize = 0; // bytes

    private Duration connectionTimeout;

    @NestedConfigurationProperty
    private Ssl ssl;

    @NestedConfigurationProperty
    private final Compression compression = new Compression();

    @NestedConfigurationProperty
    private final Http2 http2 = new Http2();

    private final Servlet servlet = new Servlet();

    private final Tomcat tomcat = new Tomcat();

    private final Jetty jetty = new Jetty();

    private final Undertow undertow = new Undertow();
}

這個類的代碼太多了,這裡就不一一貼出來了,我們平常在application.properties中配置的server.xxx就是這個類中屬性

  1. @Import引入了4個類,看都是什麼吧
  2. BeanPostProcessorsRegistrar
public static class BeanPostProcessorsRegistrar
            implements ImportBeanDefinitionRegistrar, BeanFactoryAware {
        @Override
        public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
                BeanDefinitionRegistry registry) {
            if (this.beanFactory == null) {
                return;
            }
            registerSyntheticBeanIfMissing(registry,
                    "webServerFactoryCustomizerBeanPostProcessor",
                    WebServerFactoryCustomizerBeanPostProcessor.class);
            registerSyntheticBeanIfMissing(registry,
                    "errorPageRegistrarBeanPostProcessor",
                    ErrorPageRegistrarBeanPostProcessor.class);
        }

        private void registerSyntheticBeanIfMissing(BeanDefinitionRegistry registry,
                String name, Class<?> beanClass) {
            if (ObjectUtils.isEmpty(
                    this.beanFactory.getBeanNamesForType(beanClass, true, false))) {
                RootBeanDefinition beanDefinition = new RootBeanDefinition(beanClass);
                beanDefinition.setSynthetic(true);
                registry.registerBeanDefinition(name, beanDefinition);
            }
        }

    }

這個類註冊了兩個bean:WebServerFactoryCustomizerBeanPostProcessorErrorPageRegistrarBeanPostProcessor關於這兩個bean的作用稍後再詳細介紹

  1. EmbeddedTomcat
@Configuration
    @ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
    @ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
    public static class EmbeddedTomcat {

        @Bean
        public TomcatServletWebServerFactory tomcatServletWebServerFactory() {
            return new TomcatServletWebServerFactory();
        }

    }

這個類會在存在Tomcat相關jar包時添加一個TomcatServletWebServerFactorybean

其他兩個相信大家都知道怎麼回事了

  1. 除了這些這個類還註入了兩個類ServletWebServerFactoryCustomizerTomcatServletWebServerFactoryCustomizer

現在前期準備工作已經做好了,看一下這個Tomcat是如何啟動的吧

啟動

啟動入口在ServletWebServerApplicationContext中的onRefresh方法

protected void onRefresh() {
        super.onRefresh();
        try {
            createWebServer();
        }
        catch (Throwable ex) {
            throw new ApplicationContextException("Unable to start web server", ex);
        }
    }

Tomcat的啟動就在createWebServer方法裡面了

private void createWebServer() {
        WebServer webServer = this.webServer;
        ServletContext servletContext = getServletContext();
        //第一次訪問的時候兩個對象都為空
        if (webServer == null && servletContext == null) {
            ServletWebServerFactory factory = getWebServerFactory();
            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();
    }

首先看一下getWebServerFactory

protected ServletWebServerFactory getWebServerFactory() {
        // 這裡獲取的beanname就是上方註冊的tomcatServletWebServerFactory了
        String[] beanNames = getBeanFactory()
                .getBeanNamesForType(ServletWebServerFactory.class);
        if (beanNames.length == 0) {
            throw new ApplicationContextException(
                    "Unable to start ServletWebServerApplicationContext due to missing "
                            + "ServletWebServerFactory bean.");
        }
        if (beanNames.length > 1) {
            throw new ApplicationContextException(
                    "Unable to start ServletWebServerApplicationContext due to multiple "
                            + "ServletWebServerFactory beans : "
                            + StringUtils.arrayToCommaDelimitedString(beanNames));
        }
        return getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);
    }

準備環境里註冊的bean現在出來一個了。註意,上方還註冊了一個後置處理器EmbeddedServletContainerCustomizerBeanPostProcessor,獲取beantomcatServletWebServerFactory的時候就會執行後置處理器的postProcessBeforeInitialization方法

public Object postProcessBeforeInitialization(Object bean, String beanName)
            throws BeansException {
    if (bean instanceof WebServerFactory) {
        postProcessBeforeInitialization((WebServerFactory) bean);
    }
    return bean;
}
private void postProcessBeforeInitialization(WebServerFactory webServerFactory) {
        LambdaSafe
                .callbacks(WebServerFactoryCustomizer.class, getCustomizers(),
                        webServerFactory)
                .withLogger(WebServerFactoryCustomizerBeanPostProcessor.class)
                .invoke((customizer) -> customizer.customize(webServerFactory));
    }

    private Collection<WebServerFactoryCustomizer<?>> getCustomizers() {
        if (this.customizers == null) {
            // Look up does not include the parent context
            this.customizers = new ArrayList<>(getWebServerFactoryCustomizerBeans());
            this.customizers.sort(AnnotationAwareOrderComparator.INSTANCE);
            this.customizers = Collections.unmodifiableList(this.customizers);
        }
        return this.customizers;
    }

    @SuppressWarnings({ "unchecked", "rawtypes" })
    private Collection<WebServerFactoryCustomizer<?>> getWebServerFactoryCustomizerBeans() {
        return (Collection) this.beanFactory
                .getBeansOfType(WebServerFactoryCustomizer.class, false, false).values();
    }

這個處理器的作用是獲得所有定製器,然後執行定製器的方法

接著往下看

這個時候就可以啟動Tomcat了

public WebServer getWebServer(ServletContextInitializer... initializers) {
        Tomcat tomcat = new Tomcat();
        File baseDir = (this.baseDirectory != null ? this.baseDirectory
                : createTempDir("tomcat"));
        tomcat.setBaseDir(baseDir.getAbsolutePath());
        Connector connector = new Connector(this.protocol);
        tomcat.getService().addConnector(connector);
        customizeConnector(connector);
        tomcat.setConnector(connector);
        tomcat.getHost().setAutoDeploy(false);
        configureEngine(tomcat.getEngine());
        for (Connector additionalConnector : this.additionalTomcatConnectors) {
            tomcat.getService().addConnector(additionalConnector);
        }
        prepareContext(tomcat.getHost(), initializers);
        return getTomcatWebServer(tomcat);
    }
protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
        return new TomcatWebServer(tomcat, getPort() >= 0);
    }
public TomcatWebServer(Tomcat tomcat, boolean autoStart) {
        Assert.notNull(tomcat, "Tomcat Server must not be null");
        this.tomcat = tomcat;
        this.autoStart = autoStart;
        initialize();
    }
private void initialize() throws WebServerException {
        TomcatWebServer.logger
                .info("Tomcat initialized with port(s): " + getPortsDescription(false));
        synchronized (this.monitor) {
            try {
                addInstanceIdToEngineName();

                Context context = findContext();
                context.addLifecycleListener((event) -> {
                    if (context.equals(event.getSource())
                            && Lifecycle.START_EVENT.equals(event.getType())) {
                        // Remove service connectors so that protocol binding doesn't
                        // happen when the service is started.
                        removeServiceConnectors();
                    }
                });

                // Start the server to trigger initialization listeners
                this.tomcat.start();

                // We can re-throw failure exception directly in the main thread
                rethrowDeferredStartupExceptions();

                try {
                    ContextBindings.bindClassLoader(context, context.getNamingToken(),
                            getClass().getClassLoader());
                }
                catch (NamingException ex) {
                    // Naming is not enabled. Continue
                }

                // Unlike Jetty, all Tomcat threads are daemon threads. We create a
                // blocking non-daemon to stop immediate shutdown
                startDaemonAwaitThread();
            }
            catch (Exception ex) {
                throw new WebServerException("Unable to start embedded Tomcat", ex);
            }
        }
    }

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

更多相關文章
  • 1. 基本類型包裝類 大家回想下,在第三篇文章中我們學習Java中的基本數據類型時,說Java中有8種基本的數據類型,可是這些數據是基本數據,想對其進行複雜操作,變的很難。怎麼辦呢? 1.1 基本類型包裝類概述 在實際程式使用中,程式界面上用戶輸入的數據都是以字元串類型進行存儲的。而程式開發中,我們 ...
  • 1. 快速入門 (1)我們從官網下載源碼 BCrypt 官網 http://www.mindrot.org/projects/jBCrypt/ (2)新建工程,將源碼類BCrypt拷貝到工程 (3)新建測試類,main方法中編寫代碼,實現對密碼的加密 String gensalt = BCrypt. ...
  • 1. 正則表達式 1.1 正則表達式的概念 正則表達式(英語:Regular Expression,在代碼中常簡寫為regex)。 正則表達式是一個字元串,使用單個字元串來描述、用來定義匹配規則,匹配一系列符合某個句法規則的字元串。在開發中,正則表達式通常被用來檢索、替換那些符合某個規則的文本。 1 ...
  • 1989,為了度過聖誕假期,Guido開始編寫Python語言編譯器。Python這個名字來自Guido的喜愛的電視連續劇《蒙蒂蟒蛇的飛行馬戲團》。他希望新的語言Python能夠滿足他在C和Shell之間創建全功能、易學、可擴展的語言的願景。 今天,世界上有將近600種編程語言,但也有20種流行語言 ...
  • 類型參數 表現形式:在名稱後面以方括弧表示, 何處使用 class 中,用於定義變數、入參、返回值 函數、方法 類型邊界 上邊界 compareTo Comparable` 的子類型,即需要添加上邊界 scala class Pair[T : LowerBound` 替換第一個元素為 T 的父類 R ...
  • ① 下載JDBC-MySQL資料庫驅動 鏈接:https://pan.baidu.com/s/1KtSZZ5hXlxu2QrmsXg3lkA 提取碼:1pbl ② 載入JDBC-MySQL資料庫驅動 範例:MySQL資料庫驅動載入 Class.forNmae("com.mysql.jdbc.Driv ...
  • python day 5 2019/10/08 學習資料來自老男孩教育 [TOC] 1. 匿名函數lambda 函數名就是變數,使用lambda表達來創建一些簡單功能的函數。 lambda定義簡單函數時,比用def定義要簡潔,會用在map,reduce,filter等函數中。 def f1(x,y) ...
  • 11 構建一個簡單的Spring Boot項目 這個章節描述如何通過Spring Boot構建一個“Hello Word”web應用,側重介紹Spring Boot的一些重要功能。因為大多數的開發工具都支持Maven,所以我們使用它來構建這個應用。 網站 "spring.io" 包含了許多如何開始使 ...
一周排行
  • 原來的需求是控制項可見時有動畫,不可見的時候沒有動畫,只寫了EnterAction,沒寫ExitAction,以為空間都隱藏了,總不會再有動畫了。剛好該動畫在一個攝像頭獲取的視頻上面,動畫期間視頻有點卡,動畫隱藏了,視頻還是卡,於是弄了一下測試,發現不寫ExitAction動畫還是在動的。 ...
  • 某些場景併發量太高,需要採用隊列輔助,特此備註:多線程隊列,先進先出 某些情況也會用到阻塞當前線程,等待伺服器返回或者耗時的處理,這種情況,可採用信號量輔助 1 ManualResetEvent allDone = new ManualResetEvent(false);//初始化,開啟阻塞 2 a ...
  • 1.環境 VS2019 16.5.1 .NET Core SDK 3.1.200 Blazor WebAssembly Templates 3.2.0-preview2.20160.5 2.預設綁定 2.1.使用方法 Blazor中Razor組件通過一個名為@bind的HTML元素屬性提供數據綁定功 ...
  • 很多人使用力軟敏捷框架的一個困擾就是表格控制項,力軟並沒有使用常規的jqgrid,而是用了自己的一套 jfgrid。所以今天在這做個簡單的說明,如果你有什麼疑問也可以在評論區提出來,後期的文章會做說明。 首先來講下jfgrid有哪些設置屬性: url 後臺請求地址 param 後臺請求參數 rowda ...
  • public partial class Form1 : Form { CancellationTokenSource cts = new CancellationTokenSource(); public Form1() { InitializeComponent(); } private voi ...
  • 進程守護工具 1.寫在前面 經常寫一些服務程式,有時要監測服務程式的運行狀態,所以就做了一個進程守護工具。 2.分析 通過Process.GetProcessesByName(ProcessName),獲得指定進程列表。 用Process.MainModule.FileName來判斷程式是否運行。 ...
  • 在做劍指offer的題目時,發現每次需要用到樹做為參數來測試自己寫的方法時,都很是痛苦。於是乎就萌生了寫一個利用數據生成樹的方法。簡單測試了下,沒什麼毛病。在這裡貼出來,如果以後發現有bug了會持續在後面更新,也歡迎大家指出其中的不足。 using System; using System.Coll ...
  • bootstrap paginator 分頁 效果圖 1. Demo前的準備 1.1. 編程環境 VS2019 1.2. 準備 分頁插件(bootstrap paginator)下載: https://github.com/lyonlai/bootstrap paginator 下載後找到 這個為該 ...
  • 因業務需要瞭解Modbus協議的使用,因此對Modbus的協議,以及相應的C#處理應用進行瞭解,針對協議的幾種方式(RTU、ASCII、TCPIP)進行了封裝,以及對Modbus的各種功能碼的特點進行了詳細的瞭解,本篇隨筆基於這些知識進行了一定的梳理和介紹,主要內容包括Modbus協議簡要介紹、Mo... ...
  • 出問題的代碼如下: public class DBBookChaptersService : IBookChaptersService { private readonly BooksContext _booksContext; public DBBookChaptersService(BooksC ...