web-dev-qa-db-ja.com

クリーンなコード-@Autowiredはどこに適用する必要がありますか?

簡単な例から始めましょう。初期化時にCommandLineRunnerクラスを実行するSpringブートアプリケーションがあります。

// 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がある場所に警告を報告します(コメントのどこにマークを付けたか)

Springチームの推奨事項: Beanでは常にコンストラクターベースの依存性注入を使用します。必須の依存関係には常にアサーションを使用します。

これに従うと、依存関係の注入に基づくコンストラクターができます

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

これは、コンストラクタが引数を必要とするため、Application.Javaも編集する必要があることも意味します。 Application.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で警告を報告しません。私は混乱しています、2番目のコードは最初のコードよりどのように優れていますか?間違ったロジックに従っていますか?これは別の方法で配線する必要がありますか?

要するに、これを行う正しい方法は何ですか?

noteDataSourceは単なる例であり、この質問は自動配線されているすべてのものに当てはまります。

注2DataSourceを自動配線/初期化する必要があるため、MyCommandLineRunner.Javaに別の空のコンストラクターを含めることはできません。エラーが報告され、コンパイルされません。

16
NemanjaT

それを改善するにはいくつかの方法があります。

  1. @Autowiredメソッドを使用してインスタンスを構築しているので、MyCommandLineRunnerから@Beanを削除できます。 DataSourceを引数として直接メソッドに注入します。

  2. または、@Autowiredを削除して@Beanを削除し、MyCommandLineRunner@Componentアノテーションをスラップして、検出してファクトリメソッドを削除します。

  3. 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); 
    }
}

これらはすべて、インスタンスを構築する有効な方法です。どちらを使うか、気持ちのいいものを使いましょう。さらに多くのオプションがあります(ここで説明したオプションのすべてのバリエーション)。

8
M. Deinum

フィールド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-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を使用しないソリューションは、Beanの作成時に依存関係を注入するために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());
    }
}
_
3
user7757360