Priyath Gregory
Priyath Gregory

Reputation: 987

Spring Boot - Auto configuration of a class that contains AutoWired dependencies

I am in the process of developing a common java library with reusable logic to interact with some AWS services, that will in turn be used by several consumer applications. For reasons outlined here, and the fact that Spring Boot seems to provide a lot of boilerplate free code for things like SQS integration, I have decided to implement this common library as a custom spring boot starter with auto configuration.

I am also completely new to the Spring framework and as a result, have run into a problem where my auto-configured class's instance variables are not getting initialized via the AutoWired annotation.

To better explain this, here is a very simplified version of my common dependency.

CommonCore.java

@Component
public class CommonCore { 

   @AutoWired
   ReadProperties readProperties;

   @AutoWired
   SqsListener sqsListener; // this will be based on spring-cloud-starter-aws-messaging 

   public CommonCore() {
       Properties props = readProperties.loadCoreProperties();
       //initialize stuff
   }

   processEvents(){
     // starts processing events from a kinesis stream.
   }
}

ReadProperties.java

@Component
public class ReadProperties {

    @Value("${some.property.from.application.properties}")
    private String someProperty;

    public Properties loadCoreProperties() {
      Properties properties = new Properties();
      properties.setProperty("some.property", someProperty);
      
      return properties;
    }
 }

CoreAutoConfiguration.java

@Configuration
public class CommonCoreAutoConfiguration {

    @Bean
    public CommonCore getCommonCore() {  
        return new CommonCore();
    }
}

The common dependency will be used by other applications like so:

@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class })
public class SampleConsumerApp implements ApplicationRunner {

    @Autowired
    CommonCore commonCore;

    public SampleConsumerApp() {
    }

    public static void main(String[] args) {
        SpringApplication.run(SampleConsumerApp.class, args);
    }

    @Override
    public void run(ApplicationArguments args) {

        try {
            commonCore.processEvents();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

The main problem I have like I mentioned, is the AutoWired objects in the CommonCore instance are not getting initialized as expected. However, I think the actual problems are more deeply rooted; but due to my lack of understanding of the Spring framework, I am finding it difficult to debug this on my own.

I am hoping for a few pointers along these points

  1. Does this approach of developing a custom starter make sense for my use case?
  2. What is the reason for the AutoWired dependencies to not get initialized with this approach?

Upvotes: 0

Views: 1114

Answers (1)

Tarmo
Tarmo

Reputation: 4081

Wild guess, but I think it's because of the order of how things are constructed. I am talking about this class:

@Component
public class CommonCore { 

   @AutoWired
   ReadProperties readProperties;

   @AutoWired
   SqsListener sqsListener; // this will be based on spring-cloud-starter-aws-messaging 

   public CommonCore() {
       Properties props = readProperties.loadCoreProperties();
       //initialize stuff
   }

   processEvents(){
     // starts processing events from a kinesis stream.
   }
}

You are trying to use a Spring injected component in a constructor, but constructor is called before Spring can do its @Autowire magic.

So one option is to autowire as a constructor argument

Something like this (untested):

@Component
public class CommonCore { 

   private final ReadProperties readProperties;

   private final SqsListener sqsListener; // this will be based on spring-cloud-starter-aws-messaging 
   @AutoWired 
   public CommonCore(SqsListener sqsListener, ReadProperties readProperties) {
       this.readProperties = readPropertis;
       this.sqsListener = sqsListener;
       Properties props = readProperties.loadCoreProperties();
       //initialize stuff
   }

   processEvents(){
     // starts processing events from a kinesis stream.
   }
}

Sidenote: I prefer to use dependency injection via constructor arguments always, wherever possible. This also makes unit testing a lot easier without any Spring specific testing libraries.

Upvotes: 1

Related Questions