1. 程式人生 > >【Spring Boot】(22)、Spring Boot啟動配置原理

【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#run
private 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 Boot22Spring Boot啟動配置原理

啟動配置原理 重要的事件回撥機制: ApplicationContextInitializer SpringApplicationRunListener ApplicationRunner CommandLineRunner前兩者需要配置在META-INF/spring.f

Spring Boot19Spring Boot嵌入式Servlet容器自動配置原理

    其中EmbeddedServletContainerAutoConfiguration是嵌入式Servlet容器的自動配置類,該類在spring-boot-autoconfigure-xxx.jar中的web模組可以找到。 @AutoConfig

Spring Boot18Spring Boot配置嵌入式Servlet容器

Spring Boot預設使用Tomcat作為嵌入式的Servlet容器,只要引入了spring-boot-start-web依賴,則預設是用Tomcat作為Servlet容器: 1、定製和修改Servlet容器的相關配置 1)、修改和server有關的配置(ServerProper

Spring Boot15Spring Boot錯誤處理機制

1、Spring Boot預設的錯誤處理機制 如果是瀏覽器,則返回一個預設的錯誤頁面: 如果是其他測試工具,如Postman,則返回一個json資料: 原理: ​ 可以參照ErrorMvcAutoConfiguration,錯誤處理的自動配置

Spring Boot24Spring Boot中使用快取之Spring快取

1、快取依賴 只要新增如下依賴,即可使用快取功能。 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter

Spring Boot23Spring Boot整合Mybatis

首先新增mybatis依賴: <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</

Spring Boot21Spring Boot使用外接的Servlet容器

嵌入式Servlet容器: ​ 優點:簡單,便攜; ​ 缺點:預設不支援jsp,優化定製比較複雜; 使用外接Servlet容器的步驟: ​ 1)、必須建立一個war專案,需要建立好web專案的目錄結構,特別是webapp/WEB-INF/web.xml; ​ 2)、嵌入式的To

Spring Boot13Spring Boot自動配置SpringMVC

1、SpringMVC自動配置官方文件2、Spring MVC auto-configurationSpring Boot 提供了大多數SpringMVC應用常用的自動配置項。以下是Spring Boo

Spring Boot10Spring Boot日誌框架

1、日誌框架市面上的日誌框架:JUL、JCL、Jboss-logging、logback、log4j、log4j2、slf4j...日誌門面(日誌的抽象層)日誌的實現層JCL(Jakarta Commo

Spring Boot31使用SpringBoot傳送mail郵件

1、前言 傳送郵件應該是網站的必備拓展功能之一,註冊驗證,忘記密碼或者是給使用者傳送營銷資訊。正常我們會用JavaMail相關api來寫傳送郵件的相關程式碼,但現在springboot提供了一套更簡易使用的封裝。   2、Mail依賴 <dependency>

Spring Boot30SpringBoot整合RabbitMQ

1、安裝 1.1、Erlang: Erlang下載地址,下載後安裝即可。 1.2、RabbitMQ安裝 RabbitMQ下載地址,下載後安裝即可。 注意:Erlang的版本要與RabbitMQ版本需要匹配才行。 RabbitMQ Mini

Spring Boot29SpringBoot整合Mybatis原始碼分析

在【Spring Boot】(23)、Spring Boot整合Mybatis的章節中講述了SpringBoot整合Mybatis的過程,以及一些配置說明,這節主要講解一下整合的原始碼。 廢話不多說,直接進入今天的主題。 閱讀過我之前寫的文章的童靴,肯定知道SpringBoot整合第三方

Spring Boot32SpringBoot整合AOP

1、新增pom依賴 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</art

Spring Boot6Profile

Profile是Spring對不同環境提供不同配置功能的支援,可通過啟用、指定引數等方式快速切換環境。1、多Profile檔案(Properties格式)在主配置檔案編寫的時候,檔名可以是applica

Spring Boot33SpringBoot事務管理@Transactional註解原理

1、依賴包 1.1、 SpringBoot中的依賴包 眾所周知,在SpringBoot中凡是需要跟資料庫打交道的,基本上都要顯式或者隱式新增jdbc的依賴: <dependency> <groupId>org.springfram

Spring Boot7配置檔案載入位置

Spring Boot啟動會掃描以下位置的application.properties/yml檔案作為Spring Boot預設配置檔案:外接,在相對於應用程式執行目錄的/config子目錄裡外接,在應

Spring Boot4配置檔案值注入

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