Quarkus の BuildItem がよくわからないので試してみる

Quarkus の拡張機能を作成する際、拡張機能に作った Bean がアプリケーション側で利用できなかったので色々試してみました。

結論

行き詰ったら jandex を依存関係に追加しよう(下記記事参照)

Quarkus – Contexts and Dependency Injection

今回作成した環境

  • main
  • classic
  • extension
  • extension-classic

の4つのプロジェクトを作成しました。main は reactive なプロジェクト、 classic は reactive じゃないプロジェクト、extension は拡張機能(reactive), extension-classic は拡張機能(reactive じゃない)プロジェクトです。

zu-min-g/playground-quarkus (github.com)

最初に

最初から拡張機能側にコードを書くと、拡張機能に書いたのが原因なのか、そもそも書いたコードが間違っているか分かりにくくなります。また、拡張機能の修正の反映はアプリケーションの再起動が必要で、少々手間がかかります。

そのため慣れるまでは、最初はアプリケーション側にコードを書いてから拡張機能側にコードを移す方法をお勧めします。

Bean

まずは基本の Bean から。Bean というと抽象的なので補足しますが、ここでは @Inject アノテーションで注入するやつのことを指します。

@Path("/hello")
public class ReactiveGreetingResource {

    @Inject
    MyService myService;

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String hello() {
        return myService.getMessage();
    }
}

次に MyService という Bean を拡張機能の runtime 側に定義します。

@ApplicationScoped
public class MyService {
    public String getMessage() {
        return "こんにちは!";
    }
}

そしてこれを実行するとエラーになります。

2021-07-10 18:00:00,995 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: javax.enterprise.inject.spi.DeploymentException: javax.enterprise.inject.UnsatisfiedResolutionException: Unsatisfied dependency for type com.zu_min.playground.quarkus.extension.runtime.MyService and qualifiers [@Default]    
        - java member: com.zu_min.playground.quarkus.ReactiveGreetingResource#myService
        - declared on CLASS bean [types=[com.zu_min.playground.quarkus.ReactiveGreetingResource, java.lang.Object], qualifiers=[@Default, @Any], target=com.zu_min.playground.quarkus.ReactiveGreetingResource]
        at io.quarkus.arc.processor.BeanDeployment.processErrors(BeanDeployment.java:1099)
        at io.quarkus.arc.processor.BeanDeployment.init(BeanDeployment.java:264)
        at io.quarkus.arc.processor.BeanProcessor.initialize(BeanProcessor.java:129)
        at io.quarkus.arc.deployment.ArcProcessor.validate(ArcProcessor.java:418)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.base/java.lang.reflect.Method.invoke(Method.java:566)
        at io.quarkus.deployment.ExtensionLoader$2.execute(ExtensionLoader.java:820)
        at io.quarkus.builder.BuildContext.run(BuildContext.java:277)
        at org.jboss.threads.ContextHandler$1.runWith(ContextHandler.java:18)
        at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2442)
        at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1476)
        at java.base/java.lang.Thread.run(Thread.java:829)
        at org.jboss.threads.JBossThread.run(JBossThread.java:501)
Caused by: javax.enterprise.inject.UnsatisfiedResolutionException: Unsatisfied dependency for type com.zu_min.playground.quarkus.extension.runtime.MyService and qualifiers [@Default]
        - java member: com.zu_min.playground.quarkus.ReactiveGreetingResource#myService
        - declared on CLASS bean [types=[com.zu_min.playground.quarkus.ReactiveGreetingResource, java.lang.Object], qualifiers=[@Default, @Any], target=com.zu_min.playground.quarkus.ReactiveGreetingResource]
        at io.quarkus.arc.processor.Beans.resolveInjectionPoint(Beans.java:492)
        at io.quarkus.arc.processor.BeanInfo.init(BeanInfo.java:460)
        at io.quarkus.arc.processor.BeanDeployment.init(BeanDeployment.java:252)
        ... 13 more

        at io.quarkus.runner.bootstrap.AugmentActionImpl.runAugment(AugmentActionImpl.java:414)
        at io.quarkus.runner.bootstrap.AugmentActionImpl.createInitialRuntimeApplication(AugmentActionImpl.java:274)
        at io.quarkus.runner.bootstrap.AugmentActionImpl.createInitialRuntimeApplication(AugmentActionImpl.java:66)
        at io.quarkus.deployment.dev.IsolatedDevModeMain.firstStart(IsolatedDevModeMain.java:87)
        at io.quarkus.deployment.dev.IsolatedDevModeMain.accept(IsolatedDevModeMain.java:448)
        at io.quarkus.deployment.dev.IsolatedDevModeMain.accept(IsolatedDevModeMain.java:61)
        at io.quarkus.bootstrap.app.CuratedApplication.runInCl(CuratedApplication.java:132)
        at io.quarkus.bootstrap.app.CuratedApplication.runInAugmentClassLoader(CuratedApplication.java:89)
        at io.quarkus.deployment.dev.DevModeMain.start(DevModeMain.java:145)
        at io.quarkus.deployment.dev.DevModeMain.main(DevModeMain.java:63)
Caused by: io.quarkus.builder.BuildException: Build failure: Build failed due to errors
        [error]: Build step io.quarkus.arc.deployment.ArcProcessor#validate threw an exception: javax.enterprise.inject.spi.DeploymentException: javax.enterprise.inject.UnsatisfiedResolutionException: Unsatisfied dependency for type com.zu_min.playground.quarkus.extension.runtime.MyService and qualifiers [@Default]    
        - java member: com.zu_min.playground.quarkus.ReactiveGreetingResource#myService
        - declared on CLASS bean [types=[com.zu_min.playground.quarkus.ReactiveGreetingResource, java.lang.Object], qualifiers=[@Default, @Any], target=com.zu_min.playground.quarkus.ReactiveGreetingResource]
        at io.quarkus.arc.processor.BeanDeployment.processErrors(BeanDeployment.java:1099)
        at io.quarkus.arc.processor.BeanDeployment.init(BeanDeployment.java:264)
        at io.quarkus.arc.processor.BeanProcessor.initialize(BeanProcessor.java:129)
        at io.quarkus.arc.deployment.ArcProcessor.validate(ArcProcessor.java:418)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.base/java.lang.reflect.Method.invoke(Method.java:566)
        at io.quarkus.deployment.ExtensionLoader$2.execute(ExtensionLoader.java:820)
        at io.quarkus.builder.BuildContext.run(BuildContext.java:277)
        at org.jboss.threads.ContextHandler$1.runWith(ContextHandler.java:18)
        at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2442)
        at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1476)
        at java.base/java.lang.Thread.run(Thread.java:829)
        at org.jboss.threads.JBossThread.run(JBossThread.java:501)
Caused by: javax.enterprise.inject.UnsatisfiedResolutionException: Unsatisfied dependency for type com.zu_min.playground.quarkus.extension.runtime.MyService and qualifiers [@Default]
        - java member: com.zu_min.playground.quarkus.ReactiveGreetingResource#myService
        - declared on CLASS bean [types=[com.zu_min.playground.quarkus.ReactiveGreetingResource, java.lang.Object], qualifiers=[@Default, @Any], target=com.zu_min.playground.quarkus.ReactiveGreetingResource]
        at io.quarkus.arc.processor.Beans.resolveInjectionPoint(Beans.java:492)
        at io.quarkus.arc.processor.BeanInfo.init(BeanInfo.java:460)
        at io.quarkus.arc.processor.BeanDeployment.init(BeanDeployment.java:252)
        ... 13 more

        at io.quarkus.builder.Execution.run(Execution.java:116)
        at io.quarkus.builder.BuildExecutionBuilder.execute(BuildExecutionBuilder.java:79)
        at io.quarkus.deployment.QuarkusAugmentor.run(QuarkusAugmentor.java:156)
        at io.quarkus.runner.bootstrap.AugmentActionImpl.runAugment(AugmentActionImpl.java:412)
        ... 9 more
Caused by: javax.enterprise.inject.spi.DeploymentException: javax.enterprise.inject.UnsatisfiedResolutionException: Unsatisfied dependency for type com.zu_min.playground.quarkus.extension.runtime.MyService and qualifiers [@Default]
        - java member: com.zu_min.playground.quarkus.ReactiveGreetingResource#myService
        - declared on CLASS bean [types=[com.zu_min.playground.quarkus.ReactiveGreetingResource, java.lang.Object], qualifiers=[@Default, @Any], target=com.zu_min.playground.quarkus.ReactiveGreetingResource]
        at io.quarkus.arc.processor.BeanDeployment.processErrors(BeanDeployment.java:1099)
        at io.quarkus.arc.processor.BeanDeployment.init(BeanDeployment.java:264)
        at io.quarkus.arc.processor.BeanProcessor.initialize(BeanProcessor.java:129)
        at io.quarkus.arc.deployment.ArcProcessor.validate(ArcProcessor.java:418)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.base/java.lang.reflect.Method.invoke(Method.java:566)
        at io.quarkus.deployment.ExtensionLoader$2.execute(ExtensionLoader.java:820)
        at io.quarkus.builder.BuildContext.run(BuildContext.java:277)
        at org.jboss.threads.ContextHandler$1.runWith(ContextHandler.java:18)
        at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2442)
        at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1476)
        at java.base/java.lang.Thread.run(Thread.java:829)
        at org.jboss.threads.JBossThread.run(JBossThread.java:501)
Caused by: javax.enterprise.inject.UnsatisfiedResolutionException: Unsatisfied dependency for type com.zu_min.playground.quarkus.extension.runtime.MyService and qualifiers [@Default]
        - java member: com.zu_min.playground.quarkus.ReactiveGreetingResource#myService
        - declared on CLASS bean [types=[com.zu_min.playground.quarkus.ReactiveGreetingResource, java.lang.Object], qualifiers=[@Default, @Any], target=com.zu_min.playground.quarkus.ReactiveGreetingResource]
        at io.quarkus.arc.processor.Beans.resolveInjectionPoint(Beans.java:492)
        at io.quarkus.arc.processor.BeanInfo.init(BeanInfo.java:460)
        at io.quarkus.arc.processor.BeanDeployment.init(BeanDeployment.java:252)
        ... 13 more

Quarkus のドキュメントを見たところ、 BuildItem を使って Bean を登録するとよいようです。以下のコードを拡張機能の deployment プロジェクトへ追加します。今回は AdditionalBeanBuildItem という BuildItem を使用します。

class ExtensionProcessor {

    private static final String FEATURE = "my-extension";

    @BuildStep
    FeatureBuildItem feature() {
        return new FeatureBuildItem(FEATURE);
    }

    @BuildStep
    void additionalBean(BuildProducer<AdditionalBeanBuildItem> beans) {
        beans.produce(AdditionalBeanBuildItem.builder().addBeanClasses(MyService.class).build());
    }
}

上記を入れると正常に起動しました。


何が起きているか確認するために、Dev UI を確認してみます。Quarkus アプリケーションが起動した状態で以下のページにアクセスすると Bean の一覧を確認できます。

使用中の Bean 一覧
http://localhost:8080/q/dev/io.quarkus.quarkus-arc/beans

削除された Bean 一覧
http://localhost:8080/q/dev/io.quarkus.quarkus-arc/removed-beans

BuildItem なしの場合は一覧に表示されませんでした。(長いのでスクリーンショットは省略します)

以下は BuildItem ありかつ ReactiveGreetingResource なしの状態です。削除された Bean 一覧に出てきます。

以下は BuildItem ありの場合かつ ReactiveGreetingResource ありの場合です。使用中の Bean 一覧に出てきます。

Quarkus ではビルド時に未使用の Bean を削除しますが、拡張機能の場合はそもそも BuildItem を使用しないと認識すらされないようです。

ちなみに、認識させる且つ削除されたくない場合は AdditionalBeanBuildItem.unremovableOf(beanClass) を使用できるようです。が、使っていないのに削除されたくない状況を想像できないです。奥はまだまだ深そうです。

フィルター(Reactive)

HTTP の通信の前処理を記述するフィルターを書いてみます。

import org.jboss.resteasy.reactive.server.ServerRequestFilter;

public class ReactiveRequestFilter {
    private static final Logger log = Logger.getLogger(ReactiveRequestFilter.class);
    
    @ServerRequestFilter
    public void getFilter(ContainerRequestContext ctx) {
        log.info(ctx.getUriInfo().getRequestUri().toString());
    }
}

この場合は CustomContainerRequestFilterBuildItem という BuildItem を使用します。

class ExtensionProcessor {

    // ...

    @BuildStep
    void filter(BuildProducer<CustomContainerRequestFilterBuildItem> filters) {
        filters.produce(new CustomContainerRequestFilterBuildItem(ReactiveRequestFilter.class.getName()));
    }
}

フィルター(Classic)

Reactive じゃない方のフィルターも書いてみます。

import javax.ws.rs.ext.Provider;

@Provider
public class ClassicRequestFilter  implements ContainerRequestFilter {
    private static final Logger log = Logger.getLogger(ClassicRequestFilter.class);

    @Override
    public void filter(ContainerRequestContext requestContext) throws IOException {
        log.info(requestContext.getUriInfo().getRequestUri().toString());
    }
    
}

この場合は ResteasyJaxrsProviderBuildItem という BuildItem を使います。

class ExtensionClassicProcessor {

    // ...

    @BuildStep
    void filter(BuildProducer<ResteasyJaxrsProviderBuildItem> filters) {
        filters.produce(new ResteasyJaxrsProviderBuildItem(ClassicRequestFilter.class.getName()));
    }
}

JPA Entity (Hibernate) の場合

拡張側にこのように Entity を書き、

@Entity
public class Fruit {
    @Id
    public Long id;

    public String name;
}

アプリケーション側で利用しようとすると

@Path("fruit")
public class FruitResource {
    @Inject
    Mutiny.Session session;
    
    @GET
    public Uni<Fruit> getByEntityManager(@RestQuery Long id) {
        return session.createQuery("from Fruit where id = :id", Fruit.class).setParameter("id", id).getSingleResultOrNull();
    }
}

以下のようなエラーになります。

2021-07-10 18:00:00,995 ERROR [org.jbo.res.rea.com.cor.AbstractResteasyReactiveContext] (vert.x-eventloop-thread-9) Request failed: javax.persistence.PersistenceException: org.hibernate.HibernateException: java.lang.IllegalArgumentException: org.hibernate.hql.internal.ast.QuerySyntaxException: Fruit is not mapped [from Fruit where id = :id]
        at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:154)
        at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:181)
        at org.hibernate.reactive.session.impl.ReactiveExceptionConverter.convert(ReactiveExceptionConverter.java:31)
        at org.hibernate.reactive.session.impl.ReactiveSessionImpl.createReactiveQuery(ReactiveSessionImpl.java:365)
        at org.hibernate.reactive.mutiny.impl.MutinySessionImpl.createQuery(MutinySessionImpl.java:195)
        at io.quarkus.hibernate.reactive.runtime.ReactiveSessionProducer_ProducerMethod_createMutinySession_1321d110ee9e92bda147899150401e0a136779c7_ClientProxy.createQuery(ReactiveSessionProducer_ProducerMethod_createMutinySession_1321d110ee9e92bda147899150401e0a136779c7_ClientProxy.zig:966)
        at com.zu_min.playground.quarkus.FruitResource.getByEntityManager(FruitResource.java:27)
        at com.zu_min.playground.quarkus.FruitResource_Subclass.getByEntityManager$$superforward1(FruitResource_Subclass.zig:137)
        at com.zu_min.playground.quarkus.FruitResource_Subclass$$function$$2.apply(FruitResource_Subclass$$function$$2.zig:33)
        at io.quarkus.arc.impl.AroundInvokeInvocationContext.proceed(AroundInvokeInvocationContext.java:54)
        at io.quarkus.arc.runtime.devconsole.InvocationInterceptor.proceed(InvocationInterceptor.java:62)
        at io.quarkus.arc.runtime.devconsole.InvocationInterceptor.monitor(InvocationInterceptor.java:49)
        at io.quarkus.arc.runtime.devconsole.InvocationInterceptor_Bean.intercept(InvocationInterceptor_Bean.zig:521)
        at io.quarkus.arc.impl.InterceptorInvocation.invoke(InterceptorInvocation.java:41)
        at io.quarkus.arc.impl.AroundInvokeInvocationContext.perform(AroundInvokeInvocationContext.java:41)
        at io.quarkus.arc.impl.InvocationContexts.performAroundInvoke(InvocationContexts.java:32)
        at com.zu_min.playground.quarkus.FruitResource_Subclass.getByEntityManager(FruitResource_Subclass.zig:293)
        at com.zu_min.playground.quarkus.FruitResource$quarkusrestinvoker$getByEntityManager_2ed24e611e99273fceab29de5cf56b910e31f90d.invoke(FruitResource$quarkusrestinvoker$getByEntityManager_2ed24e611e99273fceab29de5cf56b910e31f90d.zig:39)
        at org.jboss.resteasy.reactive.server.handlers.InvocationHandler.handle(InvocationHandler.java:29)
        at org.jboss.resteasy.reactive.server.handlers.InvocationHandler.handle(InvocationHandler.java:7)
        at org.jboss.resteasy.reactive.common.core.AbstractResteasyReactiveContext.run(AbstractResteasyReactiveContext.java:132)
        at org.jboss.resteasy.reactive.server.handlers.RestInitialHandler.beginProcessing(RestInitialHandler.java:47)
        at org.jboss.resteasy.reactive.server.vertx.ResteasyReactiveVertxHandler.handle(ResteasyReactiveVertxHandler.java:17)
        at org.jboss.resteasy.reactive.server.vertx.ResteasyReactiveVertxHandler.handle(ResteasyReactiveVertxHandler.java:7)
        at io.vertx.ext.web.impl.RouteState.handleContext(RouteState.java:1127)
        at io.vertx.ext.web.impl.RoutingContextImplBase.iterateNext(RoutingContextImplBase.java:151)
        at io.vertx.ext.web.impl.RoutingContextImpl.next(RoutingContextImpl.java:133)
        at io.quarkus.vertx.http.runtime.StaticResourcesRecorder.lambda$start$1(StaticResourcesRecorder.java:65)
        at io.vertx.ext.web.impl.RouteState.handleContext(RouteState.java:1127)
        at io.vertx.ext.web.impl.RoutingContextImplBase.iterateNext(RoutingContextImplBase.java:114)
        at io.vertx.ext.web.impl.RoutingContextImpl.next(RoutingContextImpl.java:133)
        at io.vertx.ext.web.handler.impl.StaticHandlerImpl.lambda$sendStatic$1(StaticHandlerImpl.java:203)
        at io.vertx.core.impl.future.FutureImpl$3.onSuccess(FutureImpl.java:124)
        at io.vertx.core.impl.future.FutureBase.lambda$emitSuccess$0(FutureBase.java:54)
        at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:164)
        at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:472)
        at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:500)
        at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989)
        at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
        at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
        at java.base/java.lang.Thread.run(Thread.java:829)
Caused by: org.hibernate.HibernateException: java.lang.IllegalArgumentException: org.hibernate.hql.internal.ast.QuerySyntaxException: Fruit is not mapped [from Fruit where id = :id]        ... 39 more
Caused by: java.lang.IllegalArgumentException: org.hibernate.hql.internal.ast.QuerySyntaxException: Fruit is not mapped [from Fruit where id = :id]
        at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:138)
        at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:181)
        at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:188)
        at org.hibernate.reactive.session.impl.ReactiveExceptionConverter.convert(ReactiveExceptionConverter.java:33)
        at org.hibernate.reactive.session.impl.ReactiveSessionImpl.createReactiveQuery(ReactiveSessionImpl.java:352)
        at org.hibernate.reactive.session.impl.ReactiveSessionImpl.createReactiveQuery(ReactiveSessionImpl.java:360)
        ... 37 more
Caused by: org.hibernate.hql.internal.ast.QuerySyntaxException: Fruit is not mapped [from Fruit where id = :id]
        at org.hibernate.hql.internal.ast.QuerySyntaxException.generateQueryException(QuerySyntaxException.java:79)
        at org.hibernate.QueryException.wrapWithQueryString(QueryException.java:103)
        at org.hibernate.hql.internal.ast.QueryTranslatorImpl.doCompile(QueryTranslatorImpl.java:220)
        at org.hibernate.hql.internal.ast.QueryTranslatorImpl.compile(QueryTranslatorImpl.java:144)
        at org.hibernate.engine.query.spi.HQLQueryPlan.<init>(HQLQueryPlan.java:113)
        at org.hibernate.engine.query.spi.HQLQueryPlan.<init>(HQLQueryPlan.java:73)
        at org.hibernate.reactive.session.impl.ReactiveHQLQueryPlan.<init>(ReactiveHQLQueryPlan.java:42)
        at org.hibernate.engine.query.spi.QueryPlanCache.getHQLQueryPlan(QueryPlanCache.java:162)
        at org.hibernate.internal.AbstractSharedSessionContract.getQueryPlan(AbstractSharedSessionContract.java:622)
        at org.hibernate.reactive.session.impl.ReactiveSessionImpl.createReactiveQuery(ReactiveSessionImpl.java:345)
        ... 38 more
Caused by: org.hibernate.hql.internal.ast.QuerySyntaxException: Fruit is not mapped
        at org.hibernate.hql.internal.ast.util.SessionFactoryHelper.requireClassPersister(SessionFactoryHelper.java:169)
        at org.hibernate.hql.internal.ast.tree.FromElementFactory.addFromElement(FromElementFactory.java:91)
        at org.hibernate.hql.internal.ast.tree.FromClause.addFromElement(FromClause.java:77)
        at org.hibernate.hql.internal.ast.HqlSqlWalker.createFromElement(HqlSqlWalker.java:333)
        at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.fromElement(HqlSqlBaseWalker.java:3765)
        at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.fromElementList(HqlSqlBaseWalker.java:3654)
        at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.fromClause(HqlSqlBaseWalker.java:737)
        at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.query(HqlSqlBaseWalker.java:593)
        at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.selectStatement(HqlSqlBaseWalker.java:330)
        at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.statement(HqlSqlBaseWalker.java:278)
        at org.hibernate.hql.internal.ast.QueryTranslatorImpl.analyze(QueryTranslatorImpl.java:276)
        at org.hibernate.hql.internal.ast.QueryTranslatorImpl.doCompile(QueryTranslatorImpl.java:192)
        ... 45 more

対処法は deployment プロジェクトに以下を記述します。

class ExtensionProcessor {
    // ...

    @BuildStep
    void entity(BuildProducer<AdditionalJpaModelBuildItem> entities) {
        entities.produce(new AdditionalJpaModelBuildItem(Fruit.class.getName()));
    }
}

Panache の場合

Panache の Entity を拡張機能側に記述し、

@Entity
public class Fruit extends PanacheEntityBase {
    @Id
    public Long id;

    public String name;
}

アプリケーション側に参照するコードを書くと

@Path("fruit")
public class FruitResource {
    @GET
    public Uni<Fruit> get(@RestQuery Long id) {
        return Fruit.findById(id);
    }
}

以下のようなエラーになります。

2021-07-10 18:00:00,995 ERROR [org.jbo.res.rea.com.cor.AbstractResteasyReactiveContext] (vert.x-eventloop-thread-11) Request failed: java.lang.IllegalStateException: This method is normally automatically overridden in subclasses: did you forget to annotate your entity with @Entity?
        at io.quarkus.hibernate.reactive.panache.common.runtime.AbstractJpaOperations.implementationInjectionMissing(AbstractJpaOperations.java:321)
        at io.quarkus.hibernate.reactive.panache.PanacheEntityBase.findById(PanacheEntityBase.java:124)
        at com.zu_min.playground.quarkus.FruitResource.get(FruitResource.java:16)
        at com.zu_min.playground.quarkus.FruitResource_Subclass.get$$superforward1(FruitResource_Subclass.zig:94)
        at com.zu_min.playground.quarkus.FruitResource_Subclass$$function$$1.apply(FruitResource_Subclass$$function$$1.zig:33)
        at io.quarkus.arc.impl.AroundInvokeInvocationContext.proceed(AroundInvokeInvocationContext.java:54)
        at io.quarkus.arc.runtime.devconsole.InvocationInterceptor.proceed(InvocationInterceptor.java:62)
        at io.quarkus.arc.runtime.devconsole.InvocationInterceptor.monitor(InvocationInterceptor.java:49)
        at io.quarkus.arc.runtime.devconsole.InvocationInterceptor_Bean.intercept(InvocationInterceptor_Bean.zig:521)
        at io.quarkus.arc.impl.InterceptorInvocation.invoke(InterceptorInvocation.java:41)
        at io.quarkus.arc.impl.AroundInvokeInvocationContext.perform(AroundInvokeInvocationContext.java:41)
        at io.quarkus.arc.impl.InvocationContexts.performAroundInvoke(InvocationContexts.java:32)
        at com.zu_min.playground.quarkus.FruitResource_Subclass.get(FruitResource_Subclass.zig:158)
        at com.zu_min.playground.quarkus.FruitResource$quarkusrestinvoker$get_781de6896d757b2522adfbcc3f1d563c30f10898.invoke(FruitResource$quarkusrestinvoker$get_781de6896d757b2522adfbcc3f1d563c30f10898.zig:39)
        at org.jboss.resteasy.reactive.server.handlers.InvocationHandler.handle(InvocationHandler.java:29)
        at org.jboss.resteasy.reactive.server.handlers.InvocationHandler.handle(InvocationHandler.java:7)
        at org.jboss.resteasy.reactive.common.core.AbstractResteasyReactiveContext.run(AbstractResteasyReactiveContext.java:132)
        at org.jboss.resteasy.reactive.server.handlers.RestInitialHandler.beginProcessing(RestInitialHandler.java:47)
        at org.jboss.resteasy.reactive.server.vertx.ResteasyReactiveVertxHandler.handle(ResteasyReactiveVertxHandler.java:17)
        at org.jboss.resteasy.reactive.server.vertx.ResteasyReactiveVertxHandler.handle(ResteasyReactiveVertxHandler.java:7)
        at io.vertx.ext.web.impl.RouteState.handleContext(RouteState.java:1127)
        at io.vertx.ext.web.impl.RoutingContextImplBase.iterateNext(RoutingContextImplBase.java:151)
        at io.vertx.ext.web.impl.RoutingContextImpl.next(RoutingContextImpl.java:133)
        at io.quarkus.vertx.http.runtime.StaticResourcesRecorder.lambda$start$1(StaticResourcesRecorder.java:65)
        at io.vertx.ext.web.impl.RouteState.handleContext(RouteState.java:1127)
        at io.vertx.ext.web.impl.RoutingContextImplBase.iterateNext(RoutingContextImplBase.java:114)
        at io.vertx.ext.web.impl.RoutingContextImpl.next(RoutingContextImpl.java:133)
        at io.vertx.ext.web.handler.impl.StaticHandlerImpl.lambda$sendStatic$1(StaticHandlerImpl.java:203)
        at io.vertx.core.impl.future.FutureImpl$3.onSuccess(FutureImpl.java:124)
        at io.vertx.core.impl.future.FutureBase.lambda$emitSuccess$0(FutureBase.java:54)
        at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:164)
        at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:472)
        at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:500)
        at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989)
        at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
        at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
        at java.base/java.lang.Thread.run(Thread.java:829)

こちらは解決できませんでした。。。

ソースを追ってみましたが、今までのケースは jandex の index に登録されていなくても機能を追加できる BuildItem が提供されていますが、 Panache はそれがないように見えます。

8/25 追記:AdditionalIndexedClassesBuildItem を使えば Panache の Entity も拾ってくれるようです。Panache は CombinedIndexBuildItem から Entity を拾っているので、それに登録してくれる AdditionalIndexedClassesBuildItem を使えば登録されます。

class ExtensionProcessor {
    // ...

    @BuildStep
    void entity(BuildProducer<AdditionalIndexedClassesBuildItem> entities) {
        entities.produce(new AdditionalIndexedClassesBuildItem(Fruit.class.getName()));
    }
}

他の方法

ここまで調べてから、そもそもアプリケーションの Bean は普通に Quarkus が拾ってくれるのに、拡張機能の Bean は拾ってくれないのはおかしいなと思いました。

もう一度ドキュメントを見直してみようと思い、 CDI のガイドを見てみると、 Bean の検出方法が一番最初に書いてあります。そこに書かれている jandex の導入方法を試してみると BuildItem なしでも動くようです。

ビルドの速度に影響しそうですが、 jandex を pom に追加する方が楽そうです。または方法が不明なクラスのみ application.properties の quarkus.index-dependency を追加する方法でもよいかもしれません。

参考にした Quarkus のガイド一覧

コメントする

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください