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

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