【Spring Boot】(22)、Spring Boot啟動配置原理
啟動配置原理
重要的事件回撥機制:
ApplicationContextInitializer
SpringApplicationRunListener
ApplicationRunner
CommandLineRunner
前兩者需要配置在META-INF/spring.factories中,而後兩者只需要放在ioc容器中。
啟動流程
1、建立SpringApplication物件:
SpringApplication#run
public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
return (new SpringApplication(sources)).run(args);
}
首先根據主類建立SpringApplication物件。
public SpringApplication(Object... sources) {
//初始化物件
initialize(sources);
}
2、進行初始化工作:
SpringApplication#initialize
private void initialize(Object[] sources) {
if (sources != null && sources.length > 0) {
//儲存主配置類
this.sources.addAll(Arrays.asList(sources));
}
//判斷是否是web應用,原始碼#2.1
this.webEnvironment = deduceWebEnvironment();
//從類路徑下找到META-INF/spring.factories配置的所有ApplicationContextInitializer,然後儲存起來,原始碼#2.2
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
//從類路徑下找到META-INF/spring.factories配置的所有ApplicationListener,同上
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
//從多個主配置類中找到有main方法的主類,原始碼#2.3
this.mainApplicationClass = deduceMainApplicationClass();
}
#2.1:SpringApplication#deduceWebEnvironment
private static final String[] WEB_ENVIRONMENT_CLASSES = new String[]{"javax.servlet.Servlet", "org.springframework.web.context.ConfigurableWebApplicationContext"};
private boolean deduceWebEnvironment() {
for (String className : WEB_ENVIRONMENT_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return false;
}
}
return true;
}
從WEB_ENVIRONMENT_CLASSES集合中遍歷元素判斷當前環境中是否有web應用的兩個重要的依賴。
#2.2:SpringApplication#getSpringFactoriesInstances
從類路徑下的所有jar包裡的META-INF/spring.factories檔案中獲取所有type型別的類。
private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type) {
return getSpringFactoriesInstances(type, new Class<?>[] {});
}
private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
//獲取ClassLoader
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
//使用ClassLoader根據type來獲取所有的value值並返回name組成的集合,原始碼#2.2.1
Set<String> names = new LinkedHashSet<String>(
SpringFactoriesLoader.loadFactoryNames(type, classLoader));
//根據name值利用反射機制得到name所在的類例項,原始碼#2.2.2
List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
#2.2.1:SpringFactoriesLoader#loadFactoryNames
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
try {
//從類路徑下所有jar包中查詢META-INF/spring.factories檔案路徑
Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
List<String> result = new ArrayList<String>();
while(urls.hasMoreElements()) {
URL url = (URL)urls.nextElement();
//依次遍歷所有的檔案路徑,從檔案中獲取key為classname對應value,並以逗號分隔加入到集合中
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 [" + FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
#2.2.2:SpringApplication#createSpringFactoriesInstances
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) {
try {
Class<?> instanceClass = ClassUtils.forName(name, classLoader);
Assert.isAssignable(type, instanceClass);
Constructor<?> constructor = instanceClass
.getDeclaredConstructor(parameterTypes);
//建立例項
T instance = (T) BeanUtils.instantiateClass(constructor, args);
instances.add(instance);
}
catch (Throwable ex) {
throw new IllegalArgumentException(
"Cannot instantiate " + type + " : " + name, ex);
}
}
//返回例項
return instances;
}
#2.3:SpringApplication#deduceMainApplicationClass
private Class<?> deduceMainApplicationClass() {
try {
StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
for (StackTraceElement stackTraceElement : stackTrace) {
//獲取main方法的主類
if ("main".equals(stackTraceElement.getMethodName())) {
return Class.forName(stackTraceElement.getClassName());
}
}
}
catch (ClassNotFoundException ex) {
// Swallow and continue
}
return null;
}
以上程式碼完成SpringApplication物件建立。
所有的initializer集合,如圖所示:
所有的listener集合,如圖所示:
以上兩個示意圖中的集合資料要看具體依賴 ,依賴其他模組,可能就不一樣了。
3、執行run方法
SpringApplication#runprivate ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
FailureAnalyzers analyzers = null;
configureHeadlessProperty();
//獲取SpringApplicationRunListeners,從類路徑下找到META-INF/spring.factories配置的所有SpringApplicationRunListener,原始碼#3.1
SpringApplicationRunListeners listeners = getRunListeners(args);
//回撥所有的SpringApplicationRunListener的starting方法,原始碼#3.2
listeners.starting();
try {
//封裝命令列引數
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
//準備環境:首先建立環境物件,建立完成後回撥每個SpringApplicationRunListener物件的environmentPrepared方法,原始碼#3.3
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
//列印Banner,原始碼#3.4
Banner printedBanner = printBanner(environment);
//建立ApplicationContext,決定建立web的ioc容器還是普通的ioc容器,具體原理看原來文章(此處略)
context = createApplicationContext();
analyzers = new FailureAnalyzers(context);
//準備上下文,將environment儲存到ioc容器中,原始碼#3.5
//prepareContext方法內部:
//首先applyInitializers(context);用來回調之前儲存的所有的ApplicationContextInitializer的initialize方法
//然後listeners.contextPrepared(context);用來回調所有的SpringApplicationRunListener的contextPrepared方法
//最後listeners.contextLoaded(context);再回調所有的SpringApplicationRunListener的contextLoaded方法
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
//重新整理ioc容器:首先建立ioc容器並初始化,如果是web應用還會建立嵌入式的Servlet容器,具體原理看原來文章(此處略)
refreshContext(context);
//從ioc容器中獲取所有的ApplicationRunner和CommandLineRunner類,原始碼#3.6
//並先回撥ApplicationRunner的run方法,再回調CommandLineRunner的run方法
afterRefresh(context, applicationArguments);
//執行所有的SpringApplicationRunListener的finished方法,原始碼#3.7
listeners.finished(context, null);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
//整個SpringBoot應用啟動完成後返回ioc容器
return context;
}
catch (Throwable ex) {
handleRunFailure(context, listeners, analyzers, ex);
throw new IllegalStateException(ex);
}
}
#3.1:SpringApplication#getRunListeners
private SpringApplicationRunListeners getRunListeners(String[] args) {
Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
SpringApplicationRunListener.class, types, this, args));
}
從類路徑下找到所有META-INF/spring.factories檔案中配置的所有SpringApplicationRunListener類,並儲存到SpringApplicationRunListeners物件的listeners物件集合中
#3.2:SpringApplicationRunListeners#starting
public void starting() {
for (SpringApplicationRunListener listener : this.listeners) {
listener.starting();
}
}
呼叫所有SpringApplicationRunListener物件的starting方法。
#3.3:SpringApplication#prepareEnvironment
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) {
//獲取或者建立環境,原始碼#3.3.1
ConfigurableEnvironment environment = getOrCreateEnvironment();
//配置環境物件,比如當前的profiles,原始碼#3.3.2
configureEnvironment(environment, applicationArguments.getSourceArgs());
//呼叫每個SpringApplicationRunListener物件的environmentPrepared方法,原始碼#3.3.3
listeners.environmentPrepared(environment);
if (!this.webEnvironment) {
environment = new EnvironmentConverter(getClassLoader())
.convertToStandardEnvironmentIfNecessary(environment);
}
//返回環境物件
return environment;
}
#3.3.1:SpringApplication#getOrCreateEnvironment
private ConfigurableEnvironment getOrCreateEnvironment() {
if (this.environment != null) {
return this.environment;
}
if (this.webEnvironment) {
return new StandardServletEnvironment();
}
return new StandardEnvironment();
}
在上面第2章節中deduceWebEnvironment()方法返回是否是web環境標識,如果是web環境,webEnvironment則為true,否則為false。在此getOrCreateEnvironment()方法中根據webEnvironment的值建立環境物件,如果webEnvironment為true,則建立StandardServletEnvironment型別的環境物件,否則建立StandardEnvironment型別的環境物件。
#3.3.2:SpringApplication#configureEnvironment
protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
this.configurePropertySources(environment, args);
//配置當前環境的profiles,預設為default
this.configureProfiles(environment, args);
}
#3.3.3:SpringApplicationRunListeners#environmentPrepared
public void environmentPrepared(ConfigurableEnvironment environment) {
for (SpringApplicationRunListener listener : this.listeners) {
listener.environmentPrepared(environment);
}
}
呼叫所有SpringApplicationRunListener物件的environmentPrepared方法。
#3.4:SpringApplication#printBanner
private Banner printBanner(ConfigurableEnvironment environment) {
//判斷當前bannerMode是否為OFF
if (this.bannerMode == Banner.Mode.OFF) {
return null;
} else {
ResourceLoader resourceLoader = (this.resourceLoader != null ? this.resourceLoader
: new DefaultResourceLoader(getClassLoader()));
//獲取BannerPrinter列印物件
SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(
resourceLoader, this.banner);
//根據BannerMode的值,來列印Banner,原始碼#3.4.1
if (this.bannerMode == Mode.LOG) {
return bannerPrinter.print(environment, this.mainApplicationClass, logger);
}
return bannerPrinter.print(environment, this.mainApplicationClass, System.out);
}
}
#3.4.1:SpringApplicationBannerPrinter#print
public Banner print(Environment environment, Class<?> sourceClass, PrintStream out) {
//獲取Banner物件
Banner banner = getBanner(environment, this.fallbackBanner);
//依次呼叫Banners集合中的Banner物件的printBanner來開始列印Banner,原始碼#3.4.1.1
banner.printBanner(environment, sourceClass, out);
return new PrintedBanner(banner, sourceClass);
}
private Banner getBanner(Environment environment, Banner definedBanner) {
Banners banners = new Banners();
//從image圖片格式中獲取banner物件,如果有則儲存到banners集合中
banners.addIfNotNull(getImageBanner(environment));
//從text文字格式中獲取banner物件,如果有則儲存到banners集合中
banners.addIfNotNull(getTextBanner(environment));
//banners集合個數只有0,1,2三種情況,然後判斷banners集合是否為空,如果不為空,則返回之前獲取到的Banners集合物件
if (banners.hasAtLeastOneBanner()) {
return banners;
}
if (this.fallbackBanner != null) {
return this.fallbackBanner;
}
return DEFAULT_BANNER;
}
static final String BANNER_LOCATION_PROPERTY = "banner.location";
static final String DEFAULT_BANNER_LOCATION = "banner.txt";
private Banner getTextBanner(Environment environment) {
//首先從環境物件中獲取banner.location的值,如果存在直接返回該值,否則使用預設的banner.txt檔案作為location的值
String location = environment.getProperty(BANNER_LOCATION_PROPERTY,
DEFAULT_BANNER_LOCATION);
//根據location的值獲取Resource物件
Resource resource = this.resourceLoader.getResource(location);
//判斷Resource是否存在,如果存在返回ResourceBanner,否則返回null
if (resource.exists()) {
return new ResourceBanner(resource);
}
return null;
}
static final String[] IMAGE_EXTENSION = new String[]{"gif", "jpg", "png"};
static final String BANNER_IMAGE_LOCATION_PROPERTY = "banner.image.location";
private Banner getImageBanner(Environment environment) {
//首先從環境物件中獲取banner.image.location的值,如果存在直接返回該值
String location = environment.getProperty(BANNER_IMAGE_LOCATION_PROPERTY);
//判斷是否有值
if (StringUtils.hasLength(location)) {
//如果banner.image.location有值,則根據該值返回Resource物件,並判斷是否存在,如果存在則返回ImageBanner,否則返回null
Resource resource = this.resourceLoader.getResource(location);
return (resource.exists() ? new ImageBanner(resource) : null);
}
//如果banner.image.location沒有值或者未設定,則依次判斷banner.gif,banner.jpg,banner.png三種檔案是否存在,如果某個檔案存在,則直接返回該檔案的ImageBanner物件,否則返回null
for (String ext : IMAGE_EXTENSION) {
Resource resource = this.resourceLoader.getResource("banner." + ext);
if (resource.exists()) {
return new ImageBanner(resource);
}
}
return null;
}
#3.4.1.1:SpringApplicationBannerPrinter#printBanner
@Override
public void printBanner(Environment environment, Class<?> sourceClass,
PrintStream out) {
for (Banner banner : this.banners) {
//依次呼叫每個banner物件的pringBanner方法
banner.printBanner(environment, sourceClass, out);
}
}
ResourceBanner#printBanner
@Override
public void printBanner(Environment environment, Class<?> sourceClass,
PrintStream out) {
try {
String banner = StreamUtils.copyToString(this.resource.getInputStream(),
environment.getProperty("banner.charset", Charset.class,
Charset.forName("UTF-8")));
for (PropertyResolver resolver : getPropertyResolvers(environment,
sourceClass)) {
banner = resolver.resolvePlaceholders(banner);
}
out.println(banner);
}
catch (Exception ex) {
//other code...
}
}
ImageBanner#printBanner
相關推薦
【Spring Boot】(22)、Spring Boot啟動配置原理
啟動配置原理
重要的事件回撥機制:
ApplicationContextInitializer
SpringApplicationRunListener
ApplicationRunner
CommandLineRunner前兩者需要配置在META-INF/spring.f
【Spring Boot】(19)、Spring Boot嵌入式Servlet容器自動配置原理
其中EmbeddedServletContainerAutoConfiguration是嵌入式Servlet容器的自動配置類,該類在spring-boot-autoconfigure-xxx.jar中的web模組可以找到。
@AutoConfig
【Spring Boot】(18)、Spring Boot配置嵌入式Servlet容器
Spring Boot預設使用Tomcat作為嵌入式的Servlet容器,只要引入了spring-boot-start-web依賴,則預設是用Tomcat作為Servlet容器:
1、定製和修改Servlet容器的相關配置
1)、修改和server有關的配置(ServerProper
【Spring Boot】(15)、Spring Boot錯誤處理機制
1、Spring Boot預設的錯誤處理機制
如果是瀏覽器,則返回一個預設的錯誤頁面:
如果是其他測試工具,如Postman,則返回一個json資料:
原理:
可以參照ErrorMvcAutoConfiguration,錯誤處理的自動配置
【Spring Boot】(24)、Spring Boot中使用快取之Spring快取
1、快取依賴
只要新增如下依賴,即可使用快取功能。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter
【Spring Boot】(23)、Spring Boot整合Mybatis
首先新增mybatis依賴:
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</
【Spring Boot】(21)、Spring Boot使用外接的Servlet容器
嵌入式Servlet容器:
優點:簡單,便攜;
缺點:預設不支援jsp,優化定製比較複雜;
使用外接Servlet容器的步驟:
1)、必須建立一個war專案,需要建立好web專案的目錄結構,特別是webapp/WEB-INF/web.xml;
2)、嵌入式的To
【Spring Boot】(13)、Spring Boot自動配置SpringMVC
1、SpringMVC自動配置官方文件2、Spring MVC auto-configurationSpring Boot 提供了大多數SpringMVC應用常用的自動配置項。以下是Spring Boo
【Spring Boot】(10)、Spring Boot日誌框架
1、日誌框架市面上的日誌框架:JUL、JCL、Jboss-logging、logback、log4j、log4j2、slf4j...日誌門面(日誌的抽象層)日誌的實現層JCL(Jakarta Commo
【Spring Boot】(31)、使用SpringBoot傳送mail郵件
1、前言
傳送郵件應該是網站的必備拓展功能之一,註冊驗證,忘記密碼或者是給使用者傳送營銷資訊。正常我們會用JavaMail相關api來寫傳送郵件的相關程式碼,但現在springboot提供了一套更簡易使用的封裝。
2、Mail依賴
<dependency>
【Spring Boot】(30)、SpringBoot整合RabbitMQ
1、安裝
1.1、Erlang:
Erlang下載地址,下載後安裝即可。
1.2、RabbitMQ安裝
RabbitMQ下載地址,下載後安裝即可。
注意:Erlang的版本要與RabbitMQ版本需要匹配才行。
RabbitMQ
Mini
【Spring Boot】(29)、SpringBoot整合Mybatis原始碼分析
在【Spring Boot】(23)、Spring Boot整合Mybatis的章節中講述了SpringBoot整合Mybatis的過程,以及一些配置說明,這節主要講解一下整合的原始碼。
廢話不多說,直接進入今天的主題。
閱讀過我之前寫的文章的童靴,肯定知道SpringBoot整合第三方
【Spring Boot】(32)、SpringBoot整合AOP
1、新增pom依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</art
【Spring Boot】(6)、Profile
Profile是Spring對不同環境提供不同配置功能的支援,可通過啟用、指定引數等方式快速切換環境。1、多Profile檔案(Properties格式)在主配置檔案編寫的時候,檔名可以是applica
【Spring Boot】(33)、SpringBoot事務管理@Transactional註解原理
1、依賴包
1.1、 SpringBoot中的依賴包
眾所周知,在SpringBoot中凡是需要跟資料庫打交道的,基本上都要顯式或者隱式新增jdbc的依賴:
<dependency>
<groupId>org.springfram
【Spring Boot】(7)、配置檔案載入位置
Spring Boot啟動會掃描以下位置的application.properties/yml檔案作為Spring Boot預設配置檔案:外接,在相對於應用程式執行目錄的/config子目錄裡外接,在應
【Spring Boot】(4)、配置檔案值注入
1、配置檔案使用上節中yaml書寫的配置資訊:server:
port: 8081
path: /hello
person:
name: zhangsan
age:
【知識積累】(二)、深入Regex(正則表示式)
\:將下一個字元標記符、或一個向後引用、或一個八進位制轉義符。例如,“\\n”匹配\n。“\n”匹配換行符。序列“\\”匹配“\”而“\(”則匹配“(”。即相當於多種程式語言中都有的“轉義字元”的概念。
^:匹配輸入字串的開始位置。如果設定了RegExp物件的Multiline屬性,^也匹配“\n
【知識積累】(一)、瞭解Regex(正則表示式)
一、正則表示式簡介
一種可以用於模式匹配和替換的規範,由普通字元 + 特殊字元構成一個模板,用於對目標字串進行匹配、查詢、替換、判斷。
原始碼:JDK1.4中的java.util.regex下的Pattern和Matcher類。
二、常用語法
1、字元取值範圍
[abc]:表示可能是a
【知識積累】(四)、訊息中介軟體 - 升級版
一、訊息釋出
二、訊息訂閱
三、總結
1、ActiveMQ的特性(1)、多種語言和協議客戶端。語言:Java,C,C++,C#,Ruby,Perl,Python,PHP。應用協議:OpenWire,Stomp REST,WS Noti