1 Spring boot原始碼環境構建
推薦環境:
idea:2020.3
gradle:版本gradle-6.5.1
jdk:1.8
注意!idea和gradle的版本有兼容性問題,要注意搭配
1.1 Spring boot原始碼下載
1、從github獲取原始碼,網址:
https://github.com/spring-projects/spring-boot
我們要搭建的是2.4.3.RELEASE版本,所以點擊release 之后在tags查找相應版本或者訪問
https://github.com/spring-projects/spring-boot/releases/tag/v2.4.3
找到 后點擊sourcecode下載原始碼壓縮包

目錄結構

Spring-boot-project 核心代碼,代碼量很多(197508 行)
Spring-boot-tests 測驗代碼
2、直接用提供的原始碼包(推薦)
將原始碼匯入到idea,漫長的等待……

1.2 Spring boot原始碼編譯
1、環境配置
推薦配置:

2、開始gradle構建
使用idea的build,不要用gradle的任務

看到下面的BUILE SUCESSFUL表示成功

1.3 Spring boot冒煙測驗
在springboot-boot-tests模塊下很多冒煙測驗的,會拖慢上面的編譯,只留下了一個:
spring-boot-smoke-test-hibernate52工程來進行冒煙測驗,打開Hibernate52Application.java檔案,直接執行main方法啟動springboot,成功!
org.springframework.boot.tests.hibernate52.Hibernate52Application
package org.springframework.boot.tests.hibernate52;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Hibernate52Application {
public static void main(String[] args) {
SpringApplication.run(Hibernate52Application.class, args);
}
}
執行run
console中出現我們熟悉的圖示,

2 Spring boot原始碼深度剖析
引言
使用過SpringBoot開發專案的讀者應該都能夠感覺到
SpringBoot的開發完成后,只需要通過執行一個main方法就可以將整個web專案啟動
無需將專案的jar檔案放在tomcat下,然后啟動tomcat,進而啟動專案,
除此之外,好多依賴的jar包也無需我們再進行手動配置,減少了配置,
同時也減少了許多xml檔案的配置,大大簡化了我們的開發程序
那么
springboot在啟動的時候到底做了哪些事情?
2.1 Spring boot啟動流程剖析
第一步:new SpringApplication(primarySources)
第二步:run!
2.1.1 Spring boot啟動流程剖析
Debug一下,追蹤一下整個啟動程序
main方法作為程式的入口,執行SpringApplication.run(),傳入引數是啟動類的class物件

1)Spring boot原始碼入口
@SpringBootApplication
public class Hibernate52Application {
public static void main(String[] args) {
SpringApplication.run(Hibernate52Application.class, args);
}
}
跟蹤run方法;進入到
引數一可支持多個主要資源,
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class<?>[] { primarySource }, args);
}
繼續進入到run方法
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}
2)構造器(new)
//方法目標
//1、初始化資源加載器(classloader)
//2、處理primarySources
//3、web應用型別推斷 (web、reactive、servlet)
//4、通過spring.factories加載配置類并初始化監聽器 (SPI) 【重點】
//5、提取主類
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
//null;資源加載器,用來獲取 Resource 和 classLoader 以及加載資源
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
//存放主加載類;set中可同時創建多個Application,最后要決議這個來源上的注解
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
//推斷 web 型別:servlet 或 reactive
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 0個,從spring.factories中找出Bootstrapper對應的屬性
this.bootstrappers = new ArrayList<>(getSpringFactoriesInstances(Bootstrapper.class));
// 7個,設定初始化器,從spring.factories中找出ApplicationContextInitializer對應的屬性
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 9個,設定監聽器 從spring.factories中找出ApplicationListener對應的屬性
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
//找出主函式main的類
this.mainApplicationClass = deduceMainApplicationClass();
}
上面 的代碼最侄訓呼叫到getSpringFactoriesInstances,從spring.factories加載屬性配置
加載核心原始碼如下
下面代碼
首先會用classLoader加載類路徑下的所有spring.factories的配置內容,loadSpringFactories方法將回傳一個key=介面名,value=https://www.cnblogs.com/jiagooushi/p/實作類集合的Map結構
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
// 先試著取快取
Map<String, List<String>> result = cache.get(classLoader);
if (result != null) {
return result;
}
result = new HashMap<>();
try {
// 獲取所有spring.factories的URL(3個地方)
Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
// 遍歷URL
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
// 加載每個URL中的properties配置
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryTypeName = ((String) entry.getKey()).trim();
// 將實作類的配置按照","符號分割開
String[] factoryImplementationNames =
StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
for (String factoryImplementationName : factoryImplementationNames) {
// 逐個添加到介面對應的集合當中
result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
.add(factoryImplementationName.trim());
}
}
}
// Replace all lists with unmodifiable lists containing unique elements
result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
.collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
//加入快取
cache.put(classLoader, result);
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
return result;
}

主要的spring.factories
spring-boot-2.4.3/spring-boot-project/spring-boot-autoconfigure/build/resources/main/META-INF/spring.factories
spring-boot-2.4.3/spring-boot-project/spring-boot/build/resources/main/META-INF/spring.factories
spring-beans-5.3.4.jar!/META-INF/spring.factories
構造器流程總結
1、處理資源加載器、主要資源primarySources
2、web應用型別推斷
3、從spring.factories中找出引導包裝器、初始化器、監聽器
4、設定應用程式主類
3)boot運行(run)
發布事件
列印banner
初始化ioc容器,啟動tomcat
七大步驟
//七大步驟
public ConfigurableApplicationContext run(String... args) {
//計時器
StopWatch stopWatch = new StopWatch();
stopWatch.start(); //開始計時
// 創建啟動背景關系物件
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
// 可配置的程式容器
ConfigurableApplicationContext context = null;
// 設定屬性 不重要
configureHeadlessProperty();
// 第一步:獲取并啟動監聽器 從spring.factories檔案中加載【測驗點】
SpringApplicationRunListeners listeners = getRunListeners(args);
//監聽器發布ApplicationStartingEvent 事件.
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
// 對引數進行包裝(ApplicationArguments)
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//第二步:準備應用程式環境【關鍵點】
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
// 配置忽略bean的資訊(不重要)
configureIgnoreBeanInfo(environment);
//第三步: 列印banner(可自定義,參考講義)【關鍵點】
Banner printedBanner = printBanner(environment);
// 第四步:創建spring容器
context = createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
//第五步:準備 applicationContext
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
//第六步:ioc的refresh創建容器,初始化bean,tomcat也在這里被啟動起來 【關鍵點】
refreshContext(context);
//第七步:背景關系重繪后觸發(空方法)
afterRefresh(context, applicationArguments);
stopWatch.stop();//停止計時
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
// 發布started事件
listeners.started(context);
//執行runner的run方法 【測驗點】
callRunners(context, applicationArguments);
} catch (Throwable ex) {
// 例外處理
handleRunFailure(context, ex, listeners);
throw new IllegalStateException(ex);
}
try {
// 觸發running事件
listeners.running(context);
} catch (Throwable ex) {
handleRunFailure(context, ex, null);
throw new IllegalStateException(ex);
}
// 回傳最終構建的容器物件
return context;
}
2.1.2 Spring boot七大步驟詳解
1)獲取并啟動監聽器
這里的啟動監聽就是我們需要監聽SpringBoot的啟動流程監聽,實作SpringApplicationRunListener類即可監聽
//獲取spring.factories中 key為SpringApplicationRunListener的物件實體,
private SpringApplicationRunListeners getRunListeners(String[] args) {
Class<?>[] types = new Class<?>[]{SpringApplication.class, String[].class};
// 通過從 spring.factories 中獲取 SpringApplicationRunListener 型別的配置類
return new SpringApplicationRunListeners(logger,
getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args),
this.applicationStartup);
}
查看具體SpringApplicationRunListener都有哪些方法
package org.springframework.boot;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.io.support.SpringFactoriesLoader;
public interface SpringApplicationRunListener {
/**
* Called immediately when the run method has first started. Can be used for very
* early initialization.
* @param bootstrapContext the bootstrap context
*/
//當呼叫run方法后會立即呼叫,可以用于非常早期的初始化
default void starting(ConfigurableBootstrapContext bootstrapContext) {
starting();
}
/**
* Called immediately when the run method has first started. Can be used for very
* early initialization.
* @deprecated since 2.4.0 in favor of {@link #starting(ConfigurableBootstrapContext)}
*/
@Deprecated
default void starting() {
}
/**
* Called once the environment has been prepared, but before the
* {@link ApplicationContext} has been created.
* @param bootstrapContext the bootstrap context
* @param environment the environment
*/
//環境準備好之后呼叫
default void environmentPrepared(ConfigurableBootstrapContext bootstrapContext,
ConfigurableEnvironment environment) {
environmentPrepared(environment);
}
/**
* Called once the environment has been prepared, but before the
* {@link ApplicationContext} has been created.
* @param environment the environment
* @deprecated since 2.4.0 in favor of
* {@link #environmentPrepared(ConfigurableBootstrapContext, ConfigurableEnvironment)}
*/
@Deprecated
default void environmentPrepared(ConfigurableEnvironment environment) {
}
/**
* Called once the {@link ApplicationContext} has been created and prepared, but
* before sources have been loaded.
* @param context the application context
*/
//在加載資源之前,ApplicationContex準備好之后呼叫
default void contextPrepared(ConfigurableApplicationContext context) {
}
/**
* Called once the application context has been loaded but before it has been
* refreshed.
* @param context the application context
*/
//在加載應用程式背景關系但在其重繪之前呼叫
default void contextLoaded(ConfigurableApplicationContext context) {
}
/**
* The context has been refreshed and the application has started but
* {@link CommandLineRunner CommandLineRunners} and {@link ApplicationRunner
* ApplicationRunners} have not been called.
* @param context the application context.
* @since 2.0.0
*/
/**
* 背景關系已經重繪且應用程式已啟動且所有{@link CommandLineRunner commandLineRunner}
* 和{@link ApplicationRunner ApplicationRunners}未呼叫之前呼叫
*/
default void started(ConfigurableApplicationContext context) {
}
/**
* Called immediately before the run method finishes, when the application context has
* been refreshed and all {@link CommandLineRunner CommandLineRunners} and
* {@link ApplicationRunner ApplicationRunners} have been called.
* @param context the application context.
* @since 2.0.0
*/
/**
* 當應用程式背景關系被重繪并且所有{@link CommandLineRunner commandLineRunner}
* 和{@link ApplicationRunner ApplicationRunners}都已被呼叫時,在run方法結束之前立即呼叫,
*/
default void running(ConfigurableApplicationContext context) {
}
/**
* Called when a failure occurs when running the application.
* @param context the application context or {@code null} if a failure occurred before
* the context was created
* @param exception the failure
* @since 2.0.0
*/
//在啟動程序發生失敗時呼叫
default void failed(ConfigurableApplicationContext context, Throwable exception) {
}
}
2)準備應用程式環境
創建并配置SpringBooty應用將要使用的Environment
//不看細節,看回傳的環境資料即可
//創建并配置SpringBooty應用將要使用的Environment
//程序如下:
// 1、創建配置環境 ConfigurableEnvironment
// 2、加載屬性檔案資源
// 3、配置監聽
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
// 根據不同的web型別創建不同實作的Environment物件
ConfigurableEnvironment environment = getOrCreateEnvironment();
// 配置環境
configureEnvironment(environment, applicationArguments.getSourceArgs());
ConfigurationPropertySources.attach(environment);
// 發送環境已準備完成事件
listeners.environmentPrepared(bootstrapContext, environment);
DefaultPropertiesPropertySource.moveToEnd(environment);
// 根據命令列引數中spring.profiles.active屬性配置Environment物件中的activeProfile(比如dev、prod、test)
configureAdditionalProfiles(environment);
// 系結環境中spring.main屬性系結到SpringApplication物件中
bindToSpringApplication(environment);
// 如果用戶使用spring.main.web-application-type屬性手動設定了webApplicationType
if (!this.isCustomEnvironment) {
// 將環境物件轉換成用戶設定的webApplicationType相關型別,他們是繼承同一個父類,直接強轉
environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
deduceEnvironmentClass());
}
ConfigurationPropertySources.attach(environment);
return environment; //環境查看(控制臺)
}
查看環境

3)控制臺列印Banner
private Banner printBanner(ConfigurableEnvironment environment) {
// banner模式,可以是console、log、off
if (this.bannerMode == Banner.Mode.OFF) {
return null;
}
ResourceLoader resourceLoader = (this.resourceLoader != null) ? this.resourceLoader
: new DefaultResourceLoader(null);
SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(resourceLoader, this.banner);
//日志列印banner
if (this.bannerMode == Mode.LOG) {
return bannerPrinter.print(environment, this.mainApplicationClass, logger);
}
//控制臺列印banner
return bannerPrinter.print(environment, this.mainApplicationClass, System.out);
}
最終列印
通過org.springframework.boot.ResourceBanner#printBanner
@Override
public void printBanner(Environment environment, Class<?> sourceClass, PrintStream out) {
try {
String banner = StreamUtils.copyToString(this.resource.getInputStream(),
environment.getProperty("spring.banner.charset", Charset.class, StandardCharsets.UTF_8));
for (PropertyResolver resolver : getPropertyResolvers(environment, sourceClass)) {
banner = resolver.resolvePlaceholders(banner);
}
out.println(banner);//此處列印
}
catch (Exception ex) {
logger.warn(LogMessage.format("Banner not printable: %s (%s: '%s')", this.resource, ex.getClass(),
ex.getMessage()), ex);
}
}
截圖如下

4)創建應用背景關系物件
protected ConfigurableApplicationContext createApplicationContext() {
return this.applicationContextFactory.create(this.webApplicationType);
}
呼叫到下面
public interface ApplicationContextFactory {
/**
* A default {@link ApplicationContextFactory} implementation that will create an
* appropriate context for the {@link WebApplicationType}.
*/
//回傳一個應用程式背景關系
ApplicationContextFactory DEFAULT = (webApplicationType) -> {
try {
switch (webApplicationType) {
case SERVLET:
return new AnnotationConfigServletWebServerApplicationContext();
case REACTIVE:
return new AnnotationConfigReactiveWebServerApplicationContext();
default:
return new AnnotationConfigApplicationContext();
}
}
catch (Exception ex) {
throw new IllegalStateException("Unable create a default ApplicationContext instance, "
+ "you may need a custom ApplicationContextFactory", ex);
}
};
5)準備應用背景關系
核心代碼如下
/**
* Spring容器準備
*/
private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
context.setEnvironment(environment);//設定環境
postProcessApplicationContext(context);//設定背景關系
// 執行所有ApplicationContextInitializer物件的initialize方法(這些物件是通過讀取spring.factories加載)
applyInitializers(context);//設定初始化作業(不用看)
// 發布背景關系準備完成事件到所有監聽器
listeners.contextPrepared(context);//觸發監聽器
bootstrapContext.close(context);
if (this.logStartupInfo) { //日志操作
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// 獲取工廠 DefaultListableBeanFactory
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
//注冊單例物件
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
//注冊banner單例物件
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
if (beanFactory instanceof DefaultListableBeanFactory) {
//是否覆寫bean
((DefaultListableBeanFactory) beanFactory)
.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
if (this.lazyInitialization) { //是否懶加載
context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
}
// Load the sources
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
//加載(業務類的注解需要掃描) bean到背景關系
load(context, sources.toArray(new Object[0]));
// 發送背景關系加載完成事件
listeners.contextLoaded(context);
}
6)重繪應用程式背景關系
ioc容器初始化
重要!
tomcat的啟動在這里!
//核心方法
private void refreshContext(ConfigurableApplicationContext context) {
// ……
// 開始執行啟動容器(呼叫模板方法)
refresh((ApplicationContext) context);
}
擴展問題:
如果在springboot里使用了web容器,它是如何啟動的?
refreshContext 里面,沿著 refresh - onRefresh,注意是 ServletWebServerApplicationContext的
我們說,在普通的spring里onRefresh是個空方法,留給子類去實作,那么,
看看這個 ServletWebServerApplicationContext 實作類它的 onRefresh偷偷干了些啥見不得人的事?……
7)容器回呼方法
空方法
protected void afterRefresh(ConfigurableApplicationContext context, ApplicationArguments args) {
}
run方法啟動后
主要做如下幾件事情:
1、發出啟動結束事件
2、執行實作ApplicationRunner、CommandLineRunner的run方法
3、發布應用程式已啟動(ApplicationStartedEvent)事件
4、例外處理
小疑問:
boot啟動了一個web,那么一定有一個DispacherServlet,它是啥時候被加載的呢???
提示:
@EnableAutoConfiguration 注解的spi,在spring-boot-autoconfigure的spring.factories里
EnableAutoConfiguration的加載類里有個:DispatcherServletAutoConfiguration 做了自動裝配
秘密就藏在這貨里
那自動裝配又是什么鬼呢?除了DS,還有各種starter,怎么加載的呢?下節課繼續……
2.2 boot自定義Banner
banner自動生成工具,ascii文字展示
http://www.network-science.de/ascii/

Spring boot啟動如下

在路徑
\spring-boot-tests\spring-boot-smoke-tests\spring-boot-smoke-test-hibernate52\src\main\resources
下創建banner.txt(注意:檔案名稱不能變,否則無法加載)
banner.txt內容如下
88b d88 88888888ba
888b d888 88 "8b ,d
88`8b d8'88 88 ,8P 88
88 `8b d8' 88 8b d8 88aaaaaa8P' ,adPPYba, ,adPPYba, MM88MMM
88 `8b d8' 88 `8b d8' 88""""""8b, a8" "8a a8" "8a 88
88 `8b d8' 88 `8b d8' 88 `8b 8b d8 8b d8 88
88 `888' 88 `8b,d8' 88 a8P "8a, ,a8" "8a, ,a8" 88,
88 `8' 88 Y88' 88888888P" `"YbbdP"' `"YbbdP"' "Y888
d8'
d8'
2.3 面試題
1、Spring Boot 的核心注解是哪個?它主要由哪幾個注解組成的
啟動類上面的注解是@SpringBootApplication,它也是 Spring Boot 的核心注解,主要組合包含了以下 3 個注解:
- @SpringBootConfiguration:組合了 @Configuration 注解,實作組態檔的功能,
- @EnableAutoConfiguration:打開自動配置的功能,也可以關閉某個自動配置的選項,如關閉資料源自動配置功能: @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class }),
? 組合了
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
- @ComponentScan:Spring組件掃描
2、Spring Boot 自動配置原理是什么?
注解 @EnableAutoConfiguration, @Configuration, @ConditionalOnClass 就是自動配置的核心,
@EnableAutoConfiguration 給容器匯入META-INF/spring.factories 里定義的自動配置類,
篩選有效的自動配置類,
每一個自動配置類結合對應的 xxx.java 讀取組態檔進行自動配置功能
3、Spring Boot 中的 starter 到底是什么 ?
首先,這個 Starter 并非什么新的技術點,基本上還是基于 Spring 已有功能來實作的,首先它提供了一個自動化配置類,一般命名為 XXXAutoConfiguration ,在這個配置類中通過條件注解來決定一個配置是否生效(條件注解就是 Spring 中原本就有的),然后它還會提供一系列的默認配置,也允許開發者根據實際情況自定義相關配置,然后通過型別安全的屬性注入將這些配置屬性注入進來,新注入的屬性會代替掉默認屬性,正因為如此,很多第三方框架,我們只需要引入依賴就可以直接使用了,當然,開發者也可以自定義 Starter
4、運行 Spring Boot 有哪幾種方式?
1)打包用命令或者放到容器中運行
2)用 Maven/ Gradle 插件運行
3)直接執行 main 方法運行
本文由
傳智教育博學谷教研團隊發布,如果本文對您有幫助,歡迎
關注和點贊;如果您有任何建議也可留言評論或私信,您的支持是我堅持創作的動力,轉載請注明出處!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/526778.html
標籤:Java
