增加外置適配代碼
增加spring啓動監聽器AppConfigFileApplicationListener
package com.mistone.cs;
import org.springframework.boot.context.event.ApplicationPreparedEvent;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
/**
* 註冊應用自定義配置文件路徑解析
*
* @author mistone
* @version 1.0
* @created
**/
public class AppConfigFileApplicationListener implements ApplicationListener {
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationPreparedEvent) {
onApplicationPreparedEvent(event);
}
}
private void onApplicationPreparedEvent(ApplicationEvent event) {
addPostProcessors(((ApplicationPreparedEvent) event).getApplicationContext());
}
protected void addPostProcessors(ConfigurableApplicationContext context) {
context.addBeanFactoryPostProcessor(new AppConfigFileBeanFactoryPostProcess(context));
}
}
增加bean 定義factoryAppConfigFileBeanFactoryPostProcess
package com.mistone.cs;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.core.Ordered;
import org.springframework.core.PriorityOrdered;
import org.springframework.core.io.ResourceLoader;
/**
* 註冊應用自定義配置文件路徑解析
*
* @author mistone
* @version 1.0
* @created
**/
public class AppConfigFileBeanFactoryPostProcess implements BeanDefinitionRegistryPostProcessor, PriorityOrdered {
private AbstractApplicationContext context;
public AppConfigFileBeanFactoryPostProcess(ConfigurableApplicationContext context) {
if(context instanceof AbstractApplicationContext){
this.context = (AbstractApplicationContext) context;
}
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
if(registry instanceof ConfigurableListableBeanFactory){
ConfigurableListableBeanFactory beanFactory = (ConfigurableListableBeanFactory) registry;
AppConfigFileBeanPostProcess acf = new AppConfigFileBeanPostProcess(context);
beanFactory.addBeanPostProcessor(acf);
beanFactory.registerResolvableDependency(ResourceLoader.class, acf);
}
}
}
增加bean 後置處理器AppConfigFileBeanPostProcess
package com.mistone.cs;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.context.config.ConfigFileApplicationListener;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.util.ResourceUtils;
import org.springframework.util.StringUtils;
import java.io.IOException;
import java.util.*;
/**
* 描述
*
* @author mistone
* @version 1.0
* @created
**/
public class AppConfigFileBeanPostProcess implements ResourcePatternResolver, BeanPostProcessor {
private static final String[] DEFAULT_SEARCH_LOCATIONS = new String[]{"file:./config/","file:./","classpath:/config/","classpath:/"};
private ApplicationContext applicationContext;
private String[] searchLocations;
public AppConfigFileBeanPostProcess(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
searchLocations = getLocationsFinal();
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
// 偷樑換柱
if (bean instanceof ResourceLoaderAware) {
((ResourceLoaderAware) bean).setResourceLoader(this);
}
return bean;
}
/**
* 查找spring.config.location和spring.config.additional-location配置
* @return
*/
private Set<String> getSearchLocations() {
if (applicationContext.getEnvironment().containsProperty(ConfigFileApplicationListener.CONFIG_LOCATION_PROPERTY)) {
return getSearchLocations(ConfigFileApplicationListener.CONFIG_LOCATION_PROPERTY);
}
Set<String> locations = getSearchLocations(ConfigFileApplicationListener.CONFIG_ADDITIONAL_LOCATION_PROPERTY);
return locations;
}
/**
* 處理參數
* @param propertyName
* @return
*/
private Set<String> getSearchLocations(String propertyName) {
Set<String> locations = new LinkedHashSet<>();
if (applicationContext.getEnvironment().containsProperty(propertyName)) {
for (String path : asResolvedSet(applicationContext.getEnvironment().getProperty(propertyName), null)) {
if (!path.contains("$")) {
path = StringUtils.cleanPath(path);
if (!ResourceUtils.isUrl(path)) {
path = ResourceUtils.FILE_URL_PREFIX + path;
}
}
locations.add(path);
}
}
return locations;
}
/**
* 和默認配置進行合併
* @return
*/
private String[] getLocationsFinal(){
Set<String> sets = getSearchLocations();
Set<String> floderSets = new LinkedHashSet<>();
// 只做目錄級別支持
sets.forEach((name)->{
if(name.endsWith("/")){
floderSets.add(name);
}
});
floderSets.addAll(Arrays.asList(DEFAULT_SEARCH_LOCATIONS));
return floderSets.toArray(new String[0]);
}
/**
* 處理參數,並反轉
* @param value
* @param fallback
* @return
*/
private Set<String> asResolvedSet(String value, String fallback) {
List<String> list = Arrays.asList(StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray(
(value != null) ? applicationContext.getEnvironment().resolvePlaceholders(value) : fallback)));
Collections.reverse(list);
return new LinkedHashSet<>(list);
}
@Override
public Resource getResource(String location) {
// 交個spring處理
if(location==null||location.startsWith("classpath:")||location.startsWith("file:")||location.startsWith("/"))
{
return applicationContext.getResource(location);
}else{
Resource resource;
// 查找並加工一下
for(String path:searchLocations){
resource = applicationContext.getResource(path+location);
if(resource.exists()){
return resource;
}
}
return applicationContext.getResource(location);
}
}
@Override
public ClassLoader getClassLoader() {
return applicationContext.getClassLoader();
}
/**
* 不對正則的進行處理了
* @param locationPattern
* @return
* @throws IOException
*/
@Override
public Resource[] getResources(String locationPattern) throws IOException {
return applicationContext.getResources(locationPattern);
}
}
增加spring.facotries配置
在resources下配置MATE-INF/spring.factories內容如下
# Application Listeners
org.springframework.context.ApplicationListener=\
com.mistone.cs.AppConfigFileApplicationListener
使用與配置
默認支持按照 classpath:/,classpath:/config/,file:./,file:./config/ 這個優先級查找不帶file,/或者classpath前綴的配置文件,會優先加載高優先級的文件,帶file:,/和classpath:前綴的依然按照原方式處理
使用spring.config.location環境變量 指定配置文件路徑
使用spring.config.additional-location環境變量 添加優先查找路徑,優先級高於默認配置(推薦)
以上兩個配置兼容spring boot指定applicaiton路徑的配置,不只指定到文件的,只支持指定查找目錄,會自動忽略指定到文件的設置
如java -Dspring.config.additional-location=file:/cofig -jar test.jar
支持範圍
像PropertySource註解這樣使用spring的resourceLoader加載配置文件的都會被替換,按照我們外置方案查找配置文件
resourLoader注入的兩種方式
使用@Autowired 或者@Resource 注入
解決方案 AppConfigFileBeanFactoryPostProcess類的 beanFactory.registerResolvableDependency(ResourceLoader.class, acf);
實現ResourceLoaderAware接口 ConfigurationClassPostProcessor屬於這種
解決方案 AppConfigFileBeanPostProcess類 ((ResourceLoaderAware) bean).setResourceLoader(this);
PropertuSource解析過程見org.springframework.context.annotation.ConfigurationClassPostProcessor
application配置文件加載過程見org.springframework.boot.context.config.ConfigFileApplicationListener
注意
spring.config.location和spring.config.additional-location 指定路徑爲目錄需要以/結尾
結語
目前classpth*:這樣的還使用原來的加載方式加載,暫不考慮做處理, 可以視需求考慮忽略項目中的classpath:和file:配置都進行自動查找,目前暫不特殊處理。
Git地址