I'll start with a simple example. You have a Spring boot application that runs a CommandLineRunner
class on initialization.
// MyCommandLineRunner.java
public class MyCommandLineRunner implements CommandLineRunner {
private final Log logger = LogFactory.getLog(getClass());
@Autowired //IntelliJ Warning
private DataSource ds;
@Override
public void run(String... args) throws Exception {
logger.info("DataSource: " + ds.toString());
}
}
// Application.java
@SpringBootApplication
public class Application {
public static void main(String... args) {
SpringApplication.run(Application.class, args);
}
@Bean
public MyCommandLineRunner schedulerRunner() {
return new MyCommandLineRunner();
}
}
Now, like this, this works, everything is OK. However, IntelliJ reports a warning where @Autowired
is located (I marked where in the comment)
Spring team recommends: Always use constructor based dependency injection in your beans. Always use assertions for mandatory dependencies.
Now if I follow this, I have a constructor based dependency injection
@Autowired
public MyCommandLineRunner(DataSource ds) { ... }
This also means that I have to edit Application.java
as well, since the constructor needs an argument. In Application.java
if I try to use the setter injection, I'll get the same warning. If I refactor that as well, I'll end up with some, in my opinion, nasty code.
// MyCommandLineRunner.java
public class MyCommandLineRunner implements CommandLineRunner {
private final Log logger = LogFactory.getLog(getClass());
private DataSource ds;
@Autowired // Note that this line is practically useless now, since we're getting this value as a parameter from Application.java anyway.
public MyCommandLineRunner(DataSource ds) { this.ds = ds; }
@Override
public void run(String... args) throws Exception {
logger.info("DataSource: " + ds.toString());
}
}
// Application.java
@SpringBootApplication
public class Application {
private DataSource ds;
@Autowired
public Application(DataSource ds) { this.ds = ds; }
public static void main(String... args) {
SpringApplication.run(Application.class, args);
}
@Bean
public MyCommandLineRunner schedulerRunner() {
return new MyCommandLineRunner(ds);
}
}
The above code yields the same result, but doesn't report any warnings in IntelliJ. I'm confused, how is the 2nd code better than the first one? Am I following an incorrect logic? Should this be wired differently?
In short, what's the correct way to do this?
note DataSource
is just a pure example, this question applies to anything being autowired.
note 2 Just to say that MyCommandLineRunner.java
can't have another, empty, constructor, since DataSource needs to be autowired/initialized. It will report an error and will not be compiled.