SpringBoot3.0启动流程研究

目前后端最常用的框架就是springboot,今天就稍微来研究一下springboot的启动流程。

要启动一个springboot项目,最常用的就是以下的代码。

java 复制代码
@SpringBootApplication
public class SpringBootDemo {
 public static void main(String[] args) {
  SpringApplication.run(SpringBootDemo.class, args);
 }
}

几乎所有的springboot项目都是这样启动的吧。这里最关键的地方就是两点,@SpringBootApplication注解和SpringApplication.run()

@SpringBootApplication原理

网上已经有了很多的文章介绍它,@SpringbootApplication本质上是很多个注解的组合。

java 复制代码
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)

这里实际上最重要的只有三个注解,分别是@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan

@SpringBootConfiguration

@SpringBootConfiguration本身也是由几个注解构成的。

java 复制代码
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration

除去Java的元注解,实际上最重要的就是@Configuration注解了。这个注解基本上大家在每个项目应该都有用到过。springboot可以不写xml配置,靠的就是@Configuration注解。

它的作用就是声明当前类为配置类,并且可以将类的实例方法中加上了@Bean注解的返回对象也一并加入到IoC容器中。

java 复制代码
@Configuration
public class OpenApiConfiguration {

    @Bean
    public OpenAPI springOpenAPI() {
        return new OpenAPI()
                .info(new Info().title("springboot")
                        .description("springboot")
                        .version("v1.0.0")
                        .license(new License().name("Apache 2.0").url("http://springdoc.org")))
                .components(new Components()
                        .addSecuritySchemes("Authorization", new SecurityScheme()
                                .type(SecurityScheme.Type.APIKEY)
                                .in(SecurityScheme.In.HEADER)
                                .name("Authorization")))
                // AddSecurityItem section applies created scheme globally
                .addSecurityItem(new SecurityRequirement().addList("Authorization"));
    }
}

以上的代码就将OpenApiConfiguration作为配置类,springboot会调用springOpenAPI()函数,将OpenAPI()对象注入进IoC容器。

@ComponentScan

@ComponentScan的作用就是扫描当前的包,以及子包,将所有的带有springboot相关的注解(@Component@Service@Controller等)的类注入到IoC容器中,等待之后使用。

如果@ComponentScan不指定basePackages,那么默认扫描当前包,所以一般都是把SpringBootApplication.class放在最外层,以便扫描所有的类。

@EnableAutoConfiguration

@EnableAutoConfiguration也是由几个注解一起实现的。

java 复制代码
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    Class<?>[] exclude() default {};

    String[] excludeName() default {};
}

其中最为重要的就是@Import这个注解,上面的@AutoConfigurationPackage进入之后也是一个@Import注解,只不过导入了不同的类。

最关键的就是 @Import(AutoConfigurationImportSelector.class)

AutoConfigurationImportSelector实现了ImportSelector接口,里面有一个selectImports函数。

java 复制代码
public interface ImportSelector {
    String[] selectImports(AnnotationMetadata importingClassMetadata);

    @Nullable
    default Predicate<String> getExclusionFilter() {
        return null;
    }
}

AutoConfigurationImportSelector里的实现是这样的

java 复制代码
 public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        } else {
            AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
            return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
        }
    }

其中最重要的就是this.getAutoConfigurationEntry(annotationMetadata)这行

java 复制代码
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        } else {
            AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
            List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
            configurations = this.<String>removeDuplicates(configurations);
            Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
            this.checkExcludedClasses(configurations, exclusions);
            configurations.removeAll(exclusions);
            configurations = this.getConfigurationClassFilter().filter(configurations);
            this.fireAutoConfigurationImportEvents(configurations, exclusions);
            return new AutoConfigurationEntry(configurations, exclusions);
        }
    }

其中有一个行List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);

继续跟踪可以看到

java 复制代码
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        List<String> configurations = ImportCandidates.load(AutoConfiguration.class, this.getBeanClassLoader()).getCandidates();
        Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you are using a custom packaging, make sure that file is correct.");
        return configurations;
    }

这里就来到了springboot最核心的机制,读取classpath下的META-INF/spring.factories文件,然后将key为org.springframework.boot.autoconfigure.EnableAutoConfiguration对应的类通过反射实例化,然后注入到IoC容器中。

比如一下是安全框架shiro-spring-boot-starter的spring.factories文件

java 复制代码
org.springframework.boot.autoconfigure.EnableAutoConfiguration = \
  org.apache.shiro.spring.config.web.autoconfigure.ShiroWebAutoConfiguration,\
  org.apache.shiro.spring.config.web.autoconfigure.ShiroWebFilterConfiguration,\
  org.apache.shiro.spring.config.web.autoconfigure.ShiroWebMvcAutoConfiguration,\
  org.apache.shiro.spring.boot.autoconfigure.ShiroBeanAutoConfiguration,\
  org.apache.shiro.spring.boot.autoconfigure.ShiroAutoConfiguration,\
  org.apache.shiro.spring.boot.autoconfigure.ShiroAnnotationProcessorAutoConfiguration

org.springframework.boot.diagnostics.FailureAnalyzer = \
  org.apache.shiro.spring.boot.autoconfigure.ShiroNoRealmConfiguredFailureAnalyzer

org.springframework.boot.env.EnvironmentPostProcessor=\
  org.apache.shiro.spring.config.web.autoconfigure.ShiroEnvironmentPostProcessor

接下来我们就可以分析一下run方法了。

SpringApplication.run()

run()方法的源码如下

java 复制代码
public ConfigurableApplicationContext run(String... args) {
        if (this.registerShutdownHook) {
            SpringApplication.shutdownHook.enableShutdowHookAddition();
        }
        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);
            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) {
            if (ex instanceof AbandonedRunException) {
                throw ex;
            }
            handleRunFailure(context, ex, listeners);
            throw new IllegalStateException(ex);
        }
        try {
            if (context.isRunning()) {
                Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
                listeners.ready(context, timeTakenToReady);
            }
        }
        catch (Throwable ex) {
            if (ex instanceof AbandonedRunException) {
                throw ex;
            }
            handleRunFailure(context, ex, null);
            throw new IllegalStateException(ex);
        }
        return context;
    }

首先是调用了createBootstrapContext初始化了DefaultBootstrapContext对象。

随后设置headless模式,也就是电脑没有显示屏,没有键盘鼠标的情况。

java 复制代码
private void configureHeadlessProperty() {
        System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS,
                System.getProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, Boolean.toString(this.headless)));
    }

当系统属性中java.awt.headless为true的时候就为headless模式。

java 复制代码
System.setProperty("java.awt.headless","true")

接下来就是获取监听器并启动

java 复制代码
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting(bootstrapContext, this.mainApplicationClass);

getRunListeners(args)中,getSpringFactoriesInstances会获取所有的监听器,调用SpringFactoriesLoader.forDefaultResourceLocation(getClassLoader()).load(type, argumentResolver),从"META-INF/spring.factories"文件里找所有的listener,最后返回SpringApplicationRunListeners实例。

在这里springboot会提供一个EventPublishingRunListener ,它实现了SpringApplicationRunListener 接口,springboot会使用这个类发布一个ApplicationContextInitializedEvent 事件,通过定义ApplicationListener就可以监听这个事件。

之后调用listeners.starting 方法,调用doWithListeners方法

java 复制代码
void starting(ConfigurableBootstrapContext bootstrapContext, Class<?> mainApplicationClass) {
        doWithListeners("spring.boot.application.starting", (listener) -> listener.starting(bootstrapContext),
                (step) -> {
                    if (mainApplicationClass != null) {
                        step.tag("mainApplicationClass", mainApplicationClass.getName());
                    }
                });
    }

进入listener.starting(bootstrapContext)方法中,这里是一个接口,实际执行的就是上文提到的EventPublishingRunListener对应的方法。

java 复制代码
@Override
    public void starting(ConfigurableBootstrapContext bootstrapContext) {
        multicastInitialEvent(new ApplicationStartingEvent(bootstrapContext, this.application, this.args));
    }

再进入multicastInitialEvent方法

java 复制代码
private void multicastInitialEvent(ApplicationEvent event) {
        refreshApplicationListeners();
        this.initialMulticaster.multicastEvent(event);
    }

这里的refreshApplicationListeners()就会循环所有上面加载到的listeners,调用this.initialMulticaster::addApplicationListener将listener添加到applicationListeners集合中。

java 复制代码
private void refreshApplicationListeners() {
        this.application.getListeners().forEach(this.initialMulticaster::addApplicationListener);
    }

完成之后进入下一步this.initialMulticaster.multicastEvent

这一步就是去执行每一个listener具体的函数,调用invokeListener方法。最后调用到每个listener的onApplicationEvent方法。具体到每个listener的实现,比如LoggingApplicationListener

java 复制代码
@Override
    public void onApplicationEvent(ApplicationEvent event) {
        if (event instanceof ApplicationStartingEvent startingEvent) {
            onApplicationStartingEvent(startingEvent);
        }
        else if (event instanceof ApplicationEnvironmentPreparedEvent environmentPreparedEvent) {
            onApplicationEnvironmentPreparedEvent(environmentPreparedEvent);
        }
        else if (event instanceof ApplicationPreparedEvent preparedEvent) {
            onApplicationPreparedEvent(preparedEvent);
        }
        else if (event instanceof ContextClosedEvent) {
            onContextClosedEvent((ContextClosedEvent) event);
        }
        else if (event instanceof ApplicationFailedEvent) {
            onApplicationFailedEvent();
        }
    }

当所有的listener执行完毕,最后将会回到doWithListeners。最后回到run(args)方法。

接着往下走就进入了try里面

java 复制代码
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);

获取命令行参数,这一步将会对jar启动最后的程序命令行参数进行封装。

然后准备环境变量,读取配置信息(bootstrap.yml,application.yml)

java 复制代码
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);

这一步读取配置信息是通过EventPublishingRunListener,发布一个ApplicationEnvironmentPreparedEvent事件,将会有一个EnvironmentPostProcessorApplicationListener来对事件进行处理,并进行application.yml的解析,并添加到Environment中。

java 复制代码
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
			DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
		// 根据不同的类型创建不同实现的Environment对象,读取环境变量
		ConfigurableEnvironment environment = getOrCreateEnvironment();
		// 将命令行参数设置到环境变量中
		configureEnvironment(environment, applicationArguments.getSourceArgs());
	
		ConfigurationPropertySources.attach(environment);
		// 发布ApplicationEnvironmentPreparedEvent
		listeners.environmentPrepared(bootstrapContext, environment);
		DefaultPropertiesPropertySource.moveToEnd(environment);
		Assert.state(!environment.containsProperty("spring.main.environment-prefix"),
				"Environment prefix cannot be set via properties.");
		// 将所有spring.main 开头的配置信息绑定到SpringApplication中
		bindToSpringApplication(environment);
		if (!this.isCustomEnvironment) {
			EnvironmentConverter environmentConverter = new EnvironmentConverter(getClassLoader());
			environment = environmentConverter.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
		}
		// 更新PropertySources配置
		ConfigurationPropertySources.attach(environment);
		return environment;
	}

下一步是打印SpringBoot的Banner,springboot启动时的字符画

然后创建上下文容器

java 复制代码
	context = createApplicationContext();
	context.setApplicationStartup(this.applicationStartup);
	prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);

进入prepareContext

java 复制代码
private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
			ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments, Banner printedBanner) {
		context.setEnvironment(environment);
		postProcessApplicationContext(context);
		addAotGeneratedInitializerIfNecessary(this.initializers);
		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 AbstractAutowireCapableBeanFactory autowireCapableBeanFactory) {
			autowireCapableBeanFactory.setAllowCircularReferences(this.allowCircularReferences);
			if (beanFactory instanceof DefaultListableBeanFactory listableBeanFactory) {
				listableBeanFactory.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
			}
		}
		if (this.lazyInitialization) {
			context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
		}
		context.addBeanFactoryPostProcessor(new PropertySourceOrderingBeanFactoryPostProcessor(context));
		if (!AotDetector.useGeneratedArtifacts()) {
			// 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);
	}

首先获取所有的ApplicationContextInitializer,调用initialize方法。然后初始化BeanFactory,检查是否有相同的bean,是否开启循环引用,将启动类注入到Spring容器中。

然后进入刷新spring容器方法

java 复制代码
refreshContext(context);

这里将会对上下文做一些初始化,调用BeanFactoryPostProcessors,注册BeanPostProcessors。然后发布ContextRefreshedEvent事件。

java 复制代码
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();
            }

        }
    }

之后就是打印启动时间,发布ApplicationStartedEvent事件。

最后获取Spring容器中ApplicationRunner/CommandLineRunner类型的Bean,并执行run方法。

java 复制代码
callRunners(context, applicationArguments);

至此springboot基本启动完成,发布ApplicationReadyEvent事件,正式开始运行。

相关推荐
一只蒟蒻ovo6 分钟前
操作系统导论——第26章 并发:介绍
java·开发语言
老华带你飞15 分钟前
音乐网站|基于SprinBoot+vue的音乐网站(源码+数据库+文档)
java·前端·数据库·vue.js·论文·毕设·音乐网站
熊文豪28 分钟前
Java+Selenium+快代理实现高效爬虫
java·爬虫·selenium·隧道代理·快代理
purrrew1 小时前
【Java ee 初阶】多线程(8)
java·java-ee
TPBoreas4 小时前
Jenkins 改完端口号启动不起来了
java·开发语言
金斗潼关4 小时前
SpringCloud GateWay网关
java·spring cloud·gateway
秋名RG5 小时前
深入解析建造者模式(Builder Pattern)——以Java实现复杂对象构建的艺术
java·开发语言·建造者模式
eternal__day5 小时前
Spring Boot 实现验证码生成与校验:从零开始构建安全登录系统
java·spring boot·后端·安全·java-ee·学习方法
陈大爷(有低保)6 小时前
swagger3融入springboot
java
宛如昨晚没早睡9 小时前
SpringBoot的自动配置和起步依赖原理
spring boot