Why @Singleton over @ApplicationScoped in Producers?
Asked Answered
V

1

7

LoggerProducer.java is a class used to produce Loggers to be injected in CDI beans with:

@Inject 
Logger LOG;

Full code:

import javax.ejb.Singleton;

/**
 * @author rveldpau
 */
@Singleton
public class LoggerProducer {

    private Map<String, Logger> loggers = new HashMap<>();

    @Produces
    public Logger getProducer(InjectionPoint ip) {
        String key = getKeyFromIp(ip);
        if (!loggers.containsKey(key)) {
            loggers.put(key, Logger.getLogger(key));
        }
        return loggers.get(key);
    }

    private String getKeyFromIp(InjectionPoint ip) {
        return ip.getMember().getDeclaringClass().getCanonicalName();
    }
}

QUESTION: can @Singleton be safely turned into @ApplicationScoped ?

I mean, why would anyone want an EJB here ? Are there technical reasons, since no transactions are involved, and (AFAIK) it would be thread-safe anyway ?

I'm obviously referring to javax.enterprise.context.ApplicationScoped, not to javax.faces.bean.ApplicationScoped.

Viridescent answered 24/11, 2014 at 11:14 Comment(6)
HashMap isn't threadsafeChet
@Chet of course, mine error was thinking that @ApplicationScoped is, thanks for the comment BTWViridescent
@AndreaLigios can you clarify whether your question is about javax.ejb.Singleton or javax.inject.Singleton?Unsung
@johnament javax.ejb.SingletonViridescent
imports are super important here. inject's singleton doesn't have the qualities of EJB's singleton.Unsung
I agree (now), though they're available in the source code linked at the beginning of the question.Viridescent
F
13

The @Singleton annotation provides not only transaction but also thread-safety by default. So if you will replace it with @ApplicationScoped, you will loose the synchronization. So in order to make it properly you need to do like this:

@ApplicationScoped
public class LoggerProducer {

   private final ConcurrentMap<String, Logger> loggers = new ConcurrentHashMap<>();

   @Produces
   public Logger getProducer(InjectionPoint ip) {
      String key = getKeyFromIp(ip);
      loggers.putIfAbsent(key, Logger.getLogger(key));
      return loggers.get(key);
   }

   private String getKeyFromIp(InjectionPoint ip) {
     return ip.getMember().getDeclaringClass().getCanonicalName();
  }
}

Also you can make it completely without any scope if you make the map as static

Foster answered 24/11, 2014 at 11:41 Comment(1)
Crystal clear. I misremembered @ApplicationScoped to be thread safe too, which turns out to be wrong. Thinking about it, it's absolutely coherent: all EJBs are thread safe, all CDI beans are not, no exception made for this scope. Thanks for the answerViridescent

© 2022 - 2024 — McMap. All rights reserved.