Spring Cloud 针对 Environment 的属性源性能做了加强,
在 spring-cloud-contenxt 这个包中,提供了 PropertySourceLocator 接口,用来实现属性文件加载的扩大。咱们能够通过这个接口来扩大本人的内部化配置加载。这个接口的定义如下
public interface PropertySourceLocator {
/**
* @param environment The current Environment.
* @return A PropertySource, or null if there is none.
* @throws IllegalStateException if there is a fail-fast condition.
*/
PropertySource<?> locate(Environment environment);
}
locate 这个形象办法,须要返回一个 PropertySource 对象。
这个 PropertySource 就是 Environment 中存储的属性源。也就是说,咱们如果要实现自定义内部化配置加载,只须要实现这个接口并返回 PropertySource 即可。
依照这个思路,咱们依照上面几个步骤来实现内部化配置的自定义加载。
自定义 PropertySource
既然 PropertySourceLocator 须要返回一个 PropertySource,那咱们必须要定义一个本人的 PropertySource,来从内部获取配置起源。
GpDefineMapPropertySource 示意一个以 Map 后果作为属性起源的类。
/**
* 咕泡教育,ToBeBetterMan
* Mic 老师微信: mic4096
* 微信公众号:跟着 Mic 学架构
* https://ke.gupaoedu.cn
* 应用 Map 作为属性起源。**/
public class GpDefineMapPropertySource extends MapPropertySource {
/**
* Create a new {@code MapPropertySource} with the given name and {@code Map}.
*
* @param name the associated name
* @param source the Map source (without {@code null} values in order to get
* consistent {@link #getProperty} and {@link #containsProperty} behavior)
*/
public GpDefineMapPropertySource(String name, Map<String, Object> source) {super(name, source);
}
@Override
public Object getProperty(String name) {return super.getProperty(name);
}
@Override
public String[] getPropertyNames() {return super.getPropertyNames();
}
}
扩大 PropertySourceLocator
扩大 PropertySourceLocator,重写 locate 提供属性源。
而属性源是从 gupao.json 文件加载保留到自定义属性源 GpDefineMapPropertySource 中。
public class GpJsonPropertySourceLocator implements PropertySourceLocator {
//json 数据起源
private final static String DEFAULT_LOCATION="classpath:gupao.json";
// 资源加载器
private final ResourceLoader resourceLoader=new DefaultResourceLoader(getClass().getClassLoader());
@Override
public PropertySource<?> locate(Environment environment) {
// 设置属性起源
GpDefineMapPropertySource jsonPropertySource=new GpDefineMapPropertySource
("gpJsonConfig",mapPropertySource());
return jsonPropertySource;
}
private Map<String,Object> mapPropertySource(){Resource resource=this.resourceLoader.getResource(DEFAULT_LOCATION);
if(resource==null){return null;}
Map<String,Object> result=new HashMap<>();
JsonParser parser= JsonParserFactory.getJsonParser();
Map<String,Object> fileMap=parser.parseMap(readFile(resource));
processNestMap("",result,fileMap);
return result;
}
// 加载文件并解析
private String readFile(Resource resource){
FileInputStream fileInputStream=null;
try {fileInputStream=new FileInputStream(resource.getFile());
byte[] readByte=new byte[(int)resource.getFile().length()];
fileInputStream.read(readByte);
return new String(readByte,"UTF-8");
} catch (IOException e) {e.printStackTrace();
}finally {if(fileInputStream!=null){
try {fileInputStream.close();
} catch (IOException e) {e.printStackTrace();
}
}
}
return null;
}
// 解析残缺的 url 保留到 result 汇合。为了实现 @Value 注入
private void processNestMap(String prefix,Map<String,Object> result,Map<String,Object> fileMap){if(prefix.length()>0){prefix+=".";}
for (Map.Entry<String, Object> entrySet : fileMap.entrySet()) {if (entrySet.getValue() instanceof Map) {processNestMap(prefix + entrySet.getKey(), result, (Map<String, Object>) entrySet.getValue());
} else {result.put(prefix + entrySet.getKey(), entrySet.getValue());
}
}
}
}
Spring.factories
在 /META-INF/spring.factories
文件中,增加上面的 spi 扩大,让 Spring Cloud 启动时扫描到这个扩大从而实现 GpJsonPropertySourceLocator 的加载。
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
com.gupaoedu.env.GpJsonPropertySourceLocator
编写 controller 测试
@RestController
public class ConfigController {@Value("${custom.property.message}")
private String name;
@GetMapping("/")
public String get(){String msg=String.format("配置值:%s",name);
return msg;
}
}
阶段性总结
通过上述案例能够发现,基于 Spring Boot 提供的 PropertySourceLocator 扩大机制,能够轻松实现自定义配置源的扩大。
于是,引出了两个问题。
- PropertySourceLocator 是在哪个被触发的?
- 既然可能从
gupao.json
中加载数据源,是否能从近程服务器上加载呢?
PropertySourceLocator 加载原理
先来摸索第一个问题,PropertySourceLocator 的执行流程。
SpringApplication.run
在 spring boot 我的项目启动时,有一个 prepareContext 的办法,它会回调所有实现了 ApplicationContextInitializer
的实例,来做一些初始化工作。
ApplicationContextInitializer 是 Spring 框架原有的货色,它的次要作用就是在,ConfigurableApplicationContext 类型 (或者子类型) 的 ApplicationContext 做 refresh 之前,容许咱们对 ConfiurableApplicationContext 的实例做进一步的设置和解决。
它能够用在须要对应用程序上下文进行编程初始化的 web 应用程序中,比方依据上下文环境来注册 propertySource,或者配置文件。而 Config 的这个配置核心的需要恰好须要这样一个机制来实现。
public ConfigurableApplicationContext run(String... args) {
// 省略代码...
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
// 省略代码
return context;
}
PropertySourceBootstrapConfiguration.initialize
其中,PropertySourceBootstrapConfiguration 就实现了 ApplicationContextInitializer
,initialize
办法代码如下。
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {List<PropertySource<?>> composite = new ArrayList<>();
// 对 propertySourceLocators 数组进行排序,依据默认的 AnnotationAwareOrderComparator
AnnotationAwareOrderComparator.sort(this.propertySourceLocators);
boolean empty = true;
// 获取运行的环境上下文
ConfigurableEnvironment environment = applicationContext.getEnvironment();
for (PropertySourceLocator locator : this.propertySourceLocators) {
// 回调所有实现 PropertySourceLocator 接口实例的 locate 办法,并收集到 source 这个汇合中。Collection<PropertySource<?>> source = locator.locateCollection(environment);
if (source == null || source.size() == 0) { // 如果 source 为空,间接进入下一次循环
continue;
}
// 遍历 source,把 PropertySource 包装成 BootstrapPropertySource 退出到 sourceList 中。List<PropertySource<?>> sourceList = new ArrayList<>();
for (PropertySource<?> p : source) {sourceList.add(new BootstrapPropertySource<>(p));
}
logger.info("Located property source:" + sourceList);
composite.addAll(sourceList);// 将 source 增加到数组
empty = false; // 示意 propertysource 不为空
}
// 只有 propertysource 不为空的状况,才会设置到 environment 中
if (!empty) {
// 获取以后 Environment 中的所有 PropertySources.
MutablePropertySources propertySources = environment.getPropertySources();
String logConfig = environment.resolvePlaceholders("${logging.config:}");
LogFile logFile = LogFile.get(environment);
// 遍历移除 bootstrapProperty 的相干属性
for (PropertySource<?> p : environment.getPropertySources()) {if (p.getName().startsWith(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {propertySources.remove(p.getName());
}
}
// 把后面获取到的 PropertySource,插入到 Environment 中的 PropertySources 中。insertPropertySources(propertySources, composite);
reinitializeLoggingSystem(environment, logConfig, logFile);
setLogLevels(applicationContext, environment);
handleIncludedProfiles(environment);
}
}
上述代码逻辑阐明如下。
- 首先
this.propertySourceLocators
,示意所有实现了PropertySourceLocators
接口的实现类,其中就包含咱们后面自定义的GpJsonPropertySourceLocator
。 - 依据默认的 AnnotationAwareOrderComparator 排序规定对 propertySourceLocators 数组进行排序。
- 获取运行的环境上下文 ConfigurableEnvironment
- 遍历 propertySourceLocators 时
- 调用 locate 办法,传入获取的上下文 environment
- 将 source 增加到 PropertySource 的链表中
- 设置 source 是否为空的标识标量 empty
- source 不为空的状况,才会设置到 environment 中
- 返回 Environment 的可变模式,可进行的操作如 addFirst、addLast
- 移除 propertySources 中的 bootstrapProperties
- 依据 config server 覆写的规定,设置 propertySources
- 解决多个 active profiles 的配置信息
留神:this.propertySourceLocators 这个汇合中的 PropertySourceLocator,是通过主动拆卸机制实现注入的,具体的实现在
BootstrapImportSelector
这个类中。
ApplicationContextInitializer 的了解和应用
ApplicationContextInitializer 是 Spring 框架原有的货色,它的次要作用就是在,ConfigurableApplicationContext 类型 (或者子类型) 的 ApplicationContext 做 refresh 之前,容许咱们对 ConfiurableApplicationContext 的实例做进一步的设置和解决。
它能够用在须要对应用程序上下文进行编程初始化的 web 应用程序中,比方依据上下文环境来注册 propertySource,或者配置文件。而 Config 的这个配置核心的需要恰好须要这样一个机制来实现。
创立一个 TestApplicationContextInitializer
public class TestApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext>{
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {ConfigurableEnvironment ce=applicationContext.getEnvironment();
for(PropertySource<?> propertySource:ce.getPropertySources()){System.out.println(propertySource);
}
System.out.println("--------end");
}
}
增加 spi 加载
创立一个文件 /resources/META-INF/spring.factories。增加如下内容
org.springframework.context.ApplicationContextInitializer= \
com.gupaoedu.example.springcloudconfigserver9091.TestApplicationContextInitializer
在控制台就能够看到以后的 PropertySource 的输入后果。
ConfigurationPropertySourcesPropertySource {name='configurationProperties'}
StubPropertySource {name='servletConfigInitParams'}
StubPropertySource {name='servletContextInitParams'}
PropertiesPropertySource {name='systemProperties'}
OriginAwareSystemEnvironmentPropertySource {name='systemEnvironment'}
RandomValuePropertySource {name='random'}
MapPropertySource {name='configServerClient'}
MapPropertySource {name='springCloudClientHostInfo'}
OriginTrackedMapPropertySource {name='applicationConfig: [classpath:/application.yml]'}
MapPropertySource {name='kafkaBinderDefaultProperties'}
MapPropertySource {name='defaultProperties'}
MapPropertySource {name='springCloudDefaultProperties'}
版权申明:本博客所有文章除特地申明外,均采纳 CC BY-NC-SA 4.0 许可协定。转载请注明来自
Mic 带你学架构
!
如果本篇文章对您有帮忙,还请帮忙点个关注和赞,您的保持是我一直创作的能源。欢送关注同名微信公众号获取更多技术干货!