干净的代码 - 应该在哪里应用@Autowired?

我将从一个简单的例子开始。您有一个在初始化时运行类的 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

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

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

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

这也意味着我还必须编辑,因为构造函数需要一个参数。如果我尝试使用设置器注射,我会得到同样的警告。如果我也重构它,在我看来,我最终会得到一些令人讨厌的代码。Application.javaApplication.java

// 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


答案 1

有几种方法可以改进它。

  1. 您可以从中删除,因为您让一个方法构造它的实例。将 作为参数直接注入到方法中。@AutowiredMyCommandLineRunner@BeanDataSource

  2. 或者移除并删除并打上注释以使其被检测到并删除工厂方法。@Autowired@Bean@ComponentMyCommandLineRunner

  3. 将方法内部内联为 lambda。MyCommandLineRunner@Bean

在 中无自动布线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); 
    }
}

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


答案 2

考虑使字段最终确定,然后您就不需要 .详细了解依赖关系注入 http://docs.spring.io/spring-boot/docs/current/reference/html/using-boot-spring-beans-and-dependency-injection.html#using-boot-spring-beans-and-dependency-injectionds@Autowired

为了保持代码干净,您是否考虑过使用龙目岛注释? 将生成带有@Autowired注释的构造函数。在此处查看更多 https://projectlombok.org/features/Constructor.html@RequiredArgsConstructor(onConstructor = @__(@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);
    }
}

稍后编辑

没有龙目岛的解决方案依靠Spring在创建豆子时注入依赖性

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