SpringBoot中實(shí)現(xiàn)加載遠(yuǎn)程配置的代碼示例
加載配置文件方式
對于一個工程來說,我們一般都會需要有各種配置,在spring工程里面,一般都是yml或者properties文件,如下所示:
server: port: 9991 # 端口 spring: application: name: ts-service1 # 應(yīng)用名稱 profiles: active: dev # 指定環(huán)境,默認(rèn)加載 default 環(huán)境 cloud: consul: # Consul 服務(wù)器地址 host: 51.6.196.200 port: 8500 # 配置中心相關(guān)配置 config: # 是否啟用配置中心,默認(rèn)值 true 開啟 enabled: true # 設(shè)置配置的基本文件夾,默認(rèn)值 config 可以理解為配置文件所在的最外層文件夾 prefix: config # 設(shè)置應(yīng)用的文件夾名稱,默認(rèn)值 application 一般建議設(shè)置為微服務(wù)應(yīng)用名稱 default-context: tsService # 配置環(huán)境分隔符,默認(rèn)值 "," 和 default-context 配置項(xiàng)搭配 # 例如應(yīng)用 orderService 分別有環(huán)境 default、dev、test、prod # 只需在 config 文件夾下創(chuàng)建 orderService、orderService-dev、orderService-test、orderService-prod 文件夾即可 profile-separator: '-' # 指定配置格式為 yaml format: YAML # Consul 的 Key/Values 中的 Key,Value 對應(yīng)整個配置文件 data-key: redisConfig # 以上配置可以理解為:加載 config/orderService/ 文件夾下 Key 為 orderServiceConfig 的 Value 對應(yīng)的配置信息 watch: # 是否開啟自動刷新,默認(rèn)值 true 開啟 enabled: true # 刷新頻率,單位:毫秒,默認(rèn)值 1000 delay: 1000 # 服務(wù)發(fā)現(xiàn)相關(guān)配置 discovery: register: true # 是否需要注冊 instance-id: ${spring.application.name}-01 # 注冊實(shí)例 id(必須唯一) service-name: ${spring.application.name} # 服務(wù)名稱 port: ${server.port} # 服務(wù)端口 prefer-ip-address: true # 是否使用 ip 地址注冊 ip-address: ${spring.cloud.client.ip-address} # 服務(wù)請求 ip
那么對于讀取這些配置文件中的值,一般有如下幾種方式
- 1、使用 @Value("${property}") 讀取比較簡單的配置信息。
- 2、通過@ConfigurationProperties讀取并通過@Component與 bean 綁定
- 3、通過@ConfigurationProperties讀取并在使用的地方使用@EnableConfigurationProperties注冊需要的配置 bean
- 4、通過@PropertySource讀取指定 properties 文件
注:spring加載配置文件有個默認(rèn)的加載順序的,根據(jù)存放的路徑來決定。
拉取遠(yuǎn)程配置
我們知道,上面說的那些一般要求配置都必須是本地,而且格式只能是 properties(或者 yaml)。那么,如果我們有遠(yuǎn)程配置,如何把他引入進(jìn)來來呢。主要有以下三步:
- 1、編寫PropertySource:編寫一個類繼承EnumerablePropertySource,然后實(shí)現(xiàn)它的抽象方法即可。
- 2、編寫PropertySourceLocator:PropertySourceLocator 其實(shí)就是用來定位我們前面的PropertySource,需要重寫的方法只有一個,就是返回一個PropertySource對象。
- 3、配置PropertySourceLocator生效
下面就以consul為例,剖析下它是怎么做的
consul的配置示例如下:
spring: application: name: ts-service1 # 應(yīng)用名稱 profiles: active: dev # 指定環(huán)境,默認(rèn)加載 default 環(huán)境 cloud: consul: # Consul 服務(wù)器地址 host: 51.6.196.200 port: 8500 # 配置中心相關(guān)配置 config: # 是否啟用配置中心,默認(rèn)值 true 開啟 enabled: true # 設(shè)置配置的基本文件夾,默認(rèn)值 config 可以理解為配置文件所在的最外層文件夾 prefix: config # 設(shè)置應(yīng)用的文件夾名稱,默認(rèn)值 application 一般建議設(shè)置為微服務(wù)應(yīng)用名稱 default-context: tsService # 配置環(huán)境分隔符,默認(rèn)值 "," 和 default-context 配置項(xiàng)搭配 # 例如應(yīng)用 orderService 分別有環(huán)境 default、dev、test、prod # 只需在 config 文件夾下創(chuàng)建 orderService、orderService-dev、orderService-test、orderService-prod 文件夾即可 profile-separator: '-' # 指定配置格式為 yaml format: YAML # Consul 的 Key/Values 中的 Key,Value 對應(yīng)整個配置文件 data-key: redisConfig # 以上配置可以理解為:加載 config/orderService/ 文件夾下 Key 為 orderServiceConfig 的 Value 對應(yīng)的配置信息 watch: # 是否開啟自動刷新,默認(rèn)值 true 開啟 enabled: true # 刷新頻率,單位:毫秒,默認(rèn)值 1000 delay: 1000
一般對于配置文件,都有一個對應(yīng)的配置屬性類,consul也不例外:
@ConfigurationProperties("spring.cloud.consul.config") @Validated public class ConsulConfigProperties { private boolean enabled = true; private String prefix = "config"; @NotEmpty private String defaultContext = "application"; @NotEmpty private String profileSeparator = ","; @NotNull private Format format = Format.KEY_VALUE; /** * If format is Format.PROPERTIES or Format.YAML then the following field is used as * key to look up consul for configuration. */ @NotEmpty private String dataKey = "data"; @Value("${consul.token:${CONSUL_TOKEN:${spring.cloud.consul.token:${SPRING_CLOUD_CONSUL_TOKEN:}}}}") private String aclToken; private Watch watch = new Watch(); ... }
上面代碼對應(yīng)的就是yml文件中***spring.consul.config
***下面的這一部分的配置。
1、編寫PropertySource
consul config下面有這樣一個類:ConsulPropertySource
,看下它的繼承關(guān)系:
public class ConsulPropertySource extends EnumerablePropertySource<ConsulClient> { private final Map<String, Object> properties = new LinkedHashMap<>(); }
可以看到,它使用了一個map來說存儲配置數(shù)據(jù)。
主要看下以下三個方法:
@Override public Object getProperty(String name) { return this.properties.get(name); } @Override public String[] getPropertyNames() { Set<String> strings = this.properties.keySet(); return strings.toArray(new String[strings.size()]); }
這兩個方法就是需要繼承實(shí)現(xiàn)的父類方法,主要就是獲取配置信息,那這些配置信息是哪里來的呢?下面看第三個方法:
public void init() { if (!this.context.endsWith("/")) { this.context = this.context + "/"; } Response<List<GetValue>> response = this.source.getKVValues(this.context, this.configProperties.getAclToken(), QueryParams.DEFAULT); this.initialIndex = response.getConsulIndex(); final List<GetValue> values = response.getValue(); ConsulConfigProperties.Format format = this.configProperties.getFormat(); switch (format) { case KEY_VALUE: parsePropertiesInKeyValueFormat(values); break; case PROPERTIES: case YAML: parsePropertiesWithNonKeyValueFormat(values, format); } }
這里的this.context可以理解為consul中key-value存儲里面的key。this.source就是ConsulClient。
上面代碼的邏輯就是:
- 通過consulclient向consul server發(fā)起請求,查詢前綴key為this.context的value信息
- 根據(jù)consul配置文件(也就是工程里面的yml或者properties文件)里面配置的format配置來決定解析該response
- 如果format是key-value,則表示consule server中該this.context對應(yīng)的value是一個key-value格式的值,按照key-value進(jìn)行解析放入this.properties中
- 如果format是yml或者properties,則表示consule server中該this.context對應(yīng)的value是一個yml或者properties格式的值,按照相應(yīng)的格式進(jìn)行解析放入this.properties中
注:consul config還提供了另外一個propertySource的實(shí)現(xiàn):
public class ConsulFilesPropertySource extends ConsulPropertySource { public void init(GetValue value) { if (this.getContext().endsWith(".yml") || this.getContext().endsWith(".yaml")) { parseValue(value, YAML); } else if (this.getContext().endsWith(".properties")) { parseValue(value, PROPERTIES); } else { throw new IllegalStateException( "Unknown files extension for context " + this.getContext()); } } }
該類繼承自上面說的那個類,實(shí)現(xiàn)了init方法:主要就是用于直接將獲取到的value根據(jù)需要解析成yml或者properties格式的數(shù)據(jù)。
2、編寫PropertySourceLocator
consul config的實(shí)現(xiàn)類如下:
@Order(0) public class ConsulPropertySourceLocator implements PropertySourceLocator {}
如上面所說,我們主要關(guān)注下locate方法:
@Override @Retryable(interceptor = "consulRetryInterceptor") public PropertySource<?> locate(Environment environment) { if (environment instanceof ConfigurableEnvironment) { ConfigurableEnvironment env = (ConfigurableEnvironment) environment; String appName = this.properties.getName(); if (appName == null) { appName = env.getProperty("spring.application.name"); } List<String> profiles = Arrays.asList(env.getActiveProfiles()); String prefix = this.properties.getPrefix(); List<String> suffixes = new ArrayList<>(); if (this.properties.getFormat() != FILES) { suffixes.add("/"); } else { suffixes.add(".yml"); suffixes.add(".yaml"); suffixes.add(".properties"); } String defaultContext = getContext(prefix, this.properties.getDefaultContext()); for (String suffix : suffixes) { this.contexts.add(defaultContext + suffix); } for (String suffix : suffixes) { addProfiles(this.contexts, defaultContext, profiles, suffix); } String baseContext = getContext(prefix, appName); for (String suffix : suffixes) { this.contexts.add(baseContext + suffix); } for (String suffix : suffixes) { addProfiles(this.contexts, baseContext, profiles, suffix); } Collections.reverse(this.contexts); CompositePropertySource composite = new CompositePropertySource("consul"); for (String propertySourceContext : this.contexts) { try { ConsulPropertySource propertySource = null; if (this.properties.getFormat() == FILES) { Response<GetValue> response = this.consul.getKVValue( propertySourceContext, this.properties.getAclToken()); addIndex(propertySourceContext, response.getConsulIndex()); if (response.getValue() != null) { ConsulFilesPropertySource filesPropertySource = new ConsulFilesPropertySource( propertySourceContext, this.consul, this.properties); filesPropertySource.init(response.getValue()); propertySource = filesPropertySource; } } else { propertySource = create(propertySourceContext, this.contextIndex); } if (propertySource != null) { composite.addPropertySource(propertySource); } } catch (Exception e) { if (this.properties.isFailFast()) { log.error( "Fail fast is set and there was an error reading configuration from consul."); ReflectionUtils.rethrowRuntimeException(e); } else { log.warn("Unable to load consul config from " + propertySourceContext, e); } } } return composite; } return null; }
上面代碼的邏輯就是:
- 通過上面配置文件中prefix和default-context、prefix和application.name通過分隔符組合成consule中的key
- 對每一個key,創(chuàng)建
ConsulPropertySource
實(shí)例并初始化(上一節(jié)我們已經(jīng)分析過了),將該實(shí)例保存下來
3、配置啟動加載
consul config實(shí)現(xiàn)這樣一個類:
@Configuration(proxyBeanMethods = false) @ConditionalOnConsulEnabled public class ConsulConfigBootstrapConfiguration { @Configuration(proxyBeanMethods = false) @EnableConfigurationProperties @Import(ConsulAutoConfiguration.class) @ConditionalOnProperty(name = "spring.cloud.consul.config.enabled", matchIfMissing = true) protected static class ConsulPropertySourceConfiguration { @Autowired private ConsulClient consul; @Bean @ConditionalOnMissingBean public ConsulConfigProperties consulConfigProperties() { return new ConsulConfigProperties(); } @Bean public ConsulPropertySourceLocator consulPropertySourceLocator( ConsulConfigProperties consulConfigProperties) { return new ConsulPropertySourceLocator(this.consul, consulConfigProperties); } } }
然后在 META-INF/spring.factories
中配置如下:
# Auto Configuration org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.cloud.consul.config.ConsulConfigAutoConfiguration # Bootstrap Configuration org.springframework.cloud.bootstrap.BootstrapConfiguration=\ org.springframework.cloud.consul.config.ConsulConfigBootstrapConfiguration
就是給Spring Boot說,這個是一個啟動配置類,spring boot在啟動的時候會自動加載。
放入environment
上面講解了對應(yīng)接口的實(shí)現(xiàn),那么consul的這些實(shí)現(xiàn)類是在哪里調(diào)用的呢?
過程是這樣的:spring boot工程在啟動的時候,會執(zhí)行BootStrapConfiguration的initize方法,PropertySourceBootstrapConfiguration
的該方法如下:
@Override public void initialize(ConfigurableApplicationContext applicationContext) { List<PropertySource<?>> composite = new ArrayList<>(); AnnotationAwareOrderComparator.sort(this.propertySourceLocators); boolean empty = true; ConfigurableEnvironment environment = applicationContext.getEnvironment(); for (PropertySourceLocator locator : this.propertySourceLocators) { Collection<PropertySource<?>> source = locator.locateCollection(environment); if (source == null || source.size() == 0) { continue; } List<PropertySource<?>> sourceList = new ArrayList<>(); for (PropertySource<?> p : source) { if (p instanceof EnumerablePropertySource) { EnumerablePropertySource<?> enumerable = (EnumerablePropertySource<?>) p; sourceList.add(new BootstrapPropertySource<>(enumerable)); } else { sourceList.add(new SimpleBootstrapPropertySource(p)); } } logger.info("Located property source: " + sourceList); composite.addAll(sourceList); empty = false; } if (!empty) { MutablePropertySources propertySources = environment.getPropertySources(); String logConfig = environment.resolvePlaceholders("${logging.config:}"); LogFile logFile = LogFile.get(environment); for (PropertySource<?> p : environment.getPropertySources()) { if (p.getName().startsWith(BOOTSTRAP_PROPERTY_SOURCE_NAME)) { propertySources.remove(p.getName()); } } insertPropertySources(propertySources, composite); reinitializeLoggingSystem(environment, logConfig, logFile); setLogLevels(applicationContext, environment); handleIncludedProfiles(environment); } }
可以看到,這里代碼中,調(diào)用了每個PropertySourceLocator
的實(shí)例方法locateCollection
,該方法里面調(diào)用了locate
方法,也就是回到了上一節(jié)所說的內(nèi)容了。最后將所有的source放入了environment
中: insertPropertySources(propertySources, composite);
讀取propertySource
通過上面的方式加載了遠(yuǎn)程配置之后,我們在其他地方就可以任意讀取了,方式如下:
//可以獲取整個系統(tǒng)所有的配置:通過這個可以獲取到更新之前的數(shù)據(jù) ConfigurableEnvironment environment = (ConfigurableEnvironment)context.getEnvironment(); PropertySources sources = environment.getPropertySources();
以上就是SpringBoot中實(shí)現(xiàn)加載遠(yuǎn)程配置的代碼示例的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot加載遠(yuǎn)程配置的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
JavaWeb實(shí)現(xiàn)學(xué)生信息管理系統(tǒng)(1)
這篇文章主要為大家詳細(xì)介紹了JavaWeb實(shí)現(xiàn)學(xué)生信息管理系統(tǒng)第一篇,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-08-08Java?InheritableThreadLocal使用示例詳解
InheritableThreadLocal繼承了ThreadLocal,此類擴(kuò)展了ThreadLocal以提供從父線程到子線程的值的繼承:當(dāng)創(chuàng)建子線程時,子線程接收父線程具有的所有可繼承線程局部變量的初始值。?通常子線程的值與父線程的值是一致的2022-09-09詳解SpringBoot如何實(shí)現(xiàn)整合微信登錄
本文主要介紹了SpringBoot實(shí)現(xiàn)整合微信登錄的過程詳解,文中的示例代碼介紹的非常詳細(xì),對我們的學(xué)習(xí)過工作有一定的參考價值,需要的朋友可以關(guān)注下2021-12-12maven中央倉庫修改驗(yàn)證方式導(dǎo)致用戶名密碼失效的解決方式
這篇文章主要介紹了maven中央倉庫修改驗(yàn)證方式導(dǎo)致用戶名密碼失效的解決方式,文中通過圖文結(jié)合的方式講解的非常詳細(xì),對大家解決問題有一定的幫助2024-11-11