Java 清洁代码-应在何处应用@Autowired?

Java 清洁代码-应在何处应用@Autowired?,java,spring,spring-boot,coding-style,autowired,Java,Spring,Spring Boot,Coding Style,Autowired,我将从一个简单的例子开始。您有一个Spring引导应用程序,它在初始化时运行CommandLineRunner类 // MyCommandLineRunner.java public class MyCommandLineRunner implements CommandLineRunner { private final Log logger = LogFactory.getLog(getClass()); @Autowired //IntelliJ Warning p

我将从一个简单的例子开始。您有一个Spring引导应用程序,它在初始化时运行
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
所在的位置报告了一条警告(我在注释中标记了where)

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不能有另一个空构造函数,因为数据源需要自动连接/初始化。它将报告错误并且不会被编译。

考虑将字段
ds
设为最终字段,这样就不需要
@Autowired
。请参阅有关依赖项注入的更多信息

为了保持代码整洁,您考虑过使用Lombok注释吗<代码>@RequiredArgsConstructor(onConstructor=@Autowired)) 将生成带有@Autowired注释的构造函数。在这里看到更多

您的代码可能如下所示:

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

有几种方法可以改进它

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

  • 或者删除
    @Autowired
    ,删除
    @Bean
    ,并在
    MyCommandLineRunner
    上添加
    @组件
    注释,以检测该组件并删除出厂方法

  • MyCommandLineRunner
    作为lambda内联到
    @Bean
    方法中

  • MyCommandLineRunner中没有自动连线 和应用程序类

    @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);
        }
    }
    
    @SpringBootApplication
    public class Application {
    
        public static void main(String... args) {
            SpringApplication.run(Application.class, args); 
        }
    
    }
    
    @组件的用法
    和应用程序类

    @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);
        }
    }
    
    @SpringBootApplication
    public class Application {
    
        public static void main(String... args) {
            SpringApplication.run(Application.class, args); 
        }
    
    }
    
    内联
    CommandLineRunner

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

    我的回答中仍然缺少一点:为什么要如此严格地避免自动连线?为什么基于构造函数的依赖关系更好?正如@m-deinum所问的:应该在哪里正确应用autowired?基于构造函数的注入(仍然是autowired BTW)更好,因为它是显式的,并且遵循常规的OO规则。而现场注入则不然。@LluísSuñol现场注入使得测试更加困难。您必须求助于ReflectionUtils或类似的方法来设置要测试的类中的模拟依赖项。
    @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); 
        }
    }