干净的代码 - @Autowired 应该应用在哪里?
Clean code - Where should @Autowired be applied?
我将从一个简单的例子开始。您有一个 Spring 启动应用程序,它在初始化时运行 CommandLineRunner
class。
// 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();
}
}
现在,像这样,这样就可以了,一切OK。但是,IntelliJ 在 @Autowired
所在位置(我在评论中标记了 where)
报告了警告
Spring team recommends: Always use constructor based dependency injection in your beans. Always use assertions for mandatory dependencies.
现在如果我遵循这个,我就有了一个基于构造函数的依赖注入
@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 需要是 autowired/initialized。会报错,不会编译。
考虑将字段 ds
设置为最终字段,这样就不需要 @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());
}
}
有几种方法可以改进它。
您可以从 MyCommandLineRunner
中删除 @Autowired
,因为您让 @Bean
方法构造它的一个实例。将 DataSource
作为参数直接注入到方法中。
或删除 @Autowired
并删除 @Bean
并在 MyCommandLineRunner
上添加 @Component
注释以检测并删除工厂方法。
将您的 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());
}
}
和应用程序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);
}
}
@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());
}
}
和应用程序class。
@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);
}
}
所有这些都是构建实例的有效方法。使用哪一个,使用您觉得舒服的那个。还有更多选项(这里提到的选项的所有变体)。
我将从一个简单的例子开始。您有一个 Spring 启动应用程序,它在初始化时运行 CommandLineRunner
class。
// 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();
}
}
现在,像这样,这样就可以了,一切OK。但是,IntelliJ 在 @Autowired
所在位置(我在评论中标记了 where)
Spring team recommends: Always use constructor based dependency injection in your beans. Always use assertions for mandatory dependencies.
现在如果我遵循这个,我就有了一个基于构造函数的依赖注入
@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 需要是 autowired/initialized。会报错,不会编译。
考虑将字段 ds
设置为最终字段,这样就不需要 @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());
}
}
有几种方法可以改进它。
您可以从
MyCommandLineRunner
中删除@Autowired
,因为您让@Bean
方法构造它的一个实例。将DataSource
作为参数直接注入到方法中。或删除
@Autowired
并删除@Bean
并在MyCommandLineRunner
上添加@Component
注释以检测并删除工厂方法。将您的
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());
}
}
和应用程序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);
}
}
@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());
}
}
和应用程序class。
@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);
}
}
所有这些都是构建实例的有效方法。使用哪一个,使用您觉得舒服的那个。还有更多选项(这里提到的选项的所有变体)。