在spring boot项目中,支持两种配置文件的方式。一种为properties,一种为yml。使用yml格式的配置文件,结构更加清晰。

         在项目中获取配置文件的值,可以在Bean类中使用@Value注解获取配置项的值。但是如果我们需要在非Bean类(如工具类)静态方法中想使用yml中的配置项时,怎么办呢?下面我们来介绍我们系统级的解决方案:YamlUtils。

         YamlUtils功能介绍:可在项目中,任意地方获取yml配置文件中的配置项。支持上下级yml配置项覆盖,支持profile切换,支持变量值嵌套等。已提供以下多种方法:

方法名

参数

返回类型

说明

get

key

Object

获取配置项对应的值

getString

key

String

获取配置项对应的字符串值

getInteger

key

Integer

获取配置项对应的整形值

getLong

key

Long

获取配置项对应的长整形值

getBoolean

key

Boolean

获取配置项对应的布尔值

getFloat

key

Float

获取配置项对应的浮点值

getDouble

key

Double

获取配置项对应的浮点值

getMap

key

Map

获取配置项对应的map值

getValueInMap

mapKey,keyInMap

Object

获取配置项对应的map内key对应的值

getList

key

List<Object>

获取配置项对应的list值

getArray

key

String[]

获取配置项对应的array值

 

  • 功能展示:
  1. 支持profile

java对象如何动态添加属性 java动态向yml文件添加属性_spring boot

java对象如何动态添加属性 java动态向yml文件添加属性_java_02

   2. 支持复杂类型

java对象如何动态添加属性 java动态向yml文件添加属性_配置文件_03

java对象如何动态添加属性 java动态向yml文件添加属性_java_04

 

  •          微服务项目结构框图

java对象如何动态添加属性 java动态向yml文件添加属性_java_05

从结构上可以将我们的框架分为四个层级。

  1. 业务层;
  2. 业务依赖层,为业务公共部分。如项目中多个微服务药连接公共的IP地址、eureka地址的维护等。
  3. 公共依赖层;业务底层公共部分,不同项目也能共用的部分。做认证、鉴权、工具包集合、配置项提取等。
  4. 基础层。第三方提供的各种基础包。

      除基础层,每一层都有对应的配置文件,且配置项随结构定位也有层级依赖,一共3层,上层可覆盖下层配置项。如common-application.yml -> center-common-application.yml -> salary-application.yml。配置项如果存在于common-application.yml,如果不满足需求,需要在center-common-application.yml或者salary-application.yml进行覆盖。

  • 实现原理
  1. 在启动类首行使用SpringUtils.initSpringConfigLocationProperty()初始化需要加载的配置文件;
  2. YamlUtils类静态代码块中按照一定顺序加载初始化的所有配置文件,将所有配置项加载到内存中,同名配置项会被替换;
  3. 根据不同的方法、不同的key值获取对应的结果即可。
  • 相关代码剖析

SpringUtils


@Slf4j
public class SpringUtils {
    /**
     * 配置文件列表
     */
    @Getter
    private static List<String> configurationFileList;

    /**
     * 设置app.name配置
     *
     * @return
     */
    public static void setAPPNameProperty(String appName) {
        System.setProperty("app.name", appName);
        System.setProperty("spring.application.name", appName);
    }

    /**
     * 初始化基础配置文件路径
     *
     * @return
     */
    public static void initSpringConfigLocationProperty() {
        initSpringConfigLocationProperty(null);
    }

    /**
     * 初始化基础配置文件路径
     *
     * @param configFileLocations 配置文件位置集合
     * @return
     */
    public static void initSpringConfigLocationProperty(String... configFileLocations) {
        List<String> configOriginList = new ArrayList<>();
        configOriginList.addAll(Arrays.asList(Constants.SPRING_CONFIG_LOCATION_PROPERTY_BOOT_BASE));

        if (configFileLocations != null) {
            configOriginList.addAll(Arrays.asList(configFileLocations));
        }
        configOriginList.addAll(Arrays.asList(Constants.SPRING_CONFIG_LOCATION_PROPERTY));

        //再次调整顺序,将所有的config文件夹提前
        List<String> configurationList = configOriginList.stream().filter(
                configuration -> StringUtils.isNotEmpty(configuration)).collect(Collectors.toList());
        log.debug("configurationList: [{}]", configurationList);

        List<String> classpathConfigurationList = configurationList.stream()
                .filter(configuration -> configuration.startsWith("classpath")).collect(Collectors.toList());
        List<String> localConfigurationList = configurationList.stream()
                .filter(configuration -> configuration.startsWith("file:")).collect(Collectors.toList());

        ArrayList<String> resultList = new ArrayList<>();
        resultList.addAll(classpathConfigurationList);
        resultList.addAll(localConfigurationList);
        configurationFileList = resultList;
        log.trace("configurationFileList: [{}]", configurationFileList);

        String newConfigurationsString = resultList.stream().collect(Collectors.joining(","));
        log.info("spring.config.location: [{}]", newConfigurationsString);
        System.setProperty("spring.config.location", newConfigurationsString);
    }


}


Constants


/**
     * spring boot基础配置文件,application.properties添加在此会影响应用从外部加载配置文件
     */
    public static final String[] SPRING_CONFIG_LOCATION_PROPERTY_BOOT_BASE = new String[]{"classpath:/config/application-common.yml"};
    /**
     * spring 基础配置文件,application.properties添加在此会影响应用从外部加载配置文件
     */
    public static final String[] SPRING_CONFIG_LOCATION_PROPERTY = new String[]{"classpath:/config/application.yml", "file:config/application.yml"};


YamlUtils 


/**
 * yml配置工具类
 */
@Slf4j
public class YamlUtils {

    /**
     * 配置表
     */
    private static Map<String, Object> sourceMap = new HashMap<>();

    private static final String HEAD = "${";
    private static final String TAIL = "}";

    static {
        loadAll();
        checkRootParentPath();
    }

    /**
     * 加载所有配置文件
     */
    public static void loadAll() {
        List<String> configurationFileList = SpringUtils.getConfigurationFileList();
        if (configurationFileList == null) {
            throw new IllegalStateException("configurationFileList未成功获取");
        }

        //查找激活的profile
        List<Object> activeProfileNameList = new ArrayList<>();
        String activeProfileKey = "spring.profiles.active";
        DefaultResourceLoader defaultResourceLoader = new DefaultResourceLoader();
        for (int i = configurationFileList.size() - 1; i >= 0; i--) {
            String configurationFile = configurationFileList.get(i);
            Resource resource = defaultResourceLoader.getResource(configurationFile);
            try {
                load(configurationFile, resource, null);
                //配置的单profile
                if (get(activeProfileKey) != null) {
                    String activeProfileName = get(activeProfileKey) + "";
                    activeProfileNameList.add(activeProfileName);
                    sourceMap.clear();
                    break;
                } else if (getList(activeProfileKey) != null && getList(activeProfileKey).size() > 0) {
                    activeProfileNameList = getList(activeProfileKey);
                    sourceMap.clear();
                    break;
                }
            } catch (Exception e) {
                log.trace("加载配置文件失败: [{}]", configurationFile, e);
            }
        }
        log.info("activeProfileNameList: {}", activeProfileNameList);

        //读取配置
        for (int i = 0; i < configurationFileList.size(); i++) {
            String configurationFile = configurationFileList.get(i);
            Resource resource = defaultResourceLoader.getResource(configurationFile);
            log.debug("加载配置文件: [{}]", configurationFile);
            load(configurationFile, resource, null);
            for (Object activeProfileName : activeProfileNameList) {
                load(configurationFile, resource, activeProfileName + "");
            }
        }
    }

    /**
     * 加载配置文件
     *
     * @param name
     * @param resource
     * @param profile
     */
    private static void load(String name, Resource resource, String profile) {
        try {
            MapPropertySource mapPropertySource = (MapPropertySource) new YamlPropertySourceLoader()
                    .load(name, resource, profile);
            if (mapPropertySource == null) {
                return;
            }
            Map<String, Object> sourceOne = mapPropertySource.getSource();
            log.trace("name: [{}], source: [{}], profile: [{}]", name, sourceOne, profile);
            sourceMap.putAll(sourceOne);
            log.trace("sourceMap after marge: [{}]", sourceMap);
        } catch (Exception e) {
            log.trace("加载配置文件失败: [{}]", name, e);
        }
    }

    /**
     * 获取对象
     *
     * @param key
     * @return
     */
    public static Object get(String key) {
        Object object = sourceMap.get(key);
        if (object == null) {
            return null;
        }

        //解析内部变量
        return parseVariable(object);
    }

    /**
     * 获取对象
     *
     * @param key
     * @return
     */
    public static String getString(String key) {
        return get(key) + "";
    }

    /**
     * 获取对象
     *
     * @param key
     * @return
     */
    public static int getInteger(String key) {
        return Integer.parseInt(getString(key));
    }

    /**
     * 获取对象
     *
     * @param key
     * @return
     */
    public static long getLong(String key) {
        return Long.parseLong(getString(key));
    }

    /**
     * 获取对象
     *
     * @param key
     * @return
     */
    public static boolean getBoolean(String key) {
        return Boolean.parseBoolean(getString(key));
    }

    /**
     * 获取对象
     *
     * @param key
     * @return
     */
    public static float getFloat(String key) {
        return Float.parseFloat(getString(key));
    }

    /**
     * 获取对象
     *
     * @param key
     * @return
     */
    public static double getDouble(String key) {
        return Double.parseDouble(getString(key));
    }

    /**
     * 获取对象
     *
     * @param key
     * @return
     */
    public static Map<String, Object> getMap(String key) {
        Set<String> keySet = sourceMap.keySet();
        List<String> matchedKeyList = keySet.stream()
                .filter(tmpKey -> {
                    if (tmpKey.startsWith(key) && !tmpKey.equals(key)) {
                        String tail = tmpKey.substring(key.length() + 1);
                        if (tail.indexOf("\\.") == -1) {
                            return true;
                        }
                    }
                    return false;
                })
                .collect(Collectors.toList());

        Map<String, Object> map = new HashMap<>();
        for (String matchKey : matchedKeyList) {
            String retKey = matchKey.substring(key.length() + 1).toLowerCase();
            map.put(retKey, parseVariable(sourceMap.get(matchKey)));
        }
        return map;
    }

    /**
     * 获取对象
     *
     * @param mapKey
     * @param keyInMap
     * @return
     */
    public static Object getValueInMap(String mapKey, String keyInMap) {
        Map<String, Object> map = getMap(mapKey);
        if (map == null) {
            throw new IllegalStateException("can not found map with key: " + mapKey);
        }
        return map.get(keyInMap.toLowerCase());
    }

    /**
     * 获取对象
     *
     * @param key
     * @return
     */
    public static List<Object> getList(String key) {
        Set<String> keySet = sourceMap.keySet();
        List<String> matchedKeyList = keySet.stream()
                .filter(tmpKey -> {
                    if (tmpKey.startsWith(key)) {
                        String tail = tmpKey.substring(key.length());
                        if (tail.matches("\\[\\d+\\]")) {
                            return true;
                        }
                    }
                    return false;
                })
                .collect(Collectors.toList());

        List<Object> objectList = new ArrayList<>();
        for (String matchKey : matchedKeyList) {
            objectList.add(sourceMap.get(matchKey));
        }
        return objectList;
    }

    /**
     * 获取对象
     *
     * @param key
     * @return
     */
    public static String[] getArray(String key) {
        return getString(key).split(",");
    }

    /**
     * 解析变量
     *
     * @param object
     * @return
     */
    private static Object parseVariable(Object object) {
        String objectString = object.toString();
        int indexHead = objectString.indexOf(HEAD);
        while (indexHead != -1) {
            int indexTail = objectString.indexOf(TAIL, indexHead);
            String variableName = objectString.substring(indexHead + HEAD.length(), indexTail);
            Object variableValue = sourceMap.get(variableName);
            objectString = objectString.substring(0, indexHead) + variableValue + objectString.substring(indexTail + TAIL.length());
            indexHead = objectString.indexOf(HEAD);
        }
        return objectString;
    }

    /**
     * 检查系统根路径是否是windows格式,是则报错
     *
     * @return
     */
    public static void checkRootParentPath() {
        String rootParentPath = getString("dwbi.base-common.root-parent-path");
        if (!rootParentPath.startsWith("/")) {
            throw new IllegalStateException("rootParentPath should be matches unix path. rootParentPath: " + rootParentPath);
        }
    }


}