Java - 配置中的动态变量引用

问题描述 投票:0回答:1

我正在开发一个应用程序,它应该是高度可配置的。

其目标是有一个XML文件来存储其配置。在配置中可以定义 "变量 "元素,这些元素可以在整个配置中重复使用。

下面是一个例子。

<var name = "QUEUE_PREFIX"      value = "TEST/QUEUE/PREFIX"   />
<var name = "IN_QUEUE-NAME"     value = "${QUEUE_PREFIX}/IN"  />
<var name = "OUT_QUEUE-NAME"    value = "${QUEUE_PREFIX}/OUT" />

<mq-client IN-QUEUE =  "${IN_QUEUE_NAME}" 
           OUT-QUEUE = "${OUT_QUEUE_NAME}"/>

它的结果应该是

<var name = "QUEUE_PREFIX" value = "TEST/QUEUE/PREFIX"     />
<var name = "IN_QUEUE"     value = "TEST/QUEUE/PREFIX/IN"  />
<var name = "OUT_QUEUE"    value = "TEST/QUEUE/PREFIX/OUT" />

<mq-client IN-QUEUE =  "TEST/QUEUE/PREFIX/IN" 
           OUT-QUEUE = "TEST/QUEUE/PREFIX/OUT"/>

这种替换是很容易的,在我的原型中已经可以正常工作了。一旦有一整个数组和多个 "层 "的变量被引用,就会变得很困难。比如一个变量引用一个变量,而这个变量也已经引用了一个变量。

比如说,一个变量引用一个变量,而这个变量也已经引用了一个变量。

<var name = "USER_NAME"         value = "TESTUSER"   />
<var name = "USER_HOME"         value = "C:\USERS\${USER_NAME}"   />
<var name = "TEST_DIR"          value = "${USER_HOME}/IN"  />
<var name = "TEST"              value = "${TEST_DIR}/${PID}" />

在这种情况下,应用程序必须决定先替换这些变量中的哪一个 为了不影响其他变量的运行,就必须先解决这个问题。

当然还有其他的问题,比如,如果两个变量相互引用,我们该怎么办?

我的问题

有人做过类似的事情吗,你是怎么解决的?有没有一个框架、库或者其他的东西能够解决这种变量的配置?

java variables configuration
1个回答
0
投票

如果你是在Spring环境中工作,你可以将你的问题委托给现有的PropertyResolvers。如何在Spring中解决属性占位符

很久以前我就遇到过和你描述的同样的问题,不得不在没有Spring的情况下解决。正如你已经认识到的,主要的困难是递归和循环依赖。所以代码有点长,但15年后仍然可以使用。尽管如此,我还是把它提高到了Java 8的水平。

这是主类。

package config;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

public class ConfigResolver {

    private static final String CYCLE_MARKER = "#CYCLE?";
    private static final String PARAM_START = "${";
    private static final String PARAM_END = "}";

    /**
     * Creates a now config map with all references resolved.
     * 
     * @throws IllegalStateException
     *             In case of undefined or circular references.
     */
    public Map<String, String> resolve(Map<String, String> config) throws IllegalStateException {
        final Map<String, String> result = new LinkedHashMap<>();
        config.keySet().stream().forEach(key -> resolve(key, config, result));
        return result;
    }

    /**
     * Copies the given key and its value from the source map into the target map.
     * If there are references to other keys, those will be resolved recursively and
     * copied as well. References to System properties are also valid. If the value
     * is already present, nothing will happen.
     * 
     * @throws IllegalStateException
     *             In case of undefined or circular references.
     */
    private String resolve(String key, Map<String, String> source, Map<String, String> target)
            throws IllegalStateException {

        String value = target.get(key);
        if (value == CYCLE_MARKER) {
            throw new IllegalStateException("Circular reference for key:" + key);
        }
        if (value != null) {
            return value;
        }
        value = source.get(key);
        if (value == null) {
            return System.getProperty(key);
        }
        target.put(key, CYCLE_MARKER);

        final List<Parameter> params = parseParams(value);
        if (!params.isEmpty()) {
            final StringBuilder resolvedValue = new StringBuilder(value);
            int deviation = 0;
            for (Parameter p : params) {
                final String v = resolve(p.getName(), source, target);
                if (v == null) {
                    throw new IllegalStateException("Undefined parameter: " + p.getName());
                }
                resolvedValue.replace(p.getStart() + deviation, p.getEnd() + deviation, v);
                deviation += v.length() - p.getEnd() + p.getStart();
            }
            value = resolvedValue.toString();
        }
        target.put(key, value);

        return value;
    }

    /**
     * Extracts all parameters from the given String
     */
    private List<Parameter> parseParams(String value) {
        final List<Parameter> result = new ArrayList<Parameter>();
        int start = 0;
        int end = 0;
        while (start >= 0) {
            start = value.indexOf(PARAM_START, end);
            end = value.indexOf(PARAM_END, start) + PARAM_END.length();
            if (start >= 0 && end > start + 1) {
                final String name = value.substring(start + PARAM_START.length(), end - PARAM_END.length());
                result.add(new Parameter(name, start, end));
            }
        }
        return result;
    }

    /**
     * Parameter with position in String
     */
    private static class Parameter {

        private final String name;
        private final int start;
        private final int end;

        Parameter(String name, int start, int end) {
            this.start = start;
            this.end = end;
            this.name = name;
        }

        public String getName() {
            return name;
        }

        public int getStart() {
            return start;
        }

        public int getEnd() {
            return end;
        }
    }
}

这是一个JUnit测试,涵盖了你的例子。

package config;

import static org.junit.Assert.assertEquals;

import java.util.LinkedHashMap;
import java.util.Map;

import org.junit.Test;

public class ConfigResolverTest {

    private ConfigResolver sut = new ConfigResolver();

    @Test
    public void testResolve() {

        System.setProperty("PID", "1234");

        final Map<String, String> raw = new LinkedHashMap<>();
        raw.put("USER_NAME", "TESTUSER");
        raw.put("USER_HOME", "C:/USERS/${USER_NAME}");
        raw.put("TEST_DIR", "${USER_HOME}/IN");
        raw.put("TEST", "${TEST_DIR}/${PID}");

        final Map<String, String> resolved = sut.resolve(raw);

        assertEquals("C:/USERS/TESTUSER/IN/1234", resolved.get("TEST"));
        assertEquals(4, resolved.size());
    }

    @Test(expected = IllegalStateException.class)
    public void testResolve_Circular() {

        final Map<String, String> raw = new LinkedHashMap<>();
        raw.put("first", "${second}");
        raw.put("second", "${third}");
        raw.put("third", "${first}");

        sut.resolve(raw);
    }

    @Test(expected = IllegalStateException.class)
    public void testResolve_Undefined() {

        final Map<String, String> raw = new LinkedHashMap<>();
        raw.put("first", "${second}");

        sut.resolve(raw);
    }

}

0
投票

你可以试试Apache commons-configuration

来自 其例

application.name = Killer App
application.version = 1.6.2

application.title = ${application.name} ${application.version}

内插的字符串是 application.title = Killer App 1.6.2


0
投票

也许像这样简单。

Map<String,String> vars = new HashMap<>();
vars.put("USER_NAME", "TESTUSER");
vars.put("USER_HOME", "C:/USERS/${USER_NAME}");
vars.put("TEST_DIR", "${USER_HOME}/IN");
vars.put("TEST", "${TEST_DIR}/${PID}");
  1. 然后把有参考价值的值和没有参考价值的值进行分类(让我们称它们为 "字元")。
  2. 如果没有找到新的字元,那么就是循环引用,你必须停止。
  3. 如果没有带有引用的项,那么你就完成了。

  4. 否则,检查所有有引用的项,然后用所有可能的字元替换变量。将带引用的值用替换的方法替换,并更新Map。

  5. 进入步骤1。

从某种程度上来说,这就是广度先搜索,通过做替换来逐级传播,直到到达终点。

© www.soinside.com 2019 - 2024. All rights reserved.