diff --git a/docs/src/main/asciidoc/cdi-reference.adoc b/docs/src/main/asciidoc/cdi-reference.adoc index 4db2098da326c..625ee59faf1f4 100644 --- a/docs/src/main/asciidoc/cdi-reference.adoc +++ b/docs/src/main/asciidoc/cdi-reference.adoc @@ -814,14 +814,49 @@ The extended `BeanConfigurator` accepts either a `io.quarkus.runtime.RuntimeValu [source,java] ---- @BuildStep -@Record(STATIC_INIT) +@Record(STATIC_INIT) <1> SyntheticBeanBuildItem syntheticBean(TestRecorder recorder) { return SyntheticBeanBuildItem.configure(Foo.class).scope(Singleton.class) + .runtimeValue(recorder.createFoo()) <2> + .done(); +} +---- +<1> By default, a synthetic bean is initialized during `STATIC_INIT`. +<2> The bean instance is supplied by a value returned from a recorder method. + +It is possible to mark a synthetic bean to be initialized during `RUNTIME_INIT`: + +.`RUNTIME_INIT` `SyntheticBeanBuildItem` Example +[source,java] +---- +@BuildStep +@Record(RUNTIME_INIT) <1> +SyntheticBeanBuildItem syntheticBean(TestRecorder recorder) { + return SyntheticBeanBuildItem.configure(Foo.class).scope(Singleton.class) + .setRuntimeInit() <2> .runtimeValue(recorder.createFoo()) .done(); } ---- +<1> The recorder must be executed in the `ExecutionTime.RUNTIME_INIT` phase. +<2> The bean instance is initialized during `RUNTIME_INIT`. +[IMPORTANT] +==== +Synthetic bean initialized during `RUNTIME_INIT` must not be accessed during `STATIC_INIT`. `RUNTIME_INIT` build steps that access a runtime-init synthetic bean should consume the `SyntheticBeansRuntimeInitBuildItem`: + +[source,java] +---- +@BuildStep +@Record(RUNTIME_INIT) +@Consume(SyntheticBeansRuntimeInitBuildItem.class) <1> +void accessFoo(TestRecorder recorder) { + recorder.foo(); <2> +} +---- +<1> This build step must be executed after `syntheticBean()` completes. +<2> This recorder method results in an invocation of the `Foo` bean instance. +==== === Annotation Transformations diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/SyntheticBeanBuildItem.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/SyntheticBeanBuildItem.java index e9a9df065be05..ec38029f39620 100644 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/SyntheticBeanBuildItem.java +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/SyntheticBeanBuildItem.java @@ -16,6 +16,7 @@ * Makes it possible to register a synthetic bean whose instance can be easily produced through a recorder. * * @see BeanConfigurator + * @see ExtendedBeanConfigurator#setRuntimeInit() */ public final class SyntheticBeanBuildItem extends MultiBuildItem { @@ -83,9 +84,14 @@ public ExtendedBeanConfigurator runtimeValue(RuntimeValue runtimeValue) { /** * By default, synthetic beans are initialized during {@link ExecutionTime#STATIC_INIT}. It is possible to mark a * synthetic bean to be initialized during {@link ExecutionTime#RUNTIME_INIT}. However, in such case a client that - * attempts to obtain such bean during {@link ExecutionTime#STATIC_INIT} will receive an exception. + * attempts to obtain such bean during {@link ExecutionTime#STATIC_INIT} or before runtime-init synthetic beans are + * initialized will receive an exception. + *

+ * {@link ExecutionTime#RUNTIME_INIT} build steps that access a runtime-init synthetic bean should consume the + * {@link SyntheticBeansRuntimeInitBuildItem}. * * @return self + * @see SyntheticBeansRuntimeInitBuildItem */ public ExtendedBeanConfigurator setRuntimeInit() { this.staticInit = false; diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/SyntheticBeansProcessor.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/SyntheticBeansProcessor.java index 0498d1d218cb6..857bec58f595b 100644 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/SyntheticBeansProcessor.java +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/SyntheticBeansProcessor.java @@ -70,7 +70,7 @@ private void initSyntheticBean(ArcRecorder recorder, Map> su } beanRegistration.getContext().configure(implClazz) .read(bean.configurator()) - .creator(creator(name)) + .creator(creator(name, bean)) .done(); } @@ -79,7 +79,7 @@ private String createName(String beanClass, String qualifiers) { + HashUtil.sha1(qualifiers); } - private Consumer creator(String name) { + private Consumer creator(String name, SyntheticBeanBuildItem bean) { return new Consumer() { @Override public void accept(MethodCreator m) { @@ -90,7 +90,7 @@ public void accept(MethodCreator m) { m.load(name)); // Throw an exception if no supplier is found m.ifNull(supplier).trueBranch().throwException(CreationException.class, - "Synthetic bean instance not initialized yet: " + name); + createMessage(name, bean)); ResultHandle result = m.invokeInterfaceMethod( MethodDescriptor.ofMethod(Supplier.class, "get", Object.class), supplier); @@ -99,4 +99,18 @@ public void accept(MethodCreator m) { }; } + private String createMessage(String name, SyntheticBeanBuildItem bean) { + StringBuilder builder = new StringBuilder(); + builder.append("Synthetic bean instance for "); + builder.append(bean.configurator().getImplClazz()); + builder.append(" not initialized yet: "); + builder.append(name); + if (!bean.isStaticInit()) { + builder.append("\n\t- a synthetic bean initialized during RUNTIME_INIT must not be accessed during STATIC_INIT"); + builder.append( + "\n\t- RUNTIME_INIT build steps that require access to synthetic beans initialized during RUNTIME_INIT should consume the SyntheticBeansRuntimeInitBuildItem"); + } + return builder.toString(); + } + } diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/SyntheticBeansRuntimeInitBuildItem.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/SyntheticBeansRuntimeInitBuildItem.java index 9857f0ce84640..ac094c35ef22f 100644 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/SyntheticBeansRuntimeInitBuildItem.java +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/SyntheticBeansRuntimeInitBuildItem.java @@ -4,6 +4,8 @@ /** * This build item should be consumed by build steps that require RUNTIME_INIT synthetic beans to be initialized. + * + * @see SyntheticBeanBuildItem */ public final class SyntheticBeansRuntimeInitBuildItem extends EmptyBuildItem {