Skip to content

Commit

Permalink
Merge pull request #11746 from mkouba/issue-11337
Browse files Browse the repository at this point in the history
RUNTIME_INIT synthetic beans - improve docs and error message
  • Loading branch information
gsmet authored Aug 31, 2020
2 parents e5b67d7 + cae3494 commit d0fe9af
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 5 deletions.
37 changes: 36 additions & 1 deletion docs/src/main/asciidoc/cdi-reference.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down Expand Up @@ -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.
* <p>
* {@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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ private void initSyntheticBean(ArcRecorder recorder, Map<String, Supplier<?>> su
}
beanRegistration.getContext().configure(implClazz)
.read(bean.configurator())
.creator(creator(name))
.creator(creator(name, bean))
.done();
}

Expand All @@ -79,7 +79,7 @@ private String createName(String beanClass, String qualifiers) {
+ HashUtil.sha1(qualifiers);
}

private Consumer<MethodCreator> creator(String name) {
private Consumer<MethodCreator> creator(String name, SyntheticBeanBuildItem bean) {
return new Consumer<MethodCreator>() {
@Override
public void accept(MethodCreator m) {
Expand All @@ -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);
Expand All @@ -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();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down

0 comments on commit d0fe9af

Please sign in to comment.