Лучший способ загрузить настройки приложения


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 ), и так далее.

Предположим, что приложение должно загрузить настройки сразу после его запуска: другими словами, первая операция, выполняемая приложением, заключается в загрузке настроек. Любые недопустимые значения параметров должны быть автоматически заменены значениями по умолчанию: если это происходит с группой связанных параметров, все эти параметры устанавливаются со значениями по умолчанию.

Самый простой способ выполнить эти операции - создать метод, который сначала анализирует все параметры, затем проверяет загруженные значения и, наконец, устанавливает любые значения по умолчанию. Однако обслуживание будет затруднено, если вы используете этот подход: по мере того, как количество параметров увеличивается при разработке приложения, становится все труднее обновлять код.

Чтобы решить эту проблему, я подумал об использовании шаблона Template Method следующим образом.

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, где вы храните сам файл во время разработки, тестирования и производства, потому что, надеюсь, он не будет находиться в том же месте. Затем приложение необходимо будет перекомпилировать с любым местоположением (dev, test или prod), если только вы не можете обнаружить среду во время выполнения, а затем иметь жестко закодированные местоположения внутри приложения.

Ответы:


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. Enum - это безопасный для типов способ проверки строки по фиксированному набору строк.

Есть некоторые вещи, которые он не обрабатывает, такие как множественные значения, значения, которые взаимосвязаны, динамический поиск таблиц и т. Д. Вы можете написать для них специализированные процедуры анализа и проверки, но если это станет слишком сложным, я бы начал сомневаться пытаетесь ли вы сделать слишком много с файлом конфигурации.

Формат хранения

Файлы свойств Java прекрасно подходят для хранения отдельных пар ключ-значение, и они довольно хорошо поддерживают типы значений, которые я описал выше. Вы также можете рассмотреть другие форматы, такие как XML или JSON, но они, вероятно, излишни, если у вас нет вложенных или повторных данных. На данный момент это кажется далеко за пределами файла конфигурации ....

Теластин упомянул сериализованные объекты. Это возможно, хотя сериализация имеет свои трудности. Это двоичный файл, а не текстовый, поэтому трудно просматривать и редактировать значения. Вы должны иметь дело с совместимостью сериализации. Если в сериализованном вводе отсутствуют значения (например, вы добавили поле в класс Config и читаете его старую сериализованную форму), новые поля инициализируются как ноль / ноль. Вы должны написать логику, чтобы определить, следует ли заполнить какое-то другое значение по умолчанию. Но означает ли ноль отсутствие значения конфигурации, или оно было указано равным нулю? Теперь вы должны отладить эту логику. Наконец (не уверен, что это проблема), вам все равно может потребоваться проверка значений в сериализованном потоке объектов. Злоумышленник может (хотя и неудобно) незаметно изменять поток сериализованных объектов.

Я бы сказал, чтобы придерживаться свойств, если это вообще возможно.


2
Эй, Стюарт, рад тебя видеть здесь :-). Я добавлю к ответу Стюартса, что, как мне кажется, ваша временная идея будет работать в Java, если вы будете использовать Generics для строгой типизации, так что вы также можете использовать параметр <T> в качестве опции.
Мартейн Вербург

@StuartMarks: Ну, моя первая мысль была просто написать Configкласс и использовать подход , предложенный вами: getInt(), getByte(), getBoolean()и т.д .. Продолжая эту идею, я сначала прочитал все значения , и я мог связать каждое значение флага (этот флаг равен false, если во время десериализации возникла проблема, например, ошибки синтаксического анализа). После этого я мог бы начать фазу проверки для всех загруженных значений и установить любые значения по умолчанию.
enzom83

2
Я бы предпочел какой-то подход JAXB или YAML, чтобы упростить все детали.
Гэри Роу

4

Как я это сделал:

Инициализируйте все до значений по умолчанию.

Разбираем файл, сохраняя значения по ходу дела. Заданные места отвечают за обеспечение приемлемости значений, неправильные значения игнорируются (и, следовательно, сохраняют значение по умолчанию).


Это также может быть хорошей идеей: класс, который загружает значения настроек, может иметь дело только с загрузкой значений из файла конфигурации, то есть его ответственность может быть только за загрузку значений Из файла конфигурации; вместо этого каждый модуль (который использует некоторые настройки) будет обязан проверять значения.
enzom83

2

Существуют ли другие решения этой проблемы?

Если все, что вам нужно, это простая конфигурация, я хотел бы сделать для нее простой старый класс. Он инициализирует значения по умолчанию и может быть загружен из файла приложением через встроенные классы сериализации. Затем приложение передает его всем, кто в этом нуждается. Никаких проблем с разбором или преобразованием, без перестановок с конфиг-строками, без лишнего мусора. И это делает конфигурации способ легче использовать для в-кода сценариев , где она должна быть сохранена / загружена с сервера или в качестве предварительных настроек, и способ проще в использовании модульных тестов.


1
Никаких проблем с разбором или преобразованием, без перестановок с конфиг-строками, без лишнего мусора. Что вы имеете в виду?
enzom83

1
Я имею в виду, что: 1. Вам не нужно брать результат AppConfig (строку) и разбирать его на то, что вы хотите. 2. Вам не нужно указывать какую-либо строку, чтобы выбрать какой параметр конфигурации вы хотите; это одна из тех вещей, которая подвержена человеческим ошибкам и трудно реорганизована, и 3. вам не нужно делать другие преобразования типов, когда вы собираетесь устанавливать значение программно.
Теластин

2

По крайней мере, в .NET вы можете довольно легко создавать собственные строго типизированные объекты конфигурации - быстрый пример см. В этой статье MSDN .

Подсказка: оберните ваш конфигурационный класс в интерфейс и дайте приложению поговорить об этом. Облегчает внедрение поддельной конфигурации для тестирования или получения прибыли.


Я прочитал статью MSDN: это интересно, по сути каждый подкласс ConfigurationElementкласса может представлять группу значений, и для любого значения вы можете указать валидатор. Но если, например, я хотел представить элемент конфигурации, состоящий из четырех вероятностей, четыре значения вероятности коррелируют, так как их сумма должна быть равна 1. Как проверить этот элемент конфигурации?
enzom83

1
Я бы вообще утверждал, что это не что-то для проверки конфигурации низкого уровня - я бы добавил метод AssertConfigrationIsValid в мой класс конфигурации, чтобы покрыть это в коде. Если это не работает для вас, я думаю, вы можете создать свои собственные валидаторы конфигурации, расширив базовый класс атрибута. У них есть валидатор сравнения, поэтому они могут говорить о перекрестном свойстве.
Уайетт Барнетт
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.