views:

4019

answers:

5

Let's assume we've got the following Java code:

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 );
   }
}

This code snippet is nothing but a bit improved listener pattern where each listener is telling what type of event it is interested in, and the provided method maintains a concurrent map of these relationships.

Initially, I wanted this method to be called via my own annotation framework, but bumped into a brick wall of various annotation limitations (e.g. you can't have java.lang.Enum as annotation param, also there's a set of various classloader issues) therefore decided to use Spring.

Could anyone tell me how do I Spring_ify_ this? What I want to achive is:
1. Define Maintainer class as a Spring bean.
2. Make it so that all sorts of listeners would be able to register themselves to Maintainer via XML by using addListener method. Spring doc nor Google are very generous in examples.

Is there a way to achieve this easily? Thanks in advance.

+2  A: 

Hi,

What would be wrong with doing something like the following:

Defining a 'Maintainer' interface with the addListener(Listener, Enum) method.

Create a DefaultMaintainer class (as above) which implements Maintainer.

Then, in each Listener class, 'inject' the Maintainer interface (constructor injection might be a good choice). The listener can then register itself with the Maintainer.

other than that, I'm not 100% clear on exactly what your difficulty is with Spring at the moment! :)

Phill Sacre
A: 

You said "... you can't have java.lang.Enum as" annotation param ..."

I think you are wrong on that. I have recently used on a project something like this :

public @interface MyAnnotation {
    MyEnum value();
}
Alexandre Victoor
Well, that's exactly what I said - "you can't have java.lang.Enum as annotation param". I was asking how do I do this in Spring?
mindas
+1  A: 

1) Define Maintainer class as a Spring bean.

Standard Spring syntax applies:

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

2) Make it so that all sorts of listeners would be able to register themselves to Maintainer via XML by using addListener method. Spring doc nor Google are very generous in examples.

This is trickier. You could use MethodInvokingFactoryBean to individually call maintainer#addListener, like so:

<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>

However, this is unwieldy, and potentially error-prone. I attempted something similar on a project, and created a Spring utility class to help out instead. I don't have the source code available at the moment, so I'll describe how to implement what I did.

1) Refactor the event types listened to into a MyListener interface

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

Which changes the registration method to

public void addListener(MyListener listener)

2) Create Spring helper class that finds all relevant listeners in the context, and calls maintainer#addListener for each listener found. I would start with BeanFilteringSupport, and also implement BeanPostProcessor (or ApplicationListener) to register the beans after all beans have been instantiated.

flicken
A: 

Thank you all for the answers. First, A quick follow up on all answers.
1. (alexvictor) Yes, you can have concrete enum as annotation param, but not java.lang.Enum.
2. Answer provided by flicken is correct, but unfortunately a bit scary. I am not a Spring expert but doing things this way (creating methods for easier Spring access) this seems to be a bit overkill, as is the MethodInvokingFactoryBean solution. Although I wanted to express my sincere thanks for your time and effort.
3. The answer by Phill is a bit unusual (instead of injecting listener bean, inject its maintainer!), but, I believe, the cleanest of all available. I think I will go down this path.

Again, a big thanks you for your help.

mindas
+2  A: 

Slightly offtopic (as this is not about Spring) but there is a race condition in your implementation of AddListener:

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

If two threads call this method at the same time (for an event type that previously had no listeners), map.get( eventType ) will return null in both threads, each thread will create its own CopyOnWriteArrayList (each containing a single listener), one thread will replace the list created by the other, and the first listener will be forgotten.

To fix this, change:

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

...

map.put( eventType, listeners );

to:

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

...

map.putIfAbsent( eventType, listeners );
listeners = map.get( eventType );
finnw
Nice catch, thanks a lot!
mindas