springboot 2.4.0 启动源码分析

SpringBoot启动的基本配置

SpringBoot启动的配置主要有以下两个部分

添加依赖

最基本的springboot依赖只需要添加一个springboot启动器的依赖即可,如果是web服务则添加web的启动器

<?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>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.0</version>
        <relativePath/>
    </parent>

    <groupId>org.example</groupId>
    <artifactId>springboot.demo</artifactId>
    <version>1.0-SNAPSHOT</version>
    <dependencies>
<!--    如果不需要web服务则直接添加spring-boot-starter依赖即可不过我们一般都是需要web容器,所以直接添加spring-boot-starter-web依赖-->
<!--        <dependency>-->
<!--            <groupId>org.springframework.boot</groupId>-->
<!--            <artifactId>spring-boot-starter</artifactId>-->
<!--        </dependency>-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>
    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>
</project>

添加启动类注解

springboot启动的时候需要在启动类上面加上@SpringBootAppliaction

@SpringBootApplication
public class ApplicationBoot {
    public static void main(String[] args) throws Exception {
        SpringApplication.run(ApplicationBoot.class, args);
    }
}

SpringBoot启动类注解解析

@SpringBootApplication 注解

// @SpringBootAppliaction注解是有三个注解组成的
@Target(ElementType.TYPE)  
// @Target 指定了注解可以应用的目标类型。在这里,ElementType.TYPE 表示该注解可以应用于类、接口、枚举或注解类型。
@Retention(RetentionPolicy.RUNTIME)
// @Retention 指定了注解的生命周期。在这里表示这个注解在运行时保留,那么在运行时可以通过反射获取这个注解信息
@Documented
//@Documented 用于指示被该注解修饰的元素将被包含在文档中,表示javadoc工具生成文档时会包含该注解的一些信息
@Inherited    
// @Inherited 用于指示注解是否被子类继承。着这里表示如果父类加了这个注解,那么子类也自动加上这个注解

// ---以上的元注解信息都是一些基本的通用设置信息,不包含spring的业务含义。

// SpringBootApplication注解的核心就是以下三个注解,当我们类上面标注@SpringBootApplication后,可以通过反射获取他上面的
// @SpringBootConfiguration,@EnableAutoConfiguration,@ComponentScan这三个注解最终获取这三个注解里面的信息
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication 
  
  

@SpringBootConfiguration

/**
说明:@SpringBootConfiguration没有做其他操作,仅加上了@Configuration,意思就是@SpringBootConfiguration具备和@Configuration一样的功能,在spring运行过程中会把这个注解当成配置类来解析
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration  

@EnableAutoConfiguration

/**
@EnableAutoConfiguration做了两件事
1、整合了@AutoConfigurationPackage注解,参考下面解释
2、整合了@Import(AutoConfigurationImportSelector.class),这里会导入AutoConfigurationImportSelector这个类
	AutoConfigurationImportSelector类实现了很多接口因此会在spring容器刷新的过程中执行各种操作,其中一个核心的在容器刷新的过	程中使用SpringFactoriesLoader加载所有的META-INF/spring.factories文件,然后将文件中的EnableAutoConfiguration类加载    	 进来,而这些EnableAutoConfiguration类就是SpringBoot融合各种组件的配置类,只要在maven中添加了其依赖,spring就会结合这个   	 配置类来加载对应的配置,将组件的各种bean对象加载到容器中,我们就可以直接使用对应的功能。
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
  
/**
 * @AutoConfigurationPackage的作用是自动配置Package扫描,就是当我们没有指定包扫描路径的时候,会调用
 * AutoConfigurationPackages.Registrar.class里面实现的逻辑进行包扫描,默认是按照启动类的类路径进行扫描 
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {  

@ComponentScan

/**
@ComponentScan注解主要是声明包扫描路径,同时提供包扫描的规则
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {

SpringBoot启动原理

SpringBoot服务的启动主要分为两个大的步骤

1、构建SpringApplication对象

2、执行SpringApplication对象的run方法

说明:表面上看是调用SpringApplication的静态方法run,实际上静态方法中先构建了SpringApplication对象,然后调用run方法

第一步:SpringApplication.run(ApplicationBoot.class, args);
第二步:run(new Class<?>[] { primarySource }, args);
第三步:new SpringApplication(primarySources).run(args);

构建SpringApplication对象

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
   //初始化resourceLoader,这里传的是null,后续在使用的时候判断是null就会调用默认的DefaultResourceLoader,直到容器启		 //动最后也没有将resourceLoader赋值,也许是留作扩展
   this.resourceLoader = resourceLoader;
   // 断言,传入的主类不能为空
   Assert.notNull(primarySources, "PrimarySources must not be null");
   // 将主类封装到属性中,方便后续使用
   this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
   // 通过类路径推断出web的容器类型,原理就是使用类加载器尝试加载容器的类型,加载到哪个就认为需要启动哪个容器
   // 比如加载到javax.servlet.Servlet,org.springframework.web.context.ConfigurableWebApplicationContext这两个				类那么设置为Servlet容器类型;
   // 如果加载到REACTIVE相关的类,那么就设置为REACTIVE容器类型;即响应式容器
   // 如果都加载不到,那么容器类型就是none;没有容器
   this.webApplicationType = WebApplicationType.deduceFromClasspath();
   // SpringFactoriesLoader加载所有的META-INF/spring.factories文件配置引导类、初始化类和监听器
  
   // 使用SpringFactoriesLoader加载引导Bootstrapper引导类,给用提供初始化的扩展功能,默认没有实现。接口的方法将在执行run 	 // 方法调用createBootstrapContext方法是调用。也就是在创建引导上下文的时候执行。
   this.bootstrappers = new ArrayList<>(getSpringFactoriesInstances(Bootstrapper.class));
   //  使用SpringFactoriesLoader加载ApplicationContextInitializer上下文初始化类,这里的接口会在执行run方法调用
   //  prepareContext方法的时候调用
   setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
   // 使用SpringFactoriesLoader加载所有的监听器,监听器会在启动过程中发布各种事件,如果我们需要在启动过程中做什么事情就可以		 // 通过监听器来做
   setListeners((Collection) getSpringFactoriesIns tances(ApplicationListener.class));
   // 推断出主类,用反射通过方法调用栈推断出主方法,设置主方法属性,后续要用到
   this.mainApplicationClass = deduceMainApplicationClass();
}

执行构建SpringApplication对象run方法

/**
run方法的主要步骤分为三个阶段:
	1、做各种准备工作:启动计时器,创建引导上下文对象,配置模拟输入输出信号,获取监听器,发布开始启动事件。
		 做一些环境信息配置的准备,设置忽略bean配置,打印logo
	2、真正的启动容器:创建容器,准备容器,刷新容器
	3、启动容器后的一些操作:刷新后操作,实际上什么也没做,停止计时器,打印启动完成信息,发布启动完成事件,
		 回调ApplicationRunner、CommandLineRunner接口的实现,最后发布启动中事件
*/
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);
			// 准备环境信息,将各种信息分装到environment对象中,比如系统信息,容器信息
			ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
			configureIgnoreBeanInfo(environment);// 配置忽略bean信息
			Banner printedBanner = printBanner(environment);// 打印banner,在这之后就真正开始做事了
			// 创建上下文信息,也就是spring容器,这里的容器是根据之前构造方法确定的容器类型创建的
			context = createApplicationContext();
			context.setApplicationStartup(this.applicationStartup);//设置启动引导器
 			// 准备容器,这个准备工作做了很多事情,包括执行各种初始化器,加载工厂后置处理器,加载监听器等等
			prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
			// 容器刷新,容器刷新是spring启动的核心流程,主要是加载我们配置的所有的组件,完成spring各种组件的启动
			refreshContext(context);
			// 刷新后处理,这里默认是什么都没有做的,应该是留给后面扩展
			afterRefresh(context, applicationArguments);
			stopWatch.stop();// 停止计时器
			if (this.logStartupInfo) {//如果开起来日志,则打印启动的日志信息
				new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
			}
			listeners.started(context);//发布启动完成时间
			// 调用ApplicationRunner和CommandLineRunner接口回调方法,当我们需要在项目启动后加载某些配置或者做一些特殊的初始化操	 		// 作可以在这里做,就是自己新增一个类实现这两个接口的其中一个就可以会掉到我们自己的方法
			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;
	}
posted @   文亮的学习笔记  阅读(53)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
点击右上角即可分享
微信分享提示