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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/43174074/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-03 07:09:39  来源:igfitidea点击:

Clean code - Where should @Autowired be applied?

javaspringspring-bootcoding-styleautowired

提问by NemanjaT

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

我将从一个简单的例子开始。您有一个 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();
    }
}

Now, like this, this works, everything is OK. However, IntelliJ reports a warning where @Autowiredis located (I marked where in the comment)

现在,像这样,这有效,一切正常。但是,IntelliJ 报告警告 where @Autowiredis location (我在评论中标记了位置)

Spring team recommends:Always use constructor based dependency injection in your beans. Always use assertions for mandatory dependencies.

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

Now if I follow this, I have a constructor based dependency injection

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

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

This also means that I have to edit Application.javaas well, since the constructor needs an argument. In Application.javaif I try to use the setter injection, I'll get the same warning. If I refactor that as well, I'll end up with some, in my opinion, nasty code.

这也意味着我也必须编辑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);
    }
}

The above code yields the same result, but doesn't report any warnings in IntelliJ. I'm confused, how is the 2nd code better than the first one? Am I following an incorrect logic? Should this be wired differently?

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

In short, what's the correct way to do this?

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

noteDataSourceis just a pure example, this question applies to anything being autowired.

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

note 2Just to say that MyCommandLineRunner.javacan't have another, empty, constructor, since DataSource needs to be autowired/initialized. It will report an error and will not be compiled.

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

采纳答案by M. Deinum

There are several ways to improve it.

有几种方法可以改进它。

  1. You can remove @Autowiredfrom your MyCommandLineRunneras you are letting a @Beanmethod construct an instance of it. Inject the DataSourcedirectly into the method as an argument.

  2. Or remove @Autowiredand remove the @Beanand slap a @Componentannotation on your MyCommandLineRunnerto have it detected and remove factory method.

  3. Inline your MyCommandLineRunnerinside your @Beanmethod as a lambda.

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

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

  3. MyCommandLineRunner您的@Bean方法内部作为 lambda内联。

No Autowiring in the MyCommandLineRunner

没有自动装配 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

@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

排队 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).

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

回答by lrv

Consider making the field dsfinal, 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

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

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

为了保持代码干净,您是否考虑过使用 Lombok 注释?@RequiredArgsConstructor(onConstructor = @__(@Autowired))将使用@Autowired 注释生成构造函数。在此处查看更多信息 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

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