1. 程式人生 > >Spring Boot啟動流程詳解

Spring Boot啟動流程詳解

複製程式碼
 1 package com.microservice.framework;
 2 
 3 import org.springframework.boot.SpringApplication;
 4 import org.springframework.boot.autoconfigure.SpringBootApplication;
 5 
 6 @SpringBootApplication
 7 public class MySpringAplication {
 8 
 9     public void run(String[] args) {
10         SpringApplication sa = new
SpringApplication(MySpringAplication.class); 11 sa.run(args); 12 } 13 14 }
複製程式碼

SpringBoot啟動過程:

1、構建SpringApplication物件

2、執行run()

一、構建SpringApplication物件

1     /**
2      * The application context will load beans from the specified sources 
3      */
4     public SpringApplication(Object... sources) {
5 initialize(sources); 6 }

說明:

  • 例項化該類的時候會載入bean到applicationContext中去
  • 這裡的入參是MySpringApplication.class這樣一個Class<com.microservice.framework.MySpringApplication>物件
複製程式碼
    private final Set<Object> sources = new LinkedHashSet<Object>();
    private boolean webEnvironment;
    
private Class<?> mainApplicationClass; private void initialize(Object[] sources) { if (sources != null && sources.length > 0) { this.sources.addAll(Arrays.asList(sources)); } this.webEnvironment = deduceWebEnvironment(); setInitializers((Collection) getSpringFactoriesInstances( ApplicationContextInitializer.class)); setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); this.mainApplicationClass = deduceMainApplicationClass(); }
複製程式碼

步驟:

  • 將傳入的MySpringApplication.class物件放入Set集合
  • 判斷是否是web環境
  • 建立ApplicationInitializer列表
  • 初始化ApplicationListener列表
  • 初始化主類mainApplicationClass

1.1、將傳入的MySpringApplication.class物件放入Set集合

1.2、判斷是否是web環境:

複製程式碼
    private static final String[] WEB_ENVIRONMENT_CLASSES = { "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;
    }
複製程式碼

說明:通過在classpath中檢視是否存在WEB_ENVIRONMENT_CLASSES這個陣列中所包含的所有類(實際上就是2個類),如果存在那麼當前程式即是一個Web應用程式,反之則不然。 

1.3、建立ApplicationContextInitializer列表

複製程式碼
 1     private List<ApplicationContextInitializer<?>> initializers;
 2 
 3     public void setInitializers(
 4             Collection<? extends ApplicationContextInitializer<?>> initializers) {
 5         this.initializers = new ArrayList<ApplicationContextInitializer<?>>();
 6         this.initializers.addAll(initializers);
 7     }
 8 
 9     private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type) {
10         return getSpringFactoriesInstances(type, new Class<?>[] {});
11     }
12 
13     private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type,
14             Class<?>[] parameterTypes, Object... args) {
15         ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
16 
17         // Use names and ensure unique to protect against duplicates
18         Set<String> names = new LinkedHashSet<String>(
19                 SpringFactoriesLoader.loadFactoryNames(type, classLoader));
20         List<T> instances = new ArrayList<T>(names.size());
21 
22         // Create instances from the names
23         for (String name : names) {
24             try {
25                 Class<?> instanceClass = ClassUtils.forName(name, classLoader);
26                 Assert.isAssignable(type, instanceClass);
27                 Constructor<?> constructor = instanceClass.getConstructor(parameterTypes);
28                 T instance = (T) constructor.newInstance(args);
29                 instances.add(instance);
30             }
31             catch (Throwable ex) {
32                 throw new IllegalArgumentException(
33                         "Cannot instantiate " + type + " : " + name, ex);
34             }
35         }
36 
37         AnnotationAwareOrderComparator.sort(instances);
38         return instances;
39     }
複製程式碼

步驟:

  • 呼叫SpringFactoriesLoader.loadFactoryNames(type, classLoader)來獲取所有Spring Factories的名字,(這裡是獲取了四個ApplicationContextInitializer實現類的全類名,見下邊)
  • 為每一個Spring Factories根據讀取到的名字建立其物件。(這裡建立了4個物件)
  • 將建立好的物件列表排序並返回。

其中,SpringFactoriesLoader.loadFactoryNames(type, classLoader)如下:

複製程式碼
 1     /**
 2      * The location to look for factories.
 3      * <p>Can be present in multiple JAR files.
 4      */
 5     public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
 6 
 7     /**
 8      * Load the fully qualified class names of factory implementations of the
 9      * given type from {@value #FACTORIES_RESOURCE_LOCATION}, using the given
10      * class loader.
11      */
12     public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
13         String factoryClassName = factoryClass.getName();
14         try {
15             Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
16                     ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
17             List<String> result = new ArrayList<String>();
18             while (urls.hasMoreElements()) {
19                 URL url = urls.nextElement();
20                 Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
21                 String factoryClassNames = properties.getProperty(factoryClassName);
22                 result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
23             }
24             return result;
25         }
26         catch (IOException ex) {
27             throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() +
28                     "] factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex);
29         }
30     }
複製程式碼

META-INF/spring-factories

1 # Application Context Initializers
2 org.springframework.context.ApplicationContextInitializer=\
3 org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
4 org.springframework.boot.context.ContextIdApplicationContextInitializer,\
5 org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
6 org.springframework.boot.context.web.ServerPortInfoApplicationContextInitializer

說明:

  • 所有jar獲取所有的META-INF/spring-factories檔案。(這裡只有spring-boot-1.3.0.RELEASE.jar下有一個)
  • 遍歷每一個spring-factories檔案,並獲取其下key為factoryClass.getName()(這裡是入參

    org.springframework.context.ApplicationContextInitializer)的value(這裡有以上四個ApplicationContextInitializer實現類)

以上四個類的作用:

至此,設定ApplicationContextInitialize就完成了。

總結:整個setInitializers實際上就是初始化了SpringApplication的屬性List<ApplicationContextInitializer<?>> initializers為一個ArrayList列表,該列表中有四個例項:

  • ConfigurationWarningsApplicationContextInitializer的例項
  • ContextIdApplicationContextInitializer的例項
  • DelegatingApplicationContextInitializer例項
  • ServerPortInfoApplicationContextInitializer例項

1.4、初始化ApplicationListener列表

複製程式碼
 1     private List<ApplicationListener<?>> listeners;    
 2 
 3         /**
 4      * Sets the {@link ApplicationListener}s that will be applied to the SpringApplication
 5      * and registered with the {@link ApplicationContext}.
 6      * @param listeners the listeners to set
 7      */
 8     public void setListeners(Collection<? extends ApplicationListener<?>> listeners) {
 9         this.listeners = new ArrayList<ApplicationListener<?>>();
10         this.listeners.addAll(listeners);
11     }    
複製程式碼

META-INF/spring-factories

複製程式碼
 1 # Application Listeners
 2 org.springframework.context.ApplicationListener=\
 3 org.springframework.boot.builder.ParentContextCloserApplicationListener,\
 4 org.springframework.boot.context.FileEncodingApplicationListener,\
 5 org.springframework.boot.context.config.AnsiOutputApplicationListener,\
 6 org.springframework.boot.context.config.ConfigFileApplicationListener,\
 7 org.springframework.boot.context.config.DelegatingApplicationListener,\
 8 org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener,\
 9 org.springframework.boot.logging.ClasspathLoggingApplicationListener,\
10 org.springframework.boot.logging.LoggingApplicationListener
複製程式碼

以上八個listener的作用如下:

至此,整個setListeners方法結束,初始化了一個包含以上8個ApplicationListener例項的List集合。

1.5、初始化主類mainApplicationClass

複製程式碼
 1     private Class<?> mainApplicationClass;
 2 
 3     private Class<?> deduceMainApplicationClass() {
 4         try {
 5             StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
 6             for (StackTraceElement stackTraceElement : stackTrace) {
 7                 if ("main".equals(stackTraceElement.getMethodName())) {
 8                     return Class.forName(stackTraceElement.getClassName());
 9                 }
10             }
11         }
12         catch (ClassNotFoundException ex) {
13             // Swallow and continue
14         }
15         return null;
16     }
複製程式碼

說明:獲取main()方法所在的主類Class物件,並賦值給SpringApplication的mainApplicationClass屬性。

至此,SpringApplication物件初始化完成了。

總結:整個SpringApplication初始化的過程,就是初始化了

  • 一個包含入參MySpringApplication.class的sources的Set<Object>
  • 一個當前環境是否是web環境的boolean webEnvironment
  • 一個包含4個ApplicationContextInitializer例項的List
  • 一個包含8個ApplicationListener例項的List
  • 一個main方法所在的主類的Class物件。

注意: