Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Эластичная конфигурация с Guice

Anna | 5.06.2014 | нет комментариев

Существует уйма разных конфигурационных библиотек, доступных в Java, скажем, одна от Apache Commons, но они как правило, следуют дюже простому шаблому: парсинг ряда конфигурационных файлов и построение на основе этих данных Property либо Map, у которого в последующем и запрашиваются значения:

Double double = config.getDouble("number");
Integer integer = config.getInteger("number");

Но данный подход меня не устраивает по нескольким причинам:

  • Во-первых, получается довольно многословно;
  • Доводится передавать конфигурационный объект целиком, даже если от него требуется каждого один параметр;
  • Дюже легко допустить ошибку в ключе и прочитать некорректные данные.


Некоторое время назад я читал документацию Guice и наткнулся на параграф, натолкнувший на мысль, что это дозволено делать отменнее. Вот релевантный отрывок:

Guice поддерживает объединяющие аннотации, имеющие аттрибуты.
В тех редких случаях, когда вам они требуются:
1) Сделайте аннотацию @interface.
2) Сделайте класс, реализующий интерфейс аннотации. Следуйте гайдлайнам в отношении equals() и hashCode(), определенным в Annotation Javadoc. Передайте экземпляр класса в annotatedWith().

И здесь возникла мысль, что применяя эту технику, дозволено получить в точности то, что мне требуется — сделать больше «разумный» конфигурационный фреймворк, правда у меня были планы не связанные с трюком annotatedWith. Востребованость этого способа станет ясна чуть позднее, а пока обозначим основные цели.

Цели

Хотелось бы реализовать:

  • Вероятность инжекта индивидуальных конфигурационных значений в мою кодовую базу, по вероятности типобезопасно. Никаких @Named и прочих строковых идентификаторов;
  • Список всех свойств, доступных приложению, совместно с типами, значениями по-умолчанию, документацией и вероятностью дальнейших совершенствований (скажем, настройка обязательна/опциональна, механическое определение неиспользуемых настроек, пометка настроек нерекомендуемыми и т.д.).

Мне не значимо какой будет применяться внешний интерфейс: как настройки получены — не релевантно фреймворку, они могут быть в виде XML, JSON, приходить по сети либо из базы данных. На входе фреймворка Map из настроек и я получаю их оттуда.

К тому времени, как мы завершим, сумеем сделать что-то как бы:

# Какой-то properties-файл
    host=foo.com
    port=1234

Используем эти значения в коде:

public class A {
    @Inject
    @Prop(Property.HOST)
    private String host;

    @Inject
    @Prop(Property.PORT)
    private Integer port;

    // ...
}

 

Реализация

Определение аннотации Prop банально:

@Retention(RUNTIME)
@Target({ ElementType.FIELD, ElementType.PARAMETER })
@BindingAnnotation
public @interface Prop {
  Property value();
}

Property — это enum, содержащий всю информацию, нужную для ваших настроек. Для примера выше:

public enum Property {
    HOST("host",
        "The host name",
        new TypeLiteral<String>() {},
        "foo.com"),

    PORT("port",
        "The port",
        new TypeLiteral<Integer>() {},
        1234);
}

Перечисление содержит строковое поле «имя свойства», изложение, значение по-умолчанию и его тип. Обратите внимание, что данный тип — TypeLiteral, так мы сумеем описать свойства, имеющие даже generic-типы, которые в отвратном случае были бы стерты, трюк разрешает вступление кэшей и других generic-коллекций. Видимо, что вы сумеете добавить добавочные параметры по своему усмотрению (скажем, «deprecated»).

Дальнейшим шагом будет привязка всех свойств, которые мы распарсили на входе — назовем Map «allProps» — в наш модуль так, Дабы Guice осознал, как инжектить их.
Для того Дабы сделать это, мы переберем все эти свойства и привяжем их к своему провайдеру. От того что мы используем типизированные поля, то обратите внимание на использование Key.get() из Guice API, тот, что разрешает отобразить качество на соответствующую аннотацию:

    for (Property prop : Property.values()) {
        Object value = PropertyConverters.getValue(prop.getType(), prop, allProps.asMap());
        binder.bind(Key.get(prop.getType(), new PropImpl(prop)))
                .toProvider(new PropertyProvider(prop, value));
    }

В примере три класса, которые я еще не объяснил. 1-й — PropertyConverters — легко считывает качество как строку и преобразует в тип Java. 2-й — PropertyProvider — примитивный провайдер из Guice:

public class PropertyProvider<T> implements Provider<T> {
    private final T value;
    private final Property property;

    public PropertyProvider(Property property, T value) {
        this.property = property;
        this.value = value;
    }

    @Override
    public T get() {
        return value;
    }
}

PropImpl чуть труднее и это все время меня останавливало, когда разрабатывал сходственный фреймворк, пока не наткнулся на тот лакомый ломтик в документации Guice. Дабы осознать надобность в этом классе, следует узнать как работает Key.get(). Guice его использует для отображения типов на уникальные ключи, которые применяются для инжекта необходимых значений. Значимая часть тут в том, что способ работает не только с Class и TypeLiteral, но и привязан к соответствующей аннотации. Эта аннотация может быть @Named, правда я не огромный ее фанат, потому что работает со строками, значит подвержена опечаткам, либо собственная аннотация, это огромнее нам подойдет. Тем не менее, аннотации в Java — специальная вещь, невозможно получить ее экземпляр легко так.

Это то место, где вступает в игру трюк, описанный в начале статьи: Java, на самом деле, разрешает реализовать аннотацию с обыкновенным классом. Реализация оказалась довольно примитивный, сложность заключалась в осознании, что это вообще допустимо.

Сейчас все части на местах, осталось проанализировать, что тут за магия:

    @Inject
    @Prop(Property.HOST)
    private String host;

Когда Guice попадает в эту точку инжекта, он обнаруживает в своем загашнике несолько биндингов к строкам, потому что они были привязаны к Key, тот, что по сути пара (String, Prop). В данному случае он будет искать пару String, Property.HOST и обнаружит там провайдера, тот, что был инстанцирован со значением из property-файла, что он и возвращает.

Обобщаем

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

@Retention(RUNTIME)
@Target({ ElementType.FIELD, ElementType.PARAMETER })
@BindingAnnotation
public @interface Prop {
    Property value();
}

Для того Дабы сделать его больше универсальным, мне пришлось возвращать Enum:

@Retention(RUNTIME)
@Target({ ElementType.FIELD, ElementType.PARAMETER })
@BindingAnnotation
public @interface Prop {
    Enum value();
}

К сожалению, это Java это не разрешает согласно JLS сегменты 8.9, Enum и его generic-вариант это не перечислимые типы, это подтверждает и J. Bloch.
Таким образом, в библиотеку преобразовать не получится, но если вы заинтересованы в применении его в своем плане, скопируйте начальный код и модифицируйте под свои нужды, начиная с Prop#value в соответствии с вашей конфигурацией.

Маленький Proof of Concept тут, верю обнаружите пригодным.

Источник: programmingmaster.ru

Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB