Каков наилучший способ чтения параметров конфигурации из файла конфигурации в Java?
Предположим, что до времени выполнения мы не знаем, какие детали конфигурации (пользователь может настроить эти параметры в файле config
перед запуском приложения.
Я хочу прочитать эти данные конфигурации и использовать их везде, где они мне нужны, в моем приложении. Для этого я хочу сделать их глобальными константами (public static final
).
Итак, я сомневаюсь, есть ли последствия для производительности, если я читаю из config
файла непосредственно из требуемого класса? так как значения времени выполнения я не могу напрямую добавить в отдельный Interface
.
Я думаю, что это повлияет на производительность. Пожалуйста, предложите мне лучший способ сделать это.
ОБНОВЛЕНИЕ: Могу ли я использовать отдельный конечный класс для деталей конфигурации?
ставя все детали конфигурации как константы в отдельный public final class
(Чтобы сразу просмотреть все данные конфигурации из файла конфигурации и сохранить их в качестве глобальных констант для последующего использования в приложении)
Ответы
Ответ 1
Я думаю, что это повлияет на производительность.
Я сомневаюсь, что это будет правдой.
Предполагая, что приложение считывает файл конфигурации только один раз при запуске, время, затраченное на чтение файла, вероятно, не имеет отношения к общей производительности вашего приложения. Действительно, чем дольше будет выполняться приложение, тем меньше будет время запуска.
Стандартная рекомендация - только оптимизировать производительность приложения, когда у вас есть конкретные доказательства (например, измерения), чтобы сказать, что производительность является значительной проблемой. Затем оптимизируйте только те части вашего кода, которые профилируют, что вы действительно являетесь узким местом производительности.
Можно ли использовать отдельный конечный класс для деталей конфигурации
Да, это возможно. Никто не остановит вас 1.
Однако это плохая идея. Все, что означает, что вам нужно перекомпилировать свой код для изменения параметров конфигурации, - плохая идея. ИМО.
Чтобы прочитать все данные конфигурации сразу из файла конфигурации и сохранить их в качестве глобальных констант для последующего использования в приложении.
А... так хочется читать значения "констант" вместо жесткой проводки.
Да, это возможно. И это имеет больше смысла, чем параметры конфигурации жесткой проводки в коде. Но это еще не очень хорошая идея (ИМО).
Почему? Хорошо давайте посмотрим, как выглядит код:
public final class Config {
public static final int CONST_1;
public static final String CONST_2;
static {
int c1;
String c2;
try (Scanner s = new Scanner(new File("config.txt"))) {
c1 = s.nextInt();
c2 = s.next();
} catch (IOException ex) {
throw RuntimeException("Cannot load config properties", ex);
}
CONST_1 = c1;
CONST_2 = c2;
}
}
Первое наблюдение заключается в том, что не имеет значения, что класс final
. Он объявляет поля как final
, что делает их постоянными. (Объявление класса как final
предотвращает подклассирование, но это не влияет на поля static
. Статические поля не зависят от наследования.)
Следующее замечание состоит в том, что этот код является хрупким в нескольких отношениях:
-
Если что-то пойдет не так в статическом блоке инициализатора. исключенное исключение, которое бросает блок, будет обернуто как ExceptionInInitializerError
(да... это Error
!!), а класс Config
будет помечен как ошибочный.
-
Если это произойдет, нет реальной надежды на восстановление, и, возможно, даже плохая идея попробовать и диагностировать Error
.
-
Приведенный выше код выполняется, когда класс Config
инициализируется, но определение того, когда это произойдет, может быть сложным.
-
Если имя файла конфигурации является параметром, тогда у вас возникает проблема получить значение параметра... до запуска статической инициализации.
Далее, код довольно грязный по сравнению с загрузкой состояния в переменные экземпляра. И эта беспорядочность во многом является результатом работы в рамках ограничений статических инициализаторов. Вот как выглядит код, если вместо этого использовать переменные экземпляра final
.
public final class Config {
public final int CONST_1;
public final String CONST_2;
public Config(File file) throws IOException {
try (Scanner s = new Scanner(file)) {
CONST_1 = s.nextInt();
CONST_2 = s.next();
}
}
}
Наконец, преимущества производительности полей static final
над полями final
крошечные:
-
возможно, одна или две машинные команды каждый раз, когда вы обращаетесь к одной из констант,
-
возможно, вообще ничего, если компилятор JIT умный, и вы соответствующим образом обрабатываете ссылку singleton Config
.
В любом случае, в подавляющем большинстве случаев выгоды будут незначительными.
1 - ОК... если ваш код проверен кодом, тогда кто-то, вероятно, остановит вас.
Ответ 2
Вы когда-нибудь слышали о конфигурации apache commons http://commons.apache.org/proper/commons-configuration/?
Это лучший читатель конфигурации, который я когда-либо нашел, и даже использую его в своем приложении, которое работает в производстве с 1 года. Никогда не возникало проблем, очень легко понять и использовать, отличная производительность. Я знаю, что это немного зависит от вашего приложения, но поверьте мне, вам понравится.
Все, что вам нужно сделать, это
Configuration config = new ConfigSelector().getPropertiesConfiguration(configFilePath);
String value = config.getString("key");
int value1 = config.getInt("key1");
String[] value2 = config.getStringArray("key2");
List<Object> value3 = config.getList("key3");
И вот оно. Ваш объект конфигурации будет содержать все значения конфигурации, и вы можете просто передать этот объект на столько классов, сколько хотите. С таким количеством доступных полезных методов вы можете извлечь любой тип ключа, который вы хотите.
Ответ 3
Это будет только одна временная стоимость, если вы помещаете их в файл свойств и читаете файл в начале вашего приложения и инициализируете все параметры как системные параметры (System.setProperty) и затем определите константы в вашем коде, например
public static final String MY_CONST = System.getProperty("my.const");
Но убедитесь, что инициализация в начале вашего приложения до загрузки любого другого класса.
Ответ 4
Существуют различные типы конфигурации.
Обычно для запуска приложения требуется некоторая конфигурация начальной загрузки, например для подключения к базе данных или службе. Способ J2EE для указания параметров подключения к базе данных - это "источник данных", указанный в вашем реестре JNDI вашего контейнера (Glassfish, JBoss, Websphere,...). Затем этот источник данных просматривается по имени в вашем файле persistence.xml. В приложениях, отличных от J2EE, более часто указывается их в контексте Spring или даже файле .properties. В любом случае вам обычно нужно что-то, чтобы подключить ваше приложение к некоторому хранилищу данных.
После начальной загрузки в хранилище данных опция предназначена для управления значениями конфигурации внутри этого хранилища данных. Например, если у вас есть база данных, вы можете использовать отдельную таблицу (представленную, например, JPA Entity в приложении) для значений конфигурации. Если вы не хотите/нуждаетесь в такой гибкости, вы можете использовать для этого простой файл .properties. Существует хорошая поддержка файлов .properties в Java (ResourceBundle) и в рамках Spring. Ванильный ResourceBundle просто загружает свойства один раз, помощник Spring предлагает настраиваемое кэширование и перезагрузку (это помогает в аспекте производительности, о котором вы говорили). Примечание. Вы также можете использовать Свойства, созданные хранилищем данных, а не файл.
Часто оба подхода сосуществуют в приложении. Значения, которые никогда не изменяются в развернутом приложении (например, имя приложения), могут быть прочитаны из файла свойств. Значения, которые могут потребоваться изменить приложением поддержки приложений во время выполнения без перераспределения (например, интервала таймаута сеанса), могут быть лучше сохранены в перезагружаемом файле .properties или в базе данных. Значения, которые могут быть изменены пользователями приложения, должны храниться в хранилище данных приложений и, как правило, иметь в приложении экран для их редактирования.
Поэтому я рекомендую разделить ваши настройки конфигурации на категории (например, bootstrap, deploy, runtime и application) и выбрать соответствующий механизм для их управления. Это также зависит от объема вашего приложения, т.е. Это веб-приложение J2EE, настольное приложение, утилита командной строки, пакетный процесс?
Ответ 5
Хорошо, это большая проблема, которая возникает в каждой жизни один раз в завещании. Теперь, придя к проблеме, это можно решить, создав одноэлементный класс, который имеет переменные экземпляра, такие же, как в файле конфигурации со значениями по умолчанию. Во-вторых, этот класс должен иметь такой метод, как getInstance()
, который читает свойства один раз и каждый раз возвращает один и тот же объект, если он существует. Для чтения файла мы можем использовать переменную окружения для получения пути или что-то вроде System.getenv("Config_path");
. Чтение свойств (метод readProperties()
) должно читать каждый элемент из файла конфигурации и устанавливать значение для переменных экземпляра одноэлементного объекта. Таким образом, теперь один объект содержит все значения параметра конфигурации, а также если параметр пуст, чем значение по умолчанию.
Ответ 6
Еще один способ - определить класс и прочитать файл свойств в этом классе.
Этот класс должен быть на уровне приложения и может быть помечен как Singleton.
Маркировка класса как Singleton позволит избежать создания нескольких экземпляров.
Ответ 7
Какой конфигурационный файл вы имеете в виду? Если это файл свойств, это может подойти вам:
public class Configuration {
// the configuration file is stored in the root of the class path as a .properties file
private static final String CONFIGURATION_FILE = "/configuration.properties";
private static final Properties properties;
// use static initializer to read the configuration file when the class is loaded
static {
properties = new Properties();
try (InputStream inputStream = Configuration.class.getResourceAsStream(CONFIGURATION_FILE)) {
properties.load(inputStream);
} catch (IOException e) {
throw new RuntimeException("Failed to read file " + CONFIGURATION_FILE, e);
}
}
public static Map<String, String> getConfiguration() {
// ugly workaround to get String as generics
Map temp = properties;
Map<String, String> map = new HashMap<String, String>(temp);
// prevent the returned configuration from being modified
return Collections.unmodifiableMap(map);
}
public static String getConfigurationValue(String key) {
return properties.getProperty(key);
}
// private constructor to prevent initialization
private Configuration() {
}
}
Вы также можете вернуть объект Properties
непосредственно из метода getConfiguration()
, но затем его потенциально можно изменить с помощью кода, который обращается к нему. Collections.unmodifiableMap()
не создает константу конфигурации (поскольку экземпляр Properties
получает свои значения методом load()
после его создания), однако, поскольку он завернут в немодифицируемую карту, конфигурация не может быть изменена другими классами.
Ответ 8
Поставить ключи конфигурации непосредственно в классы плохо: ключи конфигурации будут разбросаны по коду. Лучшей практикой является разделение кода приложения и кода конфигурации. Обычно используется инфраструктура инъекций зависимостей, например spring. Он загружает конфигурационный файл и создает объекты с использованием значений конфигурации. Если вам нужно какое-то значение конфигурации в вашем классе, вы должны создать установщик для этого значения. spring установит это значение во время инициализации контекста.
Ответ 9
Я рекомендую использовать JAXB или аналогичную инфраструктуру привязки, которая работает с текстовыми файлами. Поскольку реализация JAXB является частью JRE, она довольно проста в использовании. Как Denis Я советую не отвечать на конфигурационные ключи.
Вот простой пример для простого в использовании и по-прежнему довольно мощного способа настройки приложения с помощью XML и JAXB. Когда вы используете инфраструктуру DI, вы можете просто добавить аналогичный объект конфигурации в контекст DI.
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class ApplicationConfig {
private static final JAXBContext CONTEXT;
public static final ApplicationConfig INSTANCE;
// configuration properties with defaults
private int number = 0;
private String text = "default";
@XmlElementWrapper
@XmlElement(name = "text")
private List<String> texts = new ArrayList<>(Arrays.asList("default1", "default2"));
ApplicationConfig() {
}
static {
try {
CONTEXT = JAXBContext.newInstance(ApplicationConfig.class);
} catch (JAXBException ex) {
throw new IllegalStateException("JAXB context for " + ApplicationConfig.class + " unavailable.", ex);
}
File applicationConfigFile = new File(System.getProperty("config", new File(System.getProperty("user.dir"), "config.xml").toString()));
if (applicationConfigFile.exists()) {
INSTANCE = loadConfig(applicationConfigFile);
} else {
INSTANCE = new ApplicationConfig();
}
}
public int getNumber() {
return number;
}
public String getText() {
return text;
}
public List<String> getTexts() {
return Collections.unmodifiableList(texts);
}
public static ApplicationConfig loadConfig(File file) {
try {
return (ApplicationConfig) CONTEXT.createUnmarshaller().unmarshal(file);
} catch (JAXBException ex) {
throw new IllegalArgumentException("Could not load configuration from " + file + ".", ex);
}
}
// usage
public static void main(String[] args) {
System.out.println(ApplicationConfig.INSTANCE.getNumber());
System.out.println(ApplicationConfig.INSTANCE.getText());
System.out.println(ApplicationConfig.INSTANCE.getTexts());
}
}
Файл конфигурации выглядит так:
<?xml version="1.0" encoding="UTF-8"?>
<applicationConfig>
<number>12</number>
<text>Test</text>
<texts>
<text>Test 1</text>
<text>Test 2</text>
</texts>
</applicationConfig>
Ответ 10
protected java.util.Properties loadParams() throws IOException {
// Loads a ResourceBundle and creates Properties from it
Properties prop = new Properties();
URL propertiesFileURL = this.getClass().getResource("/conf/config.properties");
prop.load(new FileInputStream(new File(propertiesFileURL.getPath())));
return prop;
}
Properties prop = loadParams();
String prop1=(String) prop.get("x.y.z");