增加外置适配代码
增加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地址