Define and inject a map in EJB 3.1 or CDI
Asked Answered
W

2

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.

Wiper answered 4/8, 2014 at 7:24 Comment(2)
Why do you have a requirement to do it via a Map?Peridium
@JohnAment Because I call something like this: handles.get(variableDeterminingStrategy).handle(context);Wiper
P
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.

Peridium answered 5/8, 2014 at 10:51 Comment(0)
B
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.

Bookshelf answered 4/8, 2014 at 18:4 Comment(1)
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 #2319348Wiper
P
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.

Peridium answered 5/8, 2014 at 10:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.