清洁代码 - 应用@Autowired应该在哪里?

Nem*_*jaT 14 java spring coding-style autowired spring-boot

我将从一个简单的例子开始.你有一个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();
    }
}
Run Code Online (Sandbox Code Playgroud)

现在,像这样,这个工作,一切都很好.但是,IntelliJ会在哪里发出警告@Autowired(我在评论中标记了哪里)

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

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

@Autowired
public MyCommandLineRunner(DataSource ds) { ... }
Run Code Online (Sandbox Code Playgroud)

这也意味着我必须编辑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);
    }
}
Run Code Online (Sandbox Code Playgroud)

上面的代码产生相同的结果,但不报告IntelliJ中的任何警告.我很困惑,第二代码比第一代更好?我是否遵循了错误的逻辑?它应该以不同方式连接吗?

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

提前致谢!

请注意,如果有语法或逻辑错误,我会直接在这里输入代码.DataSource这只是一个纯粹的例子,这个问题适用于任何自动装配的东西.

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

M. *_*num 8

有几种方法可以改善它.

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

  2. 或删除@Autowired并删除@Bean并打上@Component您的注释MyCommandLineRunner以检测它并删除工厂方法.

  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());
    }
}
Run Code Online (Sandbox Code Playgroud)

和应用程序类.

@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);
    }
}
Run Code Online (Sandbox Code Playgroud)

用法 @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());
    }
}
Run Code Online (Sandbox Code Playgroud)

和应用程序类.

@SpringBootApplication
public class Application {

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

}
Run Code Online (Sandbox Code Playgroud)

排队 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); 
    }
}
Run Code Online (Sandbox Code Playgroud)

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

  • 我仍然缺乏回应:为什么要避免自动接线?为什么基于构造函数的依赖关系更好?正如@ m-deinum所问:自动接线应该在哪里正确应用? (3认同)
  • @LluísSuñol现场注入使测试更加困难。您必须求助于ReflectionUtils或类似的方法来在要测试的类中设置模拟依赖项。 (2认同)

lrv*_*lrv 5

考虑将字段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依赖注入

为了保持代码整洁,您是否考虑过使用 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

稍后编辑

没有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());
    }
}
Run Code Online (Sandbox Code Playgroud)