加载应用程序设置的最佳方法


24

保留Java应用程序设置的一种简单方法是使用扩展名为“ .properties”的文本文件表示,该文件包含与特定值(此值可以是数字,字符串,日期等)关联的每个设置的标识符。 。C#使用类似的方法,但文本文件必须命名为“ App.config”。在这两种情况下,都必须在源代码中初始化一个特定的类来读取设置:此类具有一种方法,该方法返回与指定的设置标识符关联的值(作为字符串)。

// Java example
Properties config = new Properties();
config.load(...);
String valueStr = config.getProperty("listening-port");
// ...

// C# example
NameValueCollection setting = ConfigurationManager.AppSettings;
string valueStr = setting["listening-port"];
// ...

在这两种情况下,我们都应该解析从配置文件加载的字符串,并将转换后的值分配给相关的类型化对象(在此阶段可能发生解析错误)。解析步骤之后,我们必须检查设置值是否属于特定的有效域:例如,队列的最大大小应为正值,某些值可能是相关的(例如:min <max ), 等等。

假设应用程序应在启动时立即加载设置:换句话说,应用程序执行的第一个操作是加载设置。设置的任何无效值必须自动替换为默认值:如果一组相关设置发生这种情况,则这些设置都将设置为默认值。

执行这些操作的最简单方法是创建一个方法,该方法首先解析所有设置,然后检查加载的值,最后设置任何默认值。但是,如果使用这种方法,维护将很困难:随着开发应用程序时设置数量的增加,更新代码变得越来越困难。

为了解决此问题,我想到了如下使用“ 模板方法”模式。

public abstract class Setting
{
    protected abstract bool TryParseValues();

    protected abstract bool CheckValues();

    public abstract void SetDefaultValues();

    /// <summary>
    /// Template Method
    /// </summary>
    public bool TrySetValuesOrDefault()
    {
        if (!TryParseValues() || !CheckValues())
        {
            // parsing error or domain error
            SetDefaultValues();
            return false;
        }
        return true;
    }
}

public class RangeSetting : Setting
{
    private string minStr, maxStr;
    private byte min, max;

    public RangeSetting(string minStr, maxStr)
    {
        this.minStr = minStr;
        this.maxStr = maxStr;
    }

    protected override bool TryParseValues()
    {
        return (byte.TryParse(minStr, out min)
            && byte.TryParse(maxStr, out max));
    }

    protected override bool CheckValues()
    {
        return (0 < min && min < max);
    }

    public override void SetDefaultValues()
    {
        min = 5;
        max = 10;
    }
}

问题在于,通过这种方式,我们需要为每个设置(甚至是单个值)创建一个新类。还有其他解决此类问题的方法吗?

综上所述:

  1. 易于维护:例如,添加一个或多个参数。
  2. 可扩展性:应用程序的第一个版本可以读取单个配置文件,但是更高版本可以提供多用户设置的可能性(管理员设置基本配置,用户只能设置某些设置,等等。)。
  3. 面向对象的设计。

对于那些建议使用.properties文件的人,您希望在开发,测试和生产期间在其中存储文件本身,因为希望它不在同一位置。然后,除非您可以在运行时检测到环境,然后在应用程序内使用硬编码的位置,否则将需要使用任何位置(开发,测试或生产)重新编译应用程序。

Answers:


8

本质上,外部配置文件被编码为YAML文档。然后在应用程序启动期间对此进行解析,并将其映射到配置对象。

最终结果是强大的,并且首先易于管理。


7

让我们从两个角度考虑这一点:用于获取配置值的API和存储格式。它们通常是相关的,但是将它们分开考虑会很有帮助。

配置API

模板方法模式非常笼统,但是我怀疑您是否真的需要这种笼统性。对于每种类型的配置值,您都需要一个类。你真的有那么多类型吗?我猜想只有少数几个可以实现:字符串,整数,浮点数,布尔值和枚举。有了这些,您可以拥有一个Config包含一些方法的类:

int getInt(name, default, min, max)
float getFloat(name, default, min, max)
boolean getBoolean(name, default)
String getString(name, default)
<T extends Enum<T>> T getEnum(name, Class<T> enumClass, T default)

(我想我在最后一个权利上获得了仿制药。)

基本上,每种方法都知道如何处理配置文件中的字符串值的解析,错误处理以及在适当时返回默认值的方法。对数值进行范围检查可能就足够了。您可能希望具有忽略范围值的重载,这等效于提供Integer.MIN_VALUE,Integer.MAX_VALUE的范围。枚举是一种针对固定的字符串集验证字符串的类型安全方式。

有些事情是无法解决的,例如多个值,相互关联的值,动态表查找等。您可以为此编写专门的解析和验证例程,但是如果这样做太复杂,我将开始质疑是否要对配置文件做太多事情。

储存格式

Java属性文件对于存储单个键值对似乎不错,它们很好地支持了我上面描述的值类型。您还可以考虑使用其他格式,例如XML或JSON,但是除非嵌套或重复数据,否则这些格式可能会过大。在这一点上,它似乎超出了配置文件的范围。

Telastyn提到了序列化对象。尽管序列化确实有困难,但这是有可能的。它是二进制文件,而不是文本文件,因此很难查看和编辑值。您必须处理序列化兼容性。如果序列化输入中缺少值(例如,您向Config类添加了一个字段,并且正在读取其旧的序列化形式),则新字段将初始化为null / zero。您必须编写逻辑以确定是否填写其他一些默认值。但是,零表示没有配置值,还是指定为零?现在,您必须调试此逻辑。最后(不确定是否要担心),您仍然可能需要验证序列化对象流中的值。恶意用户有可能(尽管不方便)无法检测到地修改序列化的对象流。

我想尽可能地坚持使用属性。


2
嗨,斯图尔特,很高兴在这里见到您:-)。我会在Stuarts回答中补充说,如果您使用泛型进行强类型键入,那么您的临时想法将在Java中起作用,因此也可以将Setting <T>作为选项。
Martijn Verburg

@StuartMarks:嗯,我的第一个想法是只写一个Config类,并使用由您提出的方法:getInt()getByte()getBoolean()继续与这个想法,等我先读所有的价值观,我可以每个值的标记关联(如果在反序列化期间发生问题,例如解析错误,则此标志为false)。之后,我可以为所有加载的值启动验证阶段并设置任何默认值。
enzom83

2
我希望使用某种JAXB或YAML方法来简化所有细节。
加里·罗

4

我是如何做到的:

将所有内容初始化为默认值。

解析文件,随时存储值。设置的位置负责确保这些值可以接受,忽略错误的值(并因此保留默认值)。


这也可能是一个好主意:加载设置值的类可能只需要处理即可从配置文件中加载值,也就是说,它的责任只能是加载值从配置文件; 相反,每个模块(使用某些设置)将负责验证值。
enzom83

2

还有其他解决此类问题的方法吗?

如果您需要的只是一个简单的配置,我喜欢为其创建一个普通的旧类。它会初始化默认值,并且可以由应用程序通过内置的序列化类从文件中加载。然后,应用将其传递给需要它的东西。无需费解解析或转换,无需费力配置字符串,也无铸造垃圾。它使配置的方式更容易使用的代码环境,此时需要保存/从服务器或预设加载,这样更容易在单元测试中使用。


1
无需费解解析或转换,无需费力配置字符串,也无铸造垃圾。你什么意思?
enzom83

1
我的意思是:1.您无需获取AppConfig结果(字符串)并将其解析为所需的内容。2.您无需指定任何类型的字符串即可选择所需的配置参数。这是容易发生人为错误且难以重构的事情之一,并且3.在以编程方式设置值时,您无需再进行其他类型转换。
Telastyn 2012年

2

至少在.NET中,您可以轻松地创建自己的强类型配置对象- 有关快速示例,请参见此MSDN文章

Protip:将您的config类包装在一个接口中,然后让您的应用程序与之对话。轻松注入虚假配置以进行测试或牟利。


我读了MSDN文章:有趣的是,基本上每个类的子ConfigurationElement类都可以代表一组值,对于任何值,您都可以指定一个验证器。但是,例如,如果我要表示一个由四个概率组成的配置元素,则四个概率值是相关的,因为它们的总和必须等于1.如何验证此配置元素?
enzom83

1
我通常会争辩说这不是用于低级别配置验证的东西-我会在我的配置类中添加一个AssertConfigrationIsValid方法以在代码中进行介绍。如果那对您不起作用,我认为您可以通过扩展属性的基类来创建自己的配置验证器。他们有一个比较验证器,因此显然可以谈论跨属性。
怀亚特·巴尼特
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.