PowerLove
PowerLove

Reputation: 303

Autowire a Spring bean in a Singleton class

I am trying to autowire a bean inside of a Singleton class, I know that it always a best idea to avoid manual autowiring, but this class is being used in so many places so I do not want to change the caller of this class.

Runner.java

@Component
public class RunnerClass {
    @Autowired
    public ConfigService configService;
}

ConfigService.java

@Service
public class ConfigService {
    private ConfigServiceDAO = ConfigServiceDAO.getInstance();
}

ConfigServiceDAO.java

public class ConfigServiceDAO {

    //Bean I want to autowire here....
    @Autowired
    ConfigServiceDAOBuilder DAOBuilder

    public static ConfigServiceDAO getInstance() {
        return SingletonHolder.INSTANCE;
    }

    private static class SingletonHolder {
        public static final ConfigServiceDAO INSTANCE = new ConfigServiceDAO();

        private SingletonHolder() {}
    }
}

DAOBuilder inside ConfigServiceDAO is always null, which makes sense because my understanding is when the class is instantiated manually, spring injection doesn't happen. What could be the solution here if I want to keep ConfigServiceDAO as non spring component?

====EDIT==== I know it is possible to make ConfigServiceDAO as a spring component and autowire all dependencies. But a lot of classes from different packages already call ConfigServiceDAO.getInstance().someMethod() So I guess the the right question is, what would be the best way to autowire a spring component to the class that is instantiated manually.

Upvotes: 1

Views: 7162

Answers (4)

Mark Bramnik
Mark Bramnik

Reputation: 42461

Spring processed @Autowired only in beans that it manages by itself. So you have two choices:

  1. Get Rid Of singleton - if you're using spring, its already a singleton in the application context. This is by far the best approach in general (assuming other parts of application that call your singleton are also spring driven). I don't think you should fear to change ConfigServiceDAO.getInstance.method() - refactoring tools in IDE will do the job.

  2. If you can't do 1, Don't use autowired annotation in the singleton - its useless anyway, instead, when you have an application context configured (in listener that spring emits when the application started for example), get the access to the ConfigServiceDAOBuilder bean by calling appCtx.getBean(ConfigServiceDAOBuilder.class) and "inject it" manually by reflection, this is what Spring does with spring managed beans anyway:

@EventListener
public void onApplicationReadyEvent(ApplicationReadyEvent event) {
  ConfigServiceDAOBuilder builder = 
    event.getApplicationContext().getBean(ConfigServiceDAOBuilder.class);

    ConfigServiceDao dao = ConfigServiceDAO.getInstance();
    dao.setDaoBuilder(builder); // or alternatively by reflection
}

As a side note, consider using method setDaoBuilder to be a package private to protect the singleton from some accidentally calling a setter

Upvotes: 2

lczapski
lczapski

Reputation: 4120

As far as I understand what you want: Create by Spring ConfigServiceDAOBuilder. After that inject it into non-managed object of class ConfigServiceDAO. You can do it after the Spring application context is instantiated. For example with CommanLineRunner:

@Component
public class CommandLineAppStartupRunner implements CommandLineRunner {
    @Autowired
    ConfigServiceDAOBuilder DAOBuilder

    @Override
    public void run(String...args) throws Exception {
        ConfigServiceDAO.getInstance().init(DAOBuilder);
    }
}

In ConfigServiceDAO has to be method init that helps to register all needed beans.

Upvotes: 1

Julian
Julian

Reputation: 4055

I don't know your use case but you cannot use @Autowired annotation outside a Spring bean. However if you really need to access a Spring bean from a non Spring piece of code you can do it like below. However this is a very non Spring way of designing your dependencies.

import org.springframework.context.ApplicationContext;

public enum ApplicationContextHolder {
    INSTANCE;

    private ApplicationContext applicationContext;

    public ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    public void setApplicationContext(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }
}

Then you have a configuration class:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration;

import javax.annotation.PostConstruct;

@Configuration
public class SomeConfig {
    @Autowired
    private ApplicationContext applicationContext;

    @PostConstruct
    public void init() {
        ApplicationContextHolder.INSTANCE.setApplicationContext(applicationContext);
    }
}

Then in your DAO class you get a reference to the builder bean you are interested. Something like this:

public class ConfigServiceDAO {
    public static ConfigServiceDAO getInstance() {
        return SingletonHolder.INSTANCE;
    }

    private static class SingletonHolder {
        public static final ConfigServiceDAO INSTANCE = 
        ApplicationContextHolder.INSTANCE.getApplicationContext().getBean(ConfigServiceDAOBuilder.class).buildConfigServiceDAO()

        private SingletonHolder() {}
    }
}

Again this is a very non Spring way of doing things.

Upvotes: 2

AK DevCraft
AK DevCraft

Reputation: 81

I'm confused after reading your comments, hence let me put in this way. What you are referring to manual autowiring is the Spring dependency injection way. Whenever you are using any of the Spring Stereotype annotations with default scope instance is always Singleton.

Your ConfigService class has the problem. Your are mixing up things, you should create a separate config class with @configuration and create Bean for the class ConfigServiceDAO, something like below

@Configuration
Class Config{ 

@Bean
public ConfigServiceDAO configServiceDAO( ){
return ConfigServiceDAO.getInstance();
}
}

then autowire the ConfigServiceDAO in the ConfigService class. With this Spring will resolve all of the dependency in correct order and DAOBuilder shouldn't be null.

Upvotes: 0

Related Questions