【问题标题】:Clean code - Where should @Autowired be applied?干净的代码 - 应该在哪里应用 @Autowired?
【发布时间】:2017-04-02 22:08:54
【问题描述】:

我将从一个简单的例子开始。您有一个 Spring Boot 应用程序,它在初始化时运行 CommandLineRunner 类。

// 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();
    }
}

现在,像这样,这行得通,一切正常。但是,IntelliJ 报告了@Autowired 所在位置的警告(我在评论中标记了位置)

Spring 团队建议: 始终在 bean 中使用基于构造函数的依赖注入。始终对强制依赖项使用断言。

现在如果我遵循这个,我有一个基于构造函数的依赖注入

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

这也意味着我也必须编辑Application.java,因为构造函数需要一个参数。在Application.java 中,如果我尝试使用setter 注入,我会得到同样的警告。如果我也重构它,我会得到一些我认为讨厌的代码。

// 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);
    }
}

上面的代码产生了相同的结果,但没有在 IntelliJ 中报告任何警告。 我很困惑,第二个代码比第一个更好吗?我是否遵循不正确的逻辑?这应该以不同的方式接线吗?

简而言之,正确的做法是什么?

注意DataSource 只是一个纯粹的例子,这个问题适用于任何被自动连接的东西。

注 2 只是说MyCommandLineRunner.java 不能有另一个空的构造函数,因为 DataSource 需要自动装配/初始化。会报错,不会编译。

【问题讨论】:

    标签: java spring spring-boot coding-style autowired


    【解决方案1】:

    有几种方法可以改进它。

    1. 您可以从MyCommandLineRunner 中删除@Autowired,因为您正在让@Bean 方法构造它的实例。将DataSource 作为参数直接注入方法中。

    2. 或删除@Autowired 并删除@Bean 并在MyCommandLineRunner 上添加@Component 注释以检测并删除工厂方法。

    3. MyCommandLineRunner 内联到 @Bean 方法中作为 lambda。

    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());
        }
    }
    

    还有应用程序类。

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

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

    还有应用程序类。

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

    内联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); 
        }
    }
    

    所有这些都是构建实例的有效方法。用哪一种,用你觉得舒服的那一种。还有更多选项(此处提到的所有变体)。

    【讨论】:

    • 我仍然缺乏回应:为什么要那么难避免自动布线?为什么基于构造函数的依赖关系更好?正如@m-deinum 所问:应该在哪里正确应用 autowired?
    • 基于构造函数的注入(BTW 仍然是自动连接)更好,因为它是明确的并且遵循常规的 OO 规则。而现场注入则不然。
    • @LluísSuñol 现场注入使测试更加困难。您必须借助 ReflectionUtils 或类似的方法在要测试的类中设置模拟依赖项。
    【解决方案2】:

    考虑将ds 字段设为final,那么您就不需要@Autowired。查看更多关于依赖注入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

    为了保持代码简洁,您是否考虑过使用 Lombok 注释? @RequiredArgsConstructor(onConstructor = @__(@Autowired)) 将生成带有 @Autowired 注释的构造函数。在这里查看更多 https://projectlombok.org/features/Constructor.html

    您的代码可能如下所示:

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

    稍后编辑

    没有Lombok的解决方案依赖Spring在创建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());
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2012-01-17
      • 1970-01-01
      • 2010-12-25
      • 2013-06-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-02-27
      相关资源
      最近更新 更多