引入spring boot actuator,开启所有endpoint,可以查看程序内部运行时的beans
什么是上下文的层次性?spring在启动时,可能需要启动多个上下文,可能某一个上下文是另一个上下文的parent。在程序初始化上下文时,parent级别的上下文优先于children初始化,parent初始化的bean能够在children中使用。这种父子的优先级与继承关系可以看作层次性。
在spring clound中,新增了一个独立的上下文bootstrap,用于提前加载某些资源。可以引入spring-cloud-starter依赖,启动一个简单的程序,利用actuator查看上下文信息。
{
"contexts": {
"application-1": {
"beans": {},
"parentId": "bootstrap"
},
"bootstrap": {
"beans": {},
"parentId": null
}
}
}
可以看出,这里初始化了两个context,其中一个是bootstrap,另一个是application-1,关系就是。
application-1.parentId -> bootstrap
意味着两者之间具有层次性。
或者我们可以手动设置spring默认上下文的parent上下文,代码如下。
@EnableAutoConfiguration
public class BootstrapTestApplication2 {
public static void main(String[] args) {
AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext();
annotationConfigApplicationContext.setId("mycontext");
annotationConfigApplicationContext.refresh();
SpringApplicationBuilder builder = new SpringApplicationBuilder(BootstrapTestApplication2.class);
builder.parent(annotationConfigApplicationContext).run(args);
}
}
最终beans的内容展示如下。
{
"contexts": {
"application-1": {
"beans": {},
"parentId": "mycontext"
},
"mycontext": {
"beans": {},
"parentId": "bootstrap"
},
"bootstrap": {
"beans": {},
"parentId": null
}
}
}
可以看出,手动设置的上下文已经是application-1的parent,但是bootstrap依旧是最顶级的上下文,这是由于bootstrap中有必须优先加载的资源信息。
BeanFactory实际管理bean,parent的bean子context可以使用。
org.springframework.context.support.AbstractApplicationContext#setParent 时, parent的environment的合并到子的environment。
org.springframework.cloud.bootstrap.BootstrapApplicationListener#onApplicationEvent监听ApplicationEnvironmentPreparedEvent事件,读取spring.cloud.bootstrap.enabled(只能命令行输入)属性判断是否需要进行Bootstrap上下文初始化。如果需要,就执行org.springframework.cloud.bootstrap.BootstrapApplicationListener#bootstrapServiceContext进行构建Bootstrap上下文。
我们都知道在spring boot中的属性外部化配置具有一定的顺序,最常见的就是命令行>application。需要注意的是在spring cloud中,对于配置在bootstrap和Application中的配置信息,spring cloud 组件涉及的配置一般都是使用bootStrap中的配置,因为spring cloud的Bootstrap context初始化时只加载Bootstrap的配置信息,此时还没加载Application.properties中配置信息 ,所以spring cloud实际使用Bootstrap而不会是Application,但是在程序中,读取相同的配置(@Value),取得值最终一定是较后加载的配置文件源配置的属性,也就是会取道Application。那么这种顺序是如何实现的,或者说为何属性配置读取时会有一种被覆盖感觉?
一个ApplicationContext的属性配置依赖如上图所示,最终都是依赖到MutablePropertySources#propertySourceList里面,这里的PropertySource列表具有顺序,接下来会看一下是如何利用这个顺序实现配置优先级。
以命令行参数为例,跟踪一个springApplication的启动调用链如下。
protected void configurePropertySources(ConfigurableEnvironment environment,
String[] args) {
//获取Environment的属性源列表
MutablePropertySources sources = environment.getPropertySources();
if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) {
sources.addLast(
new MapPropertySource("defaultProperties", this.defaultProperties));
}
if (this.addCommandLineProperties && args.length > 0) {
String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME;
if (sources.contains(name)) {
PropertySource<?> source = sources.get(name);
CompositePropertySource composite = new CompositePropertySource(name);
composite.addPropertySource(new SimpleCommandLinePropertySource(
"springApplicationCommandLineArgs", args));
composite.addPropertySource(source);
//如果已经有命令行属性源,就替换这个
sources.replace(name, composite);
}
else {
//没有命令行属性源的话就加到第一个中
sources.addFirst(new SimpleCommandLinePropertySource(args));
}
}
}
sources.addFirst的操作就是保证属性读取优先级的关键,可以推测,属性读取是优先读取前面的,如果读取到就获取最前面的值。尝试跟踪读取Environment的属性的代码调用链,如下所示。
protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
if (this.propertySources != null) {
for (PropertySource<?> propertySource : this.propertySources) {
if (logger.isTraceEnabled()) {
logger.trace("Searching for key '" + key + "' in PropertySource '" +
propertySource.getName() + "'");
}
Object value = propertySource.getProperty(key);
if (value != null) {
if (resolveNestedPlaceholders && value instanceof String) {
value = resolveNestedPlaceholders((String) value);
}
logKeyFound(key, propertySource, value);
return convertValueIfNecessary(value, targetValueType);
}
}
}
if (logger.isDebugEnabled()) {
logger.debug("Could not find key '" + key + "' in any property source");
}
return null;
}
上面 的代码很明显按照propertySources的顺序遍历属性源,查找属性,越前面的属性源优先级越高,因此保证读取时,在前面的属性源属性优先级高。
actuator的env可以查看所有propertysource,优先级为从上到下的顺序。
Spring Boot uses a very particular PropertySource order that is designed to allow sensible overriding of values. Properties are considered in the following order:
1,Devtools global settings properties on your home directory (~/.spring-boot-devtools.properties when devtools is active).
2.@TestPropertySource annotations on your tests.
3.properties attribute on your tests. Available on @SpringBootTest and the test annotations for testing a particular slice of your application.
4.Command line arguments.(命令行参数)
5.Properties from SPRING_APPLICATION_JSON (inline JSON embedded in an environment variable or system property).
6.ServletConfig init parameters.
7.ServletContext init parameters.
8.JNDI attributes from java:comp/env.
9.Java System properties (System.getProperties()).
10.OS environment variables.
11.A RandomValuePropertySource that has properties only in random.*.
12.Profile-specific application properties outside of your packaged jar (application-{profile}.properties and YAML variants).(jar外的配置文件-指明dev\test\product)
13.Profile-specific application properties packaged inside your jar (application-{profile}.properties and YAML variants).(jar内部配置文件-指明dev\test\product)
14.Application properties outside of your packaged jar (application.properties and YAML variants).(jar外配置文件)
15.Application properties packaged inside your jar (application.properties and YAML variants).(jar内配置文件)
16.@PropertySource annotations on your @Configuration classes.
17.Default properties (specified by setting SpringApplication.setDefaultProperties).