Search code examples
javaspringdependency-injectionconstructor-injection

Constructor Injection in SimpleMessageListenerContainer


I'm trying to get a better understanding of how to implement constructor injection in my application. I have some background processes that are executed by SimpleMessageListenerContainer workers that pull messages off of an AMQP server.

My application contains a service layer and a repo layer, a worker uses the services for data reads/writes. My controller, services, and repos are all setup using constructor injection, however since new versions of workers need to be instantiated I am stuck on how to initialize the worker.

Worker

public class RandomWorker extends Worker {

    private UserService userService;

    @Autowired
    public RandomWorker(UserService userService) {
        this.userService = userService;
    }

    @Override
    public byte[] handleMessage(byte[] message) {
        ... do work ...
    }
}

Service Layer

@Service
public class UserService {

    private SecurityAreaRepo securityAreaRepo;
    private SecurityRoleRepo securityRoleRepo;
    private UserRepo userRepo;

    @Autowired
    public UserService(SecurityAreaRepo securityAreaRepo,
                       SecurityRoleRepo securityRoleRepo,
                       UserRepo userComponent) {
        this.securityAreaRepo = securityAreaRepo;
        this.securityRoleRepo = securityRoleRepo;
        this.userRepo = userRepo;
    }
}

WorkerConfig

@Configuration
public class WorkerConfig {

    @Bean
    public RandomWorker randomWorker() {
        return new RandomWorker();
    }

    @Bean(name="randomWorkerContainer")
    public SimpleMessageListenerContainer randomWorkerContainer() {
        SimpleMessageListenerContainer smlc = new SimpleMessageListenerContainer();
        smlc.setConnectionFactory(connectionFactory());
        smlc.setMessageListener(new MessageListenerAdapter(randomWorker(), "handleMessage"));
        smlc.setQueueNames("random.worker.queue");
        smlc.setConcurrentConsumers(5);
        smlc.start();
        return smlc;
    }
}

Since my worker requires the UserService, I must provide an instance of this in the WorkerConfig when I initialize a new RandomWorker. So am I going to have to create a @Bean for EVERY service that all of workers use? My WorkerConfig would look something like this:

@Configuration
public class WorkerConfig {
    @Bean
    public UserService userService() {
        return new UserService(new SecurityAreaRepo(), new SecurityRoleRepo(), new UserRepo());
    }

    @Bean
    public RandomWorker randomWorker() {
        return new RandomWorker(userService());
    }

    @Bean(name="randomWorkerContainer")
    public SimpleMessageListenerContainer randomWorkerContainer() {
        SimpleMessageListenerContainer smlc = new SimpleMessageListenerContainer();
        smlc.setConnectionFactory(connectionFactory());
        smlc.setMessageListener(new MessageListenerAdapter(randomWorker(), "handleMessage"));
        smlc.setQueueNames("random.worker.queue");
        smlc.setConcurrentConsumers(5);
        smlc.start();
        return smlc;
    }
}

If this is the case, I just do not see the point of constructor injection, when field injection makes everything so much simpler. Can somebody shed some light on this?


Solution

  • Spring automatically injects dependencies if you specify them as bean method's arguments. In your case, you just need to modify your worker bean method to:

    @Bean
    public RandomWorker randomWorker(UserService userService) {
        return new RandomWorker(userService);
    }
    

    If UserService service is available in context, Spring will automatically inject it as userService parameter. You don't need to use @Bean methods for every service - any method of registering beans in context will work (e.g. @ComponentScan, @SpringBootApplication or even manually adding the bean to context). It doesn't matter if you use constructor or setter injection.

    As a side note - constructor injection is better because you can be sure your object is always instantiated in valid state. It's generally a good design to keep all of your objects in valid state all the time.