引言
早在15年的時候就開始用spring boot進行開發了,然而一直就只是用用,并沒有深入去了解spring boot是以什么原理怎樣工作的,說來也慚愧。今天讓我們從spring boot啟動開始,深入了解一下spring boot的工作原理。
為什么用spring boot
在使用一個東西或者一個工具之前,我們總是會問自己,我為什么要用?用他能給我帶來什么好處?
* 最大的好處就是spring boot遵從了java**約定大于配置**不用面對一大堆的配置文件,spring boot是根據你用的包來決定提供什么配置。
* 服務器以jar包的形式內嵌于項目中,對于微服務滿天飛的情況,spring boot天生適合微服務架構,方便部署。
* 提供devtools從此改代碼就需重啟成為歷史。
有優點就一定有缺點,缺點來源于優點優點來源于缺點(感覺在說哲學問題了哈哈哈)
* 正因為配置對開發者不透明,不看源碼會不清楚spring boot如何進行諸如JDBC加載、事務管理等,出現錯誤也很難調錯。
* 自動配置之后要自定義配置需編碼javaConfig,需要了解這些配置類api。
* 版本迭代太快,新版本對老版本改動太多導致不兼容,比如1.3.5之前的springBootTest和1.4.0之后的springBootTest。
只有合適的架構才是最好的架構如果能接受spring boot這些缺點,spring boot確實是一個可以提高開發效率的不錯的選擇。
啟動流程
扯了這么多,該上正題了,讓我們來看看spring boot是怎樣啟動和啟動做了哪些事情。
以下代碼是spring boot項目標準的啟動方式,使用注解@SpringBootApplication并且在main方法中調用SpringApplication的run方法,就可以完成。我們就從這個run方法開始看看spring boot的啟動過程。
1
2
3
4
5
6
|
@SpringBootApplication public class Application { public static void main(String[] args){ SpringApplication.run(Application. class ,args); } } |
我們進入run方法,可以看到最終是調用了 new SpringApplication(sources).run(args);new SpringApplication(sources).run(args); 這個方法,可以看到,springBoot的啟動可以分為兩個部分,第一部分:SpringApplication的實例化;第二部分:調用該實例運行run方法。我們先來看看這個SpringApplication的實例化過程。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
private void initialize(Object[] sources) { if (sources != null && sources.length > 0 ) { this .sources.addAll(Arrays.asList(sources)); } //判定是否為webEnvironment this .webEnvironment = deduceWebEnvironment(); //實例化并加載所有可以加載的ApplicationContextInitializer setInitializers((Collection) getSpringFactoriesInstances( ApplicationContextInitializer. class )); //實例化并加載所有可以加載的ApplicationListener setListeners((Collection) getSpringFactoriesInstances(ApplicationListener. class )); this .mainApplicationClass = deduceMainApplicationClass(); } |
關鍵點在兩個set方法上**
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class))** 和 **setListeners((Collection)
getSpringFactoriesInstances(ApplicationListener.class))**
這兩個方法一毛一樣,挑實例化ApplicationContextInitializer講一講。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) { //拿到類加載器 ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); // Use names and ensure unique to protect against duplicates //使用loadFactoryNames方法載入所有的ApplicationContextInitializer的類全限定名 Set<String> names = new LinkedHashSet<String>( SpringFactoriesLoader.loadFactoryNames(type, classLoader)); //使用反射將所有的ApplicationContextInitializer實例化 List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names); //排序 AnnotationAwareOrderComparator.sort(instances); return instances; } |
自動配置的關鍵就是這個 getSpringFactoriesInstances方法,確切的說是這個方法里的loadFactoryNames方法,浪我們看看這個loadFactoryNames方法干了啥,咋就能實現自動配置。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) { String factoryClassName = factoryClass.getName(); try { Enumeration<URL> urls = classLoader != null ?classLoader.getResources( "META-INF/spring.factories" ):ClassLoader.getSystemResources( "META-INF/spring.factories" ); ArrayList result = new ArrayList(); while (urls.hasMoreElements()) { URL url = (URL)urls.nextElement(); Properties properties = PropertiesLoaderUtils.loadProperties( new UrlResource(url)); String factoryClassNames = properties.getProperty(factoryClassName); result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames))); } return result; } catch (IOException var8) { throw new IllegalArgumentException( "Unable to load [" + factoryClass.getName() + "] factories from location [" + "META-INF/spring.factories" + "]" , var8); } } |
可以看到這個方法就做了一件事,就是從META-INF/spring.factories這個路徑取出所有”url”來,我們可以去到這個路徑下看看到底是些啥?
1
2
3
4
5
6
|
# Initializers org.springframework.context.ApplicationContextInitializer=\ org.springframework.boot.autoconfigure.logging.AutoConfigurationReportLoggingInitializer # Application Listeners org.springframework.context.ApplicationListener=\ org.springframework.boot.autoconfigure.BackgroundPreinitializer |
這下大家都應該明白了,spring是通過將所有你加載的jar包中找到它需要的ApplicationContextInitializer來進行動態的配置的,只要你有用到特定的maven包,初始化的時候會找這個包下的META-INF/spring.factories的需要的類比如ApplicationContextInitializer進行實例化bean,你就可以用了,不需要任何配置。
說到這已經將所有SpringApplication實例化說完了,只是在加載完ApplicationContextInitializer和ApplicationListener這之后還有一步,就是找到啟動類所在的位置并且設入屬性mainApplicationClass中。
接下來讓我們回到new SpringApplication(sources).run(args)
方法來看看run方法是怎么run的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
public ConfigurableApplicationContext run(String... args) { //開啟啟動計時器,項目啟動完會打印執行時間出來 StopWatch stopWatch = new StopWatch(); stopWatch.start(); ConfigurableApplicationContext context = null ; FailureAnalyzers analyzers = null ; configureHeadlessProperty(); //獲取SpringApplicationRunListener并啟動監聽器 SpringApplicationRunListeners listeners = getRunListeners(args); listeners.starting(); try { ApplicationArguments applicationArguments = new DefaultApplicationArguments( args); //環境變量的加載 ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); //啟動后console的打印出來的一堆配置信息 Banner printedBanner = printBanner(environment); //終極大boss->ApplicationContext實例化 context = createApplicationContext(); analyzers = new FailureAnalyzers(context); prepareContext(context, environment, listeners, applicationArguments, printedBanner); refreshContext(context); afterRefresh(context, applicationArguments); listeners.finished(context, null ); stopWatch.stop(); if ( this .logStartupInfo) { new StartupInfoLogger( this .mainApplicationClass) .logStarted(getApplicationLog(), stopWatch); } return context; } catch (Throwable ex) { handleRunFailure(context, listeners, analyzers, ex); throw new IllegalStateException(ex); } } |
從這個方法里面做的最關鍵的三件事情就是:
獲取監聽器并啟動
加載環境變量,該環境變量包括system environment、classpath environment和用戶自己加的application.properties
創建ApplicationContext
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) { context.setEnvironment(environment); postProcessApplicationContext(context); applyInitializers(context); listeners.contextPrepared(context); if ( this .logStartupInfo) { logStartupInfo(context.getParent() == null ); logStartupProfileInfo(context); } // Add boot specific singleton beans context.getBeanFactory().registerSingleton( "springApplicationArguments" , applicationArguments); if (printedBanner != null ) { context.getBeanFactory().registerSingleton( "springBootBanner" , printedBanner); } // Load the sources Set<Object> sources = getSources(); Assert.notEmpty(sources, "Sources must not be empty" ); load(context, sources.toArray( new Object[sources.size()])); listeners.contextLoaded(context); } |
前兩點沒什么好說的,重點說說第三個,創建ApplicationContext。創建applicationContext又分為幾部:實例化applicationContext、prepareContext、refreshContext。實例化applicationContext會根據在之前我們說的webEnvironment這個屬性判斷是使用webContext類AnnotationConfigEmbeddedWebApplicationContext還是普通context類AnnotationConfigApplicationContext(在這里我們使用的是webContext為例)然后通過反射進行實例化。applicationContext實例化完了會進入prepareContext流程,這個prepareContext方法會加載之前準備好的environment進入context中,然后如果有beanNameGenerator和resourceLoader那么提前創建bean加載進applicationContext,但是一般這兩個都是空的,所以直接進入applyInitializers方法,將之前實例化的所有initializers進行初始化,所有的bean就是在這里進行bean的掃描和加載的因這次講的是啟動過程,所以不再細講。最后把創建好的applicationContext設置進入listener,prepareContext過程就結束了。最后是refreshContext,這個就和spring的bean加載過程一致了,bean的注入、beanFactory、postProcessBeanFactory等等,詳情可以去看看spring bean的生命周期。
總結
spring boot 初始化內容還是很多的,但是總結起來就四點:
* 創建SpringApplication實例,判定環境,是web環境還是普通環境。加載所有需要用到的Initializers和Listeners,這里使用約定大于配置的理念揭開了自動配置的面紗。
* 加載環境變量,環境變量包括system environment、classpath environment、application environment(也就是我們自定義的application.properties配置文件)
* 創建SpringApplicationRunListeners
* 創建ApplicationContext,設置裝配context,在這里將所有的bean進行掃描最后在refreshContext的時候進行加載、注入。最終將裝配好的context作為屬性設置進SpringApplicationRunListeners,這就完成了一個spring boot項目的啟動。
以上所述是小編給大家介紹的Spring Boot啟動流程,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對服務器之家網站的支持!
原文鏈接:http://blog.csdn.net/nethackatschool/article/details/78051220