从Spring Boot2.0启动流程看自动装配EnableAu

2021-08-10  本文已影响0人  文艺小程序员

本文将从Spring Boot 2.0的启动流程来解析其中的一些关键内容,本文源码的版本为spring-boot-starter-parent 2.4.6,不熟悉spring源码的建议先熟悉下spring源码,话不多说上代码(女朋友之前吐槽很讨厌上来直接就写源码的博客,后面尽量总结下流程和知识点)。

  public static void main(String[] args) {
        SpringApplication.run(MergePayApplication.class, args);
    }

以上的代码大家可能最熟悉不过了,这也是我们了解Spring Boot原理的入口,小伙伴最好可以跟着代码进行调试来加深印象(其实是检查写的内容是否有错误)。

一、SpringApplication

1.构造函数
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 = WebApplicationType.deduceFromClasspath();
        *this.bootstrapRegistryInitializers = getBootstrapRegistryInitializersFromSpringFactories();
        *setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
        *setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
        this.mainApplicationClass = deduceMainApplicationClass();
    }
private List<BootstrapRegistryInitializer> getBootstrapRegistryInitializersFromSpringFactories() {
        ArrayList<BootstrapRegistryInitializer> initializers = new ArrayList<>();
        getSpringFactoriesInstances(Bootstrapper.class).stream()
                .map((bootstrapper) -> ((BootstrapRegistryInitializer) bootstrapper::initialize))
                .forEach(initializers::add);
        initializers.addAll(getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
        return initializers;
    }

其实有的时候看源码不用看到每一行都想去弄明白,之前我也是这么看代码,但是到最后根本记不住大体的流程,满脑子都是一些无关紧要的细节,就像上面代码的BootstrapRegistryInitializer初看不知道是什么鬼,但是接着看它后面的赋值和调用猜个八九不离十。

    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;
    }

终于到了重点的方法,该方法主要分为两个不步骤,一个是SpringFactoriesLoader.loadFactoryNames,一个是createSpringFactoriesInstances;

2.loadFactoryNames

在loadFactoryNames方法中主要是通过SpringFactoriesLoader#loadSpringFactories方法读取classpath下所有jar中META-INF文件夹下的spring.factories文件,以spring-boot-autoconfigure-2.4.6为例,实例如下第一\前的为实现的接口名称,\后面的内容为需要加载的类,一般我们在Spring Boot进行一些扩展到时候都是使用到EnableAutoConfiguration,大家也可以将这部分的实现理解为我们自定义一个starter包(如 mybatis-spring-boot-starter),我们就需要将自己实现的功能(如MybatisAutoConfiguration)与Spring Boot进行融合从而实现自动配置。

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

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

# Auto Configuration Import Listeners
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener

# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
org.springframework.boot.autoconfigure.condition.OnClassCondition,\
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.LifecycleAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration

在该方法中将META-INF/spring.factories文件转化为urls,最后将spring.factories中的对应关系存储在result中,并在cache中进行缓存,整个流程只需要加载一次这个过程即可。

private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
        //static final Map<ClassLoader, Map<String, List<String>>> cache = new ConcurrentReferenceHashMap<>();
        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;
    }

result中存储的<key,value>结构<key,value>如下,上层的调用方法会根据,并根据BootstrapRegistryInitializer、ApplicationContextInitializer、ApplicationListener(可以提前看看源码的注释呦)三种类型从result中返回不同的三种集合初始化到刚刚说的bootstrapRegistryInitializers、initializers、listeners三个变量中。

3.createSpringFactoriesInstances

这部分比较简单就不贴源码了(要不女朋友又要骂我了),主要就是根据result中筛选出的类利用反射及逆行初始化,然后应用AnnotationAwareOrderComparator根据注解(@Order、@Priority)进行排序。

截至到现在SpringApplication的初始化就介绍完啦,是不是很简单呀,如果还不是特别的了解,可以看下我的小伙伴的提纲博客加深印象,下面我们介绍下启动流程吧。

二、Run启动流程

Spring Boot的启动流程可以浓缩成这一个方法,我们的介绍也是从这个方法进行,大家不要慌,慢慢看应该可以看得懂的。

public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        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);
            stopWatch.stop();
            if (this.logStartupInfo) {
                new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
            }
            listeners.started(context);
            callRunners(context, applicationArguments);
        }
        catch (Throwable ex) {
            handleRunFailure(context, ex, listeners);
            throw new IllegalStateException(ex);
        }

        try {
            listeners.running(context);
        }
        catch (Throwable ex) {
            handleRunFailure(context, ex, null);
            throw new IllegalStateException(ex);
        }
        return context;
    }
1.EventPublishingRunListener

看到这大家应该有点晕了吧,一堆Listener绕来绕去,写到这我自己差点都晕了,下面针对在这些Listener进行以下简单的梳理。


该部分是通过doWithListeners方法将spring.boot.application.starting事件ApplicationStartingEvent通过Spring的SimpleApplicationEventMulticaster发布到Spring的事件传播器中,在multicastEvent方法中getApplicationListeners会筛选与ApplicationStartingEvent匹配的ApplicationListeners,然后通过invokeListener执行onApplicationEvent方法实现starting事件的传播。

    void starting(ConfigurableBootstrapContext bootstrapContext, Class<?> mainApplicationClass) {
        doWithListeners("spring.boot.application.starting", (listener) -> listener.starting(bootstrapContext),
                (step) -> {
                    if (mainApplicationClass != null) {
                        step.tag("mainApplicationClass", mainApplicationClass.getName());
                    }
                });
    }
@Override
    public void starting(ConfigurableBootstrapContext bootstrapContext) {
        //通过initialMulticaster实现与Spring事件发布的融合
        this.initialMulticaster
                .multicastEvent(new ApplicationStartingEvent(bootstrapContext, this.application, this.args));
    }
public void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType) {
        ResolvableType type = eventType != null ? eventType : this.resolveDefaultEventType(event);  
        Executor executor = this.getTaskExecutor();
        //得到与发布的event的一致的listener
        Iterator var5 = this.getApplicationListeners(event, type).iterator();

        while(var5.hasNext()) {
            ApplicationListener<?> listener = (ApplicationListener)var5.next();
            if (executor != null) {
                executor.execute(() -> {
                    this.invokeListener(listener, event);
                });
            } else {
                //listener回调参数为发布的event
                this.invokeListener(listener, event);
            }
        }

    }
2.prepareEnvironment

然后我们接着回到run方法中继续执行第一try的代码中,DefaultApplicationArguments主要是讲args进行封装,args这个参数之前一直没有介绍,其实就是启动的参数入在jar -jar启动项目包的时候制定的一些参数,如--server.port等。

紧接着是prepareEnvironment方法,这个方法看着其实比较头痛,其实要是给每一个方法都讲清楚和明白,其实和写源码的注释就差不多了,这个文章给关键方法的着重讲解下, 其他的就简单说一下用处。

    private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
            DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
        // Create and configure the environment
        ConfigurableEnvironment environment = getOrCreateEnvironment();
        configureEnvironment(environment, applicationArguments.getSourceArgs());
        ConfigurationPropertySources.attach(environment);
        listeners.environmentPrepared(bootstrapContext, environment);
        DefaultPropertiesPropertySource.moveToEnd(environment);
        bindToSpringApplication(environment);
        if (!this.isCustomEnvironment) {
            environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
                    deduceEnvironmentClass());
        }
        ConfigurationPropertySources.attach(environment);
        return environment;
    }

EnvironmentPostProcessor 这多说一句如果在SpringBoot的应用程序中,如果想对配置进行一些修改就可以实现这个接口然后进行自定义的扩展,这部分在我们的开发中也有使用过,如不通过配置在项目启动的时候添加一些后面会使用的参数 。

至此prepareEnvironment方法已经介绍完了是不是很好理解,其实有时候看源码也不用说每一行都看懂在干什么,知道大概的流程就可以了,那回来头我们接着说我们的run方法。

3.prepareContext

这个方法是上下文的准备工作,看参数的个数大家应该就知道这个类比较重要,参数基本上包括了上述的大部分内容。

    private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
            ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
            ApplicationArguments applicationArguments, Banner printedBanner) {
        context.setEnvironment(environment);
        postProcessApplicationContext(context);
        applyInitializers(context);
        listeners.contextPrepared(context);
        bootstrapContext.close(context);
        if (this.logStartupInfo) {
            logStartupInfo(context.getParent() == null);
            logStartupProfileInfo(context);
        }
        // Add boot specific singleton beans
        **ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
        if (printedBanner != null) {
            beanFactory.registerSingleton("springBootBanner", printedBanner);
        }
        if (beanFactory instanceof DefaultListableBeanFactory) {
            ((DefaultListableBeanFactory) beanFactory)
                    .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
        }
        if (this.lazyInitialization) {
            context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
        }**
        // Load the sources
        Set<Object> sources = getAllSources();
        Assert.notEmpty(sources, "Sources must not be empty");
        load(context, sources.toArray(new Object[0]));
        listeners.contextLoaded(context);
    }
4.refreshContext

refreshContext中有两部分工作,一部分是在Runtime中注册一个钩子(可以理解为一个线程),当程序执行完成后会做一个操作,SpringBoot写的钩子大体上是完成一写资源的销毁。另一部分是核心,也是SpringBoot与Spring的连接点,在方法的最后执行了Spring的applicationContext的refresh方法,然后完成Spring上下文中所有Bean的初始化;

    private void refreshContext(ConfigurableApplicationContext context) {
        if (this.registerShutdownHook) {
            try {
                context.registerShutdownHook();
            }
            catch (AccessControlException ex) {
                // Not allowed in some environments.
            }
        }
        refresh((ApplicationContext) context);
    }

截至现在run方法中我们还有2行比较重要的代码就结束了,listeners.started就不多说了,另一个是callRunners,它的主要作用是SpringBoot应用启动完成后进行一个回调,可以实现ApplicationRunner接口也可以实现CommandLineRunner接口,代码如下。

    private void callRunners(ApplicationContext context, ApplicationArguments args) {
        List<Object> runners = new ArrayList<>();
        runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
        runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
        AnnotationAwareOrderComparator.sort(runners);
        for (Object runner : new LinkedHashSet<>(runners)) {
            if (runner instanceof ApplicationRunner) {
                callRunner((ApplicationRunner) runner, args);
            }
            if (runner instanceof CommandLineRunner) {
                callRunner((CommandLineRunner) runner, args);
            }
        }
    }

主要的流程是得到实现两个接口的类然后按照@Order注解的顺序进行排序,他们两个接口的区别就是回调的参数不同,一个是ApplicationArguments,一个是String... args,然后循环执行各自的run方法的进行回调。还有一个小细节就是在执行的过程中如果有异常依旧会调用listeners.failed方法。

三、@EnableAutoConfiguration注解

上一篇下一篇

猜你喜欢

热点阅读