【从入门到放弃-SpringBoot】SpringBoot源码分析-启动

简介: 前言 上一篇我们一起简单了解了【从入门到放弃-MySQL】数据库连接过程分析-客户端,写完之后通读一遍,感觉分析的不是很透彻。有很多地方都没搞通,因此决定从Springboot源码开始从头研究下。 main 入口分析 package com.

前言

上一篇我们一起简单了解了【从入门到放弃-MySQL】数据库连接过程分析-客户端,写完之后通读一遍,感觉分析的不是很透彻。有很多地方都没搞通,因此决定从Springboot源码开始从头研究下。

main 入口分析

package com.springboot.demo;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.scheduling.annotation.EnableScheduling;

@MapperScan("com.springboot.demo.repository.dao")
@SpringBootApplication
@EnableScheduling
public class DemoApplication {

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

}
  • 一个简单的springboot项目启动文件中 一行代码就能搞定。
    我们从这一行代码开始看起。

SpringApplication::SpringApplication

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();
    setInitializers((Collection) getSpringFactoriesInstances(
            ApplicationContextInitializer.class));
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    this.mainApplicationClass = deduceMainApplicationClass();
}
  • 主要是初始化成员变量,比如参数列表,应用类型、监听器等。

getSpringFactoriesInstances

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

这个方法主要是根据type类型获取配置中默认的类列表,并进行初始化。

  • loadFactoryNames:从META-INF/spring.factories中获取type对应的配置类名称列表。
  • createSpringFactoriesInstances:校验获取到的类是否是parameterTypes类的子类,并将获取到的类通过反射机制实例化,

SpringApplication::run

/**
 * Run the Spring application, creating and refreshing a new
 * {@link ApplicationContext}.
 * @param args the application arguments (usually passed from a Java main method)
 * @return a running {@link ApplicationContext}
 */
public ConfigurableApplicationContext run(String... args) {
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    ConfigurableApplicationContext context = null;
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    configureHeadlessProperty();
    SpringApplicationRunListeners listeners = getRunListeners(args);
    listeners.starting();
    try {
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                args);
        ConfigurableEnvironment environment = prepareEnvironment(listeners,
                applicationArguments);
        configureIgnoreBeanInfo(environment);
        Banner printedBanner = printBanner(environment);
        context = createApplicationContext();
        exceptionReporters = getSpringFactoriesInstances(
                SpringBootExceptionReporter.class,
                new Class[] { ConfigurableApplicationContext.class }, context);
        prepareContext(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, exceptionReporters, listeners);
        throw new IllegalStateException(ex);
    }

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

StopWatch

是一个计时器工具类,这里先记录了项目的启动时间

变量初始化

各种变量初始化,用法在后面具体分析

configureHeadlessProperty

设置系统java.awt.headless属性,为true的话是告知系统不要指望显示器、鼠标、键盘等可以正常运行,这是一个服务端程序,用到这些外设的时候需要靠自己模拟

getRunListeners

获取SpringApplicationRunListeners各项监听器并实例化,这些监听器配置在spring.factories资源文件中。

prepareEnvironment

配置环境,如配置文件、系统变量等并通过监听器广播环境变量准备完毕事件。

printBanner

打印启动显示的banner。

createApplicationContext

根据webApplicationType初始化spring上下文

exceptionReporters

初始化配置的异常报告类

prepareContext

private void prepareContext(ConfigurableApplicationContext context,
        ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
        ApplicationArguments applicationArguments, Banner printedBanner) {
    context.setEnvironment(environment);
    postProcessApplicationContext(context);
    applyInitializers(context);
    listeners.contextPrepared(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);
    }
    // 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);
}
  • setEnvironment:设置上下文环境
  • postProcessApplicationContext:上下文后置处理,默认没做什么事。
  • applyInitializers:调用之前实例化的几个ApplicationContextInitializer类的initialize方法
  • contextPrepared:向listeners发送上下文已准备完毕的通知。
  • load:BeanDefinitionLoader可以加载各种bean,比如注解、XML、package等多种类型的bean,在后面利用BeanDefinitionLoader将这些beans都加载进上下文中。
  • contextLoaded:向listeners发送上下文已加载完毕的通知。

refreshContext

@Override
public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        // 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);

            // Invoke factory processors registered as beans in the context.
            invokeBeanFactoryPostProcessors(beanFactory);

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

            // 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();
        }
    }
}
  • prepareRefresh:一些准备工作,比如设置启动时间、初始化资源、必须属性校验等。
  • obtainFreshBeanFactory:通知子类刷新内置的beanFactory
  • prepareBeanFactory:对容器的beanFactory做一些准备工作,比如设置classloader、设置&取消设置一些类的bean
  • invokeBeanFactoryPostProcessors:

    • 主要看invokeBeanDefinitionRegistryPostProcessors方法,会调用ConfigurationClassPostProcessor::processConfigBeanDefinitions。
    public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
        List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
        String[] candidateNames = registry.getBeanDefinitionNames();
    
        for (String beanName : candidateNames) {
            BeanDefinition beanDef = registry.getBeanDefinition(beanName);
            if (ConfigurationClassUtils.isFullConfigurationClass(beanDef) ||
                    ConfigurationClassUtils.isLiteConfigurationClass(beanDef)) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
                }
            }
            else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
                configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
            }
        }
    
        // Return immediately if no @Configuration classes were found
        if (configCandidates.isEmpty()) {
            return;
        }
    
        // Sort by previously determined @Order value, if applicable
        configCandidates.sort((bd1, bd2) -> {
            int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
            int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
            return Integer.compare(i1, i2);
        });
    
        // Detect any custom bean name generation strategy supplied through the enclosing application context
        SingletonBeanRegistry sbr = null;
        if (registry instanceof SingletonBeanRegistry) {
            sbr = (SingletonBeanRegistry) registry;
            if (!this.localBeanNameGeneratorSet) {
                BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(CONFIGURATION_BEAN_NAME_GENERATOR);
                if (generator != null) {
                    this.componentScanBeanNameGenerator = generator;
                    this.importBeanNameGenerator = generator;
                }
            }
        }
    
        if (this.environment == null) {
            this.environment = new StandardEnvironment();
        }
    
        // Parse each @Configuration class
        ConfigurationClassParser parser = new ConfigurationClassParser(
                this.metadataReaderFactory, this.problemReporter, this.environment,
                this.resourceLoader, this.componentScanBeanNameGenerator, registry);
    
        Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
        Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
        do {
            parser.parse(candidates);
            parser.validate();
    
            Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
            configClasses.removeAll(alreadyParsed);
    
            // Read the model and create bean definitions based on its content
            if (this.reader == null) {
                this.reader = new ConfigurationClassBeanDefinitionReader(
                        registry, this.sourceExtractor, this.resourceLoader, this.environment,
                        this.importBeanNameGenerator, parser.getImportRegistry());
            }
            this.reader.loadBeanDefinitions(configClasses);
            alreadyParsed.addAll(configClasses);
    
            candidates.clear();
            if (registry.getBeanDefinitionCount() > candidateNames.length) {
                String[] newCandidateNames = registry.getBeanDefinitionNames();
                Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));
                Set<String> alreadyParsedClasses = new HashSet<>();
                for (ConfigurationClass configurationClass : alreadyParsed) {
                    alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
                }
                for (String candidateName : newCandidateNames) {
                    if (!oldCandidateNames.contains(candidateName)) {
                        BeanDefinition bd = registry.getBeanDefinition(candidateName);
                        if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
                                !alreadyParsedClasses.contains(bd.getBeanClassName())) {
                            candidates.add(new BeanDefinitionHolder(bd, candidateName));
                        }
                    }
                }
                candidateNames = newCandidateNames;
            }
        }
        while (!candidates.isEmpty());
    
        // Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes
        if (sbr != null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
            sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
        }
    
        if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {
            // Clear cache in externally provided MetadataReaderFactory; this is a no-op
            // for a shared cache since it'll be cleared by the ApplicationContext.
            ((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();
        }
    }

    ConfigurationClassParser:处理@Configuration/@Component等注解,扫描、注册包下的类

    ConfigurationClassBeanDefinitionReader:处理@Import/@ImportResource/@Bean等注解。

    将所有扫描到的bean都装载至registry

    • invokeBeanDefinitionRegistryPostProcessors:执行invokeBeanDefinitionRegistryPostProcessors回调
    • invokeBeanFactoryPostProcessors:执行invokeBeanFactoryPostProcessors回调
  • registerBeanPostProcessors:注册拦截创建bean的bean处理器
  • initMessageSource:初始化消息源
  • initApplicationEventMulticaster:初始化事件广播
  • onRefresh:对一些特殊子类上下文中初始化一些特殊的bean,比如在ServletWebServerApplicationContext中就做了createWebServer的操作
  • registerListeners:注册bean监听器
  • finishBeanFactoryInitialization:实例化所有单例bean,比如我们之前分析的【从入门到放弃-MySQL】数据库连接过程分析-客户端dataSource就是在这一步实例化的。

finishRefresh

结束上下文更新,并发布事件。

callRunners

如果有ApplicationRunner或者CommandLineRunner类型的bean,则触发run函数,启动任务。

总结

至此,springboot就已经启动完毕。概述下主要的启动过程就是

  • 初始化环境
  • 初始化默认配置
  • 初始化各类监听器、事件
  • 创建上下文
  • 在上下文中添加默认的bean
  • 扫描文件、注解等各种类型的bean添加在上下文中
  • 实例化各个bean
  • 启动完毕

更多文章见:https://nc2era.com

目录
相关文章
|
6月前
|
安全 Java Spring
SpringBoot2 | SpringBoot监听器源码分析 | 自定义ApplicationListener(六)
SpringBoot2 | SpringBoot监听器源码分析 | 自定义ApplicationListener(六)
87 0
|
6月前
|
设计模式 Java 容器
SpringBoot2 | SpringBoot启动流程源码分析(二)
SpringBoot2 | SpringBoot启动流程源码分析(二)
77 0
|
5月前
|
Java 关系型数据库 MySQL
Mybatis入门之在基于Springboot的框架下拿到MySQL中数据
Mybatis入门之在基于Springboot的框架下拿到MySQL中数据
48 4
|
6月前
|
Java Nacos 开发者
Java从入门到精通:4.2.1学习新技术与框架——以Spring Boot和Spring Cloud Alibaba为例
Java从入门到精通:4.2.1学习新技术与框架——以Spring Boot和Spring Cloud Alibaba为例
QGS
|
SQL 弹性计算 Java
手搭手入门Spring boot+Mybatis+达梦数据库(国产数据库)
手搭手入门Spring boot+Mybatis+达梦数据库(国产数据库)
QGS
639 0
|
Java 应用服务中间件 Maven
Spring Boot入门(一) 之 第一个Spring Boot程序
Spring Boot入门(一) 之 第一个Spring Boot程序
102 0
|
6月前
|
前端开发 Java Spring
SpringBoot2 | SpringBoot Environment源码分析(四)
SpringBoot2 | SpringBoot Environment源码分析(四)
58 0
|
6月前
|
Java 中间件 容器
SpringBoot2 | SpringBoot启动流程源码分析(一)
SpringBoot2 | SpringBoot启动流程源码分析(一)
82 0
|
6月前
|
Dubbo Java 应用服务中间件
分布式应用简单入门及SpringBoot整合Dubbo+Zookeeper
分布式应用简单入门及SpringBoot整合Dubbo+Zookeeper
173 1
|
6月前
|
XML Java 应用服务中间件
SpringBoot配置外部Tomcat项目启动流程源码分析(长文)
SpringBoot配置外部Tomcat项目启动流程源码分析(长文)
510 0