Clean Code - Where should @Autowired be used?

I'll start with a simple example. You have a Spring boot application that runs the 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, it works, everything is in order. However, IntelliJ reports a warning that @Autowired is @Autowired (I noted in the comment)

The Spring team recommends: Always use constructor-based dependency injection in beans. Always use assertions for required dependencies.

Now, if I follow this, I have a constructor-based dependency injection

 @Autowired public MyCommandLineRunner(DataSource ds) { ... } 

It also means that I need to edit Application.java as well, since the constructor needs an argument. In Application.java , if I try to use installer injection, I will get the same warning. If I reorganize this, I will eventually find 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 code above gives the same result, but does not report any warnings in IntelliJ. I am confused, how is the second code better than the first? Am I following the wrong logic? Should it be connected otherwise?

In short, what is the right way to do this?

Thanks in advance!

Note that if there is syntax or a small logical error, I am typing the code here directly. DataSource is just a pure example, this question applies to everything that was auto-notified.

note 2 Just to say that MyCommandLineRunner.java cannot have another, empty, constructor, since the DataSource must be auto-updated / initialized. It will report an error and will not be compiled.

+5
source share
2 answers

There are several ways to improve it.

  • You can remove @Autowired from your MyCommandLineRunner as you allow the @Bean method to instantiate it. Inject the DataSource directly into the method as an argument.

  • Or remove @Autowired and delete @Bean and click on the @Component annotation on MyCommandLineRunner to discover and remove the factory method.

  • MyCommandLineRunner your MyCommandLineRunner inside your @Bean method as lambda.

No auto MyCommandLineRunner in MyCommandLineRunner

 public class MyCommandLineRunner implements CommandLineRunner { private final Log logger = LogFactory.getLog(getClass()); private final DataSource ds; public MyCommandLineRunner(DataSource ds) { this.ds = ds; } @Override public void run(String... args) throws Exception { logger.info("DataSource: " + ds.toString()); } } 

And the application class.

 @SpringBootApplication public class Application { public static void main(String... args) { SpringApplication.run(Application.class, args); } @Bean public MyCommandLineRunner schedulerRunner(DataSource ds) { return new MyCommandLineRunner(ds); } } 

Using @Component

 @Component public class MyCommandLineRunner implements CommandLineRunner { private final Log logger = LogFactory.getLog(getClass()); private final DataSource ds; public MyCommandLineRunner(DataSource ds) { this.ds = ds; } @Override public void run(String... args) throws Exception { logger.info("DataSource: " + ds.toString()); } } 

And the application class.

 @SpringBootApplication public class Application { public static void main(String... args) { SpringApplication.run(Application.class, args); } } 

Inline CommandLineRunner

 @SpringBootApplication public class Application { private static final Logger logger = LoggerFactory.getLogger(Application.class) public static void main(String... args) { SpringApplication.run(Application.class, args); } @Bean public MyCommandLineRunner schedulerRunner(DataSource ds) { return (args) -> (logger.info("DataSource: {}", ds); } } 

These are all valid ways to build your instances. Which one to use, use the one with which you are comfortable. There are more options (all options mentioned here).

+3
source

Consider creating a ds final field, then you don't need @Autowired . Read more about dependency injection http://docs.spring.io/spring-boot/docs/current/reference/html/using-boot-spring-beans-and-dependency-injection.html#using-boot-spring-beans- and-dependency-injection

To keep the code clean, have you considered using Lombok annotations? @RequiredArgsConstructor(onConstructor = @__(@Autowired)) will generate a constructor with @Autowired annotations. Read more here https://projectlombok.org/features/Constructor.html

Your code might look like this:

 @Slf4j @RequiredArgsConstructor // MyCommandLineRunner.java public class MyCommandLineRunner implements CommandLineRunner { //final fields are included in the constructor generated by Lombok private final DataSource ds; @Override public void run(String... args) throws Exception { log.info("DataSource: {} ", ds.toString()); } } // Application.java @SpringBootApplication @RequiredArgsConstructor(onConstructor = @__(@Autowired)) public class Application { private final Datasource ds; public static void main(String... args) { SpringApplication.run(Application.class, args); } @Bean public MyCommandLineRunner schedulerRunner() { return new MyCommandLineRunner(ds); } } 

Edit later

Lombok-free solution relies on Spring to inject dependency when creating bean

 @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } @Bean /** * dependency ds is injected by Spring */ public MyCommandLineRunner schedulerRunner(DataSource ds) { return new MyCommandLineRunner(ds); } } // MyCommandLineRunner.java public class MyCommandLineRunner implements CommandLineRunner { private final Log logger = LogFactory.getLog(getClass()); private final DataSource ds; public MyCommandLineRunner(DataSource ds){ this.ds = ds; } @Override public void run(String... args) throws Exception { logger.info("DataSource: "+ ds.toString()); } } 
+1
source

Source: https://habr.com/ru/post/1266242/


All Articles