主要了解和學(xué)習(xí)下SpringBoot啟動(dòng)的大致原理是如何须揣,以及知道幾個(gè)注解的真正含義和用途是什么暮屡,SpringBoot就可以以SpringApplication.run(Bootstrap.class);
這樣的一句代碼作為啟動(dòng)的入口
1奴艾、SpringApplication 對象實(shí)例化
SpringApplication 文件
public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
// 傳遞的source其實(shí)就是類Bootstrap
return new SpringApplication(sources).run(args);
// 實(shí)例化一個(gè)SpringApplication對象執(zhí)行run方法
}
實(shí)例化的時(shí)候又會(huì)執(zhí)行initialize 方法
private void initialize(Object[] sources) {
// 這個(gè)source依舊是上文說的Bootstrap.class 類
if (sources != null && sources.length > 0) {
this.sources.addAll(Arrays.asList(sources));
// 添加到source資源列表里面去
}
this.webEnvironment = deduceWebEnvironment();
// 設(shè)置其是否為web環(huán)境
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 拆分為兩步谅海,一步是getSpringFactoriesInstances,再者就是set操作
// set操作很簡單图筹,就是設(shè)置當(dāng)前對象的初始化對象以及監(jiān)聽器
this.mainApplicationClass = deduceMainApplicationClass();
// 通過堆棧信息卫漫,推斷 main方法的類對象為當(dāng)前的主程序類
}
private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet",
"org.springframework.web.context.ConfigurableWebApplicationContext" };
private boolean deduceWebEnvironment() {
for (String className : WEB_ENVIRONMENT_CLASSES) {
// 遍歷包含上述兩個(gè)類名稱的數(shù)組
if (!ClassUtils.isPresent(className, null)) {
// 一旦發(fā)現(xiàn)不存在該類菲饼,就立即返回 deduce 推斷不是web環(huán)境
return false;
}
}
// 必須同時(shí)包含兩個(gè)類,才推斷出為web環(huán)境
return true;
}
getSpringFactoriesInstances 方法操作
private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type,
Class<?>[] parameterTypes, Object... args) {
// 傳遞的type就是上面說的ApplicationContextInitializer.class以及ApplicationListener.class類
// 類型以及參數(shù)目前都沒有具體指
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// Use names and ensure unique to protect against duplicates
Set<String> names = new LinkedHashSet<String>(
SpringFactoriesLoader.loadFactoryNames(type, classLoader));
// 通過SpringFactoriesLoader 獲取對應(yīng)的名稱列赎,具體詳情可以看下面的代碼塊
// 這點(diǎn)需要重點(diǎn)關(guān)注下:暝谩!包吝!
// 結(jié)果就是返回一個(gè)set集合
List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
classLoader, args, names);
// 看樣子就是創(chuàng)建一個(gè)實(shí)例的集合
AnnotationAwareOrderComparator.sort(instances);
// 然后通過AnnotationAwareOrderComparator 的排序規(guī)則跪?qū)嵗线M(jìn)行排序
// 排序就是看是否存在Order或者Priority注解饼煞,然后取得注解的值,排在集合前面
return instances;
}
private <T> List<T> createSpringFactoriesInstances(Class<T> type,
Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args,
Set<String> names) {
List<T> instances = new ArrayList<T>(names.size());
for (String name : names) {
// 遍歷上面取到的name 集合
try {
Class<?> instanceClass = ClassUtils.forName(name, classLoader);
// 取到這個(gè)類名稱的類
Assert.isAssignable(type, instanceClass);
Constructor<?> constructor = instanceClass
.getDeclaredConstructor(parameterTypes);
// 獲取當(dāng)前類的符合當(dāng)前參數(shù)的構(gòu)造器
T instance = (T) BeanUtils.instantiateClass(constructor, args);
// 利用反射的方式生成具體的對象
instances.add(instance);
}
catch (Throwable ex) {
throw new IllegalArgumentException(
"Cannot instantiate " + type + " : " + name, ex);
}
}
// 最后生成name映射的實(shí)例集合
return instances;
}
SpringFactoriesLoader.loadFactoryNames 方法
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
// 傳遞的factoryClass 就是上面的ApplicationContextInitializer诗越、ApplicationListener.等
String factoryClassName = factoryClass.getName();
// 獲取類的全名稱
try {
Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
// 如果類加載器為null砖瞧,則使用系統(tǒng)默認(rèn)的方法,否則使用當(dāng)前傳遞的類加載器讀取
// 當(dāng)前類加載器可以獲取到的所有文件路徑為“META-INF/spring.factories” 的地址
List<String> result = new ArrayList<String>();
while (urls.hasMoreElements()) {
// 迭代遍歷url
URL url = urls.nextElement();
Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
// 讀取映射的spring.factories 文件的KV鍵值對嚷狞,存放到properties對象中
String factoryClassNames = properties.getProperty(factoryClassName);
// 類似于map一般块促,獲取對應(yīng)的值
result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
// 對值使用逗號(hào)分隔,生成list感耙,然后去重添加到result
}
// 總結(jié)下來就是遍歷當(dāng)前類環(huán)境中的所有路徑為“META-INF/spring.factories”的文件
// 讀取文件褂乍,然后獲取k為當(dāng)前類名稱的所有值,然后存儲(chǔ)到set中返回
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() +
"] factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
到這里整個(gè)的initialize操作就已經(jīng)清楚了即硼,通過類加載器可獲取的所有為“META-INF/spring.factories” 的地址的文件內(nèi)容,然后獲取key為ApplicationContextInitializer.class以及ApplicationListener.class的類名稱的值集合
然后依次就行實(shí)例化屡拨,最后排序返回只酥,最后保存到當(dāng)前對象的初始化集合以及監(jiān)聽器集合中褥实,便于后續(xù)操作
需要注意到SpringFactoriesLoader.loadFactoryNames 后面很多地方都需要使用該方法去獲取相關(guān)內(nèi)容
當(dāng)然現(xiàn)在只是完成了SpringApplication構(gòu)造器里面的方法,還剩下后面的run(args)
方法執(zhí)行
如下代碼塊就是SpringBoot的執(zhí)行過程(最后的套路依舊是Spring Framework的執(zhí)行策略)
2裂允、SpringApplication的run方法啟動(dòng)
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
// 記錄當(dāng)前服務(wù)開始啟動(dòng)
ConfigurableApplicationContext context = null;
// 上下文context损离,非常關(guān)鍵
FailureAnalyzers analyzers = null;
configureHeadlessProperty();
// 給系統(tǒng)設(shè)置headless屬性值
SpringApplicationRunListeners listeners = getRunListeners(args);
// 就是通過SpringFactoriesLoader 獲取到所有SpringApplicationRunListener.class的對象
// 其中args是用來進(jìn)行實(shí)例化SpringApplicationRunListener對應(yīng)的對象的構(gòu)造器參數(shù)
// 最后返回listener是整個(gè)系統(tǒng)的監(jiān)聽器
listeners.starting();
// 監(jiān)聽器開始執(zhí)行
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
// 默認(rèn)程序參數(shù)
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
// 準(zhǔn)備運(yùn)行的環(huán)境上下文
Banner printedBanner = printBanner(environment);
// 打印banner,默認(rèn)輸出當(dāng)前springboot版本等內(nèi)容绝编,可以自定義設(shè)置文本或者圖片
// 具體看下面的方法詳解
context = createApplicationContext();
// 創(chuàng)建SpringBoot最重要的上下文容器
analyzers = new FailureAnalyzers(context);
// 分析上下文出現(xiàn)問題的點(diǎn)僻澎,便于使用者可以直觀的發(fā)現(xiàn)問題出現(xiàn)在哪里
// 其實(shí)套路類似,就是使用SpringFactoriesLoader獲取所有的FailureAnalyzer實(shí)例對象十饥,然后設(shè)置其bean工廠為context的bean工廠上下文
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
// 看名稱就是對context的前置準(zhǔn)備工作窟勃,細(xì)節(jié)在后面說
refreshContext(context);
// 切入到spring framework的方式去完成context內(nèi)容的裝載
// 如果需要注冊終止鉤子,則注冊一個(gè)
afterRefresh(context, applicationArguments);
// 基本上認(rèn)為springboot所需的服務(wù)都加載完成逗堵,進(jìn)行最后的處理操作
// 里面常用的就是CommandLineRunner
listeners.finished(context, null);
// 監(jiān)聽器的啟動(dòng)結(jié)束事件秉氧,
stopWatch.stop();
// 表示SpringBoot服務(wù)啟動(dòng)步驟完成,統(tǒng)計(jì)下啟動(dòng)時(shí)間等操作
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
// 打印SpringBoot啟動(dòng)成功的消息蜒秤,例如 Started xxx in 12.4 seconds 等信息
}
return context;
}
catch (Throwable ex) {
handleRunFailure(context, listeners, analyzers, ex);
// 啟動(dòng)失敗了就會(huì)輸出Application startup failed 日志
// 并且會(huì)輸出具體的錯(cuò)誤內(nèi)容信息
throw new IllegalStateException(ex);
}
}
private ConfigurableEnvironment prepareEnvironment(
SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// Create and configure the environment
ConfigurableEnvironment environment = getOrCreateEnvironment();
// 如果當(dāng)前環(huán)境值不為null汁咏,直接返回
// 否則根據(jù)上文推斷出的webEnvironment boolean 值 生成對象的環(huán)境對象
// 當(dāng)為true的時(shí)候,生成StandardServletEnvironment
// 否則生成的是StandardEnvironment
configureEnvironment(environment, applicationArguments.getSourceArgs());
listeners.environmentPrepared(environment);
if (!this.webEnvironment) {
// 如果不是web的環(huán)境作媚,再對當(dāng)前的環(huán)境進(jìn)行包裝攘滩,生成一個(gè)新的運(yùn)行環(huán)境對象
environment = new EnvironmentConverter(getClassLoader())
.convertToStandardEnvironmentIfNecessary(environment);
}
return environment;
}
private Banner printBanner(ConfigurableEnvironment environment) {
// 參數(shù)environment就是上面生成的環(huán)境對象
if (this.bannerMode == Banner.Mode.OFF) {
// 如果設(shè)置了banner關(guān)閉模式,則不進(jìn)行打印輸出操作
return null;
}
ResourceLoader resourceLoader = this.resourceLoader != null ? this.resourceLoader
: new DefaultResourceLoader(getClassLoader());
// 資源加載器生成
SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(
resourceLoader, this.banner);
// 后續(xù)使用SpringApplicationBannerPrinter 類的print進(jìn)行輸出操作
if (this.bannerMode == Mode.LOG) {
// 打印模式纸泡,如果是log則輸出到log中轰驳,否則輸出到終端中
return bannerPrinter.print(environment, this.mainApplicationClass, logger);
}
return bannerPrinter.print(environment, this.mainApplicationClass, System.out);
// 大致操作就是先看是否存在自定義的圖片類型或者文字類型 banner,如果有就優(yōu)先確定banner對象
// 否則就默認(rèn)使用SpringBootBanner的banner(這個(gè)里面就包含了常規(guī)的springboot輸出內(nèi)容)
// 然后解析banner的資源弟灼,得出將要輸出的字符串內(nèi)容(利用日志直接輸出)级解,存儲(chǔ)到PrintedBanner
}
public static final String DEFAULT_WEB_CONTEXT_CLASS = "org.springframework."
+ "boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext";
public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context."
+ "annotation.AnnotationConfigApplicationContext";
protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
contextClass = Class.forName(this.webEnvironment
? DEFAULT_WEB_CONTEXT_CLASS : DEFAULT_CONTEXT_CLASS);
// 如果是web環(huán)境,則使用AnnotationConfigEmbeddedWebApplicationContext
// 否則就使用AnnotationConfigApplicationContext
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException(
"Unable create a default ApplicationContext, "
+ "please specify an ApplicationContextClass",
ex);
}
}
return (ConfigurableApplicationContext) BeanUtils.instantiate(contextClass);
// 直接通過類田绑,反射生成無構(gòu)造參數(shù)的對象勤哗,一般情況就是AnnotationConfigEmbeddedWebApplicationContext對象了
}
private void prepareContext(ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
// 傳遞上下文、環(huán)境掩驱、上下文參數(shù)等數(shù)據(jù)
context.setEnvironment(environment);
postProcessApplicationContext(context);
// 前置處理context上下文芒划,包含了beanNameGenerator和resourceLoader
// 其中beanNameGenerator 可以自定義規(guī)則約定bean的名稱功能
applyInitializers(context);
// 應(yīng)用ApplicationContextInitializer去初始化完成對context的操作
// 具體的ApplicationContextInitializer對象就是在SpringApplication對象的構(gòu)造方法中實(shí)例化創(chuàng)建的
// 可以給context添加額外的操作,同時(shí)也可以很方便的自定義完成自己需要的功能
listeners.contextPrepared(context);
// 執(zhí)行contextPrepared 上下文準(zhǔn)備工作的事件
if (this.logStartupInfo) {
// 日志啟動(dòng)標(biāo)志位欧穴,默認(rèn)為true
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
// 明確當(dāng)前執(zhí)行的主函數(shù)log民逼,輸出SpringBoot的開始啟動(dòng)信息
}
// 注冊springApplicationArguments 這個(gè)bean到context中去
context.getBeanFactory().registerSingleton("springApplicationArguments",
applicationArguments);
if (printedBanner != null) {
context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
// 同樣是注冊,打印早就完成了
}
// Load the sources
Set<Object> sources = getSources();
// 一般情況下這個(gè)source就是SpringBoot 啟動(dòng)的主類Class涮帘,注意不是實(shí)例對象
Assert.notEmpty(sources, "Sources must not be empty");
load(context, sources.toArray(new Object[sources.size()]));
// 把source也就是主類當(dāng)做bean拼苍,加載到spring的容器中
listeners.contextLoaded(context);
// 監(jiān)聽器的上下文導(dǎo)入完成事件 執(zhí)行
}
private void callRunners(ApplicationContext context, ApplicationArguments args) {
List<Object> runners = new ArrayList<Object>();
runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
AnnotationAwareOrderComparator.sort(runners);
// 從context獲取ApplicationRunner和CommandLineRunner 對象
// 然后按照對應(yīng)的規(guī)則進(jìn)行排序
for (Object runner : new LinkedHashSet<Object>(runners)) {
if (runner instanceof ApplicationRunner) {
callRunner((ApplicationRunner) runner, args);
}
if (runner instanceof CommandLineRunner) {
callRunner((CommandLineRunner) runner, args);
}
// 分別執(zhí)行各自的run方法
}
// 一般情況,我們?nèi)绻枰赟pringBoot加載完成后需要完成一些自定義操作就是注冊
// ApplicationRunner或者CommandLineRunner 的bean對象调缨,然后自定義實(shí)現(xiàn)run方法即可
}
3疮鲫、總結(jié)
就SpringBoot的啟動(dòng)整個(gè)過程而已吆你,還是很清晰的,SpringBoot的套用SpringFramework的機(jī)制俊犯,為我們自定義實(shí)現(xiàn)功能提供了很好的便利妇多,整個(gè)的SpringBoot就是重新包裝了一個(gè)SpringFramework。
里面有一個(gè)點(diǎn)是SpringFactoriesLoader.loadFactoryNames燕侠,從Spring3.2加入的功能者祖,可以讀取META-INF/spring.factories文件需要的內(nèi)容數(shù)據(jù),例如SpringBoot中EnableAutoConfiguration也是充分使用了該功能實(shí)現(xiàn)的绢彤,后續(xù)也會(huì)針對該功能總結(jié)一篇學(xué)習(xí)筆記
更多關(guān)于Spring的內(nèi)容可以看看Spring 源碼學(xué)習(xí)