SpringBoot配置文件外置方案

增加外置适配代码

增加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地址

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章