简体   繁体   English

解析属性文件Java ResourceBundle的环境变量

[英]Resolve environment variables of property file Java ResourceBundle

Our application is using java8 and spring.我们的应用程序使用 java8 和 spring。 We are working to moving to kubernetes.我们正在努力迁移到 kubernetes。 For that reason, I want to use environment variables in the properties file like as follow and declare the -出于这个原因,我想在properties文件中使用环境变量,如下所示并声明 -

conf.dir.path = ${APPLICATION_CONF_PATH}
database.name = ${APPLICATION_DB_SCHEMA}
save.file.path = ${COMMON_SAVE_PATH}${APPLICATION_SAVE_PATH}
# And many more keys

But right now the values are not resolved/expanded by environment variable.但是现在这些值没有被环境变量解析/扩展。

Application initialization of property is as below -属性的应用程序初始化如下 -

public enum ApplicationResource {
    CONF_DIR_PATH("conf.dir.path"),
    DB_NAME("database.name")
    FILE_SAVE_PATH("save.file.path"),
    // And many more keys

    private final String value;

    ApplicationResource(String value) {
        this.value = value;
    }

    private static final String BUNDLE_NAME = "ApplicationResource";
    private static Properties props;

    static {
        try {
            Properties defaults = new Properties();
            initEnvironment(defaults, BUNDLE_NAME);
            props = new Properties(defaults);
        } catch (Throwable t) {
            t.printStackTrace();
        }
    }

    private static void initEnvironment(Properties props, String bundleName) throws Throwable {
        ResourceBundle rb = ResourceBundle.getBundle(bundleName);
        Enumeration<?> enu = rb.getKeys();
        String key = null;
        String value = null;
        while (enu.hasMoreElements()) {
            key = (String) enu.nextElement();
            value = rb.getString(key);
            props.setProperty(key, value);
        }
    }

    public String getString() {
        return props.getProperty(value);
    }

    public int getInt() throws NumberFormatException {
        String str = getString();
        if (str == null || str.length() == 0) {
            return 0;
        } else {
            return Integer.parseInt(str);
        }
    }
}

getString is used extensively. getString被广泛使用。 Right now when getString is called, it returns the literal string from the properties file.现在,当调用getString时,它会从属性文件中返回文字字符串。 Is there any way to properly resolve environment variables without impacting the codebase?有没有办法在不影响代码库的情况下正确解析环境变量?

Edit : By [without impacting the codebase], I meant only changing/editing code in the above enum/class file and the change being transparent in other areas.编辑:[不影响代码库],我的意思是仅更改/编辑上述枚举/类文件中的代码,并且更改在其他区域是透明的。

The simplest variant based on the Regex engine would be:基于 Regex 引擎的最简单的变体是:

private static final Pattern VARIABLE = Pattern.compile("\\$\\{(.*?)\\}");
public String getString() {
    return VARIABLE.matcher(props.getProperty(value))
        .replaceAll(mr -> Matcher.quoteReplacement(System.getenv(mr.group(1))));
}

This replaces all occurrences of ${VAR} with the result of looking up System.getenv("VAR") .这会将所有出现的${VAR}替换为查找System.getenv("VAR")的结果。 If the string contains no variable references, the original string is returned.如果字符串不包含变量引用,则返回原始字符串。 It does, however, not handle absent variables.但是,它不处理不存在的变量。 If you want to handle them (in a different way than failing with a runtime exception), you have to add the policy to the function.如果您想处理它们(以不同于运行时异常失败的方式),您必须将策略添加到 function。

Eg the following code keeps variable references in their original form if the variable has not been found:例如,如果未找到变量,则以下代码将变量引用保留为其原始形式:

public String getString() {
    return VARIABLE.matcher(props.getProperty(value))
        .replaceAll(mr -> {
            String envVal = System.getenv(mr.group(1));
            return Matcher.quoteReplacement(envVal != null? envVal: mr.group());
        });
}

replaceAll(Function<MatchResult, String>) requires Java 9 or newer. replaceAll(Function<MatchResult, String>)需要 Java 9 或更高版本。 For previous versions, you'd have to implement such a replacement loop yourself.对于以前的版本,您必须自己实现这样的替换循环。 Eg例如

public String getString() {
    String string = props.getProperty(value);
    Matcher m = VARIABLE.matcher(string);
    if(!m.find()) return string;
    StringBuilder sb = new StringBuilder();
    int last = 0;
    do {
        String replacement = System.getenv(m.group(1));
        if(replacement != null) {
            sb.append(string, last, m.start()).append(replacement);
            last = m.end();
        }
    } while(m.find());
    return sb.append(string, last, string.length()).toString();
}

This variant does not use appendReplacement / appendTail which is normally used to build such loops, for two reasons.此变体不使用通常用于构建此类循环的appendReplacement / appendTail ,原因有两个。

  • First, it provides more control over how the replacement is inserted, ie by inserting it literally via append(replacement) we don't need Matcher.quoteReplacement(…) .首先,它提供了对如何插入替换的更多控制,即通过append(replacement)逐字插入,我们不需要Matcher.quoteReplacement(…)
  • Second, we can use StringBuilder instead of StringBuffer which might also be more efficient.其次,我们可以使用StringBuilder代替StringBuffer ,这也可能更有效。 The Java 9 solution uses StringBuilder under the hood, as support for it has been added to appendReplacement / appendTail in this version too. Java 9 解决方案在后台使用StringBuilder ,因为在此版本中也已将对它的支持添加到appendReplacement / appendTail中。 But for previous versions, StringBuilder can only be used when implementing the logic manually.但是对于以前的版本, StringBuilder只能在手动实现逻辑时使用。

Note that unlike the replaceAll variant, the case of absent variables can be handled simpler and more efficient with a manual replacement loop, as we can simply skip them.请注意,与replaceAll变体不同,缺少变量的情况可以通过手动替换循环更简单、更有效地处理,因为我们可以简单地跳过它们。


You said you don't want to change the initialization code, but I still recommend bringing it into a more idiomatic form, ie你说你不想改变初始化代码,但我还是建议把它变成更惯用的形式,即

private static void initEnvironment(Properties props, String bundleName) {
    ResourceBundle rb = ResourceBundle.getBundle(bundleName);
    for(Enumeration<String> enu = rb.getKeys(); enu.hasMoreElements(); ) {
        String key = enu.nextElement();
        String value = rb.getString(key);
        props.setProperty(key, value);
    }
}

In the end, it's still doing the same.最后,它仍然在做同样的事情。 But iteration loops should be using for , to keep initialization expression, loop condition and fetching the next element as close as possible.但是迭代循环应该使用for ,以保持初始化表达式、循环条件和获取下一个元素尽可能接近。 Further, there is no reason to use Enumeration<?> with a type cast when you can use Enumeration<String> in the first place.此外,当您可以首先使用Enumeration<String>时,没有理由将Enumeration<?>与类型转换一起使用。 And don't declare variables outside the necessary scope.并且不要在必要的 scope 之外声明变量。 And there's no reason to pre-initialize them with null .并且没有理由使用null预初始化它们。

Spring support environment variable or system variable or application.property file if you able to use kubernates configmap its better choice. Spring 支持环境变量或系统变量或 application.property 文件,如果您能够使用 kubernates configmap 是更好的选择。 How to set environment variable dynamically in spring test 如何在 spring 测试中动态设置环境变量

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM