Springboot的jar啓動方式,是經過IOC容器啓動 帶動了Web容器的啓動java
而Springboot的war啓動方式,是經過Web容器(如Tomcat)的啓動 帶動了IOC容器相關的啓動react
無論是jar啓動仍是war包啓動,都繞不開web容器相關。先了解這個怎麼工做的,以Tomcat爲例,web
看看Springboot 怎麼來自動裝配tomcat 相關的組件?spring
相關包org.springframework.boot.autoconfigure.web,在springboot的自動配置包的web下(自動配置功能都在這個autoconfigure包下)。tomcat
embedded(內嵌)裏面四個類一個A四B,一個:springboot
EmbeddedWebServerFactoryCustomizerAutoConfiguration(內嵌web容器工廠自定義定製器裝配類)app
四個具體容器相關:less
JettyWebServerFactoryCustomizer、NettyWebServerFactoryCustomizer、TomcatWebServerFactoryCustomizer、UndertowWebServerFactoryCustomizeride
一個自動配置類+四個經常使用web容器定製器工具
以Tomcat定製器切入,斷點落在構造器上,啓動。
總結出它的工做流程:
1.啓動=》2.createWebServer=》
3.拿TomcatServletWebServerFactory(tomcatWeb容器工廠)=》
4.拿WebServerFactoryCustomizer(工廠定製器)
也就是拿工廠定製器獲取工廠,再拿工廠獲取web容器,這麼個流程
能夠仔細看下相關工廠是如何配置建立容器運行的
TomcatServletWebServerFactory的建立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); }
建立Tocmat類並設置相關這些組件,應該很熟悉(之後出Tomcat源碼分析)。
TomcatWebServerFactoryCustomizer的customize定製方法,經過類serverProperties配置文件設置工廠的屬性
來自:
public static void main(String[] args) { SpringApplication.run(StudySpringbootApplication.class, args); }
打開源碼:
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) { return new SpringApplication(primarySources).run(args); }
其實兩步,一步建立SpringApplication ,一步run運行。
建立類作的事情比較簡單,主要包括判斷web應用類型、用SpringFactories技術從 spring.factories 文件裏獲取ApplicationContextInitializer 對應類和ApplicationListener,最後獲取當前應用的啓動類的類對象。
public ConfigurableApplicationContext run(String... args) { //StopWatch是記錄時間的工具,爲了打印那句SpringBoot啓動耗時的 StopWatch stopWatch = new StopWatch(); stopWatch.start(); ConfigurableApplicationContext context = null; Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>(); //系統設置,在缺失顯示屏、鼠標或者鍵盤時也讓這個java應用相關正常工做 configureHeadlessProperty(); //去meta-info/spring.factories中獲取SpringApplicationRunListener 監聽器(事件發佈監聽器) SpringApplicationRunListeners listeners = getRunListeners(args); //發佈容器 starting事件(for循環一個個調用,經過spring的事件多播器) listeners.starting(); try { //封裝命令行參數 ApplicationArguments applicationArguments = new DefaultApplicationArguments( args); //準備容器環境 //1:獲取或者建立環境 //2:把命令行參數設置到環境中 //3:經過監聽器發佈環境準備事件 ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); //配置是否跳過搜索BeanInfo類,默認忽略跳過 configureIgnoreBeanInfo(environment); //打印控制檯那個SpringBoot圖標 Banner printedBanner = printBanner(environment); //根據類型(servlet或者reactive?)建立應用上下文ApplicationContext context = createApplicationContext(); //到spring.factoris文件裏拿springboot異常報告類的集合 exceptionReporters = getSpringFactoriesInstances( SpringBootExceptionReporter.class, new Class[] { ConfigurableApplicationContext.class }, context); //準備環境 //1:把應用上下文ApplicationContext環境設置到容器中 //2:循環調用AppplicationInitnazlier 進行容器初始化工做 //3:發佈 容器上下文準備 完成事件 //4:註冊關於springboot特定特性的相關單例Bean //5:BeanDefinitionLoader加載資源源碼,將啓動類注入容器 //6:發佈 容器上下文加載 完畢事件 prepareContext(context, environment, listeners, applicationArguments, printedBanner); //IOC容器refresh,見之前IOC源碼分析 refreshContext(context); //springboot2.x已經改爲空方法,之前裏面是後面的callRunners afterRefresh(context, applicationArguments); //計時(耗時統計)中止 stopWatch.stop(); //打印那句springboot在多少秒內啓動了 if (this.logStartupInfo) { new StartupInfoLogger(this.mainApplicationClass) .logStarted(getApplicationLog(), stopWatch); } //發佈容器啓動事件 listeners.started(context); //運行 ApplicationRunner 和CommandLineRunner callRunners(context, applicationArguments); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, listeners); throw new IllegalStateException(ex); } try { //發佈容器運行事件 listeners.running(context); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, null); throw new IllegalStateException(ex); } return context; }
其實大可能是準備、工具、事件等,最核心的仍是裏面的refreshContext(context);帶動了IOC容器啓動
其實大部份內容在以前IOC容器源碼寫過,惟一的區別在於:
SpringIOC的refresh方法裏的onRefresh方法是空的,而SpringBoot繼承重寫了這個方法!
SpringBoot的onRefresh:
@Override protected void onRefresh() { super.onRefresh(); try { createWebServer(); } catch (Throwable ex) { throw new ApplicationContextException("Unable to start web server", ex); } }
SpringBoot裏應用上下文是用的新的ServletWebServerApplicationContext類(更具體實現之一是AnnotationConfigServletWebServerApplicationContext)
這裏就開始和上面說過的Web容器相關知識銜接上了,這裏進行的Web容器(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) { //... } } initPropertySources(); }
這裏就是判斷有沒有Server以及環境,沒有的話就獲取web容器製造工廠,最後經過工廠獲取Tomcat賦值。
實際上獲取Tomcat建立的時候,此時構造器最後的代碼就是啓動,TomcatWebServer類構造器以下:
public TomcatWebServer(Tomcat tomcat, boolean autoStart) { Assert.notNull(tomcat, "Tomcat Server must not be null"); this.tomcat = tomcat; this.autoStart = autoStart; initialize(); }
TomcatWebServer的initialize:
private void initialize() throws WebServerException { 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())) { removeServiceConnectors(); } }); this.tomcat.start(); rethrowDeferredStartupExceptions(); try { ContextBindings.bindClassLoader(context, context.getNamingToken(), getClass().getClassLoader()); } catch (NamingException ex) { } startDaemonAwaitThread(); } catch (Exception ex) { stopSilently(); throw new WebServerException("Unable to start embedded Tomcat", ex); } } }
最終在IOC 容器中的 org.springframework.context.support.AbstractApplicationContext的refresh 的
onReFresh方法帶動了Tomcat啓動
@SpringBootApplication public class StudySpringbootApplication extends SpringBootServletInitializer { @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { return builder.sources(StudySpringbootApplication.class); } }
Springboot的war啓動方式,是經過Web容器(如Tomcat)的啓動 帶動了IOC容器相關的啓動
要說Tomcat怎麼加載war包就不得不從servlet3.0的特性提及:
1.web應用啓動,會建立當前Web應用導入jar包中的 ServletContainerInitializer類的實例
2.ServletContainerInitializer 類必須放在jar包的 META-INF/services目錄下,文件名稱爲javax.servlet.ServletContainerInitializer
3.文件的內容指向ServletContainerInitializer實現類的全路徑
4.ServletContainerInitializer實現類使用@HandlesTypes註解, 在咱們應用啓動的時候,加載註解指定的的類
Spring中實現ServletContainerInitializer的類是SpringServletContainerInitializer
@HandlesTypes(WebApplicationInitializer.class) public class SpringServletContainerInitializer implements ServletContainerInitializer { @Override public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) { //建立保存須要加載的類的集合 List<WebApplicationInitializer> initializers = new LinkedList<>(); if (webAppInitializerClasses != null) { for (Class<?> waiClass : webAppInitializerClasses) { //判斷須要加載的類不是接口不是抽象類 if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) && WebApplicationInitializer.class.isAssignableFrom(waiClass)) { try { //經過反射建立實例而且加入到集合中 initializers.add((WebApplicationInitializer) ReflectionUtils.accessibleConstructor(waiClass).newInstance()); } catch (Throwable ex) { //... } } } } if (initializers.isEmpty()) { return; } AnnotationAwareOrderComparator.sort(initializers); //循環調用集合中的感興趣類對象的onstartup的方法 for (WebApplicationInitializer initializer : initializers) { initializer.onStartup(servletContext); } } }
總結:HandlesTypes指定了WebApplicationInitializer類,並在onStartup方法中,建立這些須要加載的類的實例,而且循環調用他們的onStartup方法。
1.Tomcat啓動,war包應用的jar包裏找ServletContainerInitializer 文件,而後找到spring-web-5.1.2.RELEASE.jar這個jar包裏的META-INF\services\javax.servlet.ServletContainerInitializer 文件指向本身實現的SpringServletContainerInitializer並執行它
2.將@HandlesTypes標註的類(WebApplicationInitializer)都傳入到 onStartup()的方法中Set<Class<?>>參數中
,經過 ReflectionUtils.accessibleConstructor(waiClass).newInstance());,爲這些類建立實例
3.調用WebApplicationInitializer的onStartup方法
4.而Springboot啓動類繼承了SpringBootServletInitializer(實現了接口WebApplicationInitializer)
5.而咱們的啓動類StudySpringbootApplication沒有重寫onStartup,調的SpringBootServletInitializer的onStartup
6.而SpringBootServletInitializer的onStartup方法調了咱們重寫的configure方法,加載啓動。
@SpringBootApplication public class StudySpringbootApplication extends SpringBootServletInitializer { @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { return builder.sources(StudySpringbootApplication.class); } }
能夠把斷點打到第六行裏builder.sources,經過斷點一看調用棧和代碼,邏輯就全出來了:
StudySpringbootApplication.configure <<==== 父類SpringBootServletInitializer(主類繼繼承的這個類).createRootApplicationContext
父類SpringBootServletInitializer.createRootApplicationContext:
protected WebApplicationContext createRootApplicationContext( ServletContext servletContext) { //建立spring應用的構建器 SpringApplicationBuilder builder = createSpringApplicationBuilder(); builder.main(getClass()); //設置環境 ApplicationContext parent = getExistingRootWebApplicationContext(servletContext); if (parent != null) { servletContext.setAttribute( WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, null); builder.initializers(new ParentContextApplicationContextInitializer(parent)); } builder.initializers( new ServletContextApplicationContextInitializer(servletContext)); builder.contextClass(AnnotationConfigServletWebServerApplicationContext.class); //調用咱們本身啓動類上的confiure方法 傳入咱們本身的主啓動類 builder = configure(builder); builder.listeners(new WebEnvironmentPropertySourceInitializer(servletContext)); SpringApplication application = builder.build(); if (application.getAllSources().isEmpty() && AnnotationUtils .findAnnotation(getClass(), Configuration.class) != null) { application.addPrimarySources(Collections.singleton(getClass())); } if (this.registerErrorPageFilter) { application.addPrimarySources( Collections.singleton(ErrorPageFilterConfiguration.class)); } //調用咱們類上的run方法 return run(application); }
注意重點是 調用了本身的方法(傳入主類)和 run方法
run源碼:
protected WebApplicationContext run(SpringApplication application) { return (WebApplicationContext) application.run(); }
繼續打開run源碼:
public ConfigurableApplicationContext run(String... args) { StopWatch stopWatch = new StopWatch(); stopWatch.start(); ConfigurableApplicationContext context = null; Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>(); configureHeadlessProperty(); SpringApplicationRunListeners listeners = getRunListeners(args); listeners.starting(); try { ApplicationArguments applicationArguments = new DefaultApplicationArguments( args); ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); configureIgnoreBeanInfo(environment); Banner printedBanner = printBanner(environment); context = createApplicationContext(); exceptionReporters = getSpringFactoriesInstances( SpringBootExceptionReporter.class, new Class[] { ConfigurableApplicationContext.class }, context); prepareContext(context, environment, listeners, applicationArguments, printedBanner); refreshContext(context); afterRefresh(context, applicationArguments); stopWatch.stop(); if (this.logStartupInfo) { new StartupInfoLogger(this.mainApplicationClass) .logStarted(getApplicationLog(), stopWatch); } listeners.started(context); callRunners(context, applicationArguments); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, listeners); throw new IllegalStateException(ex); } try { listeners.running(context); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, null); throw new IllegalStateException(ex); } return context; }
是否是很熟悉,和jar啓動的run IOC同樣!因此最終仍是異曲同工,仍是走了application.run()方法,走了IOC容器啓動Refresh!