Micronautでインタフェース実装Beanを切り替える方法!複数実装を安全に管理するDI入門
生徒
「Micronautでインタフェースを作ったら、実装クラスが複数あってエラーが出ました……」
先生
「DIでは、どの実装を使うか指定しないと、Micronautが判断できなくなることがあります」
生徒
「Springみたいに設定で切り替えたりできるんですか?」
先生
「できます。Micronautではアノテーションを使って、複数実装の管理や切り替えができます」
生徒
「初心者でも混乱しない方法を知りたいです」
先生
「では、インタフェースと複数Beanの基本から順番に整理していきましょう」
1. Micronautとインタフェース設計の基本
MicronautはDIを前提としたJavaフレームワークであり、インタフェースを使った設計と非常に相性が良いです。 インタフェースを用いることで、実装を差し替えやすくなり、テストや機能追加が簡単になります。 その一方で、同じインタフェースを実装したクラスが複数存在すると、DI時にどれを使うか決められずエラーになります。
初心者が遭遇しやすいのが、インタフェースを定義し、実装クラスを二つ以上作った瞬間に発生する依存性解決エラーです。 Micronautでは、これを明示的に制御する仕組みが用意されています。
2. インタフェースと複数実装を定義する
まずは、基本となるインタフェースと、その実装クラスを作成します。 ここでは通知処理を例にして、コンソール出力とログ出力の二つの実装を用意します。
public interface NotificationService {
void notifyMessage(String message);
}
import jakarta.inject.Singleton;
@Singleton
public class ConsoleNotificationService implements NotificationService {
@Override
public void notifyMessage(String message) {
System.out.println("Console通知: " + message);
}
}
import jakarta.inject.Singleton;
@Singleton
public class LogNotificationService implements NotificationService {
@Override
public void notifyMessage(String message) {
System.out.println("Log通知: " + message);
}
}
この状態でNotificationServiceをDIしようとすると、Micronautはどちらを使うべきか判断できません。 そのため、複数実装を管理する仕組みが必要になります。
3. Primaryを使ってデフォルト実装を決める
最も簡単な方法がPrimaryアノテーションを使う方法です。 Primaryを付与したBeanは、複数候補がある場合のデフォルトとして選ばれます。 初心者にとって理解しやすく、設定量も少ないのが特徴です。
import jakarta.inject.Singleton;
import io.micronaut.context.annotation.Primary;
@Singleton
@Primary
public class ConsoleNotificationService implements NotificationService {
@Override
public void notifyMessage(String message) {
System.out.println("Console通知: " + message);
}
}
この指定により、NotificationServiceを注入した場合はConsoleNotificationServiceが使われます。 まず一つの実装を基準にしたい場合に有効な方法です。
4. Qualifierを使って明示的に切り替える
実装を用途ごとに切り替えたい場合は、Qualifierを使う方法が適しています。 Qualifierは、Beanに名前や識別子を付けて区別する仕組みです。 MicronautではNamedアノテーションがよく使われます。
import jakarta.inject.Singleton;
import jakarta.inject.Named;
@Singleton
@Named("console")
public class ConsoleNotificationService implements NotificationService {
@Override
public void notifyMessage(String message) {
System.out.println("Console通知: " + message);
}
}
import jakarta.inject.Singleton;
import jakarta.inject.Named;
@Singleton
@Named("log")
public class LogNotificationService implements NotificationService {
@Override
public void notifyMessage(String message) {
System.out.println("Log通知: " + message);
}
}
import jakarta.inject.Inject;
import jakarta.inject.Named;
import jakarta.inject.Singleton;
@Singleton
public class NotificationClient {
private final NotificationService notificationService;
@Inject
public NotificationClient(@Named("console") NotificationService notificationService) {
this.notificationService = notificationService;
}
public void execute() {
notificationService.notifyMessage("処理が完了しました");
}
}
このようにすることで、使用する実装をコード上で明示できます。 複数実装を安全に使い分けたい場合に非常に便利です。
5. 複数実装をリストとして受け取る方法
Micronautでは、同じインタフェースを実装したBeanをすべてまとめて受け取ることも可能です。 これにより、処理をループで実行したり、条件によって動的に選択できます。
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import java.util.List;
@Singleton
public class BatchNotificationService {
private final List<NotificationService> services;
@Inject
public BatchNotificationService(List<NotificationService> services) {
this.services = services;
}
public void notifyAllServices(String message) {
for (NotificationService service : services) {
service.notifyMessage(message);
}
}
}
この方法は拡張性が高く、新しい実装を追加してもコード修正が最小限で済みます。 プラグイン的な設計を行いたい場合にも役立ちます。
6. 初心者が混乱しやすいポイントと考え方
初心者が混乱しやすいのは、インタフェースを作っただけで自動的に切り替わると誤解してしまう点です。 Micronautは明示的な指定を重視するため、複数実装がある場合は必ず選択ルールを与える必要があります。
まずはPrimaryで一つに決める、次にQualifierで切り替える、最後に一覧取得という順番で理解すると、 MicronautのDIとインタフェース設計が自然に身につきます。