0501源码分析-启动过程-springboot2.7.x系列

1前言

下面我们通过源码来初步了解下springboot启动流程,从下面链接2处借一张图整体展示下启动过程,如下图1-1所示:

在这里插入图片描述

追踪下启动类main方法的run方法,SpringApplicaiton#run源代码如下所示:

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

这里启动分为两个部分:

  • 启动第一阶段:new SpringApplication(),实例化SpringApplication对象;
  • 启动第二阶段:调用SpringApplication对象的run方法。

2 启动第一阶段

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));
  // 推断应用类型:None,Reactive,Servlet
   this.webApplicationType = WebApplicationType.deduceFromClasspath();
  // spring.factories获取BootstrapRegistryInitializer接口实现类
   this.bootstrapRegistryInitializers = new ArrayList<>(
         getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
  //  使用SpringFactoriesLoader加载 实例化ApplicationContextInitializer接口实现类初始器
   setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
  //  使用SpringFactoriesLoader加载 实例化ApplicationListener接口实现类监听器
   setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
   this.mainApplicationClass = deduceMainApplicationClass();
}

2.1 deduceFromClasspath 推断应用类型

WebApplicationType#deduceFromClasspath()源代码如下所示:

static WebApplicationType deduceFromClasspath() {
  // 判断应用是不是web Reactive
   if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
         && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
     // 判断如果存在Webflux标志类且不存在webmvc标志类DispatcherServlet和jersey标志类ServletContainer
      return WebApplicationType.REACTIVE;
   }
  // 非web应用
   for (String className : SERVLET_INDICATOR_CLASSES) {
     // Servlet与ConfigurableWebApplicationContext任一不存在,非web应用
      if (!ClassUtils.isPresent(className, null)) {
         return WebApplicationType.NONE;
      }
   }
  // web servlet应用
   return WebApplicationType.SERVLET;
}
  • 判断原理就是通过类加载器去加载对应应用的标志类,如果加载成功,为对应的应用类型;否则不是。

2.2 getSpringFactoriesInstances(Class)

获取spring.factories文件中对应类或者接口的实现类,我们通过源码来下如果实现的。

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
  // 获取类加载器
   ClassLoader classLoader = getClassLoader();
   // SpringFactoriesLoader加载type类型的类名
   Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
  // 实例化
   List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
  // 根据注解@Order指定的顺序排序
   AnnotationAwareOrderComparator.sort(instances);
   return instances;
}

SpringFactoriesLoader#loadFactoryNames()源代码如下所示:

public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
   ClassLoader classLoaderToUse = classLoader;
   if (classLoaderToUse == null) {
      classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
   }
   String factoryTypeName = factoryType.getName();
   return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}

loadSpringFactories()源代码如下所示:

private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
  // 优先从缓存中获取,key为classLoader
   Map<String, List<String>> result = cache.get(classLoader);
   if (result != null) {
     // 如果已经加载过,直接返回
      return result;
   }

   result = new HashMap<>();
   try {
     // 资源(文件)位置FACTORIES_RESOURCE_LOCATION:"META-INF/spring.factories"
      Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
      while (urls.hasMoreElements()) {
         URL url = urls.nextElement();
         UrlResource resource = new UrlResource(url);
        // 提取属性
         Properties properties = PropertiesLoaderUtils.loadProperties(resource);
         for (Map.Entry<?, ?> entry : properties.entrySet()) {
           // 属性名称
            String factoryTypeName = ((String) entry.getKey()).trim();// 以逗号分割的值
            String[] factoryImplementationNames =
                  StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
            for (String factoryImplementationName : factoryImplementationNames) {
               result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
                     .add(factoryImplementationName.trim());
            }
         }
      }

      // Replace all lists with unmodifiable lists containing unique elements
      result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
            .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
     // 缓存结果
      cache.put(classLoader, result);
   }
   catch (IOException ex) {
      throw new IllegalArgumentException("Unable to load factories from location [" +
            FACTORIES_RESOURCE_LOCATION + "]", ex);
   }
   return result;
}
  • SpringFactoriesLoader为springboot SPI机制的具体实现的关键类。

2.3 ApplicationContextInitializer

ApplicationContextInitializer是Spring框架中的一个接口,用于在应用程序上下文完全创建和刷新之前对其进行初始化。它通常用于自定义应用程序上下文或在应用程序启动之前配置某些方面。

ApplicationContextInitializer接口定义了一个名为initialize的方法,该方法接受一个ApplicationContext作为参数。该方法在应用程序上下文初始化过程中由应用程序上下文调用。

您可以创建自己的ApplicationContextInitializer实现来执行自定义的初始化任务。例如,您可以使用它设置属性源、注册额外的Bean或根据外部条件配置特定的配置文件。实现类可以通过使用配置类或XML配置文件将其注册到Spring应用程序上下文中。

下面是一个使用ApplicationContextInitializer的示例:

javaCopy code
public class MyApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {

    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        // 自定义的初始化逻辑在这里
        // 您可以访问应用程序上下文并执行任何所需的设置
        // 例如,注册额外的Bean定义或配置配置文件
    }
}

要将初始化程序注册到应用程序上下文中,您可以通过编程方式或通过配置来完成。以下是使用基于Java的配置类的示例:

javaCopy code
@Configuration
public class AppConfig {

    @Bean
    public static MyApplicationContextInitializer myApplicationContextInitializer() {
        return new MyApplicationContextInitializer();
    }

    public static void main(String[] args) {
        SpringApplication application = new SpringApplication(AppConfig.class);
        application.addInitializers(myApplicationContextInitializer());
        application.run(args);
    }
}

在上述示例中,MyApplicationContextInitializer被注册为一个Bean,并添加为SpringApplication的初始化程序。当应用程序上下文创建时,将调用MyApplicationContextInitializerinitialize方法,允许您执行自定义的初始化逻辑。

通过使用ApplicationContextInitializer,您可以在应用程序上下文初始化阶段灵活地自定义应用程序上下文,以根据特定需求进行配置和调整。

2.4 ApplicationListener

ApplicationListener是Spring框架中的一个接口,用于监听和响应应用程序事件。它是Spring事件处理机制的关键组件之一。通过实现ApplicationListener接口,您可以创建事件监听器,对在Spring应用程序上下文中发布的事件做出响应。

ApplicationListener接口定义了一个名为onApplicationEvent的方法,当发布一个应用程序事件时,该方法将被调用。该方法接受一个ApplicationEvent参数,表示发生的事件。

下面是一个使用ApplicationListener的示例:

javaCopy code
public class MyEventListener implements ApplicationListener<MyEvent> {

    @Override
    public void onApplicationEvent(MyEvent event) {
        // 在这里处理事件的逻辑
        // 您可以访问事件并执行任何所需的操作
    }
}

在上述示例中,MyEventListener是一个自定义的事件监听器,专门监听MyEvent类型的事件。当在应用程序上下文中发布MyEvent的实例时,将调用MyEventListeneronApplicationEvent方法,允许您处理事件。

要在应用程序上下文中发布事件,您可以使用ApplicationEventPublisher接口。以下是发布事件的示例:

javaCopy code
@Component
public class MyEventPublisher {

    @Autowired
    private ApplicationEventPublisher eventPublisher;

    public void publishEvent() {
        MyEvent event = new MyEvent(this, "Event data");
        eventPublisher.publishEvent(event);
    }
}

在上述示例中,MyEventPublisher是一个组件,它使用@Autowired自动装配一个ApplicationEventPublisher来发布事件。当调用publishEvent方法时,它创建一个MyEvent的实例,并使用ApplicationEventPublisherpublishEvent方法发布事件。

要将ApplicationListener注册到应用程序上下文中,您可以通过编程方式或通过配置来完成。以下是使用基于Java的配置类的示例:

javaCopy code
@Configuration
public class AppConfig {

    @Bean
    public static MyEventListener myEventListener() {
        return new MyEventListener();
    }
}

在上述示例中,MyEventListener被注册为一个Bean。当发布MyEvent类型的事件时,将调用MyEventListeneronApplicationEvent方法。

通过使用ApplicationListener,您可以创建自定义的事件监听器,以响应Spring应用程序上下文中发生的特定事件。这使您可以将组件解耦,并根据应用程序中发生的事件实现响应性行为。

2.5 自定义接口实现配置示例

实现自定义的MyApplicationContextInitializer,代码2.5-1如下:

public class MyApplicationContextInitializer implements ApplicationContextInitializer {
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        // 容器初始化
        System.out.println("initializer");
    }
}

实现自定义的MyApplicationListener,代码2.5-1如下:

public class MyApplicationListener implements ApplicationListener {
    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        System.out.println("==== " + event);
    }
}

在spring.factories中配置,文件内容如下:

# initializer
org.springframework.context.ApplicationContextInitializer=\
com.gaogzhen.initializer.MyApplicationContextInitializer

# listener
org.springframework.context.ApplicationListener=\
com.gaogzhen.listener.MyApplicationListener

3 启动第二阶段

下面我们看下SpringApplication#run()方法,源代码如下:

public ConfigurableApplicationContext run(String... args) {
  // 计时
  long startTime = System.nanoTime();
  // 创建默认的启动上下文
  DefaultBootstrapContext bootstrapContext = createBootstrapContext();
  // 应用上下文
  ConfigurableApplicationContext context = null;
  configureHeadlessProperty();
  // srpingboot生命周期监听器
  SpringApplicationRunListeners listeners = getRunListeners(args);
  // 监听正在启动中,执行监听器对应的starting方法
  listeners.starting(bootstrapContext, this.mainApplicationClass);
  try {
    // 封装参数对象
    ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
    // 环境变量对象
    ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
    configureIgnoreBeanInfo(environment);
    // 打印banner
    Banner printedBanner = printBanner(environment);
    // 根据不同的应用类型创建对应容器
    context = createApplicationContext();
    context.setApplicationStartup(this.applicationStartup);
    prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
    // 刷新容器,执行AbstractApplicationContext#fresh()方法
    refreshContext(context);
    afterRefresh(context, applicationArguments);
    Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
    if (this.logStartupInfo) {
      new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
    }
    // 监听启动完成,执行监听器对应的started方法
    listeners.started(context, timeTakenToStartup);
    callRunners(context, applicationArguments);
  }
  catch (Throwable ex) {
    handleRunFailure(context, ex, listeners);
    throw new IllegalStateException(ex);
  }
  try {
    Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
    // 监听准备就绪,执行监听器对应的ready方法
    listeners.ready(context, timeTakenToReady);
  }
  catch (Throwable ex) {
    handleRunFailure(context, ex, null);
    throw new IllegalStateException(ex);
  }
  return context;
}

3.1 SpringApplicationRunListener

SpringApplicationRunListener 接口是 Spring Framework 中的一个接口,用于自定义 SpringApplication 的运行过程中的事件监听器。

SpringApplicationRunListener接口源代码如下:

public interface SpringApplicationRunListener {

   default void starting(ConfigurableBootstrapContext bootstrapContext) {
   }

   default void environmentPrepared(ConfigurableBootstrapContext bootstrapContext,
         ConfigurableEnvironment environment) {
   }

   default void contextPrepared(ConfigurableApplicationContext context) {
   }

   default void contextLoaded(ConfigurableApplicationContext context) {
   }

   default void started(ConfigurableApplicationContext context, Duration timeTaken) {
      started(context);
   }
  
   @Deprecated
   default void started(ConfigurableApplicationContext context) {
   }
  
   default void ready(ConfigurableApplicationContext context, Duration timeTaken) {
      running(context);
   }

   @Deprecated
   default void running(ConfigurableApplicationContext context) {
   }

   default void failed(ConfigurableApplicationContext context, Throwable exception) {
   }

}

该接口定义了一组方法,用于处理 SpringApplication 生命周期中的不同阶段的事件。以下是该接口定义的方法:

  • starting(): 当 SpringApplication 开始启动时调用。
  • environmentPrepared(ConfigurableEnvironment environment): 在应用程序环境准备完成后、应用程序上下文创建之前调用。
  • contextPrepared(ConfigurableApplicationContext context): 在应用程序上下文准备完成后、但尚未刷新之前调用。
  • contextLoaded(ConfigurableApplicationContext context): 在应用程序上下文加载完成后、但尚未刷新之前调用。
  • started(ConfigurableApplicationContext context): 在应用程序上下文刷新完成并且应用程序启动后调用。
  • running(ConfigurableApplicationContext context): 当应用程序正在运行时调用。
  • failed(ConfigurableApplicationContext context, Throwable exception): 如果应用程序启动失败时调用。

该接口的重要实现类EventPublishingRunListener,以starting()方法为例,看看它做了什么,源代码如下:

@Override
public void starting(ConfigurableBootstrapContext bootstrapContext) {
   this.initialMulticaster
      .multicastEvent(new ApplicationStartingEvent(bootstrapContext, this.application, this.args));
}
  • 通过广播发布了ApplicationStartingEvent类型的广播事件,

您可以通过实现 SpringApplicationRunListener 接口来创建自定义的 SpringApplicationRunListener,以便在应用程序的生命周期中处理特定的事件。请注意,您需要提供一个与默认构造函数匹配的合适构造函数。

以下是一个示例:

public class MyRunListener implements SpringApplicationRunListener {
    
    public MyRunListener(SpringApplication application, String[] args) {
        
    }
    
    @Override
    public void starting(ConfigurableBootstrapContext bootstrapContext) {
        System.out.println("===启动中===");
    }
}

要激活自定义的 SpringApplicationRunListener,您需要在项目的 META-INF 目录下的 spring.factories 文件中指定映射关系。spring.factories 文件应包含 SpringApplicationRunListener 接口和您自定义实现的映射关系:

org.springframework.boot.SpringApplicationRunListener=\
com.gaogzhen.listener.MyRunListener

运行结果:

==== org.springframework.boot.context.event.ApplicationStartingEvent[source=org.springframework.boot.SpringApplication@23e028a9]
===启动中===

通过这样的配置,您的自定义 SpringApplicationRunListener 将在 SpringApplication 的运行过程中被自动检测并调用。

请注意,SpringApplicationRunListener 的可用性和使用方式可能在更近期的 Spring Boot 版本中有所改变,因此建议参考官方的 Spring Boot 文档或相关资源以获取最新的信息和使用指南。

3.2 容器创建和准备

  • 容器创建

创建容器createApplicationContext()源代码如下所示:

protected ConfigurableApplicationContext createApplicationContext() {
   return this.applicationContextFactory.create(this.webApplicationType);
}
private ApplicationContextFactory applicationContextFactory = ApplicationContextFactory.DEFAULT;

DefaultApplicationContextFactory#create()方法源代码如下所示:

@Override
public ConfigurableApplicationContext create(WebApplicationType webApplicationType) {
   try {
      return getFromSpringFactories(webApplicationType, ApplicationContextFactory::create,
            AnnotationConfigApplicationContext::new);
   }
   catch (Exception ex) {
      throw new IllegalStateException("Unable create a default ApplicationContext instance, "
            + "you may need a custom ApplicationContextFactory", ex);
   }
}
	private <T> T getFromSpringFactories(WebApplicationType webApplicationType,
			BiFunction<ApplicationContextFactory, WebApplicationType, T> action, Supplier<T> defaultResult) {
    // 遍历SpringFactoriesLoader加载spring.factories文件中ApplicationContextFactory接口的实现类
		for (ApplicationConSpringFactoriesLoader加载spring.factories文件中textFactory candidate : SpringFactoriesLoader.loadFactories(ApplicationContextFactory.class,
				getClass().getClassLoader())) {
      // 执行实现类的create方法
			T result = action.apply(candidate, webApplicationType);
			if (result != null) {
				return result;
			}
		}
		return (defaultResult != null) ? defaultResult.get() : null;
	}

spring.factories文件ApplicationContextFactory配置如下:

# Application Context Factories
org.springframework.boot.ApplicationContextFactory=\
org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext.Factory,\
org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext.Factory

以AnnotationConfigReactiveWebServerApplicationContext.Factory为例看下create()方法源代码如下:

@Override
public ConfigurableApplicationContext create(WebApplicationType webApplicationType) {
   return (webApplicationType != WebApplicationType.REACTIVE) ? null
         : new AnnotationConfigReactiveWebServerApplicationContext();
}

注:根据不同的应用类型创建不同类型的容器

  • 容器准备

prepareContext()方法源代码如下:

private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
      ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
      ApplicationArguments applicationArguments, Banner printedBanner) {
  // 设置环境变量
   context.setEnvironment(environment);
   postProcessApplicationContext(context);
  // 应用初始化器
   applyInitializers(context);
  // 发布容器准备完成事件
   listeners.contextPrepared(context);
  // 发布applicationContext准备好,bootstrapConxtex关闭事件
   bootstrapContext.close(context);
   if (this.logStartupInfo) {
      logStartupInfo(context.getParent() == null);
      logStartupProfileInfo(context);
   }
   // 注册指定的启动相关的单例bean
   ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
   beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
   if (printedBanner != null) {
      beanFactory.registerSingleton("springBootBanner", printedBanner);
   }
   if (beanFactory instanceof AbstractAutowireCapableBeanFactory) {
      ((AbstractAutowireCapableBeanFactory) beanFactory).setAllowCircularReferences(this.allowCircularReferences);
      if (beanFactory instanceof DefaultListableBeanFactory) {
         ((DefaultListableBeanFactory) beanFactory)
            .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
      }
   }
   if (this.lazyInitialization) {
      context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
   }
   context.addBeanFactoryPostProcessor(new PropertySourceOrderingBeanFactoryPostProcessor(context));
   // 加载注册souces,其中包括我们的启动类
   Set<Object> sources = getAllSources();
   Assert.notEmpty(sources, "Sources must not be empty");
   load(context, sources.toArray(new Object[0]));
   listeners.contextLoaded(context);
}
  • applyInitializers()应用初始化器,我们可以看下之前示例打印会在banner打印之后执行,如下控制台输出

      .   ____          _            __ _ _
     /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
    ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
     \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
      '  |____| .__|_| |_|_| |_\__, | / / / /
     =========|_|==============|___/=/_/_/_/
     :: Spring Boot ::               (v2.7.10)
    
    ===initializer===
    

4 总结

springboot启动过程简介:

  1. 实例化SpringApplication对象
    • 启动类赋值给primarySource
    • 推断webApplicationType,应用类型
    • 从spring.factories文件中读取ApplicationContextInitializer实现类,加载并实例化
    • 从spring.factories文件中读取ApplicationListener实现类,加载并实例化
    • 推断main方法所在类
  2. 调用SpringApplication#run()方法
    • 从spring.factories文件中读取SpringApplicationRunListener实现类,加载并实例化
    • 执行上述listeners的starting()方法
    • 创建对应应用类型的环境变量Environment,做相应的设置
    • 打印banner
    • 创建相应应用类型的容器,设置容器
      • 执行初始化器
      • contextPrepared()
      • 注册指定启动相关单例bean
      • contextLoaded()f
    • 刷新容器
    • started()
    • callRunners
    • fail()
    • ready()

结语

如果小伙伴什么问题或者指教,欢迎交流。

❓QQ:806797785

⭐️源代码仓库地址:https://gitee.com/gaogzhen/springboot-custom

参考:

[1]Springboot视频教程[CP/OL].P10,11,20,21.

[2]【SpringBoot】SpringBoot启动流程图和扩展点说明[CP/OL].

[3]spring boot 启动流程分析[CP/OL].

[4]Java常用机制 - SPI机制详解[CP/OL].

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

gaog2zh

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值