SpringBoot啟動流程原始碼分析

2022-11-13 21:03:14

前言

SpringBoot專案的啟動流程是很多面試官面試中高階Java程式設計師喜歡問的問題。這個問題的答案涉及到了SpringBoot工程中的原始碼,也許我們之前看過別的大牛寫過的有關SpringBoot專案啟動流程的文章,但是自己沒有去研究一遍總是會記憶不深刻。有句話叫做「紙上來得終覺淺,絕知此事要躬行」,我覺得說得非常在理。底層的東西,也只有自己深入研究過一遍甚至好幾遍原始碼才能徹底搞懂並記憶牢固。下面筆者來帶領大家詳細分析SpringBoot啟動過程中到底做了哪些事情,把本文仔細看完了,面對面試官問的有關SpringBoot啟動過程做了哪些工作的面試題就迎刃而解了!

啟動類入口方法

首先我們通過SpringApplication類的靜態Run方法進入SpringBoot專案的啟動入口

/**
* @param primarySource springboot啟動類
* @param args 啟動引數
*/
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {   
    return run(new Class[]{primarySource}, args);
}

從上面的原始碼中我們可以看到SpringBoot啟動類返回的應用上下文類是ConfigurableApplicationContext

然後我們進入另一個靜態run方法

public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
        return (new SpringApplication(primarySources)).run(args);
    }

在上面這個靜態run方法裡面最終會通過SpringApplication類別建構函式範例化一個SpringApplication類範例物件,後面在呼叫SpringApplication範例物件的run方法

SpringApplication類範例化和初始化

接下來我們看看SpringApplication類在範例化時做了什麼事情

public SpringApplication(Class<?>... primarySources) {
        this((ResourceLoader)null, primarySources);
    }

可以看到在SpringApplication類上面這個構造方法裡面又呼叫了另一個構造方法

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
        // 範例化sources屬性    
        this.sources = new LinkedHashSet(); 
        // 列印模式為控制檯列印
        this.bannerMode = Mode.CONSOLE;
        // 設定記錄啟動紀錄檔資訊標識為true
        this.logStartupInfo = true; 
        // 設定新增命令列屬性標識為true
        this.addCommandLineProperties = true;
        //設定addConversionService屬性為true
        this.addConversionService = true;
        // 設定headless屬性為true
        this.headless = true; 
        // 設定註冊應用關停勾點屬性為true
        this.registerShutdownHook = true; 
        // 範例化additionalProfiles屬性
        this.additionalProfiles = new HashSet();
        //預設非自定義環境 
        this.isCustomEnvironment = false;
        // 上一步傳過來的resourceLoader為null
        this.resourceLoader = resourceLoader; 
        // 斷言primarySources引數不能為空,也就是springboot應用類不能為空
        Assert.notNull(primarySources, "PrimarySources must not be null");
        // 將傳遞過來的springboot啟動類引數轉成List後加入LinkedHashSet集合後賦值給primarySources屬性
        this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
        // 根據類路徑推斷web應用型別
        this.webApplicationType = WebApplicationType.deduceFromClasspath(); 
 this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));  // 設定初始化器屬性   
        // 設定監聽器屬性
        this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
        // 推斷主啟動類
        this.mainApplicationClass = this.deduceMainApplicationClass();
    }

從上面的原始碼中我們分析的結果來看,範例化SpringApplication類的過程做了以下幾件事情:

  • 初始化SpringApplication啟動類中的大部分屬性變數
  • 推斷web應用型別
  • 通過載入類路徑目錄META-INF下的spring.factories檔案讀取出初始化器和監聽器集合並設定到SpringApplication範例對應的初始化器和監聽器屬性列表中
  • 推斷主啟動類並賦值給SpringApplication啟動類的mainApplicationClass屬性

推斷Web應用型別

進入WebApplicationType#deduceFromClasspath方法

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

static WebApplicationType deduceFromClasspath() {
        if (ClassUtils.isPresent("org.springframework.web.reactive.DispatcherHandler", (ClassLoader)null) && !ClassUtils.isPresent("org.springframework.web.servlet.DispatcherServlet", (ClassLoader)null) && !ClassUtils.isPresent("org.glassfish.jersey.servlet.ServletContainer", (ClassLoader)null)) {
            return REACTIVE;
        } else {
            String[] var0 = SERVLET_INDICATOR_CLASSES;
            int var1 = var0.length;

            for(int var2 = 0; var2 < var1; ++var2) {
                String className = var0[var2];
                if (!ClassUtils.isPresent(className, (ClassLoader)null)) {
                    return NONE;
                }
            }

            return SERVLET;
        }
    }
  • 若滿足類路徑(包括依賴jar包)中存在org.springframework.web.reactive.DispatcherHandler類,同時不存在org.springframework.web.servlet.DispatcherServlet類和org.glassfish.jersey.servlet.ServletContainer類則返回REACTIVE型別Web應用
  • 遍歷判斷類路徑中是否同時存在javax.servlet.Servlet類和org.springframework.web.context.ConfigurableWebApplicationContext類,滿足則返回SERVLET型別Web應用,否則返回非Web應用

載入spring.factories檔案中設定的初始化器和監聽器

通過呼叫SpringApplication#getSpringFactoriesInstances方法得到的返回值設定初始化器和監聽器屬性,傳入的引數分別為ApplicationContextInitializer類和ApplicationListener

/**
*根據傳入的type引數獲取類路徑META-INF/spring.factories檔案中的自動設定初始化類集合
@param type 型別
@return Collection<T> 泛型集合
*/
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
        return this.getSpringFactoriesInstances(type, new Class[0]);
    }
/**
*上一個方法的多引數過載方法
* @param type 型別
* @param parameterTypes 引數型別陣列
* @param args 引數陣列
*/
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
        ClassLoader classLoader = this.getClassLoader();
        // 通過SpringFactoriesLoader#loadFactoryNames方法獲取
        Set<String> names = new LinkedHashSet(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
        List<T> instances = this.createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
        AnnotationAwareOrderComparator.sort(instances);
        return instances;
    }

public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
        String factoryClassName = factoryClass.getName();
        return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
    }

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
        // 如果快取裡有則直接從雙層快取中獲取類載入器對應的結果
        MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
        if (result != null) {
            return result;
        } else {
            // 快取中沒有則去META-INF/spring.factories檔案中載入並讀取解析
            try {
                Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
                LinkedMultiValueMap result = new LinkedMultiValueMap();

                while(urls.hasMoreElements()) {
                    URL url = (URL)urls.nextElement();
                    UrlResource resource = new UrlResource(url);
                    Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                    Iterator var6 = properties.entrySet().iterator();

                    while(var6.hasNext()) {
                        Entry<?, ?> entry = (Entry)var6.next();
                        String factoryClassName = ((String)entry.getKey()).trim();
                        String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                        int var10 = var9.length;

                        for(int var11 = 0; var11 < var10; ++var11) {
                            String factoryName = var9[var11];
                            result.add(factoryClassName, factoryName.trim());
                        }
                    }
                }

                cache.put(classLoader, result);
                return result;
            } catch (IOException var13) {
                throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
            }
        }
    }

/**
* 建立spring.factories檔案中的初始化類範例集合
*/
private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args, Set<String> names) {
        List<T> instances = new ArrayList(names.size());
        Iterator var7 = names.iterator();
        while(var7.hasNext()) {
            String name = (String)var7.next();
            try {
                // 呼叫反射工具類載入spring.factories檔案中的初始化類
                Class<?> instanceClass = ClassUtils.forName(name, classLoader);
                Assert.isAssignable(type, instanceClass);
                Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
                // 通過建構函式範例化類
                T instance = BeanUtils.instantiateClass(constructor, args);
                instances.add(instance);
            } catch (Throwable var12) {
                throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, var12);
            }
        }

        return instances;
    }

推斷主應用類

 private Class<?> deduceMainApplicationClass() {
        try {
            StackTraceElement[] stackTrace = (new RuntimeException()).getStackTrace();
            StackTraceElement[] var2 = stackTrace;
            int var3 = stackTrace.length;
            for(int var4 = 0; var4 < var3; ++var4) {
                StackTraceElement stackTraceElement = var2[var4];
                if ("main".equals(stackTraceElement.getMethodName())) {
                    return Class.forName(stackTraceElement.getClassName());
                }
            }
        } catch (ClassNotFoundException var6) {
        }

        return null;
    }

通過上面的原始碼我們可以看到推斷主應用類是通過範例化一個執行時異常,並拿到該執行時異常的堆疊陣列,然後迴圈遍歷堆疊陣列,判斷堆疊元素的方法名是否為main方法,若是則返回通過反射載入全類名後的主啟動類;若是執行時異常堆疊元素中不存在main方法,則返回空。SpringApplication類範例化後就會呼叫run方法,下面我們再回到SpringApplication類非靜態的run方法原始碼

SpringApplication類範例run方法

public ConfigurableApplicationContext run(String... args) {
        // new了一個 StopWatch並啟動了它
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        // springboot啟動時使用ConfigurableApplicationContext作為BeanFactory介面的實現類
        ConfigurableApplicationContext context = null;
        // 範例化一個異常報告集合
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
        // 設定系統屬性java.awt.headless,預設為false
        this.configureHeadlessProperty();
        // 獲取所有的啟動監聽器
        SpringApplicationRunListeners listeners = this.getRunListeners(args);
        // 遍歷啟動類監聽器列表,並逐個啟動
        listeners.starting();
        Collection exceptionReporters;
        try {
            // 範例化啟動類命令引數
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            // 準備啟動環境
            ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
            // 設定環境中可忽略的bean資訊
            this.configureIgnoreBeanInfo(environment);
            // 列印springboot專案的logo圖示
            Banner printedBanner = this.printBanner(environment);
            // 建立應用上下文,也就是Spring IOC容器
            context = this.createApplicationContext();
            // 收集異常報告集合
            exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
            // 準備應用上下文環境,會去載入設定類基於註解的bean、xml組態檔中定義的bean
            this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
            // 重新整理上下文,對於servlet應用程式這個方法會去建立和啟動web伺服器
            this.refreshContext(context);
            // 這個方法啥也沒幹
            this.afterRefresh(context, applicationArguments);
            // 啟動完成,記時停止
            stopWatch.stop();
            if (this.logStartupInfo) {
                // 如果開啟了記錄啟動紀錄檔,則記錄應用程式啟動過程耗時
                (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
            }
            // 應用執行時監聽器釋出應用啟動事件
            listeners.started(context);
            // 呼叫啟動類中的任務
            this.callRunners(context, applicationArguments);
        } catch (Throwable var10) {
            // 啟動過程發生異常則處理異常,並跑出IllegalStateException型別異常
            this.handleRunFailure(context, var10, exceptionReporters, listeners);
            throw new IllegalStateException(var10);
        }

SpringApplicationRunListeners#starting方法

public void starting() {
        Iterator var1 = this.listeners.iterator();

        while(var1.hasNext()) {
            SpringApplicationRunListener listener = (SpringApplicationRunListener)var1.next();
            listener.starting();
        }

    }

通過上面的原始碼可以看到通過迴圈遍歷啟動類監聽器集合中的每個啟動類監聽器,然後呼叫每個啟動類監聽器的starting方法

這個starting方法實際上就是通過事件廣播發布了一個應用啟動事件

private final SimpleApplicationEventMulticaster initialMulticaster;
public void starting() {
        this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));
    }

準備啟動環境

然後我們再回過去看SpringApplication#prepareEnvironment方法,這個方法是準備啟動環境的意思

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) {
        // 獲取或者建立一個設定環境
        ConfigurableEnvironment environment = this.getOrCreateEnvironment();
        // 設定環境
        this.configureEnvironment((ConfigurableEnvironment)environment, applicationArguments.getSourceArgs());
        // 通過啟動類應用監聽器釋出環境準備事件
        listeners.environmentPrepared((ConfigurableEnvironment)environment);
        // 將設定環境繫結到SpringApplication
        this.bindToSpringApplication((ConfigurableEnvironment)environment);
        if (!this.isCustomEnvironment) {
            // 如果是非自定義環境則根據需要轉換成推斷出的Web環境
            environment = (new EnvironmentConverter(this.getClassLoader())).convertEnvironmentIfNecessary((ConfigurableEnvironment)environment, this.deduceEnvironmentClass());
        }
        // 通過環境變數新增屬性設定源
        ConfigurationPropertySources.attach((Environment)environment);
        // 返回經過處理的設定環境
        return (ConfigurableEnvironment)environment;
    }

getOrCreateEnvironment方法

private ConfigurableEnvironment environment;

private ConfigurableEnvironment getOrCreateEnvironment() {
        if (this.environment != null) {
            // this.environment不為空則直接返回
            return this.environment;
        } else {
            switch(this.webApplicationType) {
            // 根據web應用型別建立環境
            case SERVLET:
                // servlet web應用返回標準StandardServletEnvironment範例
                return new StandardServletEnvironment();
            case REACTIVE:
                // reactive web應用環境返回StandardReactiveWebEnvironment範例
                return new StandardReactiveWebEnvironment();
            default:
                // 預設返回非web應用的StandardEnvironment範例
                return new StandardEnvironment();
            }
        }
    }

前面的WebApplicationType#deduceFromClasspath方法碼分析中我們知道返回的是一個SERVLET列舉。因此,spring-boot專案中具有spring-boot-starter-web起步依賴時getOrCreateEnvironment方法返回的是一個StandardServletEnvironment範例

SpringApplicationRunListeners#environmentPrepared

接下來我們進入SpringApplicationRunListeners#environmentPrepared方法

public void environmentPrepared(ConfigurableEnvironment environment) {
        Iterator var2 = this.listeners.iterator();

        while(var2.hasNext()) {
            SpringApplicationRunListener listener = (SpringApplicationRunListener)var2.next();
            // 通過迭代遍歷啟動監聽器釋出環境準備事件
            listener.environmentPrepared(environment);
        }

    }

SpringApplication#bindToSpringApplication方法

準備好環境後進入SpringApplication#bindToSpringApplication方法

protected void bindToSpringApplication(ConfigurableEnvironment environment) {
        try {
            Binder.get(environment).bind("spring.main", Bindable.ofInstance(this));
        } catch (Exception var3) {
            throw new IllegalStateException("Cannot bind to SpringApplication", var3);
        }
    }

Binder#get方法

public static Binder get(Environment environment) {
        // 這裡傳入了一個屬性源預留位置解析器類範例引數
        return new Binder(ConfigurationPropertySources.get(environment), new PropertySourcesPlaceholdersResolver(environment));
    }

Binder類構造方法

public Binder(Iterable<ConfigurationPropertySource> sources, PlaceholdersResolver placeholdersResolver) {
        this(sources, placeholdersResolver, (ConversionService)null, (Consumer)null);
    }

public Binder(Iterable<ConfigurationPropertySource> sources, PlaceholdersResolver placeholdersResolver, ConversionService conversionService, Consumer<PropertyEditorRegistry> propertyEditorInitializer) {
        Assert.notNull(sources, "Sources must not be null");
        this.sources = sources;
        this.placeholdersResolver = placeholdersResolver != null ? placeholdersResolver : PlaceholdersResolver.NONE;
        this.conversionService = conversionService != null ? conversionService : ApplicationConversionService.getSharedInstance();
        this.propertyEditorInitializer = propertyEditorInitializer;
    }

PropertySourcesPlaceholdersResolver類建構函式

public PropertySourcesPlaceholdersResolver(Environment environment) {
        this(getSources(environment), (PropertyPlaceholderHelper)null);
    }

public PropertySourcesPlaceholdersResolver(Iterable<PropertySource<?>> sources, PropertyPlaceholderHelper helper) {
        this.sources = sources;
        // 屬性預留位置解析器會去解析"${" 和 "}"兩個符號包裹的環境變數
        this.helper = helper != null ? helper : new PropertyPlaceholderHelper("${", "}", ":", true);
    }

PropertySourcesPlaceholdersResolver#getSources方法

private static PropertySources getSources(Environment environment) {
        // 斷言environment不為空 
        Assert.notNull(environment, "Environment must not be null");
        // 斷言environment是一個ConfigurableEnvironment類範例
        Assert.isInstanceOf(ConfigurableEnvironment.class, environment, "Environment must be a ConfigurableEnvironment");
        return ((ConfigurableEnvironment)environment).getPropertySources();
    }

ConfigurationPropertySources#attach方法

public static void attach(Environment environment) {
        Assert.isInstanceOf(ConfigurableEnvironment.class, environment);
        // 從環境變數中獲取多屬性設定源
        MutablePropertySources sources = ((ConfigurableEnvironment)environment).getPropertySources();
        PropertySource<?> attached = sources.get("configurationProperties");
        if (attached != null && attached.getSource() != sources) {
            // 若attached且attach#getSource得到的結果不等於sources,則刪除sources中configurationProperties對應的鍵值對並置空attached
            sources.remove("configurationProperties");
            attached = null;
        }
        if (attached == null) {
            // 若attached為空則構建新的屬性源並新增到sources的屬性源列表的第一個位置
            sources.addFirst(new ConfigurationPropertySourcesPropertySource("configurationProperties", new SpringConfigurationPropertySources(sources)));
        }

    }

Binder#bind方法

public <T> BindResult<T> bind(String name, Bindable<T> target) {
        return this.bind((ConfigurationPropertyName)ConfigurationPropertyName.of(name), target, (BindHandler)null);
    }

public <T> BindResult<T> bind(ConfigurationPropertyName name, Bindable<T> target, BindHandler handler) {
        Assert.notNull(name, "Name must not be null");
        Assert.notNull(target, "Target must not be null");
        handler = handler != null ? handler : BindHandler.DEFAULT;
        Binder.Context context = new Binder.Context();
        T bound = this.bind(name, target, handler, context, false);
        return BindResult.of(bound);
    }

protected final <T> T bind(ConfigurationPropertyName name, Bindable<T> target, BindHandler handler, Binder.Context context, boolean allowRecursiveBinding) {
        context.clearConfigurationProperty();

        try {
            target = handler.onStart(name, target, context);
            if (target == null) {
                return null;
            } else {
                Object bound = this.bindObject(name, target, handler, context, allowRecursiveBinding);
                return this.handleBindResult(name, target, handler, context, bound);
            }
        } catch (Exception var7) {
            return this.handleBindError(name, target, handler, context, var7);
        }
    }
// 繫結物件
private <T> Object bindObject(ConfigurationPropertyName name, Bindable<T> target, BindHandler handler, Binder.Context context, boolean allowRecursiveBinding) {
        ConfigurationProperty property = this.findProperty(name, context);
        if (property == null && this.containsNoDescendantOf(context.getSources(), name)) {
            return null;
        } else {
            AggregateBinder<?> aggregateBinder = this.getAggregateBinder(target, context);
            if (aggregateBinder != null) {
                return this.bindAggregate(name, target, handler, context, aggregateBinder);
            } else if (property != null) {
                try {
                    return this.bindProperty(target, context, property);
                } catch (ConverterNotFoundException var10) {
                    Object bean = this.bindBean(name, target, handler, context, allowRecursiveBinding);
                    if (bean != null) {
                        return bean;
                    } else {
                        throw var10;
                    }
                }
            } else {
                return this.bindBean(name, target, handler, context, allowRecursiveBinding);
            }
        }
    }

private <T> Object bindProperty(Bindable<T> target, Binder.Context context, ConfigurationProperty property) {
        context.setConfigurationProperty(property);
        Object result = property.getValue();
        // 使用預留位置解析器解析屬性預留位置
        result = this.placeholdersResolver.resolvePlaceholders(result);
        // 從context中獲取轉換器轉換result
        result = context.getConverter().convert(result, target);
        return result;
    }

預備環境其實就做了下面幾件事情:

  • 建立一個ConfigurableEnvironment型別的設定環境
  • 將設定環境範例ConfigurableEnvironment與SpringApplication啟動類範例繫結到一個Binder物件中去
  • 上一步的 繫結屬性的過程會去解析屬性預留位置,並按照設定環境設定的轉換服務轉轉系結結果,如果繫結成功或失敗都會有對應的事件處理方法

建立Spring應用上下文

現在我們回到SpringApplication#run方法的context = this.createApplicationContext();這行程式碼的具體實現,進入SpringApplication#createApplicationContext方法體,原始碼如下:

protected ConfigurableApplicationContext createApplicationContext() {
        Class<?> contextClass = this.applicationContextClass;
        if (contextClass == null) {
            // 若啟動類的應用上下文為空,則根據範例化SpringApplication啟動類過程中推斷出來的web應用型別載入對應的應用上下文類ApplicationContext
            try {
                switch(this.webApplicationType) {
                case SERVLET:
                    contextClass = Class.forName("org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext");
                    break;
                case REACTIVE:
                    contextClass = Class.forName("org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext");
                    break;
                default:
                    contextClass = Class.forName("org.springframework.context.annotation.AnnotationConfigApplicationContext");
                }
            } catch (ClassNotFoundException var3) {
                throw new IllegalStateException("Unable create a default ApplicationContext, please specify an ApplicationContextClass", var3);
            }
        }

        return (ConfigurableApplicationContext)BeanUtils.instantiateClass(contextClass);
    }

從以上的原始碼可以看出建立應用上下文過程主要做了下面兩件事情:

  1. 根據web應用型別載入對應的ApplicationContext實現類:
  • 如果是Servlet應用程式則載入AnnotationConfigServletWebServerApplicationContext類作為應用上下文類;
  • 如果是Reactive應用程式則載入AnnotationConfigReactiveWebServerApplicationContext類作為應用上下文類;
  • 預設載入AnnotationConfigApplicationContext類作為應用上下文類
  1. 呼叫BeanUtils工具類範例化應用上下文類,並返回這個範例化的應用上下文物件

準備Spring應用上下文

接下來我們再回到SpringApplication#run方法的this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);這行程式碼,並進入SpringApplication#prepareContext方法體內部

private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
        // 應用上下文設定設定環境屬性
        context.setEnvironment(environment);
        // 應用上下文後置處理
        this.postProcessApplicationContext(context);
        // 申請初始化器
        this.applyInitializers(context);
        // 啟動執行時監聽器釋出應用上下文預備事件
        listeners.contextPrepared(context);
        if (this.logStartupInfo) {
            // 記錄紀錄檔
            this.logStartupInfo(context.getParent() == null);
            this.logStartupProfileInfo(context);
        }
        // 獲取ConfigurableListableBeanFactory型別beanFactory,注意該類是個介面類
        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        // 註冊啟動引數型別單例bean
        beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
        if (printedBanner != null) {
            // 註冊PrintedBanner型別單例bean
            beanFactory.registerSingleton("springBootBanner", printedBanner);
        }
        // 判斷應用上下文中的beanFactory是否是一個DefaultListableBeanFactory型別的範例
        if (beanFactory instanceof DefaultListableBeanFactory) {
            ((DefaultListableBeanFactory)beanFactory).setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);// 設定是否允許覆寫bean定義標識
        }

        if (this.lazyInitialization) {
            // 若是延遲初始化,則新增延遲初始化型別beanFactory後置處理器
            context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
        }
        // 獲取啟動來源類集合,也就是我們專案中加上@SpringBootApplication註解的啟動類集合
        Set<Object> sources = this.getAllSources();
        // 斷言啟動來源類不為空
        Assert.notEmpty(sources, "Sources must not be empty");
        // 載入應用上下文,這個方法會通過beanDefinationReader讀取通過註解和xml設定的bean
        this.load(context, sources.toArray(new Object[0]));
        // 啟動監聽器釋出上下文完成載入事件
        listeners.contextLoaded(context);
    }

SpringApplication#postProcessApplicationContext方法

protected void postProcessApplicationContext(ConfigurableApplicationContext context) {
        if (this.beanNameGenerator != null) {            context.getBeanFactory().registerSingleton("org.springframework.context.annotation.internalConfigurationBeanNameGenerator", this.beanNameGenerator); // 應用上下文中的beanFactory註冊單例BeanNameGenerator bean
        }
        if (this.resourceLoader != null) {
            if (context instanceof GenericApplicationContext) {
                ((GenericApplicationContext)context).setResourceLoader(this.resourceLoader);// 設定資源載入器
            }

            if (context instanceof DefaultResourceLoader) {
                ((DefaultResourceLoader)context).setClassLoader(this.resourceLoader.getClassLoader());// 設定類載入器
            }
        }

        if (this.addConversionService) {
           context.getBeanFactory().setConversionService(ApplicationConversionService.getSharedInstance());// 設定conversionService屬性
        }

    }

SpringApplication#applyInitializers方法

protected void applyInitializers(ConfigurableApplicationContext context) {
        // 獲取初始化器迭代器
        Iterator var2 = this.getInitializers().iterator();
        // 迴圈遍歷初始化器迭代器
        while(var2.hasNext()) {
            ApplicationContextInitializer initializer = (ApplicationContextInitializer)var2.next();
            // 根據解析器calss型別和應用上下文初始化器calss型別解析引數型別
            Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(), ApplicationContextInitializer.class);
            Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
            initializer.initialize(context);
        }
    }

GenericTypeResolver#resolveTypeArgument方法

@Nullable
public static Class<?> resolveTypeArgument(Class<?> clazz, Class<?> genericIfc) {
        ResolvableType resolvableType = ResolvableType.forClass(clazz).as(genericIfc);
        return !resolvableType.hasGenerics() ? null : getSingleGeneric(resolvableType);
}
// 載入解析類
public static ResolvableType forClass(@Nullable Class<?> clazz) {
        return new ResolvableType(clazz);
}
// 判斷是否有解析型別陣列
public boolean hasGenerics() {
        return this.getGenerics().length > 0;
}
// 獲取單個解析型別
@Nullable
private static Class<?> getSingleGeneric(ResolvableType resolvableType) {
        Assert.isTrue(resolvableType.getGenerics().length == 1, () -> {
            return "Expected 1 type argument on generic interface [" + resolvableType + "] but found " + resolvableType.getGenerics().length;
        });
        return resolvableType.getGeneric(new int[0]).resolve();
}

載入Spring應用上下文中的bean

SpringApplication#load方法

protected void load(ApplicationContext context, Object[] sources) {
        if (logger.isDebugEnabled()) {
            // 如果開啟了debug級別紀錄檔,則記錄debug紀錄檔
            logger.debug("Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
        }
        // 建立BeanDefinitionLoader類範例
        BeanDefinitionLoader loader = this.createBeanDefinitionLoader(this.getBeanDefinitionRegistry(context), sources);
        if (this.beanNameGenerator != null) {
            loader.setBeanNameGenerator(this.beanNameGenerator);
        }

        if (this.resourceLoader != null) {
            loader.setResourceLoader(this.resourceLoader);
        }

        if (this.environment != null) {
            loader.setEnvironment(this.environment);
        }
        loader.load();
    }
// 通過BeanDefinitionRegistry類範例引數和應用源陣列構造BeanDefinitionLoader類範例
protected BeanDefinitionLoader createBeanDefinitionLoader(BeanDefinitionRegistry registry, Object[] sources) {
        return new BeanDefinitionLoader(registry, sources);
 }

BeanDefinitionLoader類帶兩個引數的構造方法原始碼:

BeanDefinitionLoader(BeanDefinitionRegistry registry, Object... sources) {
        // 斷言registry和sources兩個引數不能為空
        Assert.notNull(registry, "Registry must not be null");
        Assert.notEmpty(sources, "Sources must not be empty");
        this.sources = sources;
        // 初始化基於註解的BeanDefinitionReader
        this.annotatedReader = new AnnotatedBeanDefinitionReader(registry);
        // 初始化基於xml的BeanDefinitionReader
        this.xmlReader = new XmlBeanDefinitionReader(registry);
        if (this.isGroovyPresent()) {
            // 如果存在groovy指令碼則初始化基於Groovy的BeanDefinitionReader
            this.groovyReader = new GroovyBeanDefinitionReader(registry);
        }
        // 初始化類路徑bean定義掃描器
        this.scanner = new ClassPathBeanDefinitionScanner(registry);
        // 掃描器新增排除過濾器,排除掃描啟動類
        this.scanner.addExcludeFilter(new BeanDefinitionLoader.ClassExcludeFilter(sources));
    }

BeanDefinitionLoader#load方法

然後我們回到BeanDefinitionLoader#load方法,springboot專案中的bean具體是如何載入的我們在springboot專案的啟動偵錯過程再來分析

int load() {
        int count = 0;
        Object[] var2 = this.sources;
        int var3 = var2.length;
        for(int var4 = 0; var4 < var3; ++var4) {
            Object source = var2[var4];
            // 每載入一個bean來源,記錄載入數量的count會+1
            count += this.load(source);
        }
        return count;
    }

// 這個載入bean的方法會根據不同的bean來源進行載入,bean是如何載入的關鍵就在下面這幾個load方法裡面
private int load(Object source) {
        Assert.notNull(source, "Source must not be null");
        if (source instanceof Class) {
            // 載入設定類中的bean
            return this.load((Class)source);
        } else if (source instanceof Resource) {
            // 載入類路徑資源中的bean,包括groovy和xml檔案中設定的bean
            return this.load((Resource)source);
        } else if (source instanceof Package) {
            // 載入包下面的不同設定類中的bean
            return this.load((Package)source);
        } else if (source instanceof CharSequence) {
            // 載入根據制定路徑的xml檔案中設定的bean
            return this.load((CharSequence)source);
        } else {
            throw new IllegalArgumentException("Invalid source type " + source.getClass());
        }
    }

private int load(Class<?> source) {
        if (this.isGroovyPresent() && BeanDefinitionLoader.GroovyBeanDefinitionSource.class.isAssignableFrom(source)) {
            BeanDefinitionLoader.GroovyBeanDefinitionSource loader = (BeanDefinitionLoader.GroovyBeanDefinitionSource)BeanUtils.instantiateClass(source, BeanDefinitionLoader.GroovyBeanDefinitionSource.class);
            this.load(loader);
        }

        if (this.isComponent(source)) {
            this.annotatedReader.register(new Class[]{source});
            return 1;
        } else {
            return 0;
        }
    }

private int load(Resource source) {
        if (source.getFilename().endsWith(".groovy")) {
            if (this.groovyReader == null) {
                throw new BeanDefinitionStoreException("Cannot load Groovy beans without Groovy on classpath");
            } else {
                return this.groovyReader.loadBeanDefinitions(source);
            }
        } else {
            return this.xmlReader.loadBeanDefinitions(source);
        }
    }

private int load(Package source) {
        return this.scanner.scan(new String[]{source.getName()});
    }

private int load(CharSequence source) {
        String resolvedSource = this.xmlReader.getEnvironment().resolvePlaceholders(source.toString());

        try {
            return this.load(ClassUtils.forName(resolvedSource, (ClassLoader)null));
        } catch (ClassNotFoundException | IllegalArgumentException var10) {
            Resource[] resources = this.findResources(resolvedSource);
            int loadCount = 0;
            boolean atLeastOneResourceExists = false;
            Resource[] var6 = resources;
            int var7 = resources.length;

            for(int var8 = 0; var8 < var7; ++var8) {
                Resource resource = var6[var8];
                if (this.isLoadCandidate(resource)) {
                    atLeastOneResourceExists = true;
                    loadCount += this.load(resource);
                }
            }

            if (atLeastOneResourceExists) {
                return loadCount;
            } else {
                Package packageResource = this.findPackage(resolvedSource);
                if (packageResource != null) {
                    return this.load(packageResource);
                } else {
                    throw new IllegalArgumentException("Invalid source '" + resolvedSource + "'");
                }
            }
        }
    }

重新整理Spring應用上下文

然後我們回到SpringApplication#run方法中的this.refreshContext(context);這行程式碼,並進入方法體

 private void refreshContext(ConfigurableApplicationContext context) {
        // 重新整理應用上下文
        this.refresh(context);
        if (this.registerShutdownHook) {
            try {
                // 如果需要註冊關閉勾點,則應用上下文註冊關閉勾點
                context.registerShutdownHook();
            } catch (AccessControlException var3) {
            }
        }

    }

protected void refresh(ApplicationContext applicationContext) {
        Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
        ((AbstractApplicationContext)applicationContext).refresh();
    }

AbstractApplicationContext#refresh方法

public void refresh() throws BeansException, IllegalStateException {
        // 重新整理應用上下文過程使用了監視器鎖
        synchronized(this.startupShutdownMonitor) {
            // 預重新整理
            this.prepareRefresh();
            // 獲取重新整理beanFactory
            ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
            // 預備beanFactory
            this.prepareBeanFactory(beanFactory);
            try {
                // 後置處理beanFactory
                this.postProcessBeanFactory(beanFactory);
                // 執行beanFactory後置處理器
                this.invokeBeanFactoryPostProcessors(beanFactory);
                // 註冊bean後置處理器
                this.registerBeanPostProcessors(beanFactory);
                // 初始化訊息源
                this.initMessageSource();
                // 初始化應用事件廣播
                this.initApplicationEventMulticaster();
                // 呼叫onRefres方法,如果是Servlet應用程式,這個方法會去建立web伺服器
                this.onRefresh();
                // 註冊監聽器
                this.registerListeners();
                // 結束beanFactory初始化
                this.finishBeanFactoryInitialization(beanFactory);
                // 結束重新整理,如果是Servlet應用程式,個方法會去啟動web伺服器
                this.finishRefresh();
            } catch (BeansException var9) {
                if (this.logger.isWarnEnabled()) {
                    this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
                }
                // 發生異常時會銷燬bean並取消重新整理
                this.destroyBeans();
                this.cancelRefresh(var9);
                throw var9;
            } finally {
                // finally語句塊中重新設定公共快取
                this.resetCommonCaches();
            }

        }
    }

ServletWebServerApplicationContext#onRefresh方法

protected void onRefresh() {
        super.onRefresh();
        try {
            this.createWebServer();
        } catch (Throwable var2) {
            throw new ApplicationContextException("Unable to start web server", var2);
        }
    }

ServletWebServerApplicationContext#createWebServer方法

private void createWebServer() {
        WebServer webServer = this.webServer;
        ServletContext servletContext = this.getServletContext();
        if (webServer == null && servletContext == null) {
            ServletWebServerFactory factory = this.getWebServerFactory();
            this.webServer = factory.getWebServer(new ServletContextInitializer[]{this.getSelfInitializer()});
        } else if (servletContext != null) {
            try {
                this.getSelfInitializer().onStartup(servletContext);
            } catch (ServletException var4) {
                throw new ApplicationContextException("Cannot initialize servlet context", var4);
            }
        }

        this.initPropertySources();
    }

Spring容器啟動後執行任務

SpringApplication#callRunners方法

private void callRunners(ApplicationContext context, ApplicationArguments args) {
        List<Object> runners = new ArrayList();
        // 通過應用上下文獲取所有ApplicationRunner介面實現類的bean集合
        runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
        // 通過應用上下文獲取所有CommandLineRunner介面實現類的bean集合
        runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
        AnnotationAwareOrderComparator.sort(runners);
        Iterator var4 = (new LinkedHashSet(runners)).iterator();
        // 遍歷執行ApplicationRunner實現類和CommandLineRunner實現類中的run方法
        while(var4.hasNext()) {
            Object runner = var4.next();
            if (runner instanceof ApplicationRunner) {
                this.callRunner((ApplicationRunner)runner, args);
            }

            if (runner instanceof CommandLineRunner) {
                this.callRunner((CommandLineRunner)runner, args);
            }
        }
    
private void callRunner(ApplicationRunner runner, ApplicationArguments args) {
        try {
            runner.run(args);
        } catch (Exception var4) {
            throw new IllegalStateException("Failed to execute ApplicationRunner", var4);
        }
}
    
private void callRunner(CommandLineRunner runner, ApplicationArguments args) {
        try {
            runner.run(args.getSourceArgs());
        } catch (Exception var4) {
            throw new IllegalStateException("Failed to execute CommandLineRunner", var4);
        }
    }

處理啟動異常

最後我們來看看SprignBoot應用程式啟動發生異常時呼叫的方法

SpringApplication#handleRunFailure方法

 private void handleRunFailure(ConfigurableApplicationContext context, Throwable exception, Collection<SpringBootExceptionReporter> exceptionReporters, SpringApplicationRunListeners listeners) {
        try {
            try {
                // 處理程式退出編碼
                this.handleExitCode(context, exception);
                if (listeners != null) {
                    // 應用啟動監聽器釋出啟動失敗事件
                    listeners.failed(context, exception);
                }
            } finally {
                // 報告異常
                this.reportFailure(exceptionReporters, exception);
                if (context != null) {
                    // 關閉Spring IOC容器
                    context.close();
                }

            }
        } catch (Exception var9) {
            logger.warn("Unable to close ApplicationContext", var9);
        }
        // 呼叫反射工具類丟擲執行時異常
        ReflectionUtils.rethrowRuntimeException(exception);
    }

小結

可以看到SpringBoot專案啟動過程的原始碼的原始碼還是非常複雜的,但是在難啃的骨頭只要堅持下去還是能啃下它的。通過分析SpringBoot專案啟動過程的原始碼分析,我們可以總結出SpringBoot專案啟動過程主要做了以下幾件事情:

一、 範例化和初始化SpringApplication物件範例,在這個過程會去初始化SpringApplication物件的屬性,包括:

  • 1.設定是夠註冊關停勾點標識
  • 2.推斷Web應用程式型別
  • 3.載入META-INF/spring.factories組態檔中設定的初始化器和啟動監聽器
  • 4.推斷專案主啟動類等工作

二、 執行SpringApplication範例物件的run方法,該方法返回的是一個AnnotationConfig在這個過程中又可以分解為以下幾個步驟

  • 1.啟動定時器記錄記錄整個SpringBoot應用啟動過程花費時長
    1. 獲取SpringApplication範例物件的啟動類監聽器並遍歷釋出應用開始啟動事件
  • 3.範例化啟動命令列引數
  • 4.列印SpringBoot專案圖示
  • 5. 啟動監聽器釋出應用開始啟動事件
  • 6. 準備啟動環境:這一過程會範例化一個ConfigurableEnvironment類的設定環境物件,並將從應用組態檔中讀取到的環境變數填充到設定環境物件中;監聽器釋出環境準備事件,然後再將初始化的設定環境物件與SpringApplication範例物件繫結,繫結過程中會解析環境變數中的屬性預留位置變數
  • 7. 建立Spring應用上下文:這一過程會根據前面範例化和初始化SpringApplication過程中推斷出的應用程式型別通過反射的方式載入和範例化具體的Spring應用上下文實現類。Servlet型別對應的Spring應用上下文是AnnotationConfigServletWebSewrverApplicationContext類範例
  • 8. 準備Spring應用上下文: 這一過程會去設定Spring應用上下文環境的環境屬性、 後處理Spring應用上下文、監聽器釋出應用上下文載入事件、新增Spring應用上下文beanFactory後置處理器、載入設定類和xml組態檔以及掃描包下的Bean定義並註冊到beanFactory,Spring應用上下文實際的beanFactoryDefaultListableBeanFactory,它裡面使用了一個初始容量為256的ConcurrentHashMap的資料結構存放BeanDefination物件
  • 9 . 重新整理應用上下文:如果是Servlet型別應用這個過程會去範例化和初始化一個web伺服器並啟動這個web伺服器,如過啟動web伺服器失敗則會在SpringApplication範例物件設定了關停勾點的情況下注冊關停勾點,同事關閉web伺服器並銷燬所有的bean -10; 執行實現了ApplicationRunnerCommandLineRunnner介面元件類中的任務 -11. 如果整個執行過程捕獲到異常則收集異常資訊,監聽器釋出應用失敗事件,丟擲捕獲到異常