SpringBoot启动流程及自动配置

SpringBoot启动流程源码:

1、启动SpringBoot启动类SpringbootdemoApplication中的main方法。

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

2、调用SpringApplication.run(SpringbootdemoApplication.class, args),该方法是一个静态方法。

复制代码
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
   return run(new Class<?>[] { primarySource }, args);
}

3、继续调用SpringApplication内部的run方法

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

,且构建了一个SpringApplication对象,应用程序将从指定的主要来源加载Bean

复制代码
public SpringApplication(Class<?>... primarySources) {
		this(null, primarySources);
}

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
   //resourceLoader赋值为Null
   this.resourceLoader = resourceLoader;
   //primarySources不为空,继续向下执行。为空抛异常
   Assert.notNull(primarySources, "PrimarySources must not be null");
   //将SpringbootdemoApplication(启动类)赋值给primarySources 
   this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
   //从classpath类路径推断Web应用类型,有三种Web应用类型,分别是
   //NONE: 该应用程序不应作为 Web 应用程序运行,也不应启动嵌入式 Web 服务器
   //SERVLET: 该应用程序应作为基于 servlet 的 Web 应用程序运行,并应启动嵌入式 servlet Web 服务器。
   //REACTIVE: 该应用程序应作为响应式 Web 应用程序运行,并应启动嵌入式响应式 Web 服务器
   this.webApplicationType = WebApplicationType.deduceFromClasspath();
   //初始化bootstrapRegistryInitializers,通过getSpringFactoriesInstances()获取工厂实例,
   //底层使用的是反射Class<?> instanceClass = ClassUtils.forName(name, classLoader)动态加载实例对象。
   this.bootstrapRegistryInitializers = new ArrayList<>(
         getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
   //初始化ApplicationContextInitializer集合
   setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
   //初始化ApplicationListener
   setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
   //获取StackTraceElement数组遍历,通过反射获取堆栈中有main方法的类。
   this.mainApplicationClass = deduceMainApplicationClass();
}

4、运行 Spring 应用程序,创建并刷新一个新的 ApplicationContext。

复制代码
public ConfigurableApplicationContext run(String... args) {
   long startTime = System.nanoTime();
   //通过BootstrapRegistryInitializer来initialize默认的DefaultBootstrapContext
   DefaultBootstrapContext bootstrapContext = createBootstrapContext();
   ConfigurableApplicationContext context = null;
   //配置java.awt.headless属性
   configureHeadlessProperty();
   //获取SpringApplicationRunListeners监听器
   SpringApplicationRunListeners listeners = getRunListeners(args);
   //启动SpringApplicationRunListeners监听,表示SpringApplication启动(触发ApplicationStartingEvent事件)
   listeners.starting(bootstrapContext, this.mainApplicationClass);
   try {
      //创建ApplicationArguments对象,封装了args参数
      ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
      //做相关环境准备,绑定到SpringApplication,返回可配置环境对象ConfigurableEnvironment 
      ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
      //配置spring.beaninfo.ignore,设置为true.即跳过搜索Bean信息
      configureIgnoreBeanInfo(environment);
      //控制台打印SpringBoot的Banner(横幅)标志
      Banner printedBanner = printBanner(environment);
      //根据WebApplicationType从ApplicationContextFactory工厂创建ConfigurableApplicationContext
      context = createApplicationContext();
      //设置ConfigurableApplicationContext中的ApplicationStartup为DefaultApplicationStartup
      context.setApplicationStartup(this.applicationStartup);
      //应用所有的ApplicationContextInitializer容器初始化器初始化context,触发ApplicationContextInitializedEvent事件监听,打印启动日志信息,启动Profile日志信息。
      //ConfigurableListableBeanFactory中注册单例Bean(springApplicationArguments),并为该BeanFactory中的部分属性赋值。
      //加载所有的source.并将Bean加载到ConfigurableApplicationContext,触发ApplicationPreparedEvent事件监听
      prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
      //刷新容器(在方法中集成了Web容器具体请看 https://editor.csdn.net/md/?articleId=123136262)
      refreshContext(context);
      //刷新容器的后置处理(空方法)
      afterRefresh(context, applicationArguments);
      //启动花费的时间
      Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
      if (this.logStartupInfo) {
         //打印日志Started xxx in xxx seconds (JVM running for xxxx)
         new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
      }
      //触发ApplicationStartedEvent事件监听。上下文已刷新,应用程序已启动。
      listeners.started(context, timeTakenToStartup);
      //调用ApplicationRunner和CommandLineRunner
      callRunners(context, applicationArguments);
   }
   //处理运行时发生的异常,触发ApplicationFailedEvent事件监听
   catch (Throwable ex) {
      handleRunFailure(context, ex, listeners);
      throw new IllegalStateException(ex);
   }
   try {
      //启动准备消耗的时间
      Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
      //在run方法完成前立即触发ApplicationReadyEvent事件监听,表示应用上下文已刷新,并且CommandLineRunners和ApplicationRunners已被调用。
      listeners.ready(context, timeTakenToReady);
   }
   catch (Throwable ex) {
      handleRunFailure(context, ex, null);
      throw new IllegalStateException(ex);
   }
   return context;
}

SpringBoot的自动配置:

SpringBoot的启动类上总是有@SpringBootApplication这个注解。接下来我们来了解一下这个注解。进入@SpringBootApplication源码:

复制代码
@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}
)}
)
public @interface SpringBootApplication {
   ......
}

由此可见,@SpringBootApplication注解是一个组合注解,由@SpringBootConfiguration,@EnableAutoConfiguration,@ComponentScan注解组成。

@SpringBootConfiguration其实就是一个@Configuration,表明这是一个配置类,可以向容器注入组件。

复制代码
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@Indexed
public @interface SpringBootConfiguration {
 	......
}

@EnableAutoConfiguration由@AutoConfigurationPackage和@Import({AutoConfigurationImportSelector.class})注解组成

复制代码
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
   ......
}

@AutoConfigurationPackage内部用到了@Import导入Registrar

复制代码
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({Registrar.class})
public @interface AutoConfigurationPackage {
  ......
}

Registrar实现了ImportBeanDefinitionRegistrar接口,因此可将组件都扫描注冊到 spring 容器中

static class Registrar implements ImportBeanDefinitionRegistrar,

DeterminableImports {

Registrar() {

}

public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {

AutoConfigurationPackages.register(registry, (String[])(new

AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new

String[0]));

}

public Set determineImports(AnnotationMetadata metadata) {

return Collections.singleton(new AutoConfigurationPackages.PackageImports(metadata));

}

}

复制代码
@AutoConfigurationPackage将主配置类(@SpringBootApplication
标注的类)所在包下的所有组件都扫描注册到Spring容器中。

@Import({AutoConfigurationImportSelector.class}) 将AutoConfigurationImportSelector(自动配置导入选择器)导入容器中

AutoConfigurationImportSelector类中的selectImports()方法的作用是选择导入过滤后的自动配置

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

getAutoConfigurationEntry(AnnotationMetadata

annotationMetadata)根据annotationMetadata(即我们的启动类SpringbootdemoApplication)获取AutoConfigurationEntry

复制代码
protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        } else {
            //获取注解属性
            AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
            //从META-INF/spring.factories中获取候选配置。
            List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
            //去除重复配置
            configurations = this.removeDuplicates(configurations);
            //获取注解中的排除项
            Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
            //检查排除类
            this.checkExcludedClasses(configurations, exclusions);
            //从上面的候选配置中移除所有排除的配置类
            configurations.removeAll(exclusions);
            //通过ConfigurationClassFilter筛选配置
            configurations = this.getConfigurationClassFilter().filter(configurations);
            //触发自动配置导入事件
            this.fireAutoConfigurationImportEvents(configurations, exclusions);
            //返回排除后的自动配置Entry
            return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
        }
    }

总结SpringBoot启动原理

1、调用有@SpringBootApplication注解的启动类的main方法

2、通过调用SpringApplication内部的run()方法构建SpringApplication对象。

创建SpringApplication对象:

2.1 PrimarySources 不为空,将启动类赋值给primarySources 对象。

2.2 从classpath类路径推断Web应用类型,有三种Web应用类型NONE、SERVLET、REACTIVE

2.3 初始化bootstrapRegistryInitializers

2.4 初始化ApplicationContextInitializer集合

2.5 初始化ApplicationListener

2.6 获取StackTraceElement数组遍历,通过反射获取堆栈中有main方法A的。 3、调用SpringBootApplication的run方法。 4、long startTime = System.nanoTime();

记录项目启动时间。 5、通过BootstrapRegistryInitializer来初始化DefaultBootstrapContext

6、getRunListeners(args)获取SpringApplicationRunListeners监听器 7、

listeners.starting()触发ApplicationStartingEvent事件

8、prepareEnvironment(listeners, bootstrapContext,

applicationArguments) 将配置文件读取到容器中,返回ConfigurableEnvironment 对象。

9、printBanner(environment) 打印Banner图,即SpringBoot启动时的图案。

10、根据WebApplicationType从ApplicationContextFactory工厂创建ConfigurableApplicationContext,并设置ConfigurableApplicationContext中的ApplicationStartup为DefaultApplicationStartup

11、

调用prepareContext()初始化context等,打印启动日志信息,启动Profile日志信息,并为BeanFactory中的部分属性赋值。

12、刷新容器,在该方法中集成了Tomcat容器 13、加载SpringMVC.

14、刷新后的方法,空方法,给用户自定义重写afterRefresh() 15、Duration timeTakenToStartup =

Duration.ofNanos(System.nanoTime() - startTime)算出启动花费的时间。

16、打印日志Started xxx in xxx seconds (JVM running for xxxx)

17、listeners.started(context,

timeTakenToStartup)触发ApplicationStartedEvent事件监听。上下文已刷新,应用程序已启动。

18、调用ApplicationRunner和CommandLineRunner 19、返回上下文。

相关推荐
Albert Edison2 小时前
【最新版】IntelliJ IDEA 2025 创建 SpringBoot 项目
java·spring boot·intellij-idea
Piper蛋窝3 小时前
深入 Go 语言垃圾回收:从原理到内建类型 Slice、Map 的陷阱以及为何需要 strings.Builder
后端·go
六毛的毛5 小时前
Springboot开发常见注解一览
java·spring boot·后端
AntBlack5 小时前
拖了五个月 ,不当韭菜体验版算是正式发布了
前端·后端·python
31535669135 小时前
一个简单的脚本,让pdf开启夜间模式
前端·后端
uzong6 小时前
curl案例讲解
后端
开开心心就好6 小时前
免费PDF处理软件,支持多种操作
运维·服务器·前端·spring boot·智能手机·pdf·电脑
一只叫煤球的猫6 小时前
真实事故复盘:Redis分布式锁居然失效了?公司十年老程序员踩的坑
java·redis·后端
猴哥源码6 小时前
基于Java+SpringBoot的农事管理系统
java·spring boot
大鸡腿同学7 小时前
身弱武修法:玄之又玄,奇妙之门
后端