共计 8335 个字符,预计需要花费 21 分钟才能阅读完成。
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 带你学架构
!
如果本篇文章对您有帮忙,还请帮忙点个关注和赞,您的保持是我一直创作的能源。欢送关注同名微信公众号获取更多技术干货!