web-dev-qa-db-ja.com

Springがどのクラスをオートワイヤーする必要があるか(依存性注入を使用する場合)?

私は、SpringでDependency Injectionをしばらく使用してきましたが、それがどのように機能するか、そしてそれを使用することの賛否両論は何ですか。ただし、新しいクラスを作成しているとき、よく疑問に思います。このクラスはSpringで管理する必要がありますかIOC Container?

また、@ Autowiredアノテーション、XML構成、セッターインジェクション、コンストラクターインジェクションなどの違いについては話したくありません。私の質問は一般的なものです。

コンバーター付きのサービスがあるとしましょう:

@Service
public class Service {

    @Autowired
    private Repository repository;

    @Autowired
    private Converter converter;

    public List<CarDto> getAllCars() {
        List<Car> cars = repository.findAll();
        return converter.mapToDto(cars);
    }
}

@Component
public class Converter {

    public CarDto mapToDto(List<Car> cars) {
        return new ArrayList<CarDto>(); // do the mapping here
    }
}

明らかに、コンバーターには依存関係がないため、自動配線する必要はありません。しかし、私にとっては自動配線の方が良いようです。コードはよりクリーンでテストが簡単です。 DIなしでこのコードを書くと、サービスは次のようになります。

@Service
public class Service {

    @Autowired
    private Repository repository;

    public List<CarDto> getAllCars() {
        List<Car> cars = repository.findAll();
        Converter converter = new Converter();
        return converter.mapToDto(cars);
    }
}

今ではそれをテストすることははるかに困難です。さらに、オーバーヘッドのように常に同じ状態であっても、すべての変換操作に対して新しいコンバーターが作成されます。

Spring MVCにはよく知られているパターンがいくつかあります。サービスを使用するコントローラーとリポジトリを使用するサービスです。次に、リポジトリが自動配線されている場合(通常は自動配線されています)、サービスも自動配線される必要があります。そして、これは非常に明確です。しかし、いつ@Componentアノテーションを使用するのですか?静的utilクラス(コンバーター、マッパーなど)がある場合、それらを自動配線しますか?

すべてのクラスを自動配線しようとしていますか?そうすれば、すべてのクラスの依存関係を簡単に注入できます(ここでも、理解しやすく、テストも簡単です)。それとも絶対に必要な場合にのみ自動配線を試みますか?

自動配線を使用するときの一般的なルールを探すのに少し時間を費やしましたが、具体的なヒントは見つかりませんでした。通常、人々は「DIを使用しますか(yes/no)」または「どのタイプの依存関係注入が好きですか」について話しますが、これは私の質問の答えにはなりません。

このトピックに関するヒントをいただければ幸いです。

34
Kacper86

@ericWのコメントに同意し、コードをコンパクトに保つ​​ためにイニシャライザを使用できることを覚えておきましょう。

@Autowired
private Converter converter;

または

private Converter converter = new Converter();

または、クラスに本当に状態がない場合

private static final Converter CONVERTER = new Converter();

SpringがBeanをインスタンス化して注入する必要があるかどうかの主要な基準の1つは、そのBeanが非常に複雑で、テストでそれを模擬したいということですか?もしそうなら、それを注入します。たとえば、コンバーターが外部システムに往復する場合は、代わりにコンポーネントにします。または、戻り値が入力に基づいて数十の可能なバリエーションを持つ大きな決定木を持っている場合、それを模擬します。などなど。

あなたはすでにその機能をロールアップしてカプセル化する優れた仕事をしているので、今度はそれがテストのための別個の「ユニット」と見なされるのに十分複雑であるかどうかの問題です。

8
Rob

すべてのクラスを@Autowiredする必要はないと思います。実際の使用方法に依存する必要があります。シナリオでは、@ Autowiredの代わりに静的メソッドを使用することをお勧めします。これらの単純なutilsクラスに@Autowiredを使用するメリットは見られません。適切に使用しないと、Springコンテナーのコストが絶対に増加します。

5
ericW

私の経験則は、あなたがすでに言った、テスト容易性に基づいています。 「簡単に単体テストできますか?」と自問してください。答えが「はい」の場合、他の理由がなければ、私はそれで大丈夫でしょう。したがって、開発中のユニットテストと同時に開発すると、多くの苦痛を軽減できます。

唯一の潜在的な問題は、コンバーターが失敗した場合、サービステストも失敗することです。ユニットテストでコンバーターの結果を模擬する必要があると言う人もいます。このようにして、エラーをより早く特定できます。しかし、それには代償が伴います。実際のコンバーターで処理を実行できた場合、すべてのコンバーターの結果を模擬する必要があります。

また、別のdtoコンバーターを使用する理由はまったくないと思います。

2
Borjab

TL; DR:DIの自動配線と、DIのコンストラクター転送のハイブリッドアプローチにより、提示したコードを簡略化できます。

Springフレームワークの起動エラーや@autowired Beanの初期化の依存関係に関連する複雑さを伴ういくつかのweblogicが原因で、私は同様の質問を見てきました。私は別のDIアプローチで混合を始めました:コンストラクター転送です。あなたが提示しているような条件が必要です(「明らかに、コンバーターには依存関係がないため、自動配線する必要はありません。」)。それにもかかわらず、私は柔軟性が非常に好きで、それはまだ非常に簡単です。

@Service
public class Service {

    @Autowired
    private Repository repository;

    public List<CarDto> getAllCars(Converter converter) {
        List<Car> cars = repository.findAll();
        return converter.mapToDto(cars);
    }
    public List<CarDto> getAllCars() {
        Converter converter = new Converter();
        return getAllCars(converter);
    }
}

またはロブの答えから離れて

@Service
public class Service {

    @Autowired
    private Repository repository;

    private final Converter converter = new Converter(); // static if safe for that

    public List<CarDto> getAllCars(Converter converter) {
        List<Car> cars = repository.findAll();
        return converter.mapToDto(cars);
    }
    public List<CarDto> getAllCars() {
        return getAllCars(converter);
    }
}

パブリックインターフェイスの変更は必要ないかもしれませんが、必要です。それでも

public List<CarDto> getAllCars(Converter converter) { ... }

テスト目的/拡張のみを目的として、スコープを限定して保護またはプライベートにすることができます。

重要な点は、DIがオプションであることです。デフォルトが提供されており、簡単に上書きできます。フィールド数が増えると弱点になりますが、1、たぶん2フィールドの場合、以下の条件でこれを使用します。

  • 非常に少数のフィールド
  • デフォルトはほとんど常に使用されます(DIはテストシームです)またはデフォルトはほとんど使用されません(ストップギャップ)。一貫性が好きなので、これは決定ツリーの一部であり、真の設計制約ではありません。

最後のポイント(少しOTですが、@ autowiredが何をどのように決定するかに関連しています):コンバータークラスは、提示されているように、ユーティリティメソッドです(フィールドなし、コンストラクターなし、静的の可能性があります)。たぶん解決策は、Carsクラスにある種のmapToDto()メソッドを含めることでしょうか?つまり、変換インジェクションをCarsの定義にプッシュします。

@Service
public class Service {

   @Autowired
   private Repository repository;

   public List<CarDto> getAllCars() {
    return repository.findAll().stream.map(c -> c.mapToDto()).collect(Collectors.toList()));
   }
}
0
Kristian H