Spring Boot 简介
Spring Boot 采用约定大于配置的思想对应用程序进行默认配置,减少了大量的配置时间。
Spring Boot 包含的特性如下:
- 创建可以独立运行的 Spring 应用。
- 直接嵌入 Tomcat 或 Jetty 服务器,不需要部署 war 文件。
- 提供推荐的基础pom文件来简化 Apache Maven配置。
- 尽可能的根据项目依赖来自动配置 Spring 框架。
- 提供可以直接在生产环境中使用的功能,如性能指标,应用信息,和应用健康检查。
- 没有代码生成,也没有 xml 配置文件。
通过 Spring Boot,创建新的 Spring 应用变得非常容易,而且创建出的 Spring 应用符合通用的最佳实践。只需要简单几个步骤就可以创建出一个Web应用。对于 Spring Boot 的创建,请参考 Spring 官方文档。
Spring Boot 加载
因为 Spring Boot 内置了 Tomcat 或 Jetty 服务器,不需要直接部署 war 文件,所以 Spring Boot 的程序入口为一个普通的主函数,主函数类如下:
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
看似和普通的程序没有什么区别,其实它最主要的步骤都是通过注解@SpringBootApplication
和方法SpringApplication.run()
完成了。
其实所有的依赖在这一步就可以完成注入,主要的步骤是:Spring 读取所有依赖中的META-INF/spring.factories
文件,该文件指明了那些依赖可以被自动加载,然后根据ImportSelector
类选择加载那些依赖,使用ConditionOn
系列注解排除掉不需要的配置文件,最后将剩余的配置文件所代表的 Bean 加载到 Spring 的 IOC 容器中去。
META-INF/spring.factories
文件的读取
Spring 所有的配置文件都分布在 jar 包的META-INF/spring.factories
中,如下:
每一个META-INF/spring.factories
为一个键值对列表,截取了部分spring-boot
jar包下的spring.factories
文件,如下:
# PropertySource Loaders
org.springframework.boot.env.PropertySourceLoader=\
org.springframework.boot.env.PropertiesPropertySourceLoader,\
org.springframework.boot.env.YamlPropertySourceLoader
# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener
# 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.context.web.ServerPortInfoApplicationContextInitializer
# Application Listeners
org.springframework.context.ApplicationListener=\
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.liquibase.LiquibaseServiceLocatorApplicationListener,\
org.springframework.boot.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.logging.LoggingApplicationListener
在程序中,这段列表会被解析为Map<K, List<V>>
的格式,即一个MutiValueMap
(一个健对应多个值的Map),键和每个值都是一个类的全限定名。
主函数中,只有一个方法,即SpringApplication.run()
,如下:
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
我们来看一个读取META-INF/spring.factories
的例子。
在程序初始化时,即运行new SpringApplication()
时,就会读取一次配置文件,我们来分析是如何读取配置文件的。
SpringApplication.run(DemoApplication.class, args)
方法是SpringApplication
类提供的一个静态方法,调用的是run(new Class<?>[] { primarySource }, args)
方法。如下:
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}
其中,读取配置文件的操作就发生在SpringApplication
类的实例化过程中。实例化的代码如下:
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 把初始化的初始化器存到数组中
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 把初始化的监听器加入到数组中
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 获得主类
this.mainApplicationClass = deduceMainApplicationClass();
}
在getSpringFactoriesInstances(ApplicationContextInitializer.class)
中 Spring 读取了spring.factories
文件。如下:
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = getClassLoader();
// Use names and ensure unique to protect against duplicates
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
// 直接利用反射创建实例
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader)
函数:该函数是SpringFactoriesLoader
类提供的一个静态方法,主要用于读取factoryClass
的类全限定名这个键在spring.factories
中的值列表。
loadSpringFactories(classLoader)
函数读取所有的配置文件中的属性的键值对,并把值缓存到一个cache
中,在需要进行读取的时候读取。
执行完loadSpringFactories(classLoader)
函数后,spring.factories
配置文件中的键值对将变成如下形式缓存到cache
中:
// 以属性文件的 key 作为键,value 作为值,注意这个值用一个链表表示,因为可能一个键对应多个值
cache :
key = classloader.toString()
->
value = {
"org.springframework.boot.env.PropertySourceLoader"
-> ["org.springframework.boot.env.PropertiesPropertySourceLoader", "org.springframework.boot.env.YamlPropertySourceLoader"],
"org.springframework.boot.SpringApplicationRunListener"
-> ["org.springframework.boot.context.event.EventPublishingRunListener"],
"org.springframework.boot.SpringBootExceptionReporter"
-> ["org.springframework.boot.diagnostics.FailureAnalyzers"],
"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"],
....只列举部分,其他的省略
}
具体加载和读取配置文件的源码如下所示:
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 ?
// FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories" 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);
}
// output
// like:
// org.springframework.boot.autoconfigure.EnableAutoConfiguration -> {LinkedList@1446} size = 118
// |
// |- org.springframework.boot.autoconfigure.EnableAutoConfiguration
}
以指定的factoryClass
的类全限定名为键,尝试从缓存中取出值,对于ApplicationContextInitializer.class
这个factoryClass
来说,会取出所有键为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"
总结
SpringFactoriesLoader
类的静态方法实现了依赖文件的读取,读取所有的配置类的全限定名名称。SpringApplication
在初始化时就完成了最基本配置的读取和实例化。- 而这个
cache
在 Spring 上下文加载当中非常重要。
Spring 上下文加载
前面描述了如何将配置文件所在的类加载到内存中。那么 Spring Boot 的自动化配置时如何实现的呢?
在 Spring Mvc 中,我们需要手工配置试图解析器等基础配置,需要如下配置:
@Bean
public ViewResolver viewResolver() {
return new ThymeleafViewResolver();
}
既然 Spring Boot 可以实现自动化配置,那么在 Spring Boot 中,则一定也有类似的语句加载默认的试图解析器。
注解简介
先介绍几个 Spring Boot 的配置注解
-
@SpringBootApplication
@SpringBootApplication
是一个复合注解,定义如下:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration // 注解一
@EnableAutoConfiguration // 注解二
// 没有配置 basepackages,默认代表当前包及其子包
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) // 注解三
public @interface SpringBootApplication {
// ...
}
注解一代表配置注解,定义如下:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
// ...
}
注解一可以把由@SpringBootApplication
注解修饰的类作为配置类来使用,在DemoApplication
类中由@Bean
注解修饰的代码都会被注入到 Spring 的 Ioc 容器中,由 Spring 来统一管理。
注解三会自动扫描该类所在包,将各种组件注入到 Ioc 容器中,所以我们也可以在DemoApplication
类上增加@Controller
等注解,将其作为一个 Controller 。
-
@EnableAutoConfiguration
@EnableAutoConfiguration
定义如下:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage // 注解一
@Import(AutoConfigurationImportSelector.class) // 注解二
public @interface EnableAutoConfiguration {
// ...
}