From 3b7ae7bce8bde886882945e7fbc13fe1b654fc30 Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Tue, 14 Jul 2020 17:33:53 +0200 Subject: [PATCH 1/2] Document SyntheticBeanBuildItem - resolves #10691 --- docs/src/main/asciidoc/cdi-reference.adoc | 37 ++++++++++++++++++++--- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/docs/src/main/asciidoc/cdi-reference.adoc b/docs/src/main/asciidoc/cdi-reference.adoc index 451c51c4b20c4..3d1d92b6b9766 100644 --- a/docs/src/main/asciidoc/cdi-reference.adoc +++ b/docs/src/main/asciidoc/cdi-reference.adoc @@ -721,10 +721,17 @@ NOTE: A bean registration that is a result of an `AdditionalBeanBuildItem` is re === Synthetic Beans -Sometimes it is very useful to register a synthetic bean, i.e. a bean that doesn't need to have a corresponding java class. -In CDI, this could be achieved using `AfterBeanDiscovery.addBean()` methods. -In Quarkus, we produce a `BeanRegistrarBuildItem` and leverage the `io.quarkus.arc.processor.BeanConfigurator` API to build a synthetic bean definition. +Sometimes it is very useful to be able to register a synthetic bean. +Bean attributes of a synthetic bean are not derived from a java class, method or field. +Instead, the attributes are specified by an extension. +In CDI, this could be achieved using the `AfterBeanDiscovery.addBean()` methods. +In Quarkus, there are three ways to register a synthetic bean. +==== `BeanRegistrarBuildItem` + +A build step can produce a `BeanRegistrarBuildItem` and leverage the `io.quarkus.arc.processor.BeanConfigurator` API to build a synthetic bean definition. + +.`BeanRegistrarBuildItem` Example [source,java] ---- @BuildStep @@ -743,9 +750,12 @@ NOTE: The output of a `BeanConfigurator` is recorded as bytecode. Therefore ther TIP: You can easily filter all class-based beans via the convenient `BeanStream` returned from the `RegistrationContext.beans()` method. -If an extension needs to produce other build items during the "bean registration" phase it should use the `BeanRegistrationPhaseBuildItem` instead. +==== `BeanRegistrationPhaseBuildItem` + +If a build step *needs to produce other build items during the registration* it should use the `BeanRegistrationPhaseBuildItem`. The reason is that injected objects are only valid during a `@BuildStep` method invocation. +.`BeanRegistrationPhaseBuildItem` Example [source,java] ---- @BuildStep @@ -757,7 +767,24 @@ void syntheticBean(BeanRegistrationPhaseBuildItem beanRegistrationPhase, } ---- -NOTE: See `BeanRegistrationPhaseBuildItem` javadoc for more information. +NOTE: See the `BeanRegistrationPhaseBuildItem` javadoc for more information. + +==== `SyntheticBeanBuildItem` + +Finally, a build step can produce a `SyntheticBeanBuildItem` to register a synthetic bean whose instance can be easily *produced through a <>*. +The extended `BeanConfigurator` accepts either a `io.quarkus.runtime.RuntimeValue` or a `java.util.function.Supplier`. + +.`SyntheticBeanBuildItem` Example +[source,java] +---- +@BuildStep +@Record(STATIC_INIT) +SyntheticBeanBuildItem syntheticBean(TestRecorder recorder) { + return SyntheticBeanBuildItem.configure(Foo.class).scope(Singleton.class) + .runtimeValue(recorder.createFoo()) + .done(); +} +---- === Annotation Transformations From f12c24e4be88fd3d7d077ed9087fe8ffecc83f56 Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Wed, 15 Jul 2020 10:34:05 +0200 Subject: [PATCH 2/2] Docs - update the "Expose your components via CDI" section --- docs/src/main/asciidoc/cdi-reference.adoc | 2 + .../src/main/asciidoc/writing-extensions.adoc | 134 +++++++++++++++++- 2 files changed, 131 insertions(+), 5 deletions(-) diff --git a/docs/src/main/asciidoc/cdi-reference.adoc b/docs/src/main/asciidoc/cdi-reference.adoc index 3d1d92b6b9766..55e6520928f8e 100644 --- a/docs/src/main/asciidoc/cdi-reference.adoc +++ b/docs/src/main/asciidoc/cdi-reference.adoc @@ -702,6 +702,7 @@ void setupResourceInjection(BuildProducer resourceA } ---- +[[additional_beans]] === Additional Beans `AdditionalBeanBuildItem` is used to specify additional bean classes to be analyzed during discovery. @@ -719,6 +720,7 @@ List additionalBeans() { NOTE: A bean registration that is a result of an `AdditionalBeanBuildItem` is removable by default. See also <>. +[[synthetic_beans]] === Synthetic Beans Sometimes it is very useful to be able to register a synthetic bean. diff --git a/docs/src/main/asciidoc/writing-extensions.adoc b/docs/src/main/asciidoc/writing-extensions.adoc index 58b33e52d6b57..bccea5b0a358f 100755 --- a/docs/src/main/asciidoc/writing-extensions.adoc +++ b/docs/src/main/asciidoc/writing-extensions.adoc @@ -80,14 +80,94 @@ TODO: Describe where Substitutions and recorders should live === Expose your components via CDI -Since CDI is the central programming model when it comes to component composition, frameworks should expose producers that are easily consumable by user applications. + +Since CDI is the central programming model when it comes to component composition, frameworks and extensions should expose their components as beans that are easily consumable by user applications. For example, Hibernate ORM exposes `EntityManagerFactory` and `EntityManager` beans, the connection pool exposes `DataSource` beans etc. -An extension must generate these bean definitions at build time. +Extensions must register these bean definitions at build time. -A very useful pattern of creating such beans but also giving application code the ability to easily override some of the beans with custom implementations, is to use -the `@DefaultBean` that Quarkus provides. This is best explained with an example. +==== Beans backed by classes + +An extension can produce an <> to instruct the container to read a bean definition from a class as if it was part of the original application: + +.Bean Class Registered by `AdditionalBeanBuildItem` +[source%nowrap,java] +---- +@ApplicationScoped <1> +public class Echo { + + public String echo(String val) { + return val; + } +} +---- +<1> If a bean registered by an `AdditionalBeanBuildItem` does not specify a scope then `@Dependent` is assumed. + +All other beans can inject such a bean: + +.Bean Injecting a Bean Produced by an `AdditionalBeanBuildItem` +[source%nowrap,java] +---- +@Path("/hello") +public class ExampleResource { + + @Inject + Echo echo; -==== Example CDI configuration using @DefaultBean + @GET + @Produces(MediaType.TEXT_PLAIN) + public String hello(String foo) { + return echo.echo(foo); + } +} +---- + +And vice versa - the extension bean can inject application beans and beans provided by other extensions: + +.Extension Bean Injection Example +[source%nowrap,java] +---- +@ApplicationScoped +public class Echo { + + @Inject + DataSource dataSource; <1> + + @Inject + Instance> listsOfStrings; <2> + + //... +} +---- +<1> Inject a bean provided by other extension. +<2> Inject all beans matching the type `List`. + +[[bean_init]] +==== Bean initialization + +Some components may require additional initialization based on information collected during augmentation. +The most straightforward solution is to obtain a bean instance and call a method directly from a build step. +However, it is _illegal_ to obtain a bean instance during the augmentation phase. +The reason is that the CDI container is not started yet. +It's started during the <>. + +TIP: `BUILD_AND_RUN_TIME_FIXED` and `RUN_TIME` config roots can be injected in any bean. `RUN_TIME` config roots should only be injected after the bootstrap though. + +It is possible to invoke a bean method from a <> though. +If you need to access a bean in a `@Record(STATIC_INIT)` build step then is must either depend on the `BeanContainerBuildItem` or wrap the logic in a `BeanContainerListenerBuildItem`. +The reason is simple - we need to make sure the CDI container is fully initialized and started. +However, it is safe to expect that the CDI container is fully initialized and running in a `@Record(RUNTIME_INIT)` build step. +You can obtain a reference to the container via `CDI.current()` or Quarkus-specific `Arc.container()`. + +IMPORTANT: Don't forget to make sure the bean state guarantees the visibility, e.g. via the `volatile` keyword. + +NOTE: There is one significant drawback of this "late initialization" approach. +An _uninitialized_ bean may be accessed by other extensions or application components that are instantiated during bootstrap. +We'll cover a more robust solution in the <>. + +==== Default beans + +A very useful pattern of creating such beans but also giving application code the ability to easily override some of the beans with custom implementations, is to use +the `@DefaultBean` that Quarkus provides. +This is best explained with an example. Let us assume that the Quarkus extension needs to provide a `Tracer` bean which application code is meant to inject into its own beans. @@ -180,6 +260,50 @@ class MyParser extends Parser { NOTE: CDI alternatives are only considered during injection and type-safe resolution. For example the default implementation would still receive observer notifications. +[[synthetic_beans]] +==== Synthetic beans + +Sometimes it is very useful to be able to register a synthetic bean. +Bean attributes of a synthetic bean are not derived from a java class, method or field. +Instead, the attributes are specified by an extension. + +NOTE: Since the CDI container does not control the instantiation of a synthetic bean the dependency injection and other services (such as interceptors) are not supported. +In other words, it's up to the extension to provide all required services to a synthetic bean instance. + +There are several ways to register a <> in Quarkus. +In this chapter, we will cover a use case that can be used to initialize extension beans in a safe manner (compared to <>). + +The `SyntheticBeanBuildItem` can be used to register a synthetic bean: + +* whose instance can be easily produced through a <>, +* to provide a "context" bean that holds all the information collected during augmentation so that the real components do not need any "late initialization" because they can inject the context bean directly. + +.Instance Produced Through Recorder +[source%nowrap,java] +---- +@BuildStep +@Record(STATIC_INIT) +SyntheticBeanBuildItem syntheticBean(TestRecorder recorder) { + return SyntheticBeanBuildItem.configure(Foo.class).scope(Singleton.class) + .runtimeValue(recorder.createFoo("parameters are recorder in the bytecode")) <1> + .done(); +} +---- +<1> The string value is recorded in the bytecode and used to initialize the instance of `Foo`. + +."Context" Holder +[source%nowrap,java] +---- +@BuildStep +@Record(STATIC_INIT) +SyntheticBeanBuildItem syntheticBean(TestRecorder recorder) { + return SyntheticBeanBuildItem.configure(TestContext.class).scope(Singleton.class) + .runtimeValue(recorder.createContext("parameters are recorder in the bytecode")) <1> + .done(); +} +---- +<1> The "real" components can inject the `TestContext` directly. + === Some types of extensions There exist multiple stereotypes of extension, let's list a few.