SpringBoot启动过程(1)SpringApplication#run方法之ioc容器的启动

SpringBoot启动过程(1)SpringApplication#run方法之ioc容器的启动

通常我们在开发一个spring boot 项目的时候都会从这段代码开始:

@SpringBootApplication
public class SpringbootLearnApplication {

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

}

在springboot的入口代码中,@SpringBootApplication 标注的类为 Spring Boot 的主配置类,Spring Boot 会运行这个类的 main 方法来启动 Spring Boot 应用。
本文主要就来探讨下Spring Boot 是如何通过run方法进行启动的(基于springboot2.5.2)。

一、SpringApplication初始化

首先进入run方法后可以看到会调用到几个静态方法并实例化一个SpringApplication对象

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

接下来看下SpringApplication的构造函数:

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
        //参数配置
        this.sources = new LinkedHashSet();
        this.bannerMode = Mode.CONSOLE;
        this.logStartupInfo = true;
        this.addCommandLineProperties = true;
        this.addConversionService = true;
        this.headless = true;
        this.registerShutdownHook = true;
        this.additionalProfiles = Collections.emptySet();
        this.isCustomEnvironment = false;
        this.lazyInitialization = false;
        this.applicationContextFactory = ApplicationContextFactory.DEFAULT;
        this.applicationStartup = ApplicationStartup.DEFAULT;
        this.resourceLoader = resourceLoader;
        Assert.notNull(primarySources, "PrimarySources must not be null");
        this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
        //使用react模式还是servlet web模式
        this.webApplicationType = WebApplicationType.deduceFromClasspath();
        //从 Spring 工厂获取 Bootstrap Registry Initializers
        this.bootstrapRegistryInitializers = this.getBootstrapRegistryInitializersFromSpringFactories();
        // 从spring.factories文件中找出key为ApplicationContextInitializer的类并实例化后设置到SpringApplication的initializers属性中。这个过程也就是找出所有的应用程序初始化器
        this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
        // 从spring.factories文件中找出key为ApplicationListener的类并实例化后设置到SpringApplication的listeners属性中。这个过程就是找出所有的应用程序事件监听器
        this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
        // 找出main类,这里是SpringbootLearnApplication类
        this.mainApplicationClass = this.deduceMainApplicationClass();
 }

着重看下getSpringFactoriesInstances方法:

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
        return this.getSpringFactoriesInstances(type, new Class[0]);
}
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
        //获取当前的类加载器
        ClassLoader classLoader = this.getClassLoader();
        //关键是loadFactoryNames方法,从spring.factories文件找对应的key
        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<?> factoryType, @Nullable ClassLoader classLoader) {
        ClassLoader classLoaderToUse = classLoader;
        if (classLoader == null) {
            classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
        }
        String factoryTypeName = factoryType.getName();
        return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
        //首先尝试从本地缓存中获取集合,有则返回
        Map<String, List<String>> result = (Map)cache.get(classLoader);
        if (result != null) {
            return result;
        } else {
            HashMap result = new HashMap();
            try {
                //从类加载路径(jar文件)中获取全部的spring.factories文件路径;
                Enumeration urls = classLoader.getResources("META-INF/spring.factories");
                //遍历
                while(urls.hasMoreElements()) {
                    URL url = (URL)urls.nextElement();
                    UrlResource resource = new UrlResource(url);
                    //加载properties对象
                    Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                    Iterator var6 = properties.entrySet().iterator();
                    //组装key和value
                    while(var6.hasNext()) {
                        Entry<?, ?> entry = (Entry)var6.next();
                        String factoryTypeName = ((String)entry.getKey()).trim();
                        String[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                        String[] var10 = factoryImplementationNames;
                        int var11 = factoryImplementationNames.length;
                        //key 和value 存入result中
                        for(int var12 = 0; var12 < var11; ++var12) {
                            String factoryImplementationName = var10[var12];
                            ((List)result.computeIfAbsent(factoryTypeName, (key) -> {
                                return new ArrayList();
                            })).add(factoryImplementationName.trim());
                        }
                    }
                }
                result.replaceAll((factoryType, implementations) -> {
                    return (List)implementations.stream().distinct().collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
                });
                cache.put(classLoader, result);
                return result;
            } catch (IOException var14) {
                throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var14);
            }
        }
    }

可以看到SpringApplication初始化做了几个动作,一是初始化默认参数,二是从spring.factories文件中找出对应的k v 资源赋值给initializers和listeners

二、SpringApplication#run方法

实例化SpringApplication方法后会进入到run方法:

public ConfigurableApplicationContext run(String... args) {
        // 可用于准确地测量运行时间,想了解的可以百度下
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        //初始化启动的上下文
        DefaultBootstrapContext bootstrapContext = this.createBootstrapContext();
        ConfigurableApplicationContext context = null;
        this.configureHeadlessProperty();
        //创建了应用的监听器SpringApplicationRunListeners并开始监听
        // 获取SpringApplicationRunListeners,内部只有一个EventPublishingRunListener
        SpringApplicationRunListeners listeners = this.getRunListeners(args);
        listeners.starting(bootstrapContext, this.mainApplicationClass);
        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            //加载SpringBoot配置环境(ConfigurableEnvironment),如果是通过web容器发布,会加载StandardEnvironment,其最终也是继承了ConfigurableEnvironment,
            //Environment最终都实现了PropertyResolver接口,我们平时通过environment对象获取配置文件中指定Key对应的value方法时,就是调用了propertyResolver接口的getProperty方法
            //配置环境(Environment)加入到监听器对象中(SpringApplicationRunListeners)
            ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments);
            this.configureIgnoreBeanInfo(environment);
            Banner printedBanner = this.printBanner(environment);
            context = this.createApplicationContext();
            context.setApplicationStartup(this.applicationStartup);
            //prepareContext方法将listeners、environment、applicationArguments、banner等重要组件与上下文对象关联
            this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
            //将是实现spring-boot-starter-*(mybatis、redis等)自动化配置的关键,包括spring.factories的加载,bean的实例化等核心工作
            this.refreshContext(context);
            this.afterRefresh(context, applicationArguments);
            stopWatch.stop();
            if (this.logStartupInfo) {
                (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
            }
            //启动事件,和spring ioc启动事件不同,可以看下
            listeners.started(context);
            this.callRunners(context, applicationArguments);
        } catch (Throwable var10) {
            this.handleRunFailure(context, var10, listeners);
            throw new IllegalStateException(var10);
        }
        try {
            listeners.running(context);
            return context;
        } catch (Throwable var9) {
            this.handleRunFailure(context, var9, (SpringApplicationRunListeners)null);
            throw new IllegalStateException(var9);
        }
    }

其他的支线暂时不说了,后续有时间再开几篇博文讲一下,接下去继续跟下refreshContext():

private void refreshContext(ConfigurableApplicationContext context) {
        if (this.registerShutdownHook) {
            shutdownHook.registerApplicationContext(context);
        }
        this.refresh(context);
}
protected void refresh(ConfigurableApplicationContext applicationContext) {
        applicationContext.refresh();
}
/**
* spring ioc 容器 生命周期
*/
public void refresh() throws BeansException, IllegalStateException {
        synchronized(this.startupShutdownMonitor) {
            StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
            this.prepareRefresh();
            ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
            this.prepareBeanFactory(beanFactory);
            try {
                this.postProcessBeanFactory(beanFactory);
                StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
                this.invokeBeanFactoryPostProcessors(beanFactory);
                this.registerBeanPostProcessors(beanFactory);
                beanPostProcess.end();
                this.initMessageSource();
                this.initApplicationEventMulticaster();
                this.onRefresh();
                this.registerListeners();
                this.finishBeanFactoryInitialization(beanFactory);
                this.finishRefresh();
            } catch (BeansException var10) {
                if (this.logger.isWarnEnabled()) {
                    this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var10);
                }
                this.destroyBeans();
                this.cancelRefresh(var10);
                throw var10;
            } finally {
                this.resetCommonCaches();
                contextRefresh.end();
            }
        }
    }

看到没有,是不是看到很熟悉的spring ioc 容器启动过程了?前面加载的listeners和initializers都会在ioc容器启动的时候执行。
今天先分析到这里,后面还会有SpringApplication#run方法的第二篇之tomcat容器的启动过程。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
SpringBoot启动过程可以通过以下几个步骤来描述: 1. 发布启动事件:在SpringBoot启动时,会发布一个启动事件(EventPublishingRunListener#starting())。这个事件可以被监听器监听到,并触发相关的操作。 2. 加载异常报告:在SpringBoot的核心方法run中,会加载所有的SpringBootExceptionReporter接口的实现类。这个接口用于支持对SpringApplication启动错误的自定义报告。其中,SpringBootExceptionReporter接口包含了报告启动失败的方法。 3. 初始化IOC容器SpringBoot会在启动过程中初始化IOC容器。在这个阶段,Spring会扫描并注册所有带有@Component注解的Bean,并进行依赖注入和初始化操作。 4. 执行自定义初始化任务:在SpringBoot中,我们可以通过实现Runner接口或使用spring.factories配置文件的方式来定义自定义的初始化任务。这些任务会在IOC容器初始化完成之后执行。 需要注意的是,异常的发生可能会打乱上述步骤的顺序。在某些情况下,异常可能在IOC容器初始化之前就发生了。为了应对这种情况,SpringBoot提供了通过spring.factories配置文件的方式来定义初始化任务。通过这种方式,我们可以确保在异常发生之前执行必要的初始化操作。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [Spring Boot启动流程](https://blog.csdn.net/quanzhan_King/article/details/130920792)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值