Spring boot啓動原理

1.入口類

/**
 * springboot應用的啓動入口
*/
@RestController
@SpringBootApplication
public class SampleApplication {
    
    @RequestMapping("/")
    public String sayhello(){
        return "hello world";
    }

    public static void main(String[] args) {
        // TODO Auto-generated method stub

        SpringApplication.run(SampleApplication.class, args);
    }

}

 

Spring Boot程序的啓動入口就一行代碼,SpringApplication.run(SampleApplication.class,args)java

2.執行過程

2.1SpringApplication的實例化

SpringApplication的靜態run方法內部實際上是new了一個SpringApplication的實例,構造函數保存了SourceClass。在SpringApplication實例初始化的時候,它作了幾件事情:react

  •  推斷應用類型是standard仍是Web。根據classpath裏面是否存在某個特徵類(org.springframework.web.context.ConfigurableWebApplicationContext)來決定是否應該建立一個爲Web應用使用的ApplicationContext類型。
  • 使用SpringFactoriesLoader在應用的classpath中查找並加載全部可用的ApplicationContextInitializer。SpringFactoriesLoader從類路徑下各個jar的配置文件META-INF/spring.factories加載配置
  • 使用SpringFactoriesLoader在應用的classpath中查找並加載全部可用的ApplicationListener。
  • 推斷應用入口類web

    public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
        this.resourceLoader = resourceLoader;
        Assert.notNull(primarySources, "PrimarySources must not be null");
        this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
        this.webApplicationType = deduceWebApplicationType();
        setInitializers((Collection) getSpringFactoriesInstances(
                ApplicationContextInitializer.class));
        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
        this.mainApplicationClass = deduceMainApplicationClass();
    }

 

下面具體從源碼來看看每一步是怎麼作的spring

2.1  推斷應用類型是Standard仍是Web

private WebApplicationType deduceWebApplicationType() {
        if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null)
                && !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)) {
            return WebApplicationType.REACTIVE;
        }
        for (String className : WEB_ENVIRONMENT_CLASSES) {
            if (!ClassUtils.isPresent(className, null)) {
                return WebApplicationType.NONE;
            }
        }
        return WebApplicationType.SERVLET;
    }

//相關常量
 private static final String REACTIVE_WEB_ENVIRONMENT_CLASS = "org.springframework."
          + "web.reactive.DispatcherHandler";
 
 

 private static final String MVC_WEB_ENVIRONMENT_CLASS = "org.springframework."
          + "web.servlet.DispatcherServlet";springboot

 

  private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet",
         "org.springframework.web.context.ConfigurableWebApplicationContext" };app

可能會出現三種結果:框架

  1. WebApplicationType.REACTIVE - 當類路徑中存在REACTIVE_WEB_ENVIRONMENT_CLASS而且不存在MVC_WEB_ENVIRONMENT_CLASS時
  2. WebApplicationType.NONE - 也就是非Web型應用(Standard型),此時類路徑中不包含WEB_ENVIRONMENT_CLASSES中定義的任何一個類時
  3. WebApplicationType.SERVLET - 類路徑中包含了WEB_ENVIRONMENT_CLASSES中定義的全部類型時

2.2 設置初始化器ApplicationContextInitializer

setInitializers((Collection) getSpringFactoriesInstances(
                ApplicationContextInitializer.class));

這裏出現了一個新的概念 - 初始化器。函數

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
        return getSpringFactoriesInstances(type, new Class<?>[] {});
    }

// 這裏的入參type就是ApplicationContextInitializer.class private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
            Class<?>[] parameterTypes, Object... args) {
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        // Use names and ensure unique to protect against duplicates
        Set<String> names = new LinkedHashSet<>(
                SpringFactoriesLoader.loadFactoryNames(type, classLoader));
        List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
                classLoader, args, names);
        AnnotationAwareOrderComparator.sort(instances);
        return instances;
    }

這裏面首先會根據入參type讀取全部的names(是一個String集合),而後根據這個集合來完成對應的實例化操做。spring-boot

// 這裏的入參type就是ApplicationContextInitializer.class
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
        String factoryClassName = factoryClass.getName();
        return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
    }

    private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
        MultiValueMap<String, String> result = cache.get(classLoader);
        if (result != null)
            return result;
        try {
            Enumeration<URL> urls = (classLoader != null ?
                    classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                    ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
            result = new LinkedMultiValueMap<>();
            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                UrlResource resource = new UrlResource(url);
                Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                for (Map.Entry<?, ?> entry : properties.entrySet()) {
                    List<String> factoryClassNames = Arrays.asList(
                            StringUtils.commaDelimitedListToStringArray((String) entry.getValue()));
                    result.addAll((String) entry.getKey(), factoryClassNames);
                }
            }
            cache.put(classLoader, result);
            return result;
        }
        catch (IOException ex) {
            throw new IllegalArgumentException("Unable to load factories from location [" +
                    FACTORIES_RESOURCE_LOCATION + "]", ex);
        }
    }

/**
* The location to look for factories.
* <p>Can be present in multiple JAR files.
*/
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";ui

 

這個方法會嘗試從類路徑的META-INF/spring.factories處讀取相應配置文件,而後進行遍歷,讀取配置文件中Key爲:org.springframework.context.ApplicationContextInitializer的value。以spring-boot-autoconfigure這個包爲例,它的META-INF/spring.factories部分定義以下所示:

# Initializers org.springframework.context.ApplicationContextInitializer=\ org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\ org.springframework.boot.autoconfigure.logging.AutoConfigurationReportLoggingInitializer

所以這兩個類名會被讀取出來,而後放入到集合中,準備開始下面的實例化操做:

private <T> List<T> createSpringFactoriesInstances(Class<T> type,
            Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args,
            Set<String> names) {
        List<T> instances = new ArrayList<>(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;
    }

類加載,確認被加載的類確實是org.springframework.context.ApplicationContextInitializer的子類,而後就是獲得構造器進行初始化最後放入到實例列表中。

所以,所謂的初始化器就是org.springframework.context.ApplicationContextInitializer的實現類,這個接口是這樣定義的:

public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {

    /**
     * Initialize the given application context.
     * @param applicationContext the application to configure
     */
    void initialize(C applicationContext);

}

根據類文檔,這個接口的主要功能是:

在Spring上下文被刷新以前進行初始化的操做。典型地好比在Web應用中,註冊Property Sources或者是激活Profiles。Property Sources比較好理解,就是配置文件。Profiles是Spring爲了在不一樣環境下(如DEV,TEST,PRODUCTION等),加載不一樣的配置項而抽象出來的一個實體。

2.3 設置監聽器ApplicationListener

setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 這裏的入參type是:org.springframework.context.ApplicationListener.class
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) { return getSpringFactoriesInstances(type, new Class<?>[] {}); } private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) { ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); // Use names and ensure unique to protect against duplicates Set<String> names = new LinkedHashSet<>( SpringFactoriesLoader.loadFactoryNames(type, classLoader)); List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names); AnnotationAwareOrderComparator.sort(instances); return instances; }

 

能夠發現,這個加載相應的類名,而後完成實例化的過程和上面在設置初始化器時一模一樣,一樣,仍是以spring-boot-autoconfigure這個包中的spring.factories爲例,看看相應的Key-Value:

# Application Listeners org.springframework.context.ApplicationListener=\ org.springframework.boot.autoconfigure.BackgroundPreinitializer

至於ApplicationListener接口,它是Spring框架中一個至關基礎的接口了,代碼以下:

   

@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {

    /**
     * Handle an application event.
     * @param event the event to respond to
     */
    void onApplicationEvent(E event);

}

2.4  推斷應用入口類

this.mainApplicationClass = deduceMainApplicationClass();

這個方法的實現有點意思:

private Class<?> deduceMainApplicationClass() {
        try {
            StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
            for (StackTraceElement stackTraceElement : stackTrace) {
                if ("main".equals(stackTraceElement.getMethodName())) {
                    return Class.forName(stackTraceElement.getClassName());
                }
            }
        }
        catch (ClassNotFoundException ex) {
            // Swallow and continue
        }
        return null;
    }

它經過構造一個運行時異常,經過異常棧中方法名爲main的棧幀來獲得入口類的名字。

至此,對於SpringApplication實例的初始化過程就結束了。

2.2  執行SpringApplication實例的run方法

SpringApplication實例的run方法內部都作了哪些呢:

1.建立SpringApplication本身的SpringApplicationRunListener。遍歷執行全部經過SpringFactoriesLoader能夠查找到並加載的SpringApplicationRunListener。調用.它們的started()方法,告訴這些SpringApplicationRunListener,「嘿,SpringBoot應用要開始執行咯!」。

2.建立並配置當前Spring Boot應用將要使用的Environment(包括配置要使用的PropertySource以及Profile)。

3. 遍歷調用全部SpringApplicationRunListener的environmentPrepared()的方法,告訴他們:「當前SpringBoot應用使用的Environment準備好了咯!」。

4. 若是SpringApplication的showBanner屬性被設置爲true,則打印banner。

5.建立ApplicationContext,由於如今應用類型是Servlet,建立的是AnnotationConfigServletWebServerApplicationContext。

6.對ApplicationContext進行配置,將以前準備好的Environment設置給建立好的ApplicationContext使用。還有配置BeanNameGenerator、ResourceLoader等。

7.ApplicationContext建立好以後,而後遍歷ApplicationContextInitializer的initialize(applicationContext)方法來對已經建立好的ApplicationContext進行進一步的處理。這裏的ApplicationContextInitializer就是建立SpringApplication實例時設置的初始化器。

8. 遍歷調用全部SpringApplicationRunListener的contextPrepared()方法。

9. 最核心的一步,將以前經過@EnableAutoConfiguration獲取的全部配置以及其餘形式的IoC容器配置加載到已經準備完畢的ApplicationContext。

10.遍歷調用全部SpringApplicationRunListener的contextLoaded()方法。

11.啓動ApplicationContext,AnnotationConfigServletWebServerApplicationContext繼承了SpringFrameWork自己提供的GenericWebApplicationContext提供的功能並進行了擴展,以支持配置並啓動Embed Tomcat。

             (1)、對BeanFactory進行一些初始化配置。

     (2)、執行BeanFactoryPostProcessor,其中包括對BeanDefinition的進一步處理。最重要的是ConfigurationClassPostProcessor,用來解析處理全部@Configuration標籤類,並將Bean定義註冊到BeanFactory中。由於@SpringBootApplication中包含了@EnableAutoConfiguration的meta-annotation,會進行自動配置處理,基本原理是判斷工程依賴了哪些第三方組件並對其進行自動化配置,這樣處理完@Configuration標籤後,BeanFactory中就已經有大量的Bean定義了。

    (3)、註冊BeanPostProcessor,這些Processor會在首次getBean時執行。主要功能包括進行Autowire、Required等標籤的處理,完成自動綁定等功能。也有特殊的關於WebServleterFactory的後續處理。

    (4)、在ApplicationContext的onRefresh方法中會對Web容器(Tomcat)進行配置,包括註冊Servlet、Filter、Listener等。

    (5)、在ApplicationContext的finishRefresh方法中啓動Web容器(Tomcat),完成應用的啓動。

3. 總結

相關文章
相關標籤/搜索