SpringBoot启动原理分析

  为更好的理解SpringBoot的启动原理,在分析之前我们可以先回顾一下关于SpringBoot的相关知识。
  什么是SpringBoot?SpringBoot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程,使用“习惯优于配置”的理念,整合了Spring平台和一些第三方类库,只需要很少的配置就可以快速开始你的项目。
  为什么要使用SpringBoot?SpringBoot自身有很多优点,比如快速构建项目、内嵌web服务器、自动配置、自动管理依赖、自带应用监控等等。
  SpringBoot的启动过程如图:
SpringBoot的启动过程

一、简述

根据启动过程流程图,可以看出SpringBoot整个启动流程可分为两个核心步骤,分别是:

  1. 初始化SpringApplication对象。
  2. 执行该对象的run方法。

因此,SpringBoot的启动都是通过@SpringBootApplication注解和SpringApplication.run方法来实现的。

springboot的标准启动入口

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

二、源码分析

1.@SpringBootApplication注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { //这两个排除过滤器TypeExcludeFilter和AutoConfigurationExcludeFilter不知道用来干什么的
        @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
    //等同于EnableAutoConfiguration注解的exclude属性
    @AliasFor(annotation = EnableAutoConfiguration.class)
    Class<?>[] exclude() default {};
    //等同于EnableAutoConfiguration注解的excludeName属性
    @AliasFor(annotation = EnableAutoConfiguration.class)
    String[] excludeName() default {};
    //等同于ComponentScan注解的basePackages属性
    @AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
    String[] scanBasePackages() default {};
    //等同于ComponentScan注解的basePackageClasses属性
    @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
    Class<?>[] scanBasePackageClasses() default {};
}

  可以看到@SpringBootApplication注解实际上是SpringBoot提供的一个复合注解,由以下三个注解组成:
  @SpringBootConfiguration:来源于@Configuration,二者功能都是将当前类标注为配置类,并将当前类里以@Bean 注解标记的方法的实例注入到srping容器中。
  @EnableAutoConfiguration:启用自动配置其可以帮助SpringBoot应用将所有符合条件的@Configuration配置都加载到当前 IOC 容器之中。
  @ComponentScan:对应于XML配置形式中的context:component-scan,用于将一些标注了特定注解的bean定义批量采集注册到Spring的IoC容器之中,这些特定的注解大致包括:@Controller @Entity @Component @Service @Repository。
  因此,@SpringBootApplication注解主要作为一个配置类,能够触发包扫描和自动配置的逻辑,从而使得SpringBoot的相关bean被注册进Spring容器。

2.创建SpringBootApplication对象

  SpringApplication类的run方法,这个方法就做了2件事:一是创建SpringApplication对象,二是启动SpringApplication。
  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;
    //断言primarySources不能为null,如果为null,抛出异常提示
    Assert.notNull(primarySources, "PrimarySources must not be null");
    //启动类传入的Class
    this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    //判断当前项目类型,有三种:NONE、SERVLET、REACTIVE
    this.bootstrappers = new ArrayList(this.getSpringFactoriesInstances(Bootstrapper.class));
    //设置ApplicationContextInitializer
    this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
    //设置监听器
    this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
    //判断主类,初始化入口类
    this.mainApplicationClass = this.deduceMainApplicationClass();
}

//判断主类
private Class<?> deduceMainApplicationClass() {
    try {
        StackTraceElement[] stackTrace = (new RuntimeException()).getStackTrace();
        StackTraceElement[] var2 = stackTrace;
        int var3 = stackTrace.length;

        for(int var4 = 0; var4 < var3; ++var4) {
            StackTraceElement stackTraceElement = var2[var4];
            if ("main".equals(stackTraceElement.getMethodName())) {
                return Class.forName(stackTraceElement.getClassName());
            }
        }
    } catch (ClassNotFoundException var6) {
    }

    return null;
}

  在构造器里主要就做了2件事,1是设置初始化器,2是设置监听器。
  设置初始化器的源码如下:

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();
    //从类路径的META-INF处读取相应配置文件spring.factories,然后进行遍历,读取配置文件中Key(type)对应的value
    Set<String> names = new LinkedHashSet(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    //将names的对象实例化
    List<T> instances = this.createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
    AnnotationAwareOrderComparator.sort(instances);
    return instances;
}

  ApplicationContextInitializer.class从类路径的META-INF处读取相应配置文件spring.factories并实例化对应Initializer。

  设置监听器:和设置初始化器一个样,都是通过getSpringFactoriesInstances函数实例化监听器。

  创建了SpringApplication实例之后,就完成了SpringApplication类的初始化工作。

3.SpringApplication.run()方法

  得到SpringApplication实例后,接下来就调用实例方法run()。
  run方法源码如下:

public ConfigurableApplicationContext run(String... args) {
    //创建计时器
    StopWatch stopWatch = new StopWatch();
    //开始计时
    stopWatch.start();
    DefaultBootstrapContext bootstrapContext = this.createBootstrapContext();
    //定义上下文对象
    ConfigurableApplicationContext context = null;
    //Headless模式设置
    this.configureHeadlessProperty();
    //加载SpringApplicationRunListeners监听器
    SpringApplicationRunListeners listeners = this.getRunListeners(args);
    //发送ApplicationStartingEvent事件
    listeners.starting(bootstrapContext, this.mainApplicationClass);

    try {
        //封装ApplicationArguments对象
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        //配置环境模块
        ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments);
        //根据环境信息配置要忽略的bean信息
        this.configureIgnoreBeanInfo(environment);
        //打印Banner标志
        Banner printedBanner = this.printBanner(environment);
        //创建ApplicationContext应用上下文
        context = this.createApplicationContext();
        context.setApplicationStartup(this.applicationStartup);
        //ApplicationContext基本属性配置
        this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
        //刷新上下文
        this.refreshContext(context);
        //刷新后的操作,由子类去扩展
        this.afterRefresh(context, applicationArguments);
        //计时结束
        stopWatch.stop();
        //打印日志
        if (this.logStartupInfo) {
            (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
        }

        //发送ApplicationStartedEvent事件,标志spring容器已经刷新,此时所有的bean实例都已经加载完毕
        listeners.started(context);
        //查找容器中注册有CommandLineRunner或者ApplicationRunner的bean,遍历并执行run方法
        this.callRunners(context, applicationArguments);
    } catch (Throwable var10) {
        //发送ApplicationFailedEvent事件,标志SpringBoot启动失败
        this.handleRunFailure(context, var10, listeners);
        throw new IllegalStateException(var10);
    }

    try {
        //发送ApplicationReadyEvent事件,标志SpringApplication已经正在运行,即已经成功启动,可以接收服务请求。
        listeners.running(context);
        return context;
    } catch (Throwable var9) {
        //报告异常,但是不发送任何事件
        this.handleRunFailure(context, var9, (SpringApplicationRunListeners)null);
        throw new IllegalStateException(var9);
    }
}

  首先遍历执行所有通过SpringFactoriesLoader,在当前classpath下的META-INF/spring.factories中查找所有可用的SpringApplicationRunListeners并实例化。调用它们的starting()方法,通知这些监听器SpringBoot应用启动。
  创建并配置当前SpringBoot应用将要使用的Environment,包括当前有效的PropertySource以及Profile。
  遍历调用所有的SpringApplicationRunListeners的environmentPrepared()的方法,通知这些监听器SpringBoot应用的Environment已经完成初始化。
  打印SpringBoot应用的banner,SpringApplication的showBanner属性为true时,如果classpath下存在banner.txt文件,则打印其内容,否则打印默认banner。
  根据启动时设置的applicationContextClass和在initialize方法设置的webEnvironment,创建对应的applicationContext。
  创建异常解析器,用在启动中发生异常的时候进行异常处理(包括记录日志、释放资源等)。
  设置SpringBoot的Environment,注册Spring Bean名称的序列化器BeanNameGenerator,并设置资源加载器ResourceLoader,通过SpringFactoriesLoader加载ApplicationContextInitializer初始化器,调用initialize方法,对创建的ApplicationContext进一步初始化。
  调用所有的SpringApplicationRunListeners的contextPrepared方法,通知这些Listener当前ApplicationContext已经创建完毕。
  最核心的一步,将之前通过@EnableAutoConfiguration获取的所有配置以及其他形式的IoC容器配置加载到已经准备完毕的ApplicationContext。
  调用所有的SpringApplicationRunListener的contextLoaded方法,加载准备完毕的ApplicationContext。
  调用refreshContext,注册一个关闭Spring容器的钩子ShutdownHook,当程序在停止的时候释放资源(包括:销毁Bean,关闭SpringBean的创建工厂等)。
   注: 钩子可以在以下几种场景中被调用:
   1)程序正常退出
   2)使用System.exit()
   3)终端使用Ctrl+C触发的中断
   4)系统关闭
   5)使用Kill pid命令杀死进程
   获取当前所有ApplicationRunner和CommandLineRunner接口的实现类,执行其run方法遍历所有的SpringApplicationRunListener的finished()方法,完成SpringBoot的启动。

总结

  SpringBoot看起来只有一个@SpringBootApplication注解和一个run方法,但其实是经过高度封装后的。通过对启动原理的分析也能感受到SpringBoot所具有的优势。例如使用了spring.factories文件来完成自动配置,提高了扩展性。在启动时使用观察者模式,以事件发布的形式通知,降低耦合,易于扩展等等。

  • 6
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值