2

After several years developing in Spring, I switched to EJB and I am not happy that I have no solution for this use-case. Let's say it is the strategy pattern implemented by a map. In Spring, it could look like this.

<bean id="myBean" class="MyBeanImpl">
    <property name="handlers">
        <map>
            <entry key="foo" value-ref="fooHandler"/>
            <entry key="bar" value-ref="barHandler"/>
        </map>
    </property>
</bean>

In EJB/CDI, I have this.

@Stateless
public class MyBeanImpl implements MyBean {

    private Map<String, Class<? extends Handler>> handlers = new HashMap<>();

    @PostConstruct
    public void init() {
        handlers.put("foo", FooHandlerImpl.class);
        handlers.put("bar", BarHandlerImpl.class);
    }

    //jndi lookup handlerClass.getSimpleName()

}

Mind that jndi lookup works with implementations, not interfaces. Isn't there any better solution? And no, I do not want to have separate fields (foo, bar), inject them and create the map afterwards (It can be huge list and changed often). Ideally, in case of any configuration change, I would not touch the MyBeanImpl class at all.

Vasil Lukach
  • 3,658
  • 3
  • 31
  • 40
banterCZ
  • 1,551
  • 1
  • 22
  • 37

2 Answers2

2

Try this:

@Inject @Any
private Instance<Handler> handlers;

private Map<String, Handler> handlerMap = new HashMap<>();


@PostConstruct
private void init() {
    for (Handler handler : handlers) {
        handlerMap.put(handler.getName(), handler);
    }
}

assuming your Handler interface has some sort of getName() method.

Harald Wellmann
  • 12,615
  • 4
  • 41
  • 63
  • Ok, it is not as elegant as I expected, but at least it works. Moreover handlers have to be java files, not only xml configuration of only one class but it would be a different question, see http://stackoverflow.com/questions/2318848/how-to-instantiate-more-then-one-cdi-weld-bean-for-one-class – banterCZ Aug 05 '14 at 09:01
2

The more CDI like way would look something like:

@Qualifier
@Target({ TYPE, METHOD, PARAMETER, FIELD })
@Retention(RUNTIME)
@Documented
public @interface Handles {
    String value();
}


public class HandlerLiteral extends AnnotationLiteral<Handles> implements Handles{
    private final String value;
    public HandlerLiteral(String value) {
        this.value = value;
    }
    @Override
    public String value() {
        return value;
    }
}

You would then annotate each of your Handler implementations with @Handles("someName"), e.g. the class name as you're using it here. The use of a qualifier here is more in line with how CDI works, and we use the internal Instance object to resolve appropriate beans. Then in your service code (or wherever) you would simply do:

@Inject @Any
private Instance<HandlerService> handlerInstance;

...
handlerInstance.select(new HandlerLiteral("whateverName")).get().handle(context);

If you're really constrained to using a map, this wouldn't work for you. But this should allow for more dynamic registration and essentially looks at every handler in your context.

John Ament
  • 11,595
  • 1
  • 36
  • 45