QuarkusのCDIでBeanが見つからなかった問題の解決備忘録

## はじめに

最近、Quarkusアプリケーションの開発中にCDI[^1]関係でハマったので、その時の解決方法を書き残しておきます。

[^1]: CDIについて簡単に触れておくと、DI（Dependency Injection: 依存性の注入）の仕様の一つで、`Context and Dependency Injection`の略です。通常のDIに対して色々と機能が増えており、たとえばコンテキスト（オブジェクトのライフサイクル）管理がその一例です。

なお`Kotlin`で`Quarkus`アプリケーションを開発している時に出くわした問題ですが、`Quarkus`や`Kotlin`特有の話ではなく、CDIの仕様に基づく挙動のため、`Jakarta EE`全般で同じようなことは起こり得ると思います。

## Quarkus + CDI で UnsatisfiedResolutionException に出くわした

自作クラスAのインスタンスを、別の自作クラスBにコンストラクタインジェクションしようとするけれど、例外（`UnsatisfiedResolutionException`）がスローされて起動できない状態になりました。
ちなみにクラスAとクラスBは別のプロジェクトに配置されていました。

クラス名は伏せますが、その時のスタックトレースは以下のとおりです。
ザックリいうと「インジェクションしようとしているBeanが見つからない」と言われています。

```sh: sh
<====2025-03-18 21:34:30,234 ERROR [io.qua.dep.dev.IsolatedDevModeMain] (main) Failed to start quarkus: java.lang.RuntimeException: io.quarkus.builder.BuildException: Build failure: Build failed due to errorsUTING [20s]
        [error]: Build step io.quarkus.arc.deployment.ArcProcessor#validate threw an exception: jakarta.enterprise.inject.spi.DeploymentException: jakarta.enterprise.inject.UnsatisfiedResolutionException: Unsatisfied dependency for type {インジェクションするクラスの完全修飾名} and qualifiers [@Default]
        - injection target: parameter '{インジェクション先クラスの変数名}' of {インジェクション先クラスの完全修飾名} constructor
        - declared on CLASS bean [types=[{インジェクション先クラスの完全修飾名}, java.lang.Object], qualifiers=[@Default, @Any], target={インジェクション先クラスの完全修飾名}]
        at io.quarkus.arc.processor.BeanDeployment.processErrors(BeanDeployment.java:1551)
        at io.quarkus.arc.processor.BeanDeployment.init(BeanDeployment.java:338)
        at io.quarkus.arc.processor.BeanProcessor.initialize(BeanProcessor.java:167)
        at io.quarkus.arc.deployment.ArcProcessor.validate(ArcProcessor.java:490)
        at java.base/java.lang.invoke.MethodHandle.invokeWithArguments(MethodHandle.java:733)
        at io.quarkus.deployment.ExtensionLoader$3.execute(ExtensionLoader.java:856)
        at io.quarkus.builder.BuildContext.run(BuildContext.java:256)
        at org.jboss.threads.ContextHandler$1.runWith(ContextHandler.java:18)
        at org.jboss.threads.EnhancedQueueExecutor$Task.doRunWith(EnhancedQueueExecutor.java:2516)
        at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2495)
        at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1521)
        at java.base/java.lang.Thread.run(Thread.java:1583)
        at org.jboss.threads.JBossThread.run(JBossThread.java:483)
Caused by: jakarta.enterprise.inject.UnsatisfiedResolutionException: Unsatisfied dependency for type {インジェクションするクラスの完全修飾名} and qualifiers [@Default]
        - injection target: parameter '{インジェクション先クラスの変数名}' of {インジェクション先クラスの完全修飾名} constructor
        - declared on CLASS bean [types=[{インジェクション先クラスの完全修飾名}, java.lang.Object], qualifiers=[@Default, @Any], target={インジェクション先クラスの完全修飾名}]
        at io.quarkus.arc.processor.Beans.resolveInjectionPoint(Beans.java:545)
        at io.quarkus.arc.processor.BeanInfo.init(BeanInfo.java:667)
        at io.quarkus.arc.processor.BeanDeployment.init(BeanDeployment.java:323)
        ... 11 more

```

## 原因と解決方法

結論から言うと、（だいぶ基礎的な部分の見落としですが、）インジェクションするクラスのプロジェクト内に`beans.xml`がなかったのが原因でした。
パスまで含めて言うと`src/main/resources/META-INF/beans.xml`ですね。

基本的に`Quarkus`では`@ApplicationScoped`などのスコープアノテーションが付いていれば、`beans.xml`がなくてもBeanとして扱ってくれるはずです。ただ今回のケースではプロジェクトが分かれていたこともあり、`beans.xml`を置いて明示的にBeanアーカイブと示さないとBeanを見つけてもらえなかったようです。

なお`beans.xml`は空ファイルで問題ありませんでした。
`beans.xml`が置かれていることが「このJARファイル（ないしプロジェクト）はBeanアーカイブである」ということを示すためのマーカーとしての意味を持つので、今回はただファイルを置いただけで解決したようです。