Как стать автором
Обновить
3
0
Дмитрий Овчинников @dcordero

Пользователь

Отправить сообщение
Проект Sumatra

This primary goal of this project is to enable Java applications to take advantage of graphics processing units (GPUs) and accelerated processing units (APUs)--whether they are discrete devices or integrated with a CPU--to improve performance.


А человеку минусов наставили.
сервис работает напрямую с environment, это нехорошо, это лишняя связанность, которая может неприятными моментами всплыть например в тестах


Для тестов можно сделать TestBeanConfig implements P1, P2,… P3.
Моей целью было показать как можно конфигурировать бины не только с помощью аннотацииями, но и из произвольных контейнеров.
Учитывая что Environment — сущность, неразрывно связанная с контекстом docs.spring.io/spring/docs/current/javadoc-api/org/springframework/context/support/AbstractApplicationContext.html#getEnvironment--, а PropertySource-конфигурирующие бины должны быть статическими, я не вижу здесь никакой лишней связанности. Вот если бы был бы одного уровня с «бизнес»-бинами, тогда ваш агрумент имел бы смысл.

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


См. вышеприведенный пример на Groovy без использования какого-либо фреймворка с IoC.
Я уже писал об этом в комментариях, но напишу еще: преимущество аннотаций в том, что они
1) не дают конфигурировать бины сложными объектами
2) обеспечивают (в моем решении) то, что уже на стадии конструктора конфигурация задана и все поля сервиса можно сделать финальными
3) конфигурация всегда сериализуема (это обеспечивается первым пунктом)
4) можно задавать дефолты в том месте, где они должны появляться: на уровне классов-потомков, причем в декларативном виде, а не где-то в коде
5) можно строить контексты на скриптовых языках типа Groovy не прибегая к помощи Spring, CDI, Guice и т.д.
Пользователь должен иметь возможность взять класс и создать нужный ему экземпляр множеством способов: через поля, через XML, через файл свойств.


Для этого в рассматриваемом случае есть конструктор с параметром: bounds у парараметра — только интерфейсы, поэтому никто не мешает написать свой контейнер, имплементирующий их. И пусть он берет данные из XML, из .properties, из БД и вообще откуда угодно.

Аннотации здесь нужны для:
  • Возможности задать дефолты для каждого класса потомка, если это требуется: аннотация, появляющаяся на классе-потомке говорит программисту какие будут дефолты еще до того, как он будет использовать какой-нибудь IoC, прямо в библиотеке
  • Типы свойств будут всегда простыми объектами или массивами их: это гарантируют интерфейсы аннотаций
  • Возможности создавать конфигурацию бинов например на Groovy


Последний случай выглядел бы примерно так:

   @CommonServiceParameters(threadCount=8)
   @HttpServerParameters(host = "0.0.0.0", port = 80)
   class HttpServer extends EmbeddedHttpServer {
   }

   @CommonServiceParameters(threadCount = 4)
   @DataStorageParameters(dir = "/ssd/data", ttl = 1, timeUnit = DAYS)
   class DataStorage extends EmbeddedDataStorage {
   }

   def conf = new Configuration(
       new HttpServer(),
       new DataStorage()
   ).start();


подразумевается, что Configuration знает как вызвать методы, помеченные @PostConstruct, @PreDestroy. Для этого требуется написать небольшую библиотеку.

Т.е. для простых приложений и для несложного IoC можно все сделать без фреймворков CDI, Spring, или Guice: только Groovy-файл с аннотациями.
В коде

public class ReportRenderer {
  //...

  public List<String> findMusic() {
    final List<String> music = new ArrayList<String>();
    for (final MusicFinder finder : ServiceLoader.load(MusicFinder.class)) {
      music.addAll(finder.getMusic());
    }
    Collections.sort(music);
    return music;
  }

  //...
}


ServiceLoader будет всякий раз создавать итератор и заполнять внутренний кэш на итерировании. Если в ClassLoader не добавляются динамически новые ресурсы с META-INF/services/..., то было бы более производительно создать ServiceLoader один раз (например, записав его в статическое финальное поле), и просто итерировать по нему в других методах.

Если все-таки в ClassLoader в рантайме добавляются ресурсы, то можно при добавлении такого ресурса вызывать ServiceLoader.reload(), чтобы очистить кэш уже загруженных инстансов.

Единственная проблема — в том, что ServiceLoader не потокобезопасен. Однако проблему можно решить, создав какой-нибудь метод типа
public static void visitAllMusicFinders(Consumer<MusicFinder> consumer) {
  synchronized (serviceLoader) {
    for (final MusicFinder musicFinder : serviceLoader) {
        consumer.accept(musicFinder);
    }
  }
}
Как раз «левые» свойства в property в моем случае никто не сможет задать. И пример с Guava Cache по-моему совсем неудачный: попробуйте задать для него weigher через .properties. Вообще CacheBuilder, если уж на то пошло, как раз-таки и является примером плохого контейнера для сервиса. Но Guava кэш, к счастью, не сервис, а его строительный блок.
Почему бы не использовать стандартные методы спринга? Вы же используете спринг в ваших примерах.


Я третий раз говорю одно и то же: статья не о том, как Spring позволяет инжектировать свойства в бины.

У вас этого как раз и нет. Ваши сервисы знают о выбранном способе параметризации (DemoService<P extends CommonServiceParams & DemoServiceParams> extends AbstractService).


P — это не свойство, а тип контейнера свойств. Получив в конструкторе в качестве единственного параметра этот контейнер, класс просетывает финальные поля для свойств.

Если прямо в классе нужно использовать ArrayBlockingQueue (например, это критично для конкретно этой реализации), то пусть оно и будет прямо в коде. Если же нет, то ему там делать нечего — конфигурация будет происходить вне кода класса, а сам класс должен использовать интерфейсы.


Зачем в коде задавать явно ArrayBlockingQueue? Речь идет о том, чтобы пользователь имел свободу выбора. В классе потомка задется свойство, которое (все еще) можно переопределить.

Я бы задался вопросом — а нужны ли финальные поля? Сервисы создаются 1 раз при старте контейнера.
Spring-way в данном случае — это сеттеры.


Если вы не видите разницы между финальным полем и не понимаете в чем преимущество использования финальных полей для хранения настроек сервиса, когда куча потоков обращается к их значениям очень и очень часто, то не вижу смысла вести бессмысленную дискуссию «финальное поле/не финальное поле».
Речь совершенно не об этом: создать как раз можно что угодно, если вы параметризуете бин неким произвольным объектом. Хорошо, если разработчик параметризующего контейнера понимает это. А если нет?

В моем случае вы вынуждаете его использовать только «хорошие» типы свойств и если на проекте есть подобная практика, то всегда есть внутренняя уверенность, что никто не будет передавать в свойства кэш объемом в 1 ГиБ, и ведь это хорошо, не так ли?
Для сложных случаях давным-давно придумали наследование определений docs.spring.io/spring-framework/docs/current/spring-framework-reference/html/beans.html#beans-child-bean-definitions


Может быть я неясно выразился в предыдущем ответе: речь здесь не о конфигурировании бинов на уровне какого-либо IoC. Речь здесь о том, как с помощью декларативного подхода с помощью аннотаций позволять задавать дефолты для контейнера свойств, соблюдать принцип «свойство сервиса — это не сложный объект».

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

Еще раз повторюсь: речь не о Spring и не о том, как можно с помощью Spring «втянуть» свойства.

Если XML принципиально не хотите, то вот stackoverflow.com/questions/23266175/bean-definition-inheritance-with-annotations


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

Можно создавать бины через Spring Java Configuration, то есть напрямую, через параметры конструктора.


Да, бины можно создавать по-разному. Инжектировать свойства тоже можно по-разному. Я привожу еще раз преимущества аннотаций:

Если у вас есть, скажем, абстрактный сервис
@A(queueType = LINKED_BLOCKING_QUEUE)
public class ServiceA {
}


а для ServiceB queueType по дефолту неплохо было бы задать как ARRAY_BLOCKING_QUEUE и размер ограничить 1000 элементами (queueSize = 1000), то написав

@A(queueType=ARRAY_BLOCKING_QUEUE, queueSize=1000)
public class ServiceB extends ServiceA {
}


вы получаете возможность конфигурировать дефолтные проперти декларативным способом.

Кроме того, у конфигураций с использованием аннотаций имеется еще один весьма серьезный плюс: все свойства являются или примитивами, или инстансами энамов, или аннотациями или массивами из всех вышеперечисленных. Иногда бывает, что разработчики, когда делают билдер свойств для какого-то бина, применяют комплексные объекты в качестве свойств, что противоречит здравому смыслу:

Например, вот такой билдер

public class MyBeanParametersBuilder {

  int x;
  int y;
  ThreadPoolExecutor executor;

  public MyBeanParametersBuilder setX(int x) {
      this.x = x;
      return this;
  }

  public MyBeanParametersBuilder setY(int y) {
      this.y = y;
      return this;
  }

  public MyBeanParametersBuilder setExecutor(ThreadPoolExecutor) {
      this.executor = executor;
      return this;
  }

  public int getX() {
     return x;
  }

  public int getY() {
    return y;
  }

  public ThreadPoolExecutor getExecutor() {
    return executor;
  }
}


плох только потому, что содержит ссылку на весьма сложный объект с состоянием. Такой контейнер свойств нельзя сериализовать.

Вариант с применением аннотаций дает полную гарантию, что вы не получите несериализуемого объекта в принципе.

Чем, на мой взгляд, плох вариант «протягивания» свойств в бины через параметры конструктора:

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

2. Если у конструктора большое число параметров, то создавать бины приходится примерно так (я привожу пример с Spring):
@Configuration
public class MyConfiguration {

   @Value("${myService.property1}")
   int property1;
   
  @Value("${myService.property2}")
   String property2;

   // very, very long list of properties

   @Bean
   public MyService myService() {
       return new MyService(
           property1,
           property2,
           property3,
           property4,
           property5,
           property6,
           property7,
           ...,
           property24
        );
   }
}


Это красиво?

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

Конечно, вы можете возразить: давайте напишем свой контейнер. Однако написание своего контейнера — это проблема с тем, что я уже озвучивал выше: кому-то может прийти в голову использовать там тяжеловесные объекты.

Теперь что касается рефлекшена: речь идет о том, что данный код будет помещен в библиотеку, покроется тестами и его можно использовать без каких-либо проблем. Загляните вглубь Spring и вы увидите не только рефлешн, но вещи и похуже, вроде модификаций байт-кода.

Информация

В рейтинге
Не участвует
Откуда
Минск, Минская обл., Беларусь
Дата рождения
Зарегистрирован
Активность