SpringBoot源碼分析(二)啓動原理

Springboot的jar啓動方式,是經過IOC容器啓動 帶動了Web容器的啓動java

而Springboot的war啓動方式,是經過Web容器(如Tomcat)的啓動 帶動了IOC容器相關的啓動react

1、不可不說的Web容器(如Tomcat)

無論是jar啓動仍是war包啓動,都繞不開web容器相關。先了解這個怎麼工做的,以Tomcat爲例,web

看看Springboot 怎麼來自動裝配tomcat 相關的組件?spring

1.1 相關類

相關包org.springframework.boot.autoconfigure.web,在springboot的自動配置包的web下(自動配置功能都在這個autoconfigure包下)。tomcat

embedded(內嵌)裏面四個類一個A四B,一個:springboot

EmbeddedWebServerFactoryCustomizerAutoConfiguration(內嵌web容器工廠自定義定製器裝配類)app

四個具體容器相關:less

JettyWebServerFactoryCustomizer、NettyWebServerFactoryCustomizer、TomcatWebServerFactoryCustomizer、UndertowWebServerFactoryCustomizeride

一個自動配置類+四個經常使用web容器定製器工具

1.2.工做流程

以Tomcat定製器切入,斷點落在構造器上,啓動。

總結出它的工做流程:

1.啓動=》2.createWebServer=》

3.拿TomcatServletWebServerFactory(tomcatWeb容器工廠)=》

4.拿WebServerFactoryCustomizer(工廠定製器)

也就是拿工廠定製器獲取工廠,再拿工廠獲取web容器,這麼個流程

1.3.具體工做

能夠仔細看下相關工廠是如何配置建立容器運行的

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配置文件設置工廠的屬性

2、SpringBoot的jar啓動方式

來自:

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,最後獲取當前應用的啓動類的類對象。

2.1 run方法

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容器啓動

2.2 refreshContext(context)

其實大部份內容在以前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啓動

3、SpringBoot的war包啓動方式

@SpringBootApplication
public class StudySpringbootApplication extends SpringBootServletInitializer {
    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        return builder.sources(StudySpringbootApplication.class);
    }
}

Springboot的war啓動方式,是經過Web容器(如Tomcat)的啓動 帶動了IOC容器相關的啓動

3.1 Tomcat加載war

要說Tomcat怎麼加載war包就不得不從servlet3.0的特性提及:

1.web應用啓動,會建立當前Web應用導入jar包中的 ServletContainerInitializer類的實例

2.ServletContainerInitializer 類必須放在jar包的 META-INF/services目錄下,文件名稱爲javax.servlet.ServletContainerInitializer

3.文件的內容指向ServletContainerInitializer實現類的全路徑
4.ServletContainerInitializer實現類使用@HandlesTypes註解, 在咱們應用啓動的時候,加載註解指定的的類

3.2 Spring中的ServletContainerInitializer

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方法。

3.2 工做過程

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方法,加載啓動。

4.1 實戰調試細節

@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!

相關文章
相關標籤/搜索