前提
org.springframework.core.env.environment是当前应用运行环境的公开接口,主要包括应用程序运行环境的两个关键方面:配置文件(profiles)和属性。environment继承自接口propertyresolver,而propertyresolver提供了属性访问的相关方法。这篇文章从源码的角度分析environment的存储容器和加载流程,然后基于源码的理解给出一个生产级别的扩展。
本文较长,请用一个舒服的姿势阅读。
environment类体系
- propertyresolver:提供属性访问功能。
- configurablepropertyresolver:继承自propertyresolver,主要提供属性类型转换(基于org.springframework.core.convert.conversionservice)功能。
- environment:继承自propertyresolver,提供访问和判断profiles的功能。
- configurableenvironment:继承自configurablepropertyresolver和environment,并且提供设置激活的profile和默认的profile的功能。
- configurablewebenvironment:继承自configurableenvironment,并且提供配置servlet上下文和servlet参数的功能。
- abstractenvironment:实现了configurableenvironment接口,默认属性和存储容器的定义,并且实现了configurableenvironment种的方法,并且为子类预留可覆盖了扩展方法。
- standardenvironment:继承自abstractenvironment,非servlet(web)环境下的标准environment实现。
- standardservletenvironment:继承自standardenvironment,servlet(web)环境下的标准environment实现。
reactive相关的暂时不研究。
environment提供的方法
一般情况下,我们在springmvc项目中启用到的是standardservletenvironment,它的父接口问configurablewebenvironment,我们可以查看此接口提供的方法:
environment的存储容器
environment的静态属性和存储容器都是在abstractenvironment中定义的,configurablewebenvironment接口提供的getpropertysources()方法可以获取到返回的mutablepropertysources实例,然后添加额外的propertysource。实际上,environment的存储容器就是org.springframework.core.env.propertysource的子类集合,abstractenvironment中使用的实例是org.springframework.core.env.mutablepropertysources,下面看下propertysource的源码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
|
public abstract class propertysource<t> {
protected final log logger = logfactory.getlog(getclass());
protected final string name;
protected final t source;
public propertysource(string name, t source) {
assert .hastext(name, "property source name must contain at least one character" );
assert .notnull(source, "property source must not be null" );
this .name = name;
this .source = source;
}
@suppresswarnings ( "unchecked" )
public propertysource(string name) {
this (name, (t) new object());
}
public string getname() {
return this .name;
}
public t getsource() {
return this .source;
}
public boolean containsproperty(string name) {
return (getproperty(name) != null );
}
@nullable
public abstract object getproperty(string name);
@override
public boolean equals(object obj) {
return ( this == obj || (obj instanceof propertysource &&
objectutils.nullsafeequals( this .name, ((propertysource<?>) obj).name)));
}
@override
public int hashcode() {
return objectutils.nullsafehashcode( this .name);
}
//省略其他方法和内部类的源码
}
|
源码相对简单,预留了一个getproperty抽象方法给子类实现,重点需要关注的是覆写了的equals和hashcode方法,实际上只和name属性相关,这一点很重要,说明一个propertysource实例绑定到一个唯一的name,这个name有点像hashmap里面的key,部分移除、判断方法都是基于name属性。propertysource的最常用子类是mappropertysource、propertiespropertysource、resourcepropertysource、stubpropertysource、comparisonpropertysource:
- mappropertysource:source指定为map实例的propertysource实现。
- propertiespropertysource:source指定为map实例的propertysource实现,内部的map实例由properties实例转换而来。
- resourcepropertysource:继承自propertiespropertysource,source指定为通过resource实例转化为properties再转换为map实例。
- stubpropertysource:propertysource的一个内部类,source设置为null,实际上就是空实现。
- comparisonpropertysource:继承自comparisonpropertysource,所有属性访问方法强制抛出异常,作用就是一个不可访问属性的空实现。
abstractenvironment中的属性定义:
1
2
3
4
5
6
7
8
9
10
11
12
|
public static final string ignore_getenv_property_name = "spring.getenv.ignore" ;
public static final string active_profiles_property_name = "spring.profiles.active" ;
public static final string default_profiles_property_name = "spring.profiles.default" ;
protected static final string reserved_default_profile_name = "default" ;
private final set<string> activeprofiles = new linkedhashset<>();
private final set<string> defaultprofiles = new linkedhashset<>(getreserveddefaultprofiles());
private final mutablepropertysources propertysources = new mutablepropertysources( this .logger);
private final configurablepropertyresolver propertyresolver = new propertysourcespropertyresolver( this .propertysources);
|
上面的propertysources(mutablepropertysources类型)属性就是用来存放propertysource列表的,propertysourcespropertyresolver是configurablepropertyresolver的实现,默认的profile就是字符串default。
mutablepropertysources的内部属性如下:
1
|
private final list<propertysource<?>> propertysourcelist = new copyonwritearraylist<>();
|
没错,这个就是最底层的存储容器,也就是环境属性都是存放在一个copyonwritearraylist<propertysource<?>>实例中。
mutablepropertysources是propertysources的子类,它提供了get(string name)、addfirst、addlast、addbefore、addafter、remove、replace等便捷方法,方便操作propertysourcelist集合的元素,这里挑选addbefore的源码分析:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
public void addbefore(string relativepropertysourcename, propertysource<?> propertysource) {
if (logger.isdebugenabled()) {
logger.debug( "adding propertysource '" + propertysource.getname() +
"' with search precedence immediately higher than '" + relativepropertysourcename + "'" );
}
//前一个propertysource的name指定为relativepropertysourcename时候必须和添加的propertysource的name属性不相同
assertlegalrelativeaddition(relativepropertysourcename, propertysource);
//尝试移除同名的propertysource
removeifpresent(propertysource);
//获取前一个propertysource在copyonwritearraylist中的索引
int index = assertpresentandgetindex(relativepropertysourcename);
//添加当前传入的propertysource到指定前一个propertysource的索引,相当于relativepropertysourcename对应的propertysource后移到原来索引值+1的位置
addatindex(index, propertysource);
}
protected void assertlegalrelativeaddition(string relativepropertysourcename, propertysource<?> propertysource) {
string newpropertysourcename = propertysource.getname();
if (relativepropertysourcename.equals(newpropertysourcename)) {
throw new illegalargumentexception(
"propertysource named '" + newpropertysourcename + "' cannot be added relative to itself" );
}
}
protected void removeifpresent(propertysource<?> propertysource) {
this .propertysourcelist.remove(propertysource);
}
private int assertpresentandgetindex(string name) {
int index = this .propertysourcelist.indexof(propertysource.named(name));
if (index == - 1 ) {
throw new illegalargumentexception( "propertysource named '" + name + "' does not exist" );
}
return index;
}
private void addatindex( int index, propertysource<?> propertysource) {
//注意,这里会再次尝试移除同名的propertysource
removeifpresent(propertysource);
this .propertysourcelist.add(index, propertysource);
}
|
大多数propertysource子类的修饰符都是public,可以直接使用,这里写个小demo:
1
2
3
4
5
6
7
8
9
10
11
12
|
mutablepropertysources mutablepropertysources = new mutablepropertysources();
map<string, object> map = new hashmap<>( 8 );
map.put( "name" , "throwable" );
map.put( "age" , 25 );
mappropertysource mappropertysource = new mappropertysource( "map" , map);
mutablepropertysources.addlast(mappropertysource);
properties properties = new properties();
propertiespropertysource propertiespropertysource = new propertiespropertysource( "prop" , properties);
properties.put( "name" , "doge" );
properties.put( "gourp" , "group-a" );
mutablepropertysources.addbefore( "map" , propertiespropertysource);
system.out.println(mutablepropertysources);
|
environment加载过程源码分析
environment加载的源码位于springapplication#prepareenvironment:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
private configurableenvironment prepareenvironment(
springapplicationrunlisteners listeners,
applicationarguments applicationarguments) {
// create and configure the environment
//创建configurableenvironment实例
configurableenvironment environment = getorcreateenvironment();
//启动参数绑定到configurableenvironment中
configureenvironment(environment, applicationarguments.getsourceargs());
//发布configurableenvironment准备完毕事件
listeners.environmentprepared(environment);
//绑定configurableenvironment到当前的springapplication实例中
bindtospringapplication(environment);
//这一步是非springmvc项目的处理,暂时忽略
if ( this .webapplicationtype == webapplicationtype.none) {
environment = new environmentconverter(getclassloader())
.converttostandardenvironmentifnecessary(environment);
}
//绑定configurationpropertysourcespropertysource到configurableenvironment中,name为configurationproperties,实例是springconfigurationpropertysources,属性实际是configurableenvironment中的mutablepropertysources
configurationpropertysources.attach(environment);
return environment;
}
|
这里重点看下getorcreateenvironment方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
private configurableenvironment getorcreateenvironment() {
if ( this .environment != null ) {
return this .environment;
}
//在springmvc项目,configurableenvironment接口的实例就是新建的standardservletenvironment实例
if ( this .webapplicationtype == webapplicationtype.servlet) {
return new standardservletenvironment();
}
return new standardenvironment();
}
//reactive_web_environment_class=org.springframework.web.reactive.dispatcherhandler
//mvc_web_environment_class=org.springframework.web.servlet.dispatcherservlet
//mvc_web_environment_class={"javax.servlet.servlet","org.springframework.web.context.configurablewebapplicationcontext"}
//这里,默认就是webapplicationtype.servlet
private webapplicationtype deducewebapplicationtype() {
if (classutils.ispresent(reactive_web_environment_class, null )
&& !classutils.ispresent(mvc_web_environment_class, null )) {
return webapplicationtype.reactive;
}
for (string classname : web_environment_classes) {
if (!classutils.ispresent(classname, null )) {
return webapplicationtype.none;
}
}
return webapplicationtype.servlet;
}
|
还有一个地方要重点关注:发布configurableenvironment准备完毕事件listeners.environmentprepared(environment),实际上这里用到了同步的eventbus,事件的监听者是configfileapplicationlistener,具体处理逻辑是onapplicationenvironmentpreparedevent方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
private void onapplicationenvironmentpreparedevent(
applicationenvironmentpreparedevent event) {
list<environmentpostprocessor> postprocessors = loadpostprocessors();
postprocessors.add( this );
annotationawareordercomparator.sort(postprocessors);
//遍历所有的environmentpostprocessor对environment实例进行处理
for (environmentpostprocessor postprocessor : postprocessors) {
postprocessor.postprocessenvironment(event.getenvironment(),
event.getspringapplication());
}
}
//从spring.factories文件中加载,一共有四个实例
//configfileapplicationlistener
//cloudfoundryvcapenvironmentpostprocessor
//springapplicationjsonenvironmentpostprocessor
//systemenvironmentpropertysourceenvironmentpostprocessor
list<environmentpostprocessor> loadpostprocessors() {
return springfactoriesloader.loadfactories(environmentpostprocessor. class ,
getclass().getclassloader());
}
|
实际上,处理工作大部分都在configfileapplicationlistener中,见它的postprocessenvironment方法:
1
2
3
4
5
6
7
8
9
10
|
public void postprocessenvironment(configurableenvironment environment,
springapplication application) {
addpropertysources(environment, application.getresourceloader());
}
protected void addpropertysources(configurableenvironment environment,
resourceloader resourceloader) {
randomvaluepropertysource.addtoenvironment(environment);
new loader(environment, resourceloader).load();
}
|
主要的配置环境加载逻辑在内部类loader,loader会匹配多个路径下的文件把属性加载到configurableenvironment中,加载器主要是propertysourceloader的实例,例如我们用到application-${profile}.yaml文件做应用主配置文件,使用的是yamlpropertysourceloader,这个时候activeprofiles也会被设置到configurableenvironment中。加载完毕之后,configurableenvironment中基本包含了所有需要加载的属性(activeprofiles是这个时候被写入configurableenvironment)。值得注意的是,几乎所有属性都是key-value形式存储,如xxx.yyyy.zzzzz=value、xxx.yyyy[0].zzzzz=value-1、xxx.yyyy[1].zzzzz=value-2。loader中的逻辑相对复杂,有比较多的遍历和过滤条件,这里不做展开。
environment属性访问源码分析
上文提到过,都是委托到propertysourcespropertyresolver,先看它的构造函数:
1
2
3
4
5
6
|
@nullable
private final propertysources propertysources;
public propertysourcespropertyresolver( @nullable propertysources propertysources) {
this .propertysources = propertysources;
}
|
只依赖于一个propertysources实例,在springboot的springmvc项目中就是mutablepropertysources的实例。重点分析一下最复杂的一个方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
protected <t> t getproperty(string key, class <t> targetvaluetype, boolean resolvenestedplaceholders) {
if ( this .propertysources != null ) {
//遍历所有的propertysource
for (propertysource<?> propertysource : this .propertysources) {
if (logger.istraceenabled()) {
logger.trace( "searching for key '" + key + "' in propertysource '" +
propertysource.getname() + "'" );
}
object value = propertysource.getproperty(key);
//选用第一个不为null的匹配key的属性值
if (value != null ) {
if (resolvenestedplaceholders && value instanceof string) {
//处理属性占位符,如${server.port},底层委托到propertyplaceholderhelper完成
value = resolvenestedplaceholders((string) value);
}
logkeyfound(key, propertysource, value);
//如果需要的话,进行一次类型转换,底层委托到defaultconversionservice完成
return convertvalueifnecessary(value, targetvaluetype);
}
}
}
if (logger.isdebugenabled()) {
logger.debug( "could not find key '" + key + "' in any property source" );
}
return null ;
}
|
这里的源码告诉我们,如果出现多个propertysource中存在同名的key,返回的是第一个propertysource对应key的属性值的处理结果,因此我们如果需要自定义一些环境属性,需要十分清楚各个propertysource的顺序。
扩展-实现分散配置
在不使用springcloud配置中心的情况下,一般的springboot项目的配置文件如下:
- src
- main
- resources
- application-prod.yaml
- application-dev.yaml
- application-test.yaml
随着项目发展,配置项越来越多,导致了application-${profile}.yaml迅速膨胀,大的配置文件甚至超过一千行,为了简化和划分不同功能的配置,可以考虑把配置文件拆分如下:
- src
- main
- resources
- profiles
- dev
- business.yaml
- mq.json
- datasource.properties
- prod
- business.yaml
- mq.json
- datasource.properties
- test
- business.yaml
- mq.json
- datasource.properties
- application-prod.yaml
- application-dev.yaml
- application-test.yaml
外层的application-${profile}.yaml只留下项目的核心配置如server.port等,其他配置打散放在/profiles/${profile}/各自的配置文件中。实现方式是:依据当前配置的spring.profiles.active属性,读取类路径中指定文件夹下的配置文件中,加载到environment中,需要注意这一个加载步骤必须在spring刷新上下文方法最后一步finishrefresh之前完成(这一点原因可以参考之前在写过的springboot刷新上下文源码的分析),否则有可能会影响到占位符属性的自动装配(例如使用了@value("${filed}"))。
先定义一个属性探索者接口:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
public interface propertysourcedetector {
/**
* 获取支持的文件后缀数组
*
* @return string[]
*/
string[] getfileextensions();
/**
* 加载目标文件属性到环境中
*
* @param environment environment
* @param name name
* @param resource resource
* @throws ioexception ioexception
*/
void load(configurableenvironment environment, string name, resource resource) throws ioexception;
}
|
然后需要一个抽象属性探索者把resource转换为字符串,额外提供map的缩进、添加propertysource到environment等方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
|
public abstract class abstractpropertysourcedetector implements propertysourcedetector {
private static final string servlet_environment_class = "org.springframework.web."
+ "context.support.standardservletenvironment" ;
public boolean support(string fileextension) {
string[] fileextensions = getfileextensions();
return null != fileextensions &&
arrays.stream(fileextensions).anymatch(extension -> extension.equals(fileextension));
}
private string findpropertysource(mutablepropertysources sources) {
if (classutils.ispresent(servlet_environment_class, null ) && sources
.contains(standardservletenvironment.jndi_property_source_name)) {
return standardservletenvironment.jndi_property_source_name;
}
return standardenvironment.system_properties_property_source_name;
}
protected void addpropertysource(configurableenvironment environment, propertysource<?> source) {
mutablepropertysources sources = environment.getpropertysources();
string name = findpropertysource(sources);
if (sources.contains(name)) {
sources.addbefore(name, source);
} else {
sources.addfirst(source);
}
}
protected map<string, object> flatten(map<string, object> map) {
map<string, object> result = new linkedhashmap<>();
flatten( null , result, map);
return result;
}
private void flatten(string prefix, map<string, object> result, map<string, object> map) {
string nameprefix = (prefix != null ? prefix + "." : "" );
map.foreach((key, value) -> extract(nameprefix + key, result, value));
}
@suppresswarnings ( "unchecked" )
private void extract(string name, map<string, object> result, object value) {
if (value instanceof map) {
flatten(name, result, (map<string, object>) value);
} else if (value instanceof collection) {
int index = 0 ;
for (object object : (collection<object>) value) {
extract(name + "[" + index + "]" , result, object);
index++;
}
} else {
result.put(name, value);
}
}
protected string getcontentstringfromresource(resource resource) throws ioexception {
return streamutils.copytostring(resource.getinputstream(), charset.forname( "utf-8" ));
}
}
|
上面的方法参考springapplicationjsonenvironmentpostprocessor,然后编写各种类型配置属性探索者的实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
|
//json
@slf4j
public class jsonpropertysourcedetector extends abstractpropertysourcedetector {
private static final jsonparser json_parser = jsonparserfactory.getjsonparser();
@override
public string[] getfileextensions() {
return new string[]{ "json" };
}
@override
public void load(configurableenvironment environment, string name, resource resource) throws ioexception {
try {
map<string, object> map = json_parser.parsemap(getcontentstringfromresource(resource));
map<string, object> target = flatten(map);
addpropertysource(environment, new mappropertysource(name, target));
} catch (exception e) {
log.warn( "加载json文件属性到环境变量失败,name = {},resource = {}" , name, resource);
}
}
}
//properties
public class propertiespropertysourcedetector extends abstractpropertysourcedetector {
@override
public string[] getfileextensions() {
return new string[]{ "properties" , "conf" };
}
@suppresswarnings ( "unchecked" )
@override
public void load(configurableenvironment environment, string name, resource resource) throws ioexception {
map map = propertiesloaderutils.loadproperties(resource);
addpropertysource(environment, new mappropertysource(name, map));
}
}
//yaml
@slf4j
public class yamlpropertysourcedetector extends abstractpropertysourcedetector {
private static final jsonparser yaml_parser = new yamljsonparser();
@override
public string[] getfileextensions() {
return new string[]{ "yaml" , "yml" };
}
@override
public void load(configurableenvironment environment, string name, resource resource) throws ioexception {
try {
map<string, object> map = yaml_parser.parsemap(getcontentstringfromresource(resource));
map<string, object> target = flatten(map);
addpropertysource(environment, new mappropertysource(name, target));
} catch (exception e) {
log.warn( "加载yaml文件属性到环境变量失败,name = {},resource = {}" , name, resource);
}
}
}
|
子类的全部propertysource都是mappropertysource,name为文件的名称,所有propertysource都用addbefore方法插入到systemproperties的前面,主要是为了提高匹配属性的优先级。接着需要定义一个属性探索者的合成类用来装载所有的子类:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
|
public class propertysourcedetectorcomposite implements propertysourcedetector {
private static final string default_suffix = "properties" ;
private final list<abstractpropertysourcedetector> propertysourcedetectors = new arraylist<>();
public void addpropertysourcedetector(abstractpropertysourcedetector sourcedetector) {
propertysourcedetectors.add(sourcedetector);
}
public void addpropertysourcedetectors(list<abstractpropertysourcedetector> sourcedetectors) {
propertysourcedetectors.addall(sourcedetectors);
}
public list<abstractpropertysourcedetector> getpropertysourcedetectors() {
return collections.unmodifiablelist(propertysourcedetectors);
}
@override
public string[] getfileextensions() {
list<string> fileextensions = new arraylist<>( 8 );
for (abstractpropertysourcedetector propertysourcedetector : propertysourcedetectors) {
fileextensions.addall(arrays.aslist(propertysourcedetector.getfileextensions()));
}
return fileextensions.toarray( new string[ 0 ]);
}
@override
public void load(configurableenvironment environment, string name, resource resource) throws ioexception {
if (resource.isfile()) {
string filename = resource.getfile().getname();
int index = filename.lastindexof( "." );
string suffix;
if (- 1 == index) {
//如果文件没有后缀,当作properties处理
suffix = default_suffix;
} else {
suffix = filename.substring(index + 1 );
}
for (abstractpropertysourcedetector propertysourcedetector : propertysourcedetectors) {
if (propertysourcedetector.support(suffix)) {
propertysourcedetector.load(environment, name, resource);
return ;
}
}
}
}
}
|
最后添加一个配置类作为入口:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
public class propertysourcedetectorconfiguration implements importbeandefinitionregistrar {
private static final string path_prefix = "profiles" ;
@override
public void registerbeandefinitions(annotationmetadata importingclassmetadata, beandefinitionregistry registry) {
defaultlistablebeanfactory beanfactory = (defaultlistablebeanfactory) registry;
configurableenvironment environment = beanfactory.getbean(configurableenvironment. class );
list<abstractpropertysourcedetector> propertysourcedetectors = new arraylist<>();
configurepropertysourcedetectors(propertysourcedetectors, beanfactory);
propertysourcedetectorcomposite propertysourcedetectorcomposite = new propertysourcedetectorcomposite();
propertysourcedetectorcomposite.addpropertysourcedetectors(propertysourcedetectors);
string[] activeprofiles = environment.getactiveprofiles();
resourcepatternresolver resourcepatternresolver = new pathmatchingresourcepatternresolver();
try {
for (string profile : activeprofiles) {
string location = path_prefix + file.separator + profile + file.separator + "*" ;
resource[] resources = resourcepatternresolver.getresources(location);
for (resource resource : resources) {
propertysourcedetectorcomposite.load(environment, resource.getfilename(), resource);
}
}
} catch (ioexception e) {
throw new illegalstateexception(e);
}
}
private void configurepropertysourcedetectors(list<abstractpropertysourcedetector> propertysourcedetectors,
defaultlistablebeanfactory beanfactory) {
map<string, abstractpropertysourcedetector> beansoftype = beanfactory.getbeansoftype(abstractpropertysourcedetector. class );
for (map.entry<string, abstractpropertysourcedetector> entry : beansoftype.entryset()) {
propertysourcedetectors.add(entry.getvalue());
}
propertysourcedetectors.add( new jsonpropertysourcedetector());
propertysourcedetectors.add( new yamlpropertysourcedetector());
propertysourcedetectors.add( new propertiespropertysourcedetector());
}
}
|
准备就绪,在/resources/profiles/dev下面添加两个文件app.json和conf:
1
2
3
4
5
6
7
8
9
|
//app.json
{
"app" : {
"name" : "throwable" ,
"age" : 25
}
}
//conf
name=doge
|
项目的application.yaml添加属性spring.profiles.active: dev,最后添加一个commandlinerunner的实现用来观察数据:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
@slf4j
@component
public class customcommandlinerunner implements commandlinerunner {
@value ( "${app.name}" )
string name;
@value ( "${app.age}" )
integer age;
@autowired
configurableenvironment configurableenvironment;
@override
public void run(string... args) throws exception {
log.info( "name = {},age = {}" , name, age);
}
}
|
自动装配的属性值和environment实例中的属性和预期一样,改造是成功的。
小结
spring中的环境属性管理的源码个人认为是最清晰和简单的:从文件中读取数据转化为key-value结构,key-value结构存放在一个propertysource实例中,然后得到的多个propertysource实例存放在一个copyonwritearraylist中,属性访问的时候总是遍历copyonwritearraylist中的propertysource进行匹配。可能相对复杂的就是占位符的解析和参数类型的转换,后者牵连到converter体系,这些不在本文的讨论范围内。最后附上一张environment存储容器的示例图:
参考资料:
spring-boot-starter-web:2.0.3.release源码。
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对服务器之家的支持。
原文链接:https://www.cnblogs.com/throwable/p/9411100.html