第二章 Spring Boot 启动流程(二)—— Spring Cloud技术初探系列

本文详细解析了SpringApplication的run方法,包括SpringBoot启动流程中的关键步骤:加载SpringApplicationRunListener,创建并初始化Environment,实例化ApplicationContext,加载BeanDefinition,以及调用refresh方法启动Spring容器。此外,介绍了BeanDefinitionLoader和BeanPostProcessor的重要性,以及如何通过ApplicationRunner和CommandLineRunner接口处理main方法参数。
摘要由CSDN通过智能技术生成

SpringApplication的构造函数在启动流程中只做了简单的初始化工作,接下来框架的主动脉——run方法才是承担整个框架启动流程的核心。本章会对流程的主干做概要介绍,具体分支细节则在后续章节中一一分析。首先来看run方法的具体代码:
SpringApplication run()
在第1步中,同样使用SpringFactoriesLoader机制加载SpringApplicationRunListener扩展,这个监听器是SpringBoot中应用启动生命周期中的事件监听接口,可以在run方法中看到在不同的启动阶段中会调用监听器的对应方法。这个接口是SpringBoot中的API,和Spring容器中的事件机制不是同一个功能。

第2步中,则根据当前的应用类型(第一章中提到的)实例化对应的Environment实例。这个接口的功能在Spring 容器用于获取当前应用运行环境的一些信息,比如profile,properties等,在Spring容器中充当很重要的角色。在以往的应用架构中,配置中心中间件的实现就可以通过扩展Environment中的PropertySource,从而达到当Spring容器启动时,从远程获取配置键值对的目的。

接下来就该敲黑板啦,重点来了。在第3步之前,有一个createApplicationContext()方法,该方法只是根据当前的应用类型实例化对应的ApplicationContext对象。例如对于SERVLET应用,实例化AnnotationConfigServletWebServerApplicationContext,对于REACTIVE应用,则实例化AnnotationConfigReactiveWebServerApplicationContext。然后在第3步中,对这个实例进行初始化操作,比如设置第2步中的Environment;依次调用第一章中介绍的ApplicationContextInitializer,对ApplicationContext进行扩展配置。贴出代码片段:

private void prepareContext(ConfigurableApplicationContext context,
			ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments, Banner printedBanner) {
		context.setEnvironment(environment);
		postProcessApplicationContext(context);
		applyInitializers(context);
		listeners.contextPrepared(context);
		if (this.logStartupInfo) {
			logStartupInfo(context.getParent() == null);
			logStartupProfileInfo(context);
		}
	context.getBeanFactory().registerSingleton("springApplicationArguments",
				applicationArguments);
		if (printedBanner != null) {
			context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
		}
		Set<Object> sources = getAllSources();
		Assert.notEmpty(sources, "Sources must not be empty");
		load(context, sources.toArray(new Object[0]));
		listeners.contextLoaded(context);
	}

接下来,重中之重——load方法。在Spring内部,使用BeanDefinition这个抽象来表示一个Bean的定义。在以往Servlet容器中集成Spring,我们是通过配置ContextLoaderListener,然后指定spring bean配置文件的位置来告诉Spring应该去哪里加载BeanDefinition。而在Spring Boot中则是通过指定sources的方式来告诉容器应该去哪里加载。source有很多种,例如可以在SpringApplication的构造函数中指定Class类型的source,Spring则会解析该类上的相关配置信息;也可以在调用run方法前,通过调用setSources(Set<String> sources) 设置string类型的source,该string则就是我们所熟悉的xml路径,当然Spring在这里将这个string类型的source进行了扩展,因此这里可以是一个xml文件路径,也可以是一个类名,也可能是一个包名,具体实现方式可以在load方法中找到。从而,在我们的启动代码中:

@SpringBootApplication
@RestController
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class,args);
    }
    @GetMapping("/hello")
    public String sayHello(){
        return "Hello World";
    }
}

意思就很明确了,使用Application.class这个类作为source。Spring容器在启动的时候会从这个类所在的包以及子包下扫描所有能用来读取BeanDefinition的类(即添加了某些注解的类),常见的有@Configuration和@Bean配合使用的场景。至于在本示例中出现的@SpringBootApplication和@RestController注解会在后续章节中讨论到。

再回到load方法的内部:

/**
	 * Load beans into the application context.
	 * @param context the context to load beans into
	 * @param sources the sources to load
	 */
	protected void load(ApplicationContext context, Object[] sources) {
		if (logger.isDebugEnabled()) {
			logger.debug(
					"Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
		}
		BeanDefinitionLoader loader = createBeanDefinitionLoader(
				getBeanDefinitionRegistry(context), sources);
		if (this.beanNameGenerator != null) {
			loader.setBeanNameGenerator(this.beanNameGenerator);
		}
		if (this.resourceLoader != null) {
			loader.setResourceLoader(this.resourceLoader);
		}
		if (this.environment != null) {
			loader.setEnvironment(this.environment);
		}
		loader.load();
	}

将load的工作较给了BeanDefinitionLoader完成,在内部,定义了

	private final AnnotatedBeanDefinitionReader annotatedReader;
	private final XmlBeanDefinitionReader xmlReader;
	private BeanDefinitionReader groovyReader;
	private final ClassPathBeanDefinitionScanner scanner;
	private ResourceLoader resourceLoader;

这些DefinitionReader来读取不同类型source的Bean定义。经过load方法后,spring 容器中则会注册各种必要的Bean的定义。

在第4步中,则是调用当前ApplicationContext的refresh方法。熟悉Spring Framework源码的的同学对这个方法就比较熟悉了,该方法是整个Spring容器启动的核心。在该方法中会完成Bean的实例化,然后进行装配,同时在容器启动生命周期的各个不同阶段中应用BeanPostProcessor扩展接口。对此流程感兴趣的同学可以参考《spring源码深度解析》或者《spring技术内幕》,开涛的《Spring杂谈》系列也有部分原理性的解析文章,在此就不再赘述。

spring 容器启动完成之后,在第5步的位置调用了容器中实现ApplicationRunner或者CommandLineRunner接口的bean对应的回调方法,这两个接口能够使得运行在spring容器中的bean实例可以有一个入口获得Spring Boot在启动时的main方法入参。因为Spring Boot是通过业务代码的main方法启动,因此可以在启动的命令行中携带main方法的入参,但是这些入参首先是被Spring Boot框架获取到的,于是通过设计这两个接口,使得业务bean也能拿到这些参数。在此不得不佩服Spring框架的设计者们,为开发者考虑到了如此方方面面的小细节。

小结

至此,Spring Boot的主要启动流程就已经分析完了,大致经过的步骤可以用下图来概括:
在这里插入图片描述
其中在第2、3、4、8步中,提供了SPI扩展接口,最常使用的应该就是第2步中的ApplicationContextInitializer扩展了。这个扩展可以在spring容器启动之前对ApplicationContext做各种设置,比如添加自定义的BeanPostProcessor,从而完成各种复杂的功能增强。BeanPostProcessor是Spring Framework中核心扩展点,常见的AOP以及基于注解的各种功能基本上都是靠这个扩展实现的。由此可见Spring框架设计的优雅,将“开闭原则”体现的淋漓尽致。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值