0

How to inject object into singleton class with spring annotations?

I have some code like in this following snippet and I want to inject object of class B into it.

public class A {
    private B b;
    private static A instance;

    private A () {
        set some timer tasks
        ...
    }

    public A getInstance() {
        if (instance == null) { instance = new A(); }
        return instance;
    }

When I use @Inject above the b object, I have NullPointerException.

public final class SessionHolder {

private static SessionHolder instance;
@Inject
@Getter
@Setter
private PdbIdContainer pdbIdContainer;

private Map<UUID, SessionData> sessionMap;

private SessionHolder() {
    this.sessionMap = new ConcurrentHashMap<>();
    pdbIdContainer.update();
    TimerTask timerTask1 = new TimerTask() {
        @Override
        public void run() {
            Date d = new Date();
            sessionMap.entrySet().stream().filter(map -> TimeUnit.MILLISECONDS.toMinutes(
                    d.getTime() - map.getValue().getLastUseTime().getTime()) >= Integer.parseInt(
                    AppController.getConfig().getSessionInterval())).forEach(map -> sessionMap.remove(map.getKey()));
        }
    };
    TimerTask timerTask2 = new TimerTask() {
        @Override
        public void run() {
            pdbIdContainer.update();
        }
    };
    Timer timer = new Timer();
    timer.scheduleAtFixedRate(timerTask1,
            Integer.parseInt(AppController.getConfig().getSessionMapDelay()),
            Integer.parseInt(AppController.getConfig().getSessionMapInterval()));
    timer.scheduleAtFixedRate(timerTask2,
            Integer.parseInt(AppController.getConfig().getPdbIdsSetDelay()),
            Integer.parseInt(AppController.getConfig().getPdbIdsSetInterval()));
}


public static SessionHolder getInstance() {
    if (instance == null) {
        instance = new SessionHolder();
    }
    return SessionHolder.instance;
}

public static SessionData getSession(UUID id) {
    return getInstance().sessionMap.get(id);
}

public static UUID createSession(StructureContainer structure) {
    UUID id = UUID.randomUUID();
    getInstance().sessionMap.put(id, new SessionData(structure, new Date()));
    return id;
}
}
Piotrowy
  • 605
  • 8
  • 20

3 Answers3

3

In Spring application, you do not need to and you should not create singleton classes. Spring will make sure that only single instance of this class exists in the context when you create a singleton bean (singleton is a default bean scope).

Your class should look like:

@Component
public class SessionHolder {

    private PdbIdContainer pdbIdContainer;

    private Map<UUID, SessionData> sessionMap;

    @Autowired // you can omit @Autowired if you use Spring 4.3 or higher
    SessionHolder(PdbIdContainer pdbIdContainer) {
        this.pdbIdContainer = pdbIdContainer;
        this.sessionMap = new ConcurrentHashMap<>();
        pdbIdContainer.update();
        TimerTask timerTask1 = new TimerTask() {
            @Override
            public void run() {
                Date d = new Date();
                sessionMap.entrySet().stream().filter(map -> TimeUnit.MILLISECONDS.toMinutes(
                        d.getTime() - map.getValue().getLastUseTime().getTime()) >= Integer.parseInt(
                        AppController.getConfig().getSessionInterval())).forEach(map -> sessionMap.remove(map.getKey()));
            }
        };
        TimerTask timerTask2 = new TimerTask() {
            @Override
            public void run() {
                pdbIdContainer.update();
            }
        };
        Timer timer = new Timer();
        timer.scheduleAtFixedRate(timerTask1,
                                  Integer.parseInt(AppController.getConfig().getSessionMapDelay()),
                                  Integer.parseInt(AppController.getConfig().getSessionMapInterval()));
        timer.scheduleAtFixedRate(timerTask2,
                                  Integer.parseInt(AppController.getConfig().getPdbIdsSetDelay()),
                                  Integer.parseInt(AppController.getConfig().getPdbIdsSetInterval()));
    }

    public SessionData getSession(UUID id) {
        return sessionMap.get(id);
    }

    public UUID createSession(StructureContainer structure) {
        UUID id = UUID.randomUUID();
        sessionMap.put(id, new SessionData(structure, new Date()));
        return id;
    }
}
Maciej Walkowiak
  • 12,372
  • 59
  • 63
  • Thank you very, very, very much. It works. Can I be definitely sure that spring allows to set these timer tasks only once per running my webapp? – Piotrowy Nov 09 '16 at 23:13
  • 1
    Once per application context. If you have more than one application context make sure that this package is scanned only by one. – Maciej Walkowiak Nov 09 '16 at 23:20
  • Btw, for scheduling tasks, have a look at `@Scheduled` annotation. It would simplify a bit your code. – Maciej Walkowiak Nov 09 '16 at 23:21
  • 1
    This is a really good point (i.e. just let spring do it). @Piotrowy if you're concerned, add logging to the class on instantiation, and check to make sure you see it once. There are ways, as Maciej mentioned, to get spring to re-create beans, so it may be worthwhile long term anyways, if this is especially sensitive. – Taylor Nov 10 '16 at 13:42
1

I would recommend using a CDI annotation (@Inject in javax.inject.Inject), as opposed to Spring's @Autowired, if feasible. This way, you are not tied to Spring, if you need to move another to another DI provider down the road.

public class A {

    @Inject //or @Autowired - Spring 
    private B b;
    private static A instance;
Mechkov
  • 4,294
  • 1
  • 17
  • 25
  • 1
    I don't think this answers the question. – Taylor Nov 09 '16 at 21:29
  • 1
    The question was how to inject class B into a Singleton... 10 mins later and after an answer was provided, more info was given that the B class might not be Spring managed.. So, it does answer the initial question. – Mechkov Nov 09 '16 at 21:33
  • 1
    @Mechkov how often do you change DI provider? I recommend sticking to Spring annotation since you will not find equivalents to all Spring annotations in `javax.inject`. – Maciej Walkowiak Nov 09 '16 at 23:08
  • @MaciejWalkowiak This depends on the needs right. Just because we used Spring DI (along with MVC) in my previous project, did not mean we couldn't switch to Weld, running on Wildfly and Jersey replacing Spring MVC. So, coding with such changes in mind is always better imo. – Mechkov Nov 10 '16 at 13:39
1

I don't think Spring will inject out of the box if it doesn't manage the lifecycle of the object.

The only way to enable this is to turn on compile-time or load-time aspectj weaving.

You could skip injection altogether, and grab an instance of the app context and retrieve your B instance from that.

More details: Dependency Injection into Spring non-managed beans

Community
  • 1
  • 1
Taylor
  • 3,942
  • 2
  • 20
  • 33