引用 @ConfigMapping 对象的 CDI 单例的 JAR-RS 过滤器注入在 Quarkus 中失败

JAR-RS Filter injection of a CDI Singleton that reference a @ConfigMapping object fails in Quarkus

我尝试使用最新的 Qarkus 配置工具:@ConfigMapping 所以我定义了一个 GreetingConfig 对象:

@ConfigMapping(prefix = "org.acme.greeting")
public interface GreetingConfig {
    String prefix();
}

该配置被注入到 CDI 单例中:

@Singleton
public class GreetingService {

    @Inject GreetingConfig config;

    public String greet(String user) {
        return config.prefix().concat(" - Hello ").concat(user).concat(" !!");
    }
}

单例被注入到 JAX-RS 过滤器和 JAX-RS 资源中

@Provider
public class GreetingFilter implements ContainerRequestFilter {

    private static final Logger LOGGER = Logger.getLogger(GreetingFilter.class.getName());

    @Inject GreetingService service;

    @Override
    public void filter(ContainerRequestContext requestContext) throws IOException {
        LOGGER.log(Level.INFO, service.greet("Quarkus"));
    }
}
@Path("/hello")
public class GreetingResource {

    @Inject GreetingService service;

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String hello(@QueryParam("user") @DefaultValue("Quarkus") String user) {
        return service.greet(user);
    }
}

application.properties 文件还包含:

org.acme.greeting.prefix=ACME Greetings

启动时应用程序崩溃:

java.lang.RuntimeException: java.lang.ExceptionInInitializerError

    at io.quarkus.test.junit.QuarkusTestExtension.throwBootFailureException(QuarkusTestExtension.java:712)
    at io.quarkus.test.junit.QuarkusTestExtension.interceptTestClassConstructor(QuarkusTestExtension.java:785)
    at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke[=15=](ExecutableInvoker.java:105)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
    at org.junit.jupiter.api.extension.InvocationInterceptor.interceptTestClassConstructor(InvocationInterceptor.java:72)
    at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke[=15=](ExecutableInvoker.java:105)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37)
    at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:104)
    at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:77)
    at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.invokeTestClassConstructor(ClassBasedTestDescriptor.java:342)
    at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.instantiateTestClass(ClassBasedTestDescriptor.java:289)
    at org.junit.jupiter.engine.descriptor.ClassTestDescriptor.instantiateTestClass(ClassTestDescriptor.java:79)
    at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.instantiateAndPostProcessTestInstance(ClassBasedTestDescriptor.java:267)
    at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$testInstancesProvider(ClassBasedTestDescriptor.java:259)
    at java.base/java.util.Optional.orElseGet(Optional.java:369)
    at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$testInstancesProvider(ClassBasedTestDescriptor.java:258)
    at org.junit.jupiter.engine.execution.TestInstancesProvider.getTestInstances(TestInstancesProvider.java:31)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$prepare[=15=](TestMethodTestDescriptor.java:101)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.prepare(TestMethodTestDescriptor.java:100)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.prepare(TestMethodTestDescriptor.java:65)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$prepare(NodeTestTask.java:111)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.prepare(NodeTestTask.java:111)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:79)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1541)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively(NodeTestTask.java:143)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively(NodeTestTask.java:129)
    at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively(NodeTestTask.java:127)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1541)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively(NodeTestTask.java:143)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively(NodeTestTask.java:129)
    at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively(NodeTestTask.java:127)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:108)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:88)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute[=15=](EngineExecutionOrchestrator.java:54)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:67)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:52)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:96)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:75)
    at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:71)
    at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
    at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:235)
    at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:54)
Caused by: java.lang.ExceptionInInitializerError
    at java.base/java.lang.Class.forName0(Native Method)
    at java.base/java.lang.Class.forName(Class.java:398)
    at io.quarkus.runner.bootstrap.StartupActionImpl.run(StartupActionImpl.java:165)
    at io.quarkus.test.junit.QuarkusTestExtension.doJavaStart(QuarkusTestExtension.java:380)
    at io.quarkus.test.junit.QuarkusTestExtension.ensureStarted(QuarkusTestExtension.java:680)
    at io.quarkus.test.junit.QuarkusTestExtension.beforeAll(QuarkusTestExtension.java:727)
    at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$invokeBeforeAllCallbacks(ClassBasedTestDescriptor.java:368)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.invokeBeforeAllCallbacks(ClassBasedTestDescriptor.java:368)
    at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.before(ClassBasedTestDescriptor.java:192)
    at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.before(ClassBasedTestDescriptor.java:78)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively(NodeTestTask.java:136)
    ... 31 more
Caused by: java.lang.RuntimeException: Failed to start quarkus
    at io.quarkus.runner.ApplicationImpl.<clinit>(ApplicationImpl.zig:225)
    ... 43 more
Caused by: java.lang.RuntimeException: Error injecting org.acme.GreetingService org.acme.api.filter.GreetingFilter.service
    at org.acme.api.filter.GreetingFilter_Bean.create(GreetingFilter_Bean.zig:148)
    at org.acme.api.filter.GreetingFilter_Bean.create(GreetingFilter_Bean.zig:171)
    at io.quarkus.arc.impl.AbstractSharedContext.createInstanceHandle(AbstractSharedContext.java:96)
    at io.quarkus.arc.impl.AbstractSharedContext.get(AbstractSharedContext.java:29)
    at io.quarkus.arc.impl.AbstractSharedContext.get(AbstractSharedContext.java:26)
    at io.quarkus.arc.impl.LazyValue.get(LazyValue.java:26)
    at io.quarkus.arc.impl.ComputingCache.computeIfAbsent(ComputingCache.java:69)
    at io.quarkus.arc.impl.AbstractSharedContext.get(AbstractSharedContext.java:26)
    at org.acme.api.filter.GreetingFilter_Bean.get(GreetingFilter_Bean.zig:203)
    at org.acme.api.filter.GreetingFilter_Bean.get(GreetingFilter_Bean.zig:219)
    at io.quarkus.arc.impl.ArcContainerImpl.beanInstanceHandle(ArcContainerImpl.java:433)
    at io.quarkus.arc.impl.ArcContainerImpl.beanInstanceHandle(ArcContainerImpl.java:446)
    at io.quarkus.arc.impl.ArcContainerImpl.get(ArcContainerImpl.java:269)
    at io.quarkus.arc.impl.ArcContainerImpl.get(ArcContainerImpl.java:266)
    at io.quarkus.resteasy.common.runtime.QuarkusConstructorInjector.construct(QuarkusConstructorInjector.java:39)
    at org.jboss.resteasy.core.providerfactory.ResteasyProviderFactoryImpl.injectedInstance(ResteasyProviderFactoryImpl.java:1399)
    at org.jboss.resteasy.core.interception.jaxrs.JaxrsInterceptorRegistryImpl$AbstractInterceptorFactory.createInterceptor(JaxrsInterceptorRegistryImpl.java:150)
    at org.jboss.resteasy.core.interception.jaxrs.JaxrsInterceptorRegistryImpl$OnDemandInterceptorFactory.initialize(JaxrsInterceptorRegistryImpl.java:168)
    at org.jboss.resteasy.core.interception.jaxrs.JaxrsInterceptorRegistryImpl$OnDemandInterceptorFactory.checkInitialize(JaxrsInterceptorRegistryImpl.java:183)
    at org.jboss.resteasy.core.interception.jaxrs.JaxrsInterceptorRegistryImpl$OnDemandInterceptorFactory.getInterceptor(JaxrsInterceptorRegistryImpl.java:193)
    at org.jboss.resteasy.core.interception.jaxrs.JaxrsInterceptorRegistryImpl$AbstractInterceptorFactory.postMatch(JaxrsInterceptorRegistryImpl.java:131)
    at org.jboss.resteasy.core.interception.jaxrs.JaxrsInterceptorRegistryImpl.postMatch(JaxrsInterceptorRegistryImpl.java:288)
    at org.jboss.resteasy.core.interception.jaxrs.ContainerRequestFilterRegistryImpl.postMatch(ContainerRequestFilterRegistryImpl.java:30)
    at org.jboss.resteasy.core.interception.jaxrs.ContainerRequestFilterRegistryImpl.postMatch(ContainerRequestFilterRegistryImpl.java:12)
    at org.jboss.resteasy.core.ResourceMethodInvoker.<init>(ResourceMethodInvoker.java:142)
    at org.jboss.resteasy.core.ResourceMethodRegistry.processMethod(ResourceMethodRegistry.java:381)
    at org.jboss.resteasy.core.ResourceMethodRegistry.register(ResourceMethodRegistry.java:308)
    at org.jboss.resteasy.core.ResourceMethodRegistry.addResourceFactory(ResourceMethodRegistry.java:259)
    at org.jboss.resteasy.core.ResourceMethodRegistry.addResourceFactory(ResourceMethodRegistry.java:227)
    at org.jboss.resteasy.core.ResourceMethodRegistry.addResourceFactory(ResourceMethodRegistry.java:208)
    at org.jboss.resteasy.core.ResourceMethodRegistry.addResourceFactory(ResourceMethodRegistry.java:192)
    at org.jboss.resteasy.core.ResourceMethodRegistry.addResourceFactory(ResourceMethodRegistry.java:175)
    at org.jboss.resteasy.core.ResourceMethodRegistry.addPerRequestResource(ResourceMethodRegistry.java:87)
    at org.jboss.resteasy.core.ResteasyDeploymentImpl.registerResources(ResteasyDeploymentImpl.java:518)
    at org.jboss.resteasy.core.ResteasyDeploymentImpl.registration(ResteasyDeploymentImpl.java:475)
    at org.jboss.resteasy.core.ResteasyDeploymentImpl.startInternal(ResteasyDeploymentImpl.java:164)
    at org.jboss.resteasy.core.ResteasyDeploymentImpl.start(ResteasyDeploymentImpl.java:121)
    at io.quarkus.resteasy.runtime.standalone.ResteasyStandaloneRecorder.staticInit(ResteasyStandaloneRecorder.java:37)
    at io.quarkus.deployment.steps.ResteasyStandaloneBuildStep$staticInit-210558872.deploy_0(ResteasyStandaloneBuildStep$staticInit-210558872.zig:897)
    at io.quarkus.deployment.steps.ResteasyStandaloneBuildStep$staticInit-210558872.deploy(ResteasyStandaloneBuildStep$staticInit-210558872.zig:40)
    at io.quarkus.runner.ApplicationImpl.<clinit>(ApplicationImpl.zig:205)
    ... 43 more
Caused by: java.lang.RuntimeException: Error injecting org.acme.config.GreetingConfig org.acme.GreetingService.config
    at org.acme.GreetingService_Bean.create(GreetingService_Bean.zig:199)
    at org.acme.GreetingService_Bean.create(GreetingService_Bean.zig:222)
    at io.quarkus.arc.impl.AbstractSharedContext.createInstanceHandle(AbstractSharedContext.java:96)
    at io.quarkus.arc.impl.AbstractSharedContext.get(AbstractSharedContext.java:29)
    at io.quarkus.arc.impl.AbstractSharedContext.get(AbstractSharedContext.java:26)
    at io.quarkus.arc.impl.LazyValue.get(LazyValue.java:26)
    at io.quarkus.arc.impl.ComputingCache.computeIfAbsent(ComputingCache.java:69)
    at io.quarkus.arc.impl.AbstractSharedContext.get(AbstractSharedContext.java:26)
    at org.acme.GreetingService_Bean.get(GreetingService_Bean.zig:254)
    at org.acme.GreetingService_Bean.get(GreetingService_Bean.zig:270)
    at org.acme.api.filter.GreetingFilter_Bean.create(GreetingFilter_Bean.zig:131)
    ... 83 more
Caused by: java.util.NoSuchElementException: SRCFG00027: Could not find a mapping for org.acme.config.GreetingConfig
    at io.smallrye.config.ConfigMappings.getConfigMapping(ConfigMappings.java:73)
    at io.smallrye.config.SmallRyeConfig.getConfigMapping(SmallRyeConfig.java:423)
    at io.quarkus.arc.runtime.ConfigMappingCreator.create(ConfigMappingCreator.java:28)
    at org.acme.config.GreetingConfig_04302c935a6cb43e5f29ca4271833ab99b620b3b_Synthetic_Bean.create(GreetingConfig_04302c935a6cb43e5f29ca4271833ab99b620b3b_Synthetic_Bean.zig:128)
    at org.acme.config.GreetingConfig_04302c935a6cb43e5f29ca4271833ab99b620b3b_Synthetic_Bean.get(GreetingConfig_04302c935a6cb43e5f29ca4271833ab99b620b3b_Synthetic_Bean.zig:159)
    at org.acme.config.GreetingConfig_04302c935a6cb43e5f29ca4271833ab99b620b3b_Synthetic_Bean.get(GreetingConfig_04302c935a6cb43e5f29ca4271833ab99b620b3b_Synthetic_Bean.zig:182)
    at io.quarkus.arc.impl.CurrentInjectionPointProvider.get(CurrentInjectionPointProvider.java:52)
    at org.acme.GreetingService_Bean.create(GreetingService_Bean.zig:182)
    ... 93 more

无论服务是什么 bean 类型(@ApplicationScope 或 @Singleton),JAR-RS 资源中的注入都有效,但在过滤器中,只能注入 @ApplicationScope bean 以避免运行时异常...

我们不能在 JAX-RS @Provider(Filter 或 BodyWritter)中注入带有配置映射的伪作用域 bean,这是一个错误还是正常现象?

不幸的是,这不是错误。这是 jax-rs 的一个特性:-)

  1. 我检查了您的配置 class,它适用于 CDI bean
  2. 我检查了你的确切代码,它因同样的异常而失败。
  3. 我仔细研究了文档,没有找到关于将 CDI 注入提供程序的解释。
  4. 此外,我发现许多供应商根本没有使用 CDI。
  5. 最后,我了解到即使 bean 还不存在,也可以将实例注入提供者。

一种解决方案是将您的服务放入实例class。

@Inject Instance<GreetingService> service;

@Override
public void filter(ContainerRequestContext requestContext) throws IOException {
   LOGGER.log(Level.INFO, service.get().greet("Quarkus"));
}

它将注入委托给后期阶段(一种运行时惰性注入)。

我在这里找到了更多信息 https://javaee.github.io/javaee-spec/javadocs/javax/enterprise/inject/Instance.html

基于 Marek 的回答(感谢您的解决方案;-))还有一个选项可以在服务 Bean 中的配置对象上使用基于实例的注入,并在提供程序中保留经典注入...

也许让bean知道它的配置注入并处理实例对象本身是一个更好的设计方法,顺便说一句,最初的想法是一样的,避免运行时问题,而配置对象在提供者不可用使用实例的实例化时间。

@Singleton
public class GreetingService {

    @Inject Instance<GreetingConfig> config;

    public String greet(String user) {
        return config.get().prefix().concat(" - Hello ").concat(user).concat(" !!");
    }
}