Spring Boot 的启动非常简单,只需执行一个简单的 main 方法即可:

  1. @SpringBootApplication
  2. public class SpringBootMain {
  3. public static void main(String[] args) {
  4. SpringApplication.run(SpringBootMain.class, args);
  5. }
  6. }

下面我们就围绕 SpringApplication 类的静态 run 方法以及初始化类 SpringApplication 自身的功能讲解。

SpringApplication 初始化

在入口类中主要通过 SpringApplication 的静态方法 run 方法进行 SpringApplication 类的实例化操作,然后再针对实例化对象调用另外一个 run 方法来完成整个项目的初始化和启动。

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

下面我们先来着重分析下 SpringApplication 类的实例化流程。

  1. public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
  2. this.resourceLoader = resourceLoader;
  3. Assert.notNull(primarySources, "PrimarySources must not be null");
  4. // primarySources为run()方法传入的引导类
  5. this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
  6. // 判断是否是web运行环境
  7. this.webApplicationType = WebApplicationType.deduceFromClasspath();
  8. // 使用SpringFactoriesLoader在应用的classpath中查找并加载所有可用的ApplicationContextInitializer实现类
  9. setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
  10. // 使用SpringFactoriesLoader在应用的classpath中查找并加载所有可用的ApplicationListener实现类
  11. setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
  12. // 推断main方法所在的类
  13. this.mainApplicationClass = deduceMainApplicationClass();
  14. }

SpringApplication 的核心构造方法有两个参数:第一个为 ResourceLoader,用于在 Spring Boot 启动时打印对应的 banner 信息,默认采用的是 DefaultResoureeLoader。实际使用时,如果程序末按照 Spring Boot 的约定将 banner 文件放置于 classpath 下,或者文件名不是 banner.* 格式,默认资源加载器是无法加载到对应的 banner 信息的,此时可通过 ResourceLoader 来指定需要加载的文件路径。

第二个参数为可变参数,默认传入 Spring Boot 入口类。如果作为项目的引导类,此参数传入的类需要满足一个条件,就是被注解 @EnableAutoConfiguration 或其组合注解标注。由于 @SpringBootApplication 注解中包含了 @EnableAutoConfiguration 注解,因此被 @SpringBootApplication 注解标注的类也可作为参数传入。当然该参数也可传入其他普通类。但只有传入被 @EnableAutoConfiguration 标注的类才能够开启 Spring Boot 的自动配置功能。

1. Web 应用类型推断

在 SpringApplication 的初始化逻辑中调用了 WebApplicationType 的 deduceFromClasspath 方法来进行 Web 应用类型的推断。WebApplicationType 为枚举类,它定义了可能的 Web 应用类型:

  1. public enum WebApplicationType {
  2. // 非Web应用类型
  3. NONE,
  4. // 基于Servlet的Web应用类型
  5. SERVLET,
  6. // 基于Reactive的Web应用类型
  7. REACTIVE;
  8. }

方法 deduceFromClasspath 是基于 classpath 下是否存在某个特征类来决定是否应该创建一个为 Web 应用使用的 ApplicationContext 类型,还是应该创建一个标准应用使用的 ApplicationContext 类型。

2. ApplicationContextInitializer 加载

ApplicationContextInitializer 是 Spring IOC 容器提供的一个接口,它是一个回调接口,主要目的是允许用户在 ConfigurableApplicationContext 或其子类型的 ApplicationContext 做 refresh 方法调用刷新之前,对 ConfigurableApplicationContext 实例做进一步的设置或处理。

ApplicationContextInitializer 接口只定义了一个 initialize 方法,代码如下:

  1. public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {
  2. // 初始化指定的应用上下文
  3. void initialize(C applicationContext);
  4. }

在 SpringApplication 初始化时通过 getSpringFactoriesInstances 方法来加载 ApplicationContextInitializer 实例,其内部还是通过 SpringFactoriesLoader 类的 loadFactoryNames 方法加载 META-INF/spring.factories 文件中注册的对应配置:

  1. private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
  2. ClassLoader classLoader = getClassLoader();
  3. // // 加载指定配置文件中的资源,通过Set集合保证类名唯一
  4. Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
  5. // 根据类的全限定名进行实例化
  6. List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
  7. // 排序
  8. AnnotationAwareOrderComparator.sort(instances);
  9. return instances;
  10. }

当获取到这些配置类的全限定名之后,便可调用 createSpringFactoriesInstances 方法进行实例化操作,最后根据 Ordered 接口或 @Order 注解进行从小到大的排序,排序值越小越早执行。

3. ApplicationListener 加载

Spring 事件传播机制是基于观察者模式(Observer)实现的。比如,在 ApplicationContext 管理 Bean 生命周期的过程中,会将一些改变定义为事件(ApplicationEvent)。ApplicationContext 通过 ApplicationListener 监听 ApplicationEvent,当事件被发布后,ApplicationListener 用来对事件做出具体的操作。

在接口 ApplicationListener 中只定义了一个 onApplicationEvent 方法,当监听事件被触发时,该方法被执行:

  1. public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
  2. void onApplicationEvent(E event);
  3. }

ApplicationListener 的整个配置和加载流程与 ApplicationContextInitializer 完全一致,不再重复。

4. 入口类推断

  1. private Class<?> deduceMainApplicationClass() {
  2. // 获取调用栈
  3. StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
  4. for (StackTraceElement stackTraceElement : stackTrace) {
  5. // 匹配第一个main方法
  6. if ("main".equals(stackTraceElement.getMethodName())) {
  7. return Class.forName(stackTraceElement.getClassName());
  8. }
  9. }
  10. ......
  11. }

该方法实现的基本流程就是先创建一个运行时异常,然后获得栈数组,遍历栈数组,判断类的方法中是否包含main 方法。第一个被匹配的类会通过 Class.forName 方法创建对象,并将其被返回,最后在上层方法中将对象赋值给 SpringApplication 的成员变量 mainApplicationClass。在遍历过程中如果发生异常,会忽略该异常并继续执行遍历操作。至此,整个 SpringApplication 类的实例化过程便完成了。

run 方法执行流程

当 SpringApplication 对象被创建之后,通过调用其 run 方法来进行 Spring Boot 的启动和运行,至此正式开始了 SpringApplication 的生命周期。下面我们详细介绍下 run 方法的核心执行流程:

  1. public ConfigurableApplicationContext run(String... args) {
  2. ConfigurableApplicationContext context = null;
  3. configureHeadlessProperty();
  4. // 获取SpringApplicationRunListeners监听器
  5. SpringApplicationRunListeners listeners = getRunListeners(args);
  6. // 启动所获取到的所有监听器,执行其starting方法
  7. listeners.starting();
  8. try {
  9. // 初始化ConfigurableEnvironment,加载属性配置
  10. ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
  11. ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
  12. configureIgnoreBeanInfo(environment);
  13. // 打印Banner图标
  14. Banner printedBanner = printBanner(environment);
  15. // 创建容器
  16. context = createApplicationContext();
  17. // 准备容器,组件对象之间进行关联
  18. prepareContext(context, environment, listeners, applicationArguments, printedBanner);
  19. // 初始化容器
  20. refreshContext(context);
  21. // 空方法,用于子类回调
  22. afterRefresh(context, applicationArguments);
  23. // 发布ApplicationStartedEvent容器开始事件,通知监听器容器启动完成
  24. listeners.started(context);
  25. // 遍历所有注册的ApplicationRunner和CommandLineRunner,并执行其run()方法
  26. callRunners(context, applicationArguments);
  27. } catch (Throwable ex) {
  28. ......
  29. }
  30. try {
  31. // 发布ApplicationReadyEvent容器就绪事件,表示可以开始正常提供服务了
  32. listeners.running(context);
  33. } catch (Throwable ex) {
  34. ......
  35. }
  36. return context;
  37. }

1. SpringApplicationRunListener 监听器

SpringApplicationRunListeners 可以理解为一个 SpringApplicationRunListener 的容器,它将 SpringApplicationRunListener 的集合以构造方法传入,并赋值给其 listeners 成员变量,然后提供了针对 listeners 成员变量的各种遍历操作方法。比如,遍历集合并调用对应的 starting、started、running 等方法。

  1. private SpringApplicationRunListeners getRunListeners(String[] args) {
  2. // 构造Class数组
  3. Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
  4. // 调用SpringApplicationRunListeners构造方法
  5. return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
  6. }

在实例化 SpringApplicationRunListener 对象时会根据 types 数组获取对应的构造器。因此,我们自定义的 SpringApplicationRunListener 的实现类必须要有默认的构造方法,且构造方法的参数必须依次为 SpringApplication 和 String[] 类型。

SpringApplicationRunListener 提供了一系列方法,用户可通过回调这些方法,在启动的各个流程中加入指定的逻辑处理,其接口定义如下所示:

  1. public interface SpringApplicationRunListener {
  2. // 当run方法第一次被执行时调用,可用于非常早期的初始化操作
  3. default void starting() {
  4. }
  5. // 当environment准备完成,在ApplicationContext创建之前,该方法被调用
  6. default void environmentPrepared(ConfigurableEnvironment environment) {
  7. }
  8. // 当ApplicationContext构建完成,资源还未被加载时,该方法被调用
  9. default void contextPrepared(ConfigurableApplicationContext context) {
  10. }
  11. // 当ApplicationContext加载完成,未被刷新之前,该方法被调用
  12. default void contextLoaded(ConfigurableApplicationContext context) {
  13. }
  14. // 当ApplicationContext刷新并启动之后,ApplicationRunner和CommandLineRunner未被调用之前,该方法被调用
  15. default void started(ConfigurableApplicationContext context) {
  16. }
  17. // 当所有准备工作就绪,run方法执行完成之前,该方法被调用
  18. default void running(ConfigurableApplicationContext context) {
  19. }
  20. // 当启动过程中出现错误时,该方法被调用
  21. default void failed(ConfigurableApplicationContext context, Throwable exception) {
  22. }
  23. }

可以看到,SpringApplicationRunListener 为 run 方法提供了各个运行阶段的监听事件处理能力。Spring 默认提供了 EventPublishingRunListener 实现类,该实现类会获取 Spring 标准的事件监听器 ApplicationListener,并在对应方法处广播一个 Spring 标准的事件。因此,通常我们只需要实现 ApplicationListener 接口并指定要处理的 Spring 事件即可。

  1. public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {
  2. public EventPublishingRunListener(SpringApplication application, String[] args) {
  3. this.application = application;
  4. this.args = args;
  5. this.initialMulticaster = new SimpleApplicationEventMulticaster();
  6. // 获取所有注册的ApplicationListener接口
  7. for (ApplicationListener<?> listener : application.getListeners()) {
  8. this.initialMulticaster.addApplicationListener(listener);
  9. }
  10. }
  11. // 广播消息事件,发布一个标准spring事件
  12. @Override
  13. public void starting() {
  14. this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));
  15. }
  16. ......
  17. }

2. 初始化 ConfigurableEnvironment

之后,通过 prepareEnvironment() 方法对 ConfigurableEnvironment 对象进行初始化操作。该接口的主要作用是提供当前运行环境的公开接口,比如:配置文件 profiles 各类系统属性和变量的设置、添加、读取、合并等功能。prepareEnvironment() 方法的代码如下:

  1. private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
  2. ApplicationArguments applicationArguments) {
  3. // 获取或创建ConfigurableEnvironment对象
  4. ConfigurableEnvironment environment = getOrCreateEnvironment();
  5. // 配置环境,加载所有Environment属性,主要包括PropertySources和activeProfiles的配置
  6. configureEnvironment(environment, applicationArguments.getSourceArgs());
  7. // 将ConfigurationPropertySources附加到指定环境中的第一位,并动态跟踪环境的添加和删除
  8. ConfigurationPropertySources.attach(environment);
  9. // 触发监听事件
  10. listeners.environmentPrepared(environment);
  11. // 将环境绑定到SpringApplication
  12. bindToSpringApplication(environment);
  13. // 判断是否是定制的环境,如果不是定制的则将环境转换为StandardEnvironment
  14. if (!this.isCustomEnvironment) {
  15. environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
  16. }
  17. // 将ConfigurationPropertySources附加到指定环境中的第一位,并动态跟踪环境的添加和删除
  18. ConfigurationPropertySources.attach(environment);
  19. return environment;
  20. }

在 configureEnvironment() 方法中完成了对资源属性的加载,具体逻辑如下:

  1. protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
  2. // 如果为true,则需要获取转换服务实例,并对环境设置转换服务
  3. if (this.addConversionService) {
  4. ConversionService conversionService = ApplicationConversionService.getSharedInstance();
  5. environment.setConversionService((ConfigurableConversionService) conversionService);
  6. }
  7. // 配置PropertySources
  8. configurePropertySources(environment, args);
  9. // 配置Profiles
  10. configureProfiles(environment, args);
  11. }

其中,configurePropertySources 方法用来对 PropertySources 进行配置,PropertySources 中的属性包括系统属性(JVM 参数)以及操作系统当前环境变量(env 命令)。代码如下:

  1. protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) {
  2. // 获得环境中的属性资源信息
  3. MutablePropertySources sources = environment.getPropertySources();
  4. // 如果默认属性配置存在则将其放置于属性资源的最后位置
  5. if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) {
  6. sources.addLast(new MapPropertySource("defaultProperties", this.defaultProperties));
  7. }
  8. // 如果命令行属性存在
  9. if (this.addCommandLineProperties && args.length > 0) {
  10. String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME;
  11. // 如果默认属性资源中包含该命令,则通过CompositePropertySource进行处理
  12. if (sources.contains(name)) {
  13. PropertySource<?> source = sources.get(name);
  14. CompositePropertySource composite = new CompositePropertySource(name);
  15. composite.addPropertySource(
  16. new SimpleCommandLinePropertySource("springApplicationCommandLineArgs", args));
  17. composite.addPropertySource(source);
  18. sources.replace(name, composite);
  19. } else {
  20. // 如果默认属性资源中不包含该命令,则将命令行属性放置在第一位
  21. sources.addFirst(new SimpleCommandLinePropertySource(args));
  22. }
  23. }
  24. }

这段代码需要重点看一下参数的优先级处理和默认参数与命令行参数之间的关系。首先,如果存在默认属性配置,则将默认属性配置放置在最后,也就是说优先级最低。然后,如果命令行参数存在则会出现两种情况:如果命令行的参数已经存在于属性配置中,则使用 CompositePropertySource 进行相同 name 的参数处理;如果命令行的参数并不存在于属性配置中,则直接将其设置为优先级最高。

完成了 PropertySources 配置,随后通过 configureProfiles 方法来完成 Profiles 的配置,代码如下:

  1. protected void configureProfiles(ConfigurableEnvironment environment, String[] args) {
  2. // 如果存在额外的Profiles,则将其放置在第一位
  3. Set<String> profiles = new LinkedHashSet<>(this.additionalProfiles);
  4. // getActiveProfiles方法保证了环境的activeProfiles属性被初始化,如果未初始化该方法会对其初始化
  5. profiles.addAll(Arrays.asList(environment.getActiveProfiles()));
  6. environment.setActiveProfiles(StringUtils.toStringArray(profiles));
  7. }

上面代码主要用来处理应用环境中哪些配置文件处于激活状态或默认激活状态,对应的配置就是我们经常使用的用来区分不同环境的 spring.profiles.active 参数指定的值。

3. 忽略信息配置

经过以上步骤,ConfigurableEnvironment 的初始化和准备工作已经完成。之后,程序又对环境中的忽略信息配置项 spring.beaninfo.ignore 的值进行获取判断,进而设置为系统参数中的忽略项。

  1. private void configureIgnoreBeanInfo(ConfigurableEnvironment environment) {
  2. if (System.getProperty(CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME) == null) {
  3. // 获取环境中spring.beaninfo.ignore的配置
  4. Boolean ignore = environment.getProperty("spring.beaninfo.ignore", Boolean.class, Boolean.TRUE);
  5. // 设置对应的系统参数
  6. System.setProperty(CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME, ignore.toString());
  7. }
  8. }

spring.beaninfo.ignore 的配置用来决定是否跳过 BeanInfo 类的扫描,如果设置为 true 则跳过。

4. 创建 Spring 应用上下文

Spring Boot 创建 Spring 的应用上下文时,如果未指定要创建的类,则会根据 WebApplicationType 来进行默认上下文类的创建。具体方法如下:

  1. protected ConfigurableApplicationContext createApplicationContext() {
  2. // 首先获取容器的类变量
  3. Class<?> contextClass = this.applicationContextClass;
  4. // 如果为null,则根据Web应用类型按照默认类进行创建
  5. if (contextClass == null) {
  6. try {
  7. switch (this.webApplicationType) {
  8. case SERVLET:
  9. contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
  10. break;
  11. case REACTIVE:
  12. contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
  13. break;
  14. default:
  15. contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
  16. }
  17. } catch (ClassNotFoundException ex) { ... }
  18. }
  19. // 通过反射来实例化应用上下文
  20. return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
  21. }

那在什么时候 applicationContextClass 变量不为 null 呢?当我们创建 SpringApplication 后,在调用 run 方法之前,调用其 setApplicationContextClass 方法可以指定 ConfigurableApplicationContext 的设置。但要注意,该方法不仅设置了 applicationContextClass 的值,同时也设置了 WebApplicationType 的值,需慎用。

5. 准备 Spring 应用上下文

  1. private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
  2. SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
  3. // 设置应用上下文环境
  4. context.setEnvironment(environment);
  5. // 应用上下文后置处理,主要是为容器设置一些属性
  6. postProcessApplicationContext(context);
  7. // 初始化容器
  8. applyInitializers(context);
  9. // 通知监听器context准备完成,该方法以上为上下文准备阶段,以下为上下文加载阶段
  10. listeners.contextPrepared(context);
  11. // 获取ConfigurableListableBeanFactory并注册单例对象
  12. ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
  13. beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
  14. // 判断并设置BeanFactory单例对象是否允许覆盖注册
  15. if (beanFactory instanceof DefaultListableBeanFactory) {
  16. ((DefaultListableBeanFactory) beanFactory).setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
  17. }
  18. // 获取全部配置源,其中包含primarySources和sources
  19. Set<Object> sources = getAllSources();
  20. // 将sources中的Bean加载到context中
  21. load(context, sources.toArray(new Object[0]));
  22. // 通知监听器context加载完成
  23. listeners.contextLoaded(context);
  24. }

5.1 准备阶段

在 applyInitializers 方法中对上下文进行了初始化操作,所使用的 ApplicationContextInitializer 正是我们在 SpringApplication 初始化阶段设置在 initializers 变量中的值,只不过在通过 getInitializers() 方法获取时进行了去重和排序操作。

  1. protected void applyInitializers(ConfigurableApplicationContext context) {
  2. for (ApplicationContextInitializer initializer : getInitializers()) {
  3. // 解析当前ApplicationContextInitializer的泛型参数
  4. Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(),
  5. ApplicationContextInitializer.class);
  6. // 断言判断所需类是否与context类型匹配
  7. Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
  8. // 回调其initialize方法用来初始化context
  9. initializer.initialize(context);
  10. }
  11. }

完成以上操作后,程序便调用 SpringApplicationRunListeners 的 contextPrepared 方法来通知监听器,该方法会发布一个 ApplicationContextInitializedEvent 事件,来告知监听器 Spring 容器已经完成准备操作。至此第一阶段的准备操作完成。

5.2 加载阶段

进入应用上下文的加载阶段,首先是获取 ConfigurableListableBeanFactory 并注册 ApplicationArguments 单例对象。当 BeanFactory 为 DefaultListableBeanFactory 时,进入设置是否允许覆盖注册的处理逻辑。注意,当进行了 ApplicationArguments 类单例对象的注册之后,也就意味着我们在使用 Spring 应用上下文的过程中可以通过依赖注入来使用该对象。

  1. @Resource
  2. private ApplicationArguments applicationArguments

完成以上操作后,便进入配置源信息的处理阶段,配置源包括调用 run 方法时传入的 primarySources,还包括通过 setSources 方法配置的配置源。当通过 getAllSources 方法获取到所有配置源信息后,之后会通过 load 方法将配置源信息加载到上下文中:

  1. protected void load(ApplicationContext context, Object[] sources) {
  2. BeanDefinitionLoader loader = createBeanDefinitionLoader(getBeanDefinitionRegistry(context), sources);
  3. if (this.beanNameGenerator != null) {
  4. loader.setBeanNameGenerator(this.beanNameGenerator);
  5. }
  6. if (this.resourceLoader != null) {
  7. loader.setResourceLoader(this.resourceLoader);
  8. }
  9. if (this.environment != null) {
  10. loader.setEnvironment(this.environment);
  11. }
  12. loader.load();
  13. }

该方法通过 BeanDefinitionLoader 来完成配置资源的加载操作,进一步查看 createBeanDefinitionLoader 方法会发现它最终调用了 BeanDefinitionLoader 的构造方法,并进行初始化操作:

  1. BeanDefinitionLoader(BeanDefinitionRegistry registry, Object... sources) {
  2. this.sources = sources;
  3. this.annotatedReader = new AnnotatedBeanDefinitionReader(registry);
  4. this.xmlReader = new XmlBeanDefinitionReader(registry);
  5. if (isGroovyPresent()) {
  6. this.groovyReader = new GroovyBeanDefinitionReader(registry);
  7. }
  8. this.scanner = new ClassPathBeanDefinitionScanner(registry);
  9. this.scanner.addExcludeFilter(new ClassExcludeFilter(sources));
  10. }

可以看到,BeanDefinitionLoader 支持基于 AnnotatedBeanDefinitionReader、XmlBeanDefinitionReader、GroovyBeanDefinitionReader 等多种类型的加载操作。之后,调用其 load 方法进行真正的 Bean 加载操作,并且会针对每个 sources 都会执行一次 load 方法,具体逻辑如下:

  1. private int load(Object source) {
  2. if (source instanceof Class<?>) {
  3. // 通过AnnotatedBeanDefinitionReader读取该类相关注解,并注册对应Bean
  4. return load((Class<?>) source);
  5. }
  6. if (source instanceof Resource) {
  7. // 通过XmlBeanDefinitionReader读取xml资源文件
  8. return load((Resource) source);
  9. }
  10. if (source instanceof Package) {
  11. // 通过ClassPathBeanDefinitionScanner递归扫描指定包路径加载bean
  12. return load((Package) source);
  13. }
  14. if (source instanceof CharSequence) {
  15. return load((CharSequence) source);
  16. }
  17. throw new IllegalArgumentException("Invalid source type " + source.getClass());
  18. }

可以看到,BeanDefinitionLoader 加载支持的范围包括:Class、Resource、Package 和 CharSequence 这四种类型。前面我们提到变量 sources 的来源有 primarySources 配置源和 sources 配置源,前者在初始化时接收的类型为 Class,而后者通过 set(Set) 方法接收的参数为 String 集合。因此,实际执行时 Resource 和 Package 的判断分支始终无法进入执行阶段。

完成以上操作后,接下来会执行 SpringApplicationRunListeners 的 contextLoaded 方法通知监听器上下文加载完成,至此整个 Spring 应用上下文的准备阶段完成。

6. 刷新 Spring 应用上下文

刷新 Spring 应用上下文的核心方法为 AbstractApplicationContext 的 refresh() 方法,该类属于 spring-context 包,其内部逻辑更多的是 Spring 相关的内容。这里我们简单了解下,不过多深入 Spring 内部:

  1. public void refresh() throws BeansException, IllegalStateException {
  2. // 整个过程进行线程同步处理
  3. synchronized (this.startupShutdownMonitor) {
  4. // 准备刷新工作
  5. prepareRefresh();
  6. // 通知子类刷新内部bean工厂
  7. ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
  8. // 为当前context准备bean工厂
  9. prepareBeanFactory(beanFactory);
  10. try {
  11. // 允许context的子类对bean工厂进行后置处理
  12. postProcessBeanFactory(beanFactory);
  13. // 调用context中注册为bean的工厂处理器
  14. invokeBeanFactoryPostProcessors(beanFactory);
  15. // 注册bean处理器(beanPostProcessors)
  16. registerBeanPostProcessors(beanFactory);
  17. // 初始化context的信息源,和国际化有关
  18. initMessageSource();
  19. // 初始化context的事件传播器
  20. initApplicationEventMulticaster();
  21. // 初始化其他子类特殊的bean
  22. onRefresh();
  23. // 检查并注册事件监听器
  24. registerListeners();
  25. // 实例化所有非懒加载单例
  26. finishBeanFactoryInitialization(beanFactory);
  27. // 发布对应容器刷新事件
  28. finishRefresh();
  29. }
  30. ......
  31. }
  32. }

当执行完 finishRefresh 方法初始化容器的生命周期处理器并发布容器的生命周期事件之后,Spring 应用上下文正式开启,Spring Boot 的核心特性也随之启动。之后,调用 SpringApplicationRunListeners 的 started 方法通知监听器容器启动完成。

7. 调用扩展处理器

在执行流程的最后,会调用 callRunners() 方法来调用实现了 CommandLineRunner 或 ApplicationRunner 接口的实现类的 run() 方法,Spring Boot 提供这两个接口的目的,是为了我们在开发的过程中,通过它们来实现在容器启动时执行的一些自定义操作,以满足需要在 Spring 应用上下文完全准备完毕后执行一些操作的场景。

  1. private void callRunners(ApplicationContext context, ApplicationArguments args) {
  2. List<Object> runners = new ArrayList<>();
  3. runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
  4. runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
  5. AnnotationAwareOrderComparator.sort(runners);
  6. for (Object runner : new LinkedHashSet<>(runners)) {
  7. if (runner instanceof ApplicationRunner) {
  8. callRunner((ApplicationRunner) runner, args);
  9. }
  10. if (runner instanceof CommandLineRunner) {
  11. callRunner((CommandLineRunner) runner, args);
  12. }
  13. }
  14. }

如果接口有多个实现类,可通过 @Order 注解或实现 Ordered 接口来控制执行顺序。这两个接口都提供了一个 run 方法,不同之处在于:ApplicationRunner 中 run 方法的参数为 ApplicationArguments,而 CommandLineRunner 接口中 run 方法的参数为 String 数组。

以上方法执行完成后,会通过 SpringApplicationRunListeners 的 running 方法通知监听器:容器此刻已处于运行状态。至此,SpringApplication 的 run 方法全部执行完毕。