服务器之家:专注于服务器技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|JavaScript|易语言|

服务器之家 - 编程语言 - Java教程 - 基于Spring Boot的Environment源码理解实现分散配置详解

基于Spring Boot的Environment源码理解实现分散配置详解

2021-05-24 13:20throwable Java教程

这篇文章主要给大家介绍了基于Spring Boot的Environment源码理解实现分散配置的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

前提

org.springframework.core.env.environment是当前应用运行环境的公开接口,主要包括应用程序运行环境的两个关键方面:配置文件(profiles)和属性。environment继承自接口propertyresolver,而propertyresolver提供了属性访问的相关方法。这篇文章从源码的角度分析environment的存储容器和加载流程,然后基于源码的理解给出一个生产级别的扩展。

本文较长,请用一个舒服的姿势阅读。

environment类体系

基于Spring Boot的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,我们可以查看此接口提供的方法:

基于Spring Boot的Environment源码理解实现分散配置详解

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);
 }
}

基于Spring Boot的Environment源码理解实现分散配置详解

自动装配的属性值和environment实例中的属性和预期一样,改造是成功的。

小结

spring中的环境属性管理的源码个人认为是最清晰和简单的:从文件中读取数据转化为key-value结构,key-value结构存放在一个propertysource实例中,然后得到的多个propertysource实例存放在一个copyonwritearraylist中,属性访问的时候总是遍历copyonwritearraylist中的propertysource进行匹配。可能相对复杂的就是占位符的解析和参数类型的转换,后者牵连到converter体系,这些不在本文的讨论范围内。最后附上一张environment存储容器的示例图:

基于Spring Boot的Environment源码理解实现分散配置详解

参考资料:

spring-boot-starter-web:2.0.3.release源码。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对服务器之家的支持。

原文链接:https://www.cnblogs.com/throwable/p/9411100.html

延伸 · 阅读

精彩推荐
  • Java教程IntelliJ IDEA maven 构建简单springmvc项目(图文教程)

    IntelliJ IDEA maven 构建简单springmvc项目(图文教程)

    在工作当中,我们有时需要创建一个全新的工程,而基于spring-mvc web的工程较为常见,这篇文章主要介绍了IntelliJ IDEA maven 构建简单springmvc项目(图文教程),...

    xuanm7592021-04-29
  • Java教程IDEA Debug模式下改变各类型变量值的方法

    IDEA Debug模式下改变各类型变量值的方法

    这篇文章主要介绍了IDEA Debug模式下改变各类型变量值的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧...

    须木一瓜10452021-04-22
  • Java教程java中实现四则运算代码

    java中实现四则运算代码

    本文给大家分享了几个java中实现四则运算的代码,有个人的也有网友的,代码写的不是很好,难免会有BUG,忘发现BUG的亲们能提醒我下,好让我改进 ...

    hebedich2682020-01-03
  • Java教程详解JAVA 强引用

    详解JAVA 强引用

    这篇文章主要介绍了JAVA 强引用的相关资料,帮助大家更好的理解和学习,感兴趣的朋友可以了解下 ...

    弗兰克的猫4372020-08-18
  • Java教程Java SwingWorkder使用实例

    Java SwingWorkder使用实例

    最近在学习Swing,我们都知道在UI表现线程里面长时间执行操作时,画面会假死,为了能够让费时操作不影响画面表现,就需要用多线程了 ...

    Java教程网3462019-11-20
  • Java教程Java实现微信网页授权的示例代码

    Java实现微信网页授权的示例代码

    这篇文章主要介绍了Java实现微信网页授权的示例代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧...

    敲不完的代码6572021-05-21
  • Java教程Java通过正则表达式获取字符串中数字的方法示例

    Java通过正则表达式获取字符串中数字的方法示例

    最近工作中遇到了一个需求,需要利用java获取字符串中的数字,尝试几种方法后发现利用正则表达式实现最为方法,下面这篇文章就主要介绍了Java通过正...

    0warning1882020-08-26
  • Java教程idea下载svn的项目并且运行操作

    idea下载svn的项目并且运行操作

    这篇文章主要介绍了idea下载svn的项目并且运行操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...

    请叫我小思4822020-09-17