好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

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

前提

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

查看更多关于基于Spring Boot的Environment源码理解实现分散配置详解的详细内容...

  阅读:43次