目录
2.2 配置ApplicationContextInitializer、ApplicationListener
3.1 SpringApplicationRunListener
3.2.2 ApplicationEnvironmentPreparedEvent事件的处理
1.Spring Boot简介
Spring MVC非常强大,但是也存在以下问题:
- 配置繁琐,哪怕一个最小项目,也要配置至少三个XML文件
- 依赖Tomcat等Web容器运行,增加了维护难度
- 与常用组件集成起来不方便,例如与Mybatis集成,每个项目都要配置数据源、事务等,但是这些配置实际都是通用的,完全可以按照约定提供默认情况
Spring Boot就是为了解决这一情况的,它具有如下特点(来源于百度百科):
- 创建独立的Spring应用程序
- 嵌入的Tomcat,无需部署WAR文件
- 简化Maven配置
- 自动配置Spring
- 提供生产就绪型功能,如指标,健康检查和外部配置
- 绝对没有代码生成并且对XML也没有配置要求
一个最简单的Spring Boot项目组成如下:
pom.xml(部分):
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.1.0.RELEASE</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.1.0.RELEASE</version>
</plugin>
</plugins>
</build>
主类:
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
Controller:
@RestController
public class TestController {
@GetMapping("/")
public String hello(){ return "hello"; }
}
运行主类后,在浏览器访问localhost:8080就可以看到打印出了“hello”。
2.Spring Boot启动原理 —— 构造方法
Spring Boot项目是通过SpringApplication.run方法作为入口启动的,它接受了两个参数:一个是Class类型,另一个是String[]类型,run方法做了两次跳转:第一次将Class对象封装为Class[]数组,转发给自身的重载;第二次用Class[]对象实例化SpringApplication对象,然后将String[]对象传入run成员方法(main函数调用的是静态方法):
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class<?>[] { primarySource }, args);
}
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}
首先来看它的构造函数,resourceLoader默认为null,主要是一些成员属性的设置:
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
this.webApplicationType = WebApplicationType.deduceFromClasspath();
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
2.1 deduceFromClasspath方法
从方法名和上下文不难看出,deduceFromClasspath的作用就是推断Web程序类型:
static WebApplicationType deduceFromClasspath() {
if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null)
&& !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
return WebApplicationType.REACTIVE;
}
for (String className : SERVLET_INDICATOR_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
}
return WebApplicationType.SERVLET;
}
- Reactive类型:这是Spring 5新增的类型,需要有DispatcherHandler类实现,且没有DispatcherServlet和ServletContainer实现
- Servlet类型:传统Web应用,需要有ConfigurableWebApplicationContext、DispatcherServlet/ServletContainer实现
- None类型:不是Web应用
2.2 配置ApplicationContextInitializer、ApplicationListener
这一步由两个方法构成:setInitializers/setListeners和getSpringFactoriesInstances,前者就是赋值操作,主要来看后者。getSpringFactoriesInstances方法源码如下,显而易见,它的核心方法是loadFactoryNames和createSpringFactoriesInstances:
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = getClassLoader();
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
首先来看loadFactoryNames:
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
loadSpringFactories源码如下,其实就是从properties文件中加载配置:
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryClassName = ((String) entry.getKey()).trim();
for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryClassName, factoryName.trim());
}
}
}
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
FACTORIES_RESOURCE_LOCATION常量的字面值是“META-INF/spring.factories”。也就是说,这里查找了以ApplicationContextInitializer和ApplicationListener的全限定名为键的配置值。
在org.springframework.boot:spring-boot包下,可以看到如下配置:
# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.ConfigFileApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.context.logging.LoggingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener
此外,在spring-boot-autoconfigure下还配置了一些:
# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer
这6+10个类就是Spring Boot默认加载的加载器和监听器。
spring.factories文件的配置生效需要靠createSpringFactoriesInstances。该方法实际就是反射实例化spring.factories中配置的类,并将实例添加到List中:
private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args, Set<String> names) {
List<T> instances = new ArrayList<>(names.size());
for (String name : names) {
try {
Class<?> instanceClass = ClassUtils.forName(name, classLoader);
Assert.isAssignable(type, instanceClass);
Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
T instance = (T) BeanUtils.instantiateClass(constructor, args);
instances.add(instance);
}
catch (Throwable ex) {
throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
}
}
return instances;
}
3.Spring Boot启动原理 —— run方法
run方法是Spring Boot的核心,在这里完成了ApplicationContext的启动和初始化。它的流程如下:
3.1 SpringApplicationRunListener
首先获取和启动了SpringApplicationRunListener,实际也借助了getSpringFactoriesInstances,Spring Boot默认提供的实现是EventPublishingRunListener:
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
starting方法广播了一个ApplicationStartingEvent。
3.2 环境准备
先上源码,一共做了三件事:构造ApplicationArguments对象、准备环境、配置是否忽略Bean信息:
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);
configureIgnoreBeanInfo(environment);
DefaultApplicationArguments构造方法很简单:
public DefaultApplicationArguments(String... args) {
Assert.notNull(args, "Args must not be null");
this.source = new Source(args);
this.args = args;
}
Source是其内部类,是一个PropertySource实现类。
3.2.1 prepareEnvironment
这里的核心逻辑是获取并配置Environment对象,首先调用getOrCreateEnvironment方法,获取已有实例或根据Web程序类型创建新实例:
private ConfigurableEnvironment getOrCreateEnvironment() {
if (this.environment != null) {
return this.environment;
}
switch (this.webApplicationType) {
case SERVLET:
return new StandardServletEnvironment();
case REACTIVE:
return new StandardReactiveWebEnvironment();
default:
return new StandardEnvironment();
}
}
一般来说创建的都是StandardServletEnvironment,它会初始化四个PropertySource:
- servletContextInitParams
- servletConfigInitParams
- System.getenv()
- System.getProperties()
后两个来自其父类StandardEnvironment。如果JNDI环境可用,还会额外增加jndiProperties。
然后调用configureEnvironment进行配置,实际做了三件事:配置ConversionService、配置PropertySource、配置Profile,这三个都是很熟悉的概念了,不做赘述:
protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
if (this.addConversionService) {
ConversionService conversionService = ApplicationConversionService.getSharedInstance();
environment.setConversionService((ConfigurableConversionService) conversionService);
}
configurePropertySources(environment, args);
configureProfiles(environment, args);
}
之后就是一些后处理操作,主要就是绑定和转换操作:
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
}
ConfigurationPropertySources.attach(environment);
isCustomEnvironment默认是false,所以会进行转换,这里是检查经过一番处理后,environment还是不是创建时的类型。
3.2.2 ApplicationEnvironmentPreparedEvent事件的处理
此时容器环境已经准备完毕,通过SpringApplicationRunListener发出ApplicationEnvironmentPreparedEvent事件:
listeners.environmentPrepared(environment);
该事件会触发ConfigFileApplicationListener进行处理:
private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
List<EnvironmentPostProcessor>