SpringBoot 啓動流程分析(尋找擴展點)

1、SpringBoot maven 依賴版本

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>SpringBootStudy</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.7.14</version>
        </dependency>
    </dependencies>

</project>

2、啓動代碼

package com.springboot.study;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Main {

    public static void main(String[] args) {
        SpringApplication springApplication = new SpringApplication(Main.class);
        springApplication.run(args);
    }
}

3、啓動流程分析

1、獲取Spring工廠實例

  private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
        ClassLoader classLoader = getClassLoader();
        // 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;
    }
  public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
        ClassLoader classLoaderToUse = classLoader;
        if (classLoaderToUse == null) {
            classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
        }
        String factoryTypeName = factoryType.getName();
        return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
    }
  private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
        Map<String, List<String>> result = cache.get(classLoader);
        if (result != null) {
            return result;
        }

        result = new HashMap<>();
        try {
            Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                UrlResource resource = new UrlResource(url);
                Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                for (Map.Entry<?, ?> entry : properties.entrySet()) {
                    String factoryTypeName = ((String) entry.getKey()).trim();
                    String[] factoryImplementationNames =
                            StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
                    for (String factoryImplementationName : factoryImplementationNames) {
                        result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
                                .add(factoryImplementationName.trim());
                    }
                }
            }

            // Replace all lists with unmodifiable lists containing unique elements
            result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
                    .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
            cache.put(classLoader, result);
        }
        catch (IOException ex) {
            throw new IllegalArgumentException("Unable to load factories from location [" +
                    FACTORIES_RESOURCE_LOCATION + "]", ex);
        }
        return result;
    }

這個地方會掃描項目中 resource 目錄下的 META-INF/spring.factories 文件,默認如果不添加其他依賴,會掃描到如下項目:

  1. spring-boot-2.7.14.jar
  2. spring-boot-autoconfigure-2.7.14.jar
  3. spring-beans-5.3.29.jar

最終會掃描到如下對象:

2、run 方法分析

  public ConfigurableApplicationContext run(String... args) {
        long startTime = System.nanoTime();
        DefaultBootstrapContext bootstrapContext = createBootstrapContext();
        ConfigurableApplicationContext context = null;
        configureHeadlessProperty();
        SpringApplicationRunListeners listeners = getRunListeners(args);
        listeners.starting(bootstrapContext, this.mainApplicationClass);
        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
            configureIgnoreBeanInfo(environment);
            Banner printedBanner = printBanner(environment);
            context = createApplicationContext();
            context.setApplicationStartup(this.applicationStartup);
            prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
            refreshContext(context);
            afterRefresh(context, applicationArguments);
            Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
            if (this.logStartupInfo) {
                new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
            }
            listeners.started(context, timeTakenToStartup);
            callRunners(context, applicationArguments);
        }
        catch (Throwable ex) {
            handleRunFailure(context, ex, listeners);
            throw new IllegalStateException(ex);
        }
        try {
            Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
            listeners.ready(context, timeTakenToReady);
        }
        catch (Throwable ex) {
            handleRunFailure(context, ex, null);
            throw new IllegalStateException(ex);
        }
        return context;
    }

1、首先是 createBootstrapContext 方法,該方法會調用第一個擴展點BootstrapRegistryInitializer->initializer,但是項目中沒有該接口的實現類。

2、其次就是 listeners.starting 方法, 該方法中會調用第二個擴展點 SpringApplicationRunListener->starting,這個 SpringApplicationRunListener 項目中只存在一個實現類:EventPublishingRunListener,它會觸發所有的 ApplicationListener 監聽 ApplicationStartingEvent 的事件,後文就不特別聲明這個實現類了。

2.1、配置屬性(prepareEnvironment->configureEnvironment)

  protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) {
        MutablePropertySources sources = environment.getPropertySources();
        if (!CollectionUtils.isEmpty(this.defaultProperties)) {
            DefaultPropertiesPropertySource.addOrMerge(this.defaultProperties, sources);
        }
        if (this.addCommandLineProperties && args.length > 0) {
            String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME;
            if (sources.contains(name)) {
                PropertySource<?> source = sources.get(name);
                CompositePropertySource composite = new CompositePropertySource(name);
                composite
                    .addPropertySource(new SimpleCommandLinePropertySource("springApplicationCommandLineArgs", args));
                composite.addPropertySource(source);
                sources.replace(name, composite);
            }
            else {
                sources.addFirst(new SimpleCommandLinePropertySource(args));
            }
        }
    }

3、然後就是 prepareEnvironment 方法,該方法會調用第三個擴展點:SpringApplicationRunListener->environmentPrepared,它會觸發所有的 ApplicationListener 監聽 ApplicationEnvironmentPreparedEvent 的事件。

2.2、配置 Banner

  private Banner getBanner(Environment environment) {
        Banners banners = new Banners();
        banners.addIfNotNull(getImageBanner(environment));
        banners.addIfNotNull(getTextBanner(environment));
        if (banners.hasAtLeastOneBanner()) {
            return banners;
        }
        if (this.fallbackBanner != null) {
            return this.fallbackBanner;
        }
        return DEFAULT_BANNER;
    }

4、第四個擴展點在 prepareContext->applyInitializers 方法裏,ApplicationContextInitializer->initialize

5、第五個擴展點在 prepareContext->listeners.contextPrepared 方法裏,SpringApplicationRunListeners->contextPrepared,它會觸發所有的 ApplicationListener 監聽 ApplicationContextInitializedEvent 的事件。

6、第六個擴展點在 prepareContext->bootstrapContext.close 方法裏,它會觸發所有的 ApplicationListener 監聽 BootstrapContextClosedEvent 的事件。

2.3、加載 Source(prepareContext)

public Set<Object> getAllSources() {
        Set<Object> allSources = new LinkedHashSet<>();
        if (!CollectionUtils.isEmpty(this.primarySources)) {
            allSources.addAll(this.primarySources);
        }
        if (!CollectionUtils.isEmpty(this.sources)) {
            allSources.addAll(this.sources);
        }
        return Collections.unmodifiableSet(allSources);
    }
  private void load(Object source) {
        Assert.notNull(source, "Source must not be null");
        if (source instanceof Class<?>) {
            load((Class<?>) source);
            return;
        }
        if (source instanceof Resource) {
            load((Resource) source);
            return;
        }
        if (source instanceof Package) {
            load((Package) source);
            return;
        }
        if (source instanceof CharSequence) {
            load((CharSequence) source);
            return;
        }
        throw new IllegalArgumentException("Invalid source type " + source.getClass());
    }

這個地方會從 SpringApplication 中的 primarySources、sources 加載源,然後註冊爲 Bean。

7、第七個擴展點在 prepareContext->listeners.contextLoaded 方法裏,SpringApplicationRunListeners->contextLoaded,會觸發 ApplicationListener 監聽 ApplicationPreparedEvent 的事件。

2.4、ServletWebServerApplicationContext->refresh

  @Override
    public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
            StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");

            // Prepare this context for refreshing.
            prepareRefresh();

            // Tell the subclass to refresh the internal bean factory.
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

            // Prepare the bean factory for use in this context.
            prepareBeanFactory(beanFactory);

            try {
                // Allows post-processing of the bean factory in context subclasses.
                postProcessBeanFactory(beanFactory);

                StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
                // Invoke factory processors registered as beans in the context.
                invokeBeanFactoryPostProcessors(beanFactory);

                // Register bean processors that intercept bean creation.
                registerBeanPostProcessors(beanFactory);
                beanPostProcess.end();

                // Initialize message source for this context.
                initMessageSource();

                // Initialize event multicaster for this context.
                initApplicationEventMulticaster();

                // Initialize other special beans in specific context subclasses.
                onRefresh();

                // Check for listener beans and register them.
                registerListeners();

                // Instantiate all remaining (non-lazy-init) singletons.
                finishBeanFactoryInitialization(beanFactory);

                // Last step: publish corresponding event.
                finishRefresh();
            }

            catch (BeansException ex) {
                if (logger.isWarnEnabled()) {
                    logger.warn("Exception encountered during context initialization - " +
                            "cancelling refresh attempt: " + ex);
                }

                // Destroy already created singletons to avoid dangling resources.
                destroyBeans();

                // Reset 'active' flag.
                cancelRefresh(ex);

                // Propagate exception to caller.
                throw ex;
            }

            finally {
                // Reset common introspection caches in Spring's core, since we
                // might not ever need metadata for singleton beans anymore...
                resetCommonCaches();
                contextRefresh.end();
            }
        }
    }

這裏我們直接看到 invokeBeanFactoryPostProcessors 方法,不過這個方法蠻長的,就先看一部分:

      for (BeanFactoryPostProcessor postProcessor : beanFactoryPostProcessors) {
                if (postProcessor instanceof BeanDefinitionRegistryPostProcessor) {
                    BeanDefinitionRegistryPostProcessor registryProcessor =
                            (BeanDefinitionRegistryPostProcessor) postProcessor;
                    registryProcessor.postProcessBeanDefinitionRegistry(registry);
                    registryProcessors.add(registryProcessor);
                }
                else {
                    regularPostProcessors.add(postProcessor);
                }
            }

這個地方可以猜到這個 postProcessBeanDefinitionRegistry 也是一個擴展點,但是這個 beanFactoryPostProcessors 的值是從哪裏來的呢?

打斷點這裏是有三個值的:

這裏直接說答案,還記得這段代碼嗎:

context = createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);

這裏獲取了 context 之後,就調用了 prepareContext 方法,prepareContext 方法裏曾提到過有第四、第五、第六、第七個擴展點

舉個例子,就拿第四個擴展點說:

ConfigurationWarningsApplicationContextInitializer 實現了 ApplicationContextInitializer 接口,然後添加了一個值:

  @Override
    public void initialize(ConfigurableApplicationContext context) {
        context.addBeanFactoryPostProcessor(new ConfigurationWarningsPostProcessor(getChecks()));
    }

相同的還有 SharedMetadataReaderFactoryContextInitializer,至於第三個值則是直接在 prepareContext 方法裏添加的。

8、第八個擴展點在 refresh->invokeBeanFactoryPostProcessors 方法裏,會調用 invokeBeanDefinitionRegistryPostProcessors

緊接着,就是如下代碼:

       String[] postProcessorNames =
                    beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
            for (String ppName : postProcessorNames) {
                if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
                    currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
                    processedBeans.add(ppName);
                }
            }

我們看看這個 postProcessorNames 是怎麼來的,追蹤到 beanFactory.getBeanNamesForType 方法,進去可以看到 doGetBeanNamesForType 方法,總而言之,是從 beanDefinitionNames 和 manualSingletonNames 的值來的,那這些值又是怎麼來的呢,仍然是如下一段代碼:

       context = createApplicationContext();
            context.setApplicationStartup(this.applicationStartup);
            prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);

在創建 context 的時候,實際上是調用 AnnotationConfigApplicationContext 的構造方法,裏面會調用 registerAnnotationConfigProcessors 方法,這個方法會初始化 5 個 beanDefinitionName。

然後就是 prepareContext 方法裏,在第四個擴展點會初始化兩個 manualSingletonName,prepareContext 方法裏的 registerSingleton 方法也添加了兩個 manualSingletonName,並且後面的 load 方法也添加了一個 main 的 beanDefinitionName,在最後的第七個擴展點裏又添加了 3 個 manualSingletonName,總之在執行 refreshContext 之前,context 的 beanFactory 裏包含 6 個 beanDefinitionName 7 個 manualSingletonName,後面的就不分析了,畢竟能擴展的就在這裏。

9、第九個擴展點也在 refresh->invokeBeanFactoryPostProcessors 方法裏,會調用 invokeBeanFactoryPostProcessors

不過有一點要注意的是,雖說第八個、第九個也算是擴展點,但其只有在第四到第七個擴展點裏面配置了才能進行擴展。

2.5、refresh->onRefresh

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

該方法會調用 createWebServer 創建一個 webserver。

2.6、refresh->finishBeanFactoryInitialization

protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
        // Initialize conversion service for this context.
        if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME) &&
                beanFactory.isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)) {
            beanFactory.setConversionService(
                    beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class));
        }

        // Register a default embedded value resolver if no BeanFactoryPostProcessor
        // (such as a PropertySourcesPlaceholderConfigurer bean) registered any before:
        // at this point, primarily for resolution in annotation attribute values.
        if (!beanFactory.hasEmbeddedValueResolver()) {
            beanFactory.addEmbeddedValueResolver(strVal -> getEnvironment().resolvePlaceholders(strVal));
        }

        // Initialize LoadTimeWeaverAware beans early to allow for registering their transformers early.
        String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false);
        for (String weaverAwareName : weaverAwareNames) {
            getBean(weaverAwareName);
        }

        // Stop using the temporary ClassLoader for type matching.
        beanFactory.setTempClassLoader(null);

        // Allow for caching all bean definition metadata, not expecting further changes.
        beanFactory.freezeConfiguration();

        // Instantiate all remaining (non-lazy-init) singletons.
        beanFactory.preInstantiateSingletons();
    }

這個方法會調用 beanFactory,實例化所有的 bean。

10、第十個擴展點在 refresh->finishRefresh 方法裏,會調用 getLifecycleProcessor().onRefresh(),會調用 SmartLifecycle->start 方法。

11、第十一個擴展點也在 refresh->finishRefresh 方法裏,會調用 publishEvent 然後觸發 ApplicationListener 監聽 ContextRefreshedEvent 的事件。

2.7、最後的三個擴展點-> run 方法

12、第十二個擴展點在 run 方法裏,會調用 listeners.started 方法,SpringApplicationRunListener->started

13、第十三個擴展點也在 run 方法裏,會調用 callRunners 方法,ApplicationRunner 或 CommandLineRunner 的 run 方法

14、第十四個擴展點也在 run 方法裏,會調用 listeners.ready方法,SpringApplicationRunListener->ready

3、結語

第一篇分析 SpringBoot 啓動源碼到這裏就結束了,我們這次的目標是找到 SpringBoot 有哪些地方可以自己進行代碼擴展的,其中不免有些遺漏的,歡迎各位補充。

目前看完還是有很多問題,比如 SpringBootApplication 註解的作用是什麼、Bean 的實例化流程是什麼、以及我們 Web 的 URL 請求是如何到方法上的...等等。

最後,這篇分析完的這些擴展點能幹些什麼呢?

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章