springboot啓動流程源碼分析

在工作中,雖然我們都在基於springboot框架開發項目,還是發現很多小夥伴熟悉業務開發,但是對框架的一些底層機制原理不夠了解。因此有了這篇文章的分享。

本文我將從springboot啓動入口開始,逐步跟蹤源碼,逐步分析,力求讓大家通過本文能夠了解springboot的整個啓動流程,主要有以下幾個關注點

  • springboot框架提供了哪些核心能力

  • springboot框架啓動關鍵流程

    • 內嵌支持哪些web容器

    • 如何實現web容器的啓動

    • 如何在不同的web容器之間實現切換

前方高能預警,需要翻閱大量源代碼,請做好心理準備!

1.springboot框架核心能力

大體上來看,springboot框架提供了四方面核心能力

  • 統一依賴管理

  • 自動裝配

  • 健康檢查

  • cli

在日常開發中,我們直接受益的有統一依賴管理、自動裝配、健康檢查。想象一下

  • 平常開發,我們只需要引入spring-boot-starter-xxx,即可正常使用xxx提供的能力,不用再因爲依賴包之間的兼容性,而花費大量的時間去調試兼容性問題了。這就是springboot統一依賴管理後,帶來的直接收益!

  • 相信使用spring框架,從xml配置時代過來的小夥伴,在使用springboot框架後,神清氣爽了很多!畢竟每每想到大量的xml配置文件內容,心有餘悸!這就是springboot自動裝配後,帶來的直接收益!

  • 原來我們做應用監控,怎麼做呢?需要在業務層面去開發一些監控接口,反正是要寫代碼!現在springboot直接提供了actuator,業務健康檢查,是那麼so easy的事情!甚至還可以擴展HealthIndicator接口,輕鬆就能實現新的指標監控檢查。這就是springboot提供的actuator,帶來的直接收益!

相比統一依賴管理、自動裝配、健康檢查,cli相對業界用的比較少。

2.springboot框架啓動流程

2.1.內嵌支持的web容器

使用springboot框架以後,最直觀的感受是,哪怕開發web應用,只需要

  • 引入spring-boot-starter-web依賴

  • 在application.yml文件中,增加配置與web容器相關的一些內容,比如端口、contextpath、靜態資源等

  • 開發springmvc相關的controller,開放端點

  • 打成一個jar包

  • 通過java -jar xxx.jar啓動應用,然後即可以通過瀏覽器訪問應用

就是這麼簡單,稍後我們在啓動流程中詳細分析,web容器是如何啓動的,先看一看,springboot都內嵌了哪些web容器。爲了支持web容器,springboot提供了一個接口

public interface WebServer {
    // 啓動容器
    void start() throws WebServerException;
    // 停止容器
    void stop() throws WebServerException;

    int getPort();

    default void shutDownGracefully(GracefulShutdownCallback callback) {
        callback.shutdownComplete(GracefulShutdownResult.IMMEDIATE);
    }
}

併爲WebServer接口,提供瞭如下實現

2.2.web容器啓動流程

默認情況下,即直接引入spring-boot-starter-web依賴,springboot啓動應用的是tomcat容器

 

接下來,我們就以springboot啓動tomcat容器爲例,來嘗試啓動流程源碼分析。

啓動入口:FollowMeSpringbootActuatorApplication

@SpringBootApplication
public class FollowMeSpringbootActuatorApplication {

	public static void main(String[] args) {
		
 //springboot應用啓動入口, SpringApplication.run()方法      SpringApplication.run(FollowMeSpringbootActuatorApplication.class, args);
	}

}

源碼解析

  • 對於springboot應用,分析它的源碼,我們只需要從啓動類開始即可

  • 進入SpringApplication內部,便可一窺究竟!源碼之下無祕密!

 

進入方法:SpringApplication.run

// 1.從啓動類main方法,進入run方法
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
    	// 調用了重載的run方法
        return run(new Class[]{primarySource}, args);
    }

// 2.重載的run方法
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
        // new 了一個SpringApplication實例,並再次調用重載run方法
        return (new SpringApplication(primarySources)).run(args);
    }

// 3.真正啓動spring應用的地方,返回ioc容器:ConfigurableApplicationContext
public ConfigurableApplicationContext run(String... args) {
        ......省略非關鍵代碼......
        try {
            ......省略非關鍵代碼......
            // 關鍵代碼:創建spring應用ioc容器
            context = this.createApplicationContext();
    		......省略非關鍵代碼......
            // 關鍵代碼:刷新ioc容器
            this.refreshContext(context);
            ......省略非關鍵代碼......
        } catch (Throwable var10) {
            ......省略非關鍵代碼......
        }

        ......省略非關鍵代碼......
 }

源碼解析

  • 在SpringApplication內部,有多個重載的run方法

  • 我們需要一直跟蹤到方法:run(String... args)

  • 在該方法內部,有兩行關鍵代碼需要我們關注

    • context = this.createApplicationContext():創建spring ioc容器

    • this.refreshContext(context):刷新ioc容器,啓動web容器具體內容的入口就是這裏

     

進入方法:createApplicationContext

protected ConfigurableApplicationContext createApplicationContext() {
        Class<?> contextClass = this.applicationContextClass;
        if (contextClass == null) {
            try {
                // 重點關注代碼:根據webApplicationType應用類型
                // 決定創建ioc容器的具體實現,取值有三類
                // SERVLET:創建web容器AnnotationConfigServletWebServerApplicationContext
                // REACTIVE:創建響應式web容器AnnotationConfigReactiveWebServerApplicationContext
                // 默認:創建普通jar應用容器AnnotationConfigApplicationContext  
                switch(this.webApplicationType) {
                case SERVLET:
                    contextClass = Class.forName("org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext");
                    break;
                case REACTIVE:
                    contextClass = Class.forName("org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext");
                    break;
                default:
                    contextClass = Class.forName("org.springframework.context.annotation.AnnotationConfigApplicationContext");
                }
            } catch (ClassNotFoundException var3) {
                throw new IllegalStateException("Unable create a default ApplicationContext, please specify an ApplicationContextClass", var3);
            }
        }

        return (ConfigurableApplicationContext)BeanUtils.instantiateClass(contextClass);
    }

源碼解析

  • 該方法,是一個關鍵方法,它決定了啓動應用最終拿到的ApplicationContext具體實現

  • 有三種情況,根據webApplicationType取值

    • SERVLET:AnnotationConfigServletWebServerApplicationContext

    • REACTIVE:AnnotationConfigReactiveWebServerApplicationContext

    • 默認:AnnotationConfigApplicationContext

  • 第三個我們很熟悉,在還沒有springboot框架以前,通過註解方式配置ioc的ApplicationContext就是它

  • 我們重點關注的應該是:AnnotationConfigServletWebServerApplicationContext,注意類名稱中,有ServletWebServer關鍵字。有點意思了,跟web容器掛上鉤了!

  • 但是最關鍵的地方是,webApplicationType的取值,從根據什麼來的呢?這纔是重點!

枚舉類:WebApplicationType

public enum WebApplicationType {
    NONE,
    SERVLET,
    REACTIVE;

    // 相關ioc容器ApplicationContext標識類常量定義,用於決定啓動時,加載具體的ApplicationContext實現
    private static final String[] SERVLET_INDICATOR_CLASSES = new String[]{"javax.servlet.Servlet", "org.springframework.web.context.ConfigurableWebApplicationContext"};
    private static final String WEBMVC_INDICATOR_CLASS = "org.springframework.web.servlet.DispatcherServlet";
    private static final String WEBFLUX_INDICATOR_CLASS = "org.springframework.web.reactive.DispatcherHandler";
    private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";
    private static final String SERVLET_APPLICATION_CONTEXT_CLASS = "org.springframework.web.context.WebApplicationContext";
    private static final String REACTIVE_APPLICATION_CONTEXT_CLASS = "org.springframework.boot.web.reactive.context.ReactiveWebApplicationContext";

    ......省略非關鍵代碼......

    // 關鍵方法:從classpath下,決定加載ApplicationContext具體實現
    static WebApplicationType deduceFromClasspath() {
        // 響應式【暫時不關注它,不關注這個if判斷】
        if (ClassUtils.isPresent("org.springframework.web.reactive.DispatcherHandler", (ClassLoader)null) && !ClassUtils.isPresent("org.springframework.web.servlet.DispatcherServlet", (ClassLoader)null) && !ClassUtils.isPresent("org.glassfish.jersey.servlet.ServletContainer", (ClassLoader)null)) {
            return REACTIVE;
        } else {
            // 重點關注這裏:
            // 如果類路徑classpath下,存在Servlet、存在ConfigurableWebApplicationContext,那麼說明該應用是web應用
            // 返回 SERVLET
            String[] var0 = SERVLET_INDICATOR_CLASSES;
            int var1 = var0.length;

            for(int var2 = 0; var2 < var1; ++var2) {
                String className = var0[var2];
                if (!ClassUtils.isPresent(className, (ClassLoader)null)) {
                    return NONE;
                }
            }

            return SERVLET;
        }
    }

   ......省略非關鍵代碼......
}

源碼解析

  • WebApplicationType枚舉類,用於從classpath下,是否存在Servlet、存在ConfigurableWebApplicationContext

  • 來決定啓動普通的spring應用,還是啓動web應用

  • 根據引入spring-boot-starter-web依賴以後

    • 會自動引入內嵌的tomcat,即存在Servlet

    • 會自動引入spring-web,即存在ConfigurableWebApplicationContext

    • 因此最終返回 SERVLET

 

類:AnnotationConfigServletWebServerApplicationContext

源碼解析

  • 如上圖所示,它的類層次結構說明一切!

 

小結:到此拿到容器(AnnotationConfigServletWebServerApplicationContext),它明確告訴我們了,這是一個web應用上下文容器。繼續往下,看是如何加載創建webServer

此時,我們需要關注另外一行關鍵代碼了,你還記得它嗎?

// 關鍵代碼:刷新ioc容器
this.refreshContext(context);

進入方法:SpringApplication.refreshContext/refresh

private void refreshContext(ConfigurableApplicationContext context) {
    // 進入resresh方法
    this.refresh((ApplicationContext)context);
    if (this.registerShutdownHook) {
        try {
            context.registerShutdownHook();
        } catch (AccessControlException var3) {
            ;
        }
 }
    
/** @deprecated */
@Deprecated
protected void refresh(ApplicationContext applicationContext) {
        Assert.isInstanceOf(ConfigurableApplicationContext.class, applicationContext);
      // 繼續進入refresh
      this.refresh((ConfigurableApplicationContext)applicationContext);
}

protected void refresh(ConfigurableApplicationContext applicationContext) {
       // 關鍵代碼:最終調用了ApplicationContext的refresh方法
        applicationContext.refresh();
}

源碼解析

  • 通過一路源碼跟蹤,最終發現代碼 applicationContext.refresh();

  • 即實現了ApplicationContext的刷新操作,而我們從上面知道,此時的applicationContext,它是ServletWebServerApplicationContext

 

進入方法:ServletWebServerApplicationContext.refresh

 public final void refresh() throws BeansException, IllegalStateException {
     try {
         // 繼續調用父類refresh方法
         super.refresh();
     } catch (RuntimeException var3) {
         WebServer webServer = this.webServer;
         if (webServer != null) {
             webServer.stop();
         }

      throw var3;
	}
 }

//抽閒父類:AbstractApplicationContext#refresh
public void refresh() throws BeansException, IllegalStateException {
    ......省略非關鍵代碼......

        try {
            ......省略非關鍵代碼......
            // 關鍵代碼:調用onRefresh方法
            this.onRefresh();
            ......省略非關鍵代碼......
        } catch (BeansException var9) {
            ......省略非關鍵代碼......
            throw var9;
        } finally {
            this.resetCommonCaches();
        }

    }
}

// onRefresh方法,在父類中是一個空方法,即鉤子方法
// 該方法的具體實現,留給子類實現
// 此時的具體實現子類是:ServletWebServerApplicationContext
protected void onRefresh() throws BeansException {
}

源碼解析

  • 一路追蹤refresh方法,最終跟到了onRefresh方法

  • 而onRefresh方法,在父類中是空實現,具體實現在子類

  • 此時的子類是:ServletWebServerApplicationContext

 

進入方法:ServletWebServerApplicationContext.onRefresh

protected void onRefresh() {
        super.onRefresh();

        try {
            // 關鍵代碼:創建WebServer,高興!快要柳暗花明了!
            this.createWebServer();
        } catch (Throwable var2) {
            throw new ApplicationContextException("Unable to start web server", var2);
        }
    }

進入方法:ServletWebServerApplicationContext.createWebServer

private void createWebServer() {
    WebServer webServer = this.webServer;
    ServletContext servletContext = this.getServletContext();
    if (webServer == null && servletContext == null) {
        // 關鍵代碼:獲取ServletWebServer工廠,該工廠用於創建WebServer
        ServletWebServerFactory factory = this.getWebServerFactory();
        this.webServer = factory.getWebServer(new ServletContextInitializer[]{this.getSelfInitializer()});
        ......省略非關鍵代碼......
    } else if (servletContext != null) {
        ......省略非關鍵代碼......
    }

    ......省略非關鍵代碼......
}

 protected ServletWebServerFactory getWebServerFactory() {
        // 關鍵代碼:從classpath中,檢查加載具體的webServer:tomcat/jetty/netty/undertow
        String[] beanNames = this.getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class);
        if (beanNames.length == 0) {
           ......省略非關鍵代碼......
        } else if (beanNames.length > 1) {
           ......省略非關鍵代碼......
        } else {
            return (ServletWebServerFactory)this.getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);
        }
    }

源碼解析

  • 到此,我們看到最終需要獲取ServletWebServerFactory工廠,該工廠用於創建具體的ServletWebServer實例

  • 即具體創建的web容器是:tomcat、或者jetty、或者netty、還是undertow,需要檢查classpath類路徑下的依賴來決定

  • 即具體的web容器依賴來決定,存在誰,那麼加載的就是誰

 

小結:到此,我們得到了這麼幾個信息

  • 首先,已經明確這是一個web應用,獲取到的ApplicationContext是ServletWebServerApplicationContext

  • 其次,啓動web應用,需要創建一個WebServer,該WebServer由具體的工廠來創建,該工廠是ServletWebServerFactory

  • 最終,在ServletWebServerFactory工廠中,需要創建哪個具體的web容器(tomcat/jetty/netty/undertow),由類路徑classpath依賴決定

最後的謎底,我們需要回到最開始介紹springboot提供的核心能力的知識點了,其中有一條是說自動裝配

最後來看一下,最終獲取的ServletWebServerFactory,到底是誰?

找到spring-boot-autoconfigure依賴,並展開它,一直找到包

org.springframework.boot.autoconfigure.web.embedded

並找到類

@Configuration(
    proxyBeanMethods = false
)
@ConditionalOnWebApplication
@EnableConfigurationProperties({ServerProperties.class})
public class EmbeddedWebServerFactoryCustomizerAutoConfiguration {
    public EmbeddedWebServerFactoryCustomizerAutoConfiguration() {
    }

    @Configuration(
        proxyBeanMethods = false
    )
    // 如果classpath下存在HttpServer類,那麼啓用NettyWebServer
    @ConditionalOnClass({HttpServer.class})
    public static class NettyWebServerFactoryCustomizerConfiguration {
        public NettyWebServerFactoryCustomizerConfiguration() {
        }

        @Bean
        public NettyWebServerFactoryCustomizer nettyWebServerFactoryCustomizer(Environment environment, ServerProperties serverProperties) {
            return new NettyWebServerFactoryCustomizer(environment, serverProperties);
        }
    }

    @Configuration(
        proxyBeanMethods = false
    )
    // 如果classpath下存在Undertow,那麼啓用UndertowWebServer
    @ConditionalOnClass({Undertow.class, SslClientAuthMode.class})
    public static class UndertowWebServerFactoryCustomizerConfiguration {
        public UndertowWebServerFactoryCustomizerConfiguration() {
        }

        @Bean
        public UndertowWebServerFactoryCustomizer undertowWebServerFactoryCustomizer(Environment environment, ServerProperties serverProperties) {
            return new UndertowWebServerFactoryCustomizer(environment, serverProperties);
        }
    }

    @Configuration(
        proxyBeanMethods = false
    )
    // 如果classpath下,存在Server類,那麼啓用JettyWebServer
    @ConditionalOnClass({Server.class, Loader.class, WebAppContext.class})
    public static class JettyWebServerFactoryCustomizerConfiguration {
        public JettyWebServerFactoryCustomizerConfiguration() {
        }

        @Bean
        public JettyWebServerFactoryCustomizer jettyWebServerFactoryCustomizer(Environment environment, ServerProperties serverProperties) {
            return new JettyWebServerFactoryCustomizer(environment, serverProperties);
        }
    }

    @Configuration(
        proxyBeanMethods = false
    )
    // 如果classpath下存在Tomcat類,那麼啓用TomcatWebServer
    @ConditionalOnClass({Tomcat.class, UpgradeProtocol.class})
    public static class TomcatWebServerFactoryCustomizerConfiguration {
        public TomcatWebServerFactoryCustomizerConfiguration() {
        }

        @Bean
        public TomcatWebServerFactoryCustomizer tomcatWebServerFactoryCustomizer(Environment environment, ServerProperties serverProperties) {
            return new TomcatWebServerFactoryCustomizer(environment, serverProperties);
        }
    }
}

源碼解析

  • EmbeddedWebServerFactoryCustomizerAutoConfiguration,是一個自動裝配類

  • 它根據classpath下,是否存在HttpServer、Undertow、Server、Tomcat類,來決定啓用哪個web容器

    • HttpServer--->Netty

    • Undertow--->Undertow

    • Server--->Jetty

    • Tomcat--->Tomcat

  • 在我們當前應用中,classpath下存在的是Tomcat,因此啓動的是Tomcat web容器

相信到這裏,你可以理解springboot應用中,整個web應用的啓動流程了。源碼有點多,如有不適,請多看幾遍!

2.3.不同web容器之間切換

從前面的內容,我們理清了springboot是如何啓動web應用的,它啓用不同的web容器關鍵點是,看classpath下依賴了誰?因此要想在不同的web容器之間切換,實現就非常簡單,分兩個步驟

  • 首先在pom.xml文件中,排除tomcat依賴

  • 加入其它web容器的依賴即可

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章