Alexander Langer
Alexander Langer

Reputation: 2893

Returning CDI bean reference in EJB call

Is the following allowed in Java EE?

I have a @Singleton session bean that works as a registry and automatically discovers certain strategies that are used throughout the application, like this:

public interface Strategy {
    Class<?> supportedType();
}

@Singleton
public class StrategyRegistry {

    @Inject
    private Instance<Strategy> strategies;

    private Map<Class<?>, Strategy> registry = new HashMap<>();

    @PostConstruct
    public void startup() {
        Iterator<Strategy> it = strategies;
        while (it.hasNext()) {
            Strategy s = it.next();
            registry.put(s.supportedType(), s);
        }
    }

    // return provided strategy to caller
    public Strategy strategyFor(Class<?> clz) {
        return registry.get(clz);
    }
}

Now my questions is whether sharing the @Injected CDI beans to other clients (via #strategyFor(...)) is allowed or whether this can lead to unexpected behavior or side effects?

The strategies are stateless and thread-safe, so in general it should be possible to use them concurrently.

Note that the owner of the CDI beans above is a @Singleton and the CDI beans are currently @Dependent, so if I'm reading the spec correctly they should be available for the whole lifetime of the application.

Originally we used EJB stateless beans, and strategyFor(...) only returned the proxy to the actual bean, which I considered reasonably safe. This is no longer possible on Wildfly 8 with recent WELD versions, and thus we switched to CDI beans. With CDI beans, however, the actual bean is returned, not a proxy.

If this is not allowed, is the following a safe alternative?

@Inject private BeanManager beanManager;

public Strategy strategyFor(Class<?> clz) {
    Strategy s = registry.get(clz);
    if (s == null) return null;
    return beanManager.getReference(s.getClass());
}

Upvotes: 1

Views: 391

Answers (2)

dngfng
dngfng

Reputation: 1943

I don't see why not, as @Depend (default scope) is a pseudo scope therefor the bean manager is no longer interested in the bean. Its life cycle from there on depends purely on the outer bean or other classes that hold direct references to it - Its a plain old POJO

Upvotes: 1

maress
maress

Reputation: 3533

See the following

@Qualifier
@Retention(RUNTIME)
@Target({FIELD,METHOD,TYPE,PARAMETER})
public interface StrategyContext {

    //Nonbinding and default so that you can have one producer method for all strategies
    @NonBinding
    Class<?> supportedType() default Object.class;
}

@Singleton
public class StrategyRegistry {

    private Map<Class<?>, Strategy> registry = new HashMap<>();

    @Inject
    void startup(@Any final Instance<Strategy> strategies) {
        for(Strategy strategy:strategies)
            registry.put(Strategy.supportedType(), Strategy);
        }
    }

    @Produces
    @StrategyContext
    @ApplicationScoped
    public Strategy strategyFor(final InjectionPoint ip) {
       final StrategyContext sc = ip.getAnnotated().getAnnotation(StrategyContext.class);
       final Class<?> supportedType = sc.supportedType();
       return registry.get(supportedType);
    }
}

And then wherever you want to use it.

@Stateless
public class MyService {

   @Inject
   @StrategyContext(supportedType=MySupportedType.class)
   private Strategy strategy;

}

On the producer and the injection point:

NOTE If at runtime, the strategy may not exist and hence the method may return null, then annotate the producer with @Dependent, not @ApplicationScope and hence at the injection point, don't inject the raw strategy, but:

  @Inject
    @StrategyContext(supportedtype=MySupportedType.class)
    private Instance<Strategy> strategy

    ...
    if(!strategy.isUnsatisfied()) { strategy.get().doSomething();}

Upvotes: 1

Related Questions