Clean code - Where should @Autowired be applied?

前端 未结 2 2130
北海茫月
北海茫月 2021-02-12 19:44

I\'ll start with a simple example. You have a Spring boot application that runs a CommandLineRunner class on initialization.

// MyCommandLineRunner.         


        
相关标签:
2条回答
  • 2021-02-12 20:19

    There are several ways to improve it.

    1. You can remove @Autowired from your MyCommandLineRunner as you are letting a @Bean method construct an instance of it. Inject the DataSource directly into the method as an argument.

    2. Or remove @Autowired and remove the @Bean and slap a @Component annotation on your MyCommandLineRunner to have it detected and remove factory method.

    3. Inline your MyCommandLineRunner inside your @Bean method as a lambda.

    No Autowiring in the 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);
        }
    }
    

    Usage of @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); 
        }
    }
    

    All of these are valid ways of constructing your instances. Which one to use, use the one that you feel comfortable with. There are more options (all variations on the ones mentioned here).

    0 讨论(0)
  • 2021-02-12 20:37

    Consider making the field ds final, then you don't need @Autowired. See 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)) would generate the constructor with @Autowired annotations. See more here https://projectlombok.org/features/Constructor.html

    Your code could 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}) // from JDK 8
    // @RequiredArgsConstructor(onConstructor = @__(@Autowired)) // up to JDK 7
    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);
        }
    }
    

    Later edit

    Solution without Lombok relies on Spring to inject dependency when the bean is created

    @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());
        }
    }
    
    0 讨论(0)
提交回复
热议问题