それでは前回の続きから。
今回はstep1からstep4までに、どのような変更を行ったか詳しく見ていきたいと思います。
目次
step1
コアライブラリ側
Springフレームワークベースで開発したアプリケーションのコアとなるライブラリ(コアライブラリ)でUTが通っており、Springフレームワークのアプリケーションコンテキストの初期化やBeanのDIが出来ている状態です。
masterブランチとの差分はありませんが、以下のConfigクラスを起点としてコンポーネントスキャンが行われており、ごく一般的なSpringフレームワークのアプリケーションとなっています。
コアライブラリは、
Quarkus側
step1のコアライブラリをbuild.gradleから参照しています。
ところが、BootApplicationを実行すると、Springフレームワークのアノテーションを付与したBeanの解決ができずに、実行に失敗します。
実行時エラーの内容
スタックトレースは割愛していますが、コアライブラリの「FileService」というBeanの解決ができないようです。
2023-11-21 15:31:50,242 ERROR [io.qua.dep.dev.IsolatedDevModeMain] (main) Failed to start quarkus: java.lang.RuntimeException: io.quarkus.builder.BuildException: Build failure: Build failed due to errors
[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 jp.co.mukeisoftllc.ex.spring.world.services.FileService and qualifiers [@Default]
- java member: jp.co.mukeisoftllc.ex.grpc.FileServiceExposer#fileService
- declared on CLASS bean [types=[io.grpc.BindableService, jp.co.mukeisoftllc.ex.quarkus.gpc.FileGrpcServiceGrpc$FileGrpcServiceImplBase, jp.co.mukeisoftllc.ex.quarkus.gpc.FileGrpcServiceGrpc$AsyncService, java.lang.Object, jp.co.mukeisoftllc.ex.grpc.FileServiceExposer], qualifiers=[@Any, @GrpcService], target=jp.co.mukeisoftllc.ex.grpc.FileServiceExposer]
「FileService」Beanを注入しているのは、gRPCサービスとして公開しているクラスの14行目です。
Quarkus側はCDIのアノテーションで依存関係の宣言をしていますが、Quarkusのspring diエクステンションを使えば、「FileService」Beanの解決はできるはずです。
step2
コアライブラリ側
step1では、「FileService」Beanの解決ができなかったため、CDIの流儀に則りbeans.xmlをMETA-INFに追加します。中身は空でも大丈夫です。

Quarkus側
コアライブラリのバージョンだけあげていますが、BootApplicationの起動ができず、別のエラーが発生します。

エラーの内容
以下のとおり、3つのエラーがあるようです。
- org.springframework.core.env.Environmentが解決できない
- FileServiceでコンストラクタインジェクションしているPath型のBeanがあいまい
- 上記と同じでコンストラクタインジェクションしているPath型のBeanがあいまい
Environmentの件は環境設定がSpringとQuarkusで異なるため、おそらく他の方法で依存性注入を試みた方が良さそうであるため、いったん無視するとして、Path型のBean定義があいまいだというエラーはコアライブラリ側の挙動を変えずに解消できそうです。
[1] Unsatisfied dependency for type org.springframework.core.env.Environment and qualifiers [@Default]
- java member: jp.co.mukeisoftllc.ex.spring.world.Config#env
- declared on CLASS bean [types=[jp.co.mukeisoftllc.ex.spring.world.Config, java.lang.Object], qualifiers=[@Default, @Any], target=jp.co.mukeisoftllc.ex.spring.world.Config]
[2] Ambiguous dependencies for type java.nio.file.@NonNull Path and qualifiers [@Default]
- java member: jp.co.mukeisoftllc.ex.spring.world.services.FileService():textPath
- declared on CLASS bean [types=[jp.co.mukeisoftllc.ex.spring.world.services.FileService, java.lang.Object], qualifiers=[@Default, @Any, @Named("fileService")], target=jp.co.mukeisoftllc.ex.spring.world.services.FileService]
- available beans:
- PRODUCER METHOD bean [types=[java.nio.file.Watchable, java.nio.file.Path, java.lang.Comparable<java.nio.file.Path>, java.lang.Object, java.lang.Iterable<java.nio.file.Path>], qualifiers=[@Default, @Any, @Named("textPath")], target=java.nio.file.Path textPath(), declaringBean=jp.co.mukeisoftllc.ex.spring.world.Config]
- PRODUCER METHOD bean [types=[java.nio.file.Watchable, java.nio.file.Path, java.lang.Comparable<java.nio.file.Path>, java.lang.Object, java.lang.Iterable<java.nio.file.Path>], qualifiers=[@Default, @Any, @Named("imagePath")], target=java.nio.file.Path imagePath(), declaringBean=jp.co.mukeisoftllc.ex.spring.world.Config]
[3] Ambiguous dependencies for type java.nio.file.@NonNull Path and qualifiers [@Default]
- java member: jp.co.mukeisoftllc.ex.spring.world.services.FileService():imagePath
- declared on CLASS bean [types=[jp.co.mukeisoftllc.ex.spring.world.services.FileService, java.lang.Object], qualifiers=[@Default, @Any, @Named("fileService")], target=jp.co.mukeisoftllc.ex.spring.world.services.FileService]
- available beans:
- PRODUCER METHOD bean [types=[java.nio.file.Watchable, java.nio.file.Path, java.lang.Comparable<java.nio.file.Path>, java.lang.Object, java.lang.Iterable<java.nio.file.Path>], qualifiers=[@Default, @Any, @Named("textPath")], target=java.nio.file.Path textPath(), declaringBean=jp.co.mukeisoftllc.ex.spring.world.Config]
- PRODUCER METHOD bean [types=[java.nio.file.Watchable, java.nio.file.Path, java.lang.Comparable<java.nio.file.Path>, java.lang.Object, java.lang.Iterable<java.nio.file.Path>], qualifiers=[@Default, @Any, @Named("imagePath")], target=java.nio.file.Path imagePath(), declaringBean=jp.co.mukeisoftllc.ex.spring.world.Config]
step3
コアライブラリ側
step2であいまいだとされていた2つのPath型のBeanについて、定義とDIの書き方を改めます。
(step2ブランチとstep3ブランチの差分はこちらのリンクから参照してください)
まずはConfigクラスで@Beanアノテーションを付与した2つのプロデューサーメソッドに明示的なBean名を付けます。Springフレームワーク上では、こういったプロデューサーメソッドの名前をBean名として別にBeanに注入してくれますが、より明示的な宣言をして曖昧さを排除した方が良いでしょう。
次に、FileServiceクラスのコンストラクタでDIしているPath型の2つのBeanに関し、@Qualifierを使って明示的にします。
Quakrus側
特に変更はありませんが、コアライブラリのバージョンをあげています。
期待値としては、Environemntに関するエラー以外は解消されているはず、ですね。
それではBootApplicationを起動してみましょう。
エラーの内容
予想どおり、Path型2つのBeanの曖昧さは排除できましたが、特になにも手を打っていないEnvironmentがエラーとなっています。
EnvironmentはSpringフレームワークで一般的に実行時のふるまいを変えるために利用しますが、Quakrusのspring diエクステンションは、Beanの解決はしてくれるものの、Springフレームワーク自体の初期処理の一切を行いませんので、step4ではQuakrus側でEnvironment型のBeanを初期化して、コアライブラリに注入するアプローチでチャレンジしてみましょう。
ERROR [io.qua.dep.dev.IsolatedDevModeMain] (main) Failed to start quarkus: java.lang.RuntimeException: io.quarkus.builder.BuildException: Build failure: Build failed due to errors
[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 org.springframework.core.env.Environment and qualifiers [@Default]
- java member: jp.co.mukeisoftllc.ex.spring.world.Config#env
- declared on CLASS bean [types=[jp.co.mukeisoftllc.ex.spring.world.Config, java.lang.Object], qualifiers=[@Default, @Any], target=jp.co.mukeisoftllc.ex.spring.world.Config]
step4
コアライブラリ側
特に変更はありません。Environmentの注入待ちです。
Quakrus側
StandardEnvironmentのインスタンスを作成して、Quakrusのapplication.propertiesで定義したプロファイルを設定してあげるようなプロデューサーメソッドを作成し、BootApplicationを実行してみます。
Press [e] to edit command line args (currently ''), [h] for more options>
Tests paused
Press [e] to edit command line args (currently ''), [r] to resume testing, [h] for more options>
Press [e] to edit command line args (currently ''), [r] to resume testing, [o] Toggle test output, [h] for more options>
__ ____ __ _____ ___ __ ____ ______
--/ __ \/ / / / _ | / _ \/ //_/ / / / __/
-/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/
2023-11-21 16:52:14,987 WARN [io.qua.grp.run.GrpcServerRecorder] (Quarkus Main Thread) Using legacy gRPC support, with separate new HTTP server instance. Switch to single HTTP server instance usage with quarkus.grpc.server.use-separate-server=false property
2023-11-21 16:52:15,135 INFO [io.qua.grp.run.GrpcServerRecorder] (Quarkus Main Thread) Registering gRPC reflection service
2023-11-21 16:52:15,359 INFO [io.qua.grp.run.GrpcServerRecorder] (vert.x-eventloop-thread-0) Started gRPC server on 0.0.0.0:9000 [TLS enabled: false]
2023-11-21 16:52:15,401 INFO [io.quarkus] (Quarkus Main Thread) spring-di-quarkus-example 1.0-SNAPSHOT on JVM (powered by Quarkus 3.4.1) started in 3.910s. Listening on: http://localhost:8080
2023-11-21 16:52:15,404 INFO [io.quarkus] (Quarkus Main Thread) Profile dev activated. Live Coding activated.
2023-11-21 16:52:15,405 INFO [io.quarkus] (Quarkus Main Thread) Installed features: [cdi, grpc-server, smallrye-context-propagation, spring-di, vertx]
StandardEnvironmentのプロデューサーメソッドに改良の余地は大きく残していますが、まずは起動できましたので、以下のコマンドで実際にgRPCクライアントからファイルの読み書きを試してみたいと思います。
gradlew test --tests "FileGrpcServiceTest"

BootApplicationで起動したgRPCサーバー(JVMモード)に対して、ファイルの読み書きを要求することはできたようです。
しかしながら、ゴールはあくまでもネイティブアプリケーションとしてビルドして実行させるという所ですので、これでめでたしめでたしとは行きません。
次回はstep5からstep7までの工程で、ネイティブ化のステップを進めていきたいと思います。
それでは!