1. 序章

ソフトウェア開発者として、私たちは常に特定のテクノロジーまたはライブラリを使用するためのベストプラクティスを探しています。 当然、議論が時々あります。

そのような議論の1つは、Springの@Serviceアノテーションの配置に関するものです。 SpringはBeanを定義するための代替方法を提供するため、ステレオタイプ注釈の所在に注意を払う価値があります。

このチュートリアルでは、 @Service アノテーションを確認し、をインターフェイス、抽象クラス、または具象クラスに配置するのが最適かどうかを調べます。

2. インターフェイス上の@Service

一部の開発者は、次の理由から@Serviceをインターフェイスに配置することを決定する場合があります。

  • インターフェイスはサービスレベルの目的でのみ使用する必要があることを明示的に示します
  • 新しいサービスの実装を定義し、起動時にSpringBeanとして自動的に検出されるようにします

インターフェイスに注釈を付けた場合の外観を見てみましょう。

@Service
public interface AuthenticationService {

    boolean authenticate(String username, String password);
}

お気づきのとおり、AuthenticationServiceはより自己記述的になりました。 @Service マークは、データアクセス層やその他の層ではなく、ビジネス層サービスにのみ使用するように開発者にアドバイスします。

通常は問題ありませんが、欠点があります。 Springの@Serviceをインターフェイスに配置することで、追加の依存関係を作成し、インターフェイスを外部ライブラリと結合します。

次に、新しいサービスBeanの自動検出をテストするために、AuthenticationServiceの実装を作成しましょう。

public class InMemoryAuthenticationService implements AuthenticationService {

    @Override
    public boolean authenticate(String username, String password) {
        //...
    }
}

新しい実装InMemoryAuthenticationServiceには、@Serviceアノテーションがないことに注意する必要があります。 @Service は、インターフェイスAuthenticationServiceにのみ残しました。

それでは、基本的なSpring Bootセットアップを使用して、Springコンテキストを実行してみましょう。

@SpringBootApplication
public class AuthApplication {

    @Autowired
    private AuthenticationService authService;

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

アプリを実行すると、悪名高いNoSuchBeanDefinitionException、が発生し、Springコンテキストの開始に失敗します。

org.springframework.beans.factory.NoSuchBeanDefinitionException: 
No qualifying bean of type 'com.baeldung.annotations.service.interfaces.AuthenticationService' available: 
expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: 
...

したがって、インターフェイスに@Serviceを配置するだけでは、Springコンポーネントを自動検出するのに十分ではありません。

3. 抽象クラスの@Service

抽象クラスで@Serviceアノテーションを使用することは一般的ではありません。

Springに実装クラスを自動検出させるという目的を達成するかどうかをテストしてみましょう。

まず、抽象クラスを最初から定義し、それに@Serviceアノテーションを付けます。

@Service
public abstract class AbstractAuthenticationService {

    public boolean authenticate(String username, String password) {
        return false;
    }
}

次に、 AbstractAuthenticationService を拡張して注釈を付けずに具体的な実装を作成します

public class LdapAuthenticationService extends AbstractAuthenticationService {

    @Override
    public boolean authenticate(String username, String password) { 
        //...
    }
}

したがって、 AuthApplication も更新して、新しいサービスクラスを挿入します。

@SpringBootApplication
public class AuthApplication {

    @Autowired
    private AbstractAuthenticationService authService;

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

ここに抽象クラスを直接挿入しようとしないことに注意してください。これは不可能です。 代わりに、抽象型のみに応じて、具象クラスLdapAuthenticationServiceのインスタンスを取得する予定です。 Liskov Substitution Principle も示唆しているように、これは良い習慣です。

したがって、AuthApplicationを再度実行します。

org.springframework.beans.factory.NoSuchBeanDefinitionException: 
No qualifying bean of type 'com.baeldung.annotations.service.abstracts.AbstractAuthenticationService' available: 
expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: 
...

ご覧のとおり、Springコンテキストは開始されません。 は、同じNoSuchBeanDefinitionException例外で終了します。

確かに、抽象クラスで@Serviceアノテーションを使用しても、Springでは効果がありません。

4. 具象クラスの@Service

上で見たものとは反対に、抽象クラスやインターフェースの代わりに実装クラスに注釈を付けることは非常に一般的な方法です。

このように、私たちの目標は主に、このクラスが @Component になることをSpringに伝え、特別なステレオタイプ(この場合は @Service )でマークすることです。

したがって、Springはクラスパスからこれらのクラスを自動検出し、それらをマネージドBeanとして自動的に定義します。

それでは、今回は具体的なサービスクラスに@Serviceを入れましょう。 インターフェイスを実装するクラスと、前に定義した抽象クラスを拡張するクラスがあります。

@Service
public class InMemoryAuthenticationService implements AuthenticationService {

    @Override
    public boolean authenticate(String username, String password) {
        //...
    }
}

@Service
public class LdapAuthenticationService extends AbstractAuthenticationService {

    @Override
    public boolean authenticate(String username, String password) {
        //...
    }
}

ここで、AbstractAuthenticationServiceAuthenticationServiceを実装していないことに注意してください。 したがって、それらを個別にテストできます。

最後に、両方のサービスクラスを AuthApplication に追加して、試してみます。

@SpringBootApplication
public class AuthApplication {

    @Autowired
    private AuthenticationService inMemoryAuthService;

    @Autowired
    private AbstractAuthenticationService ldapAuthService;

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

最終テストで成功した結果が得られ、Springコンテキストは例外なく起動します。 どちらのサービスも自動的にBeanとして登録されます。

5. 結果

最終的に、唯一の有効な方法は、実装クラスに @Service を配置して、それらを自動検出可能にすることであることがわかりました。 Springのコンポーネントスキャンは、クラスが別の@Serviceアノテーション付きインターフェイスまたは抽象クラスから派生している場合でも、個別にアノテーションが付けられていない限り、クラスを取得しません。

さらに、 Springのドキュメントには、実装クラスで @Service を使用すると、コンポーネントスキャンによって自動検出できると記載されています。

6. 結論

この記事では、Springの @Service アノテーションを使用するさまざまな場所を調べ、@Serviceを保持してサービスレベルのSpringBeanを定義する場所を学習しました。コンポーネントのスキャン中に自動検出されます。

具体的には、 @Service アノテーションをインターフェイスまたは抽象クラスに配置しても効果がなく、 @Service アノテーションが付けられている場合、コンポーネントスキャンによって具象クラスのみが取得されることがわかりました。 。

いつものように、すべてのコードサンプルとそれ以上がGitHub利用できます。