Карта перечислений и внедрение зависимостей в Spring 2.5 - PullRequest
2 голосов
/ 16 сентября 2008

Предположим, у нас есть следующий код Java:

public class Maintainer {
   private Map<Enum, List<Listener>> map;

   public Maintainer() {
      this.map = new java.util.ConcurrentHashMap<Enum, List<Listener>>();
   }

   public void addListener( Listener listener, Enum eventType ) {
      List<Listener> listeners;
      if( ( listeners = map.get( eventType ) ) == null ) {
         listeners = new java.util.concurrent.CopyOnWriteArrayList<Listener>();
         map.put( eventType, listeners );
      }
      listeners.add( listener );
   }
}

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

Первоначально я хотел, чтобы этот метод вызывался через мою собственную структуру аннотаций, но столкнулся с кирпичной стеной с различными ограничениями аннотаций (например, вы не можете иметь java.lang.Enum в качестве параметра аннотации, также есть множество различных проблем загрузчика классов) поэтому решил использовать Spring.

Может кто-нибудь сказать мне, как мне Spring_ify_ это? То, чего я хочу достичь, это:
1. Определите класс Maintainer как бин Spring.
2. Сделайте так, чтобы всевозможные слушатели могли регистрироваться в Maintainer через XML с помощью метода addListener . Spring doc и Google очень щедры в примерах.

Есть ли способ достичь этого легко?

Ответы [ 5 ]

2 голосов
/ 18 сентября 2008

Немного оффтоп (поскольку речь идет не о Spring), но в вашей реализации AddListener есть условие гонки:

  if( ( listeners = map.get( eventType ) ) == null ) {
     listeners = new java.util.concurrent.CopyOnWriteArrayList<Listener>();
     map.put( eventType, listeners );
  }
  listeners.add( listener );

Если два потока вызывают этот метод одновременно (для типа события, у которого ранее не было прослушивателей), map.get (eventType) вернет значение null в обоих потоках, каждый поток создаст свой собственный CopyOnWriteArrayList (каждый из которых содержит один слушатель), один поток заменит список, созданный другим, а первый слушатель будет забыт.

Чтобы исправить это, измените:

private Map<Enum, List<Listener>> map;

...

map.put( eventType, listeners );

до:

private ConcurrentMap<Enum, List<Listener>> map;

...

map.putIfAbsent( eventType, listeners );
listeners = map.get( eventType );
2 голосов
/ 16 сентября 2008

Что было бы неправильно делать что-то вроде следующего:

Определение интерфейса 'Maintainer' с помощью метода addListener (Listener, Enum).

Создайте класс DefaultMaintainer (как указано выше), который реализует Maintainer.

Затем в каждом классе Listener «внедряют» интерфейс Maintainer (инъекция конструктора может быть хорошим выбором). Затем слушатель может зарегистрироваться в Maintainer.

Кроме этого, я не на 100% точно знаю, какие у вас сложности с Spring на данный момент! :)

1 голос
/ 16 сентября 2008

1) Определить класс Maintainer как бин Spring.

Применяется стандартный синтаксис Spring:

<bean id="maintainer" class="com.example.Maintainer"/>

2) Сделайте так, чтобы всевозможные слушатели могли зарегистрироваться в Maintainer через XML с помощью метода addListener. Spring doc и Google очень щедры в примерах.

Это сложнее. Вы могли бы использовать MethodInvokingFactoryBean для индивидуального вызова maintainer#addListener, например:

<bean id="listener" class="com.example.Listener"/>

<bean id="maintainer.addListener" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
  <property name="targetObject" ref="maintainer"/>
  <property name="targetMethod" value="addListener"/>
  <property name="arguments">
    <list>
      <ref>listener</ref>
      <value>com.example.MyEnum</value>
   </list>
 </property>
</bean>

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

1) Рефакторинг прослушиваемых типов событий в MyListener интерфейс

public interface MyListener extends Listener {
  public Enum[] getEventTypes()
}

Что меняет метод регистрации на

public void addListener(MyListener listener)

2) Создайте вспомогательный класс Spring, который найдет всех соответствующих слушателей в контексте и вызовет сопровождающий # addListener для каждого найденного слушателя. Я бы начал с BeanFilteringSupport, а также реализовал BeanPostProcessor (или ApplicationListener) для регистрации бинов после того, как были созданы все бины.

0 голосов
/ 17 сентября 2008

Спасибо всем за ответы. Во-первых, быстрый ответ на все ответы.
1. (alexvictor) Да, вы можете иметь конкретный enum в качестве параметра аннотации, но не java.lang.Enum .
2. Ответ, предоставленный flicken, является правильным, но, к сожалению, немного пугающим. Я не эксперт по Spring, но делаю так (создаю методы для более легкого доступа к Spring), это немного излишне, как и решение MethodInvokingFactoryBean . Хотя я хотел выразить свою искреннюю благодарность за ваше время и усилия.
3. Ответ Фила немного необычен (вместо того, чтобы вводить компонент-слушатель, введите его сопровождающий!), Но, я полагаю, самый чистый из всех доступных. Я думаю, что пойду по этому пути.

Опять же, большое спасибо за вашу помощь.

0 голосов
/ 16 сентября 2008

Вы сказали "... вы не можете иметь java.lang.Enum as" параметр аннотации ... "

Я думаю, что вы не правы в этом. Я недавно использовал в проекте что-то вроде этого:

public @interface MyAnnotation {
    MyEnum value();
}
...