diff --git a/docs/asciidoc/dependency-injection.adoc b/docs/asciidoc/dependency-injection.adoc index 93b3f01ae0..af2d8da9d1 100644 --- a/docs/asciidoc/dependency-injection.adoc +++ b/docs/asciidoc/dependency-injection.adoc @@ -1,6 +1,6 @@ == Dependency Injection -include::di-avaje.adoc[] +include::modules/avaje-inject.adoc[] include::di-dagger.adoc[] diff --git a/docs/asciidoc/di-avaje.adoc b/docs/asciidoc/di-avaje.adoc deleted file mode 100644 index 24823604b5..0000000000 --- a/docs/asciidoc/di-avaje.adoc +++ /dev/null @@ -1,136 +0,0 @@ -=== Avaje Inject - -1) Add Avaje Inject to your project - -[dependency, groupId="io.avaje", artifactId="avaje-inject", version="9.8"] -. - -2) Configure annotation processor - -.Maven -[source, xml, role = "primary"] ----- - - - - org.apache.maven.plugins - maven-compiler-plugin - ... - - - - io.avaje - avaje-inject-generator - 9.8 - - - - - - ----- - -.Gradle -[source, kotlin, role = "secondary"] ----- -plugins { - id "org.jetbrains.kotlin.kapt" version "1.9.10" -} - -dependencies { - kapt 'io.avaje:avaje-inject-generator:9.8' -} ----- - -3) Bootstrap Avaje from application: - -.Java -[source, java, role = "primary"] ----- -import static io.jooby.Jooby.runApp; - -public class App extends Jooby { - - { - /** Avaje: */ - BeanScope beanScope = BeanScope.builder() <1> - .build(); - - MyService service = beanScope.get(MyService.class); <2> - - get("/", ctx -> { - return service.doSomething(); - }); - } - - public static void main(String[] args) { - runApp(args, App::new); - } -} ----- - -.Kotlin -[source, kotlin, role = "secondary"] ----- -import io.jooby.kt.runApp - -fun main(args: Array) { - runApp(args) { - val beanScope = BeanScope.builder() <1> - .build() - - val service = beanScope.getMyService() <2> - - get("/") { - service.doSomething() - } - } -} ----- - -<1> Bootstrap avaje inject bean container -<2> Use Avaje provided objects - -==== MVC routes - -Integration of MVC routes with Avaje is as simple as: - -.MVC and Avaje -[source, java, role = "primary"] ----- - -import static io.jooby.Jooby.runApp; - -public class App extends Jooby { - - { - /** Avaje: */ - BeanScope beanScope = BeanScope.builder() <1> - .build(); - - mvc(beanScope.get(MyController.class)); <2> - } - - public static void main(String[] args) { - runApp(args, App::new); - } -} ----- - -.Kotlin -[source, kotlin, role = "secondary"] ----- -import io.jooby.kt.runApp - -fun main(args: Array) { - runApp(args) { - val beanScope = BeanScope.builder() <1> - .build() - - mvc(beanScope.get(MyController.class)) <2> - } -} ----- - -<1> Bootstrap Avaje bean container -<2> Register MVC route provided by Avaje diff --git a/docs/asciidoc/modules/avaje-inject.adoc b/docs/asciidoc/modules/avaje-inject.adoc new file mode 100644 index 0000000000..490f68541e --- /dev/null +++ b/docs/asciidoc/modules/avaje-inject.adoc @@ -0,0 +1,121 @@ +=== Avaje Inject + +1) Add Avaje Inject to your project + +[dependency, groupId="io.jooby", artifactId="jooby-avaje-inject", version="1.1.0"] +. + + +2) Install Avaje Inject: + +.Installing Avaje Inject +[source,java,role = "primary"] +---- +public class App extends Jooby { + + { + install(AvajeInjectModule.of()); <1> + + get("/", ctx -> { + MyService service = require(MyService.class); <2> + return service.doSomething(); + }); +} + + public static void main(String[] args) { + runApp(args, App::new); + } +} +---- + +.Kotlin +[source, kotlin, role = "secondary"] +---- +fun main(args: Array) { + runApp(args) { + install(AvajeInjectModule.of()) <1> + + get ("/") { + val service = require(MyService::class) <2> + service.doSomething() + } + } +} +---- + +<1> Install Avaje Inject module +<2> The javadoc:Jooby[require, java.lang.Class] call is now resolved by Avaje Inject + +==== Property Injection + +Configuration properties can be injected using the `@Named` annotation. As Avaje checks beans at compile time, `@InjectModule(requires={String.class})`: + +.application.conf +[source, bash] +---- +currency = USD +---- + +.Java +[source,java,role="primary"] +---- +@Singleton +public class BillingService { + + @Inject + public BillingService(@Named("currency") String currency) { + ... + } + +} +---- + +.Kotlin +[source,kotlin,role="secondary"] +---- +@Singleton +class BillingService @Inject constructor(@Named("currency") currency: String) { + ... +} +---- + +==== MVC routes + +Avaje Inject will also provisioning MVC routes + +.MVC and Avaje Inject +[source,java,role = "primary"] +---- +public class App extends Jooby { + + { + install(AvajeInjectModule.of()); <1> + + mvc(MyController.class); <2> + } + + public static void main(String[] args) { + runApp(args, App::new); + } +} +---- + +.Kotlin +[source, kotlin, role = "secondary"] +---- +fun main(args: Array) { + runApp(args) { + install(AvajeInjectModule.of()) <1> + + mvc(MyController::class) <2> + } +} +---- + +<1> Install Avaje Inject module +<2> Register a MVC route + +The lifecycle of `MyController` is now managed by Avaje Inject. + +In Avaje Inject, the dependency graph is typically validated when the application compiles. As beans provided by Jooby Modules are registered at runtime, you must add `@InjectModules(requires={String.class, ...})` to inform the avaje processor that these beans are provided at runtime. + diff --git a/modules/jooby-avaje-inject/pom.xml b/modules/jooby-avaje-inject/pom.xml new file mode 100644 index 0000000000..f5f9e84144 --- /dev/null +++ b/modules/jooby-avaje-inject/pom.xml @@ -0,0 +1,54 @@ + + + + + io.jooby + modules + 3.1.0-SNAPSHOT + + + 4.0.0 + jooby-avaje-inject + + + + io.jooby + jooby + ${jooby.version} + + + + + io.avaje + avaje-inject + + + + io.avaje + avaje-inject-generator + provided + + + + + org.junit.jupiter + junit-jupiter-engine + test + + + + org.mockito + mockito-core + test + + + + org.jacoco + org.jacoco.agent + runtime + test + + + diff --git a/modules/jooby-avaje-inject/src/main/java/io/jooby/avaje/inject/AvajeInjectModule.java b/modules/jooby-avaje-inject/src/main/java/io/jooby/avaje/inject/AvajeInjectModule.java new file mode 100644 index 0000000000..7ac6f62a3f --- /dev/null +++ b/modules/jooby-avaje-inject/src/main/java/io/jooby/avaje/inject/AvajeInjectModule.java @@ -0,0 +1,97 @@ +/* + * Jooby https://jooby.io + * Apache License Version 2.0 https://jooby.io/LICENSE.txt + * Copyright 2014 Edgar Espina + */ +package io.jooby.avaje.inject; + +import java.util.List; +import java.util.stream.Collectors; + +import com.typesafe.config.Config; + +import io.avaje.inject.BeanScope; +import io.avaje.inject.BeanScopeBuilder; +import io.jooby.Extension; +import io.jooby.Jooby; + +/** + * Avaje Inject module: https://jooby.io/modules/avaje-inject. + * + *

Jooby integrates the {@link io.jooby.ServiceRegistry} into the Avaje DI framework. + * + *

Usage: + * + *

{@code
+ * {
+ *
+ *
+ *   install(AvajeInjectModule.of());
+ *
+ * }
+ *
+ * }
+ * + * Require calls are going to be resolved by Avaje inject now. + * + * @author josiah + * @since 3.0.0 + */ +public class AvajeInjectModule implements Extension { + + private final BeanScopeBuilder beanScope; + + public static AvajeInjectModule of() { + return new AvajeInjectModule(BeanScope.builder()); + } + + public static AvajeInjectModule of(BeanScopeBuilder beanScope) { + return new AvajeInjectModule(beanScope); + } + + AvajeInjectModule(BeanScopeBuilder beanScope) { + this.beanScope = beanScope; + } + + @Override + public boolean lateinit() { + return true; + } + + @Override + public void install(Jooby application) throws Exception { + + application + .getServices() + .entrySet() + .forEach( + e -> { + final var key = e.getKey(); + if (key.getName() == null) { + beanScope.provideDefault(key.getType(), e::getValue); + } else { + beanScope.bean(key.getName(), key.getType(), e.getValue()); + } + }); + + final var environment = application.getEnvironment(); + beanScope.profiles(environment.getActiveNames().toArray(String[]::new)); + + // configuration properties + final var config = environment.getConfig(); + beanScope.propertyPlugin(new JoobyPropertyPlugin(config)); + beanScope.bean(Config.class, config); + + for (var entry : config.entrySet()) { + String name = entry.getKey(); + Object value = entry.getValue().unwrapped(); + + if (value instanceof List values) { + value = values.stream().map(Object::toString).collect(Collectors.joining(",")); + } + beanScope.bean(name, String.class, value.toString()); + } + + application.registry(new AvajeInjectRegistry(beanScope.build())); + } +} diff --git a/modules/jooby-avaje-inject/src/main/java/io/jooby/avaje/inject/AvajeInjectRegistry.java b/modules/jooby-avaje-inject/src/main/java/io/jooby/avaje/inject/AvajeInjectRegistry.java new file mode 100644 index 0000000000..6123bbba70 --- /dev/null +++ b/modules/jooby-avaje-inject/src/main/java/io/jooby/avaje/inject/AvajeInjectRegistry.java @@ -0,0 +1,42 @@ +package io.jooby.avaje.inject; + +import java.util.NoSuchElementException; + +import io.avaje.inject.BeanScope; +import io.jooby.Registry; +import io.jooby.ServiceKey; +import io.jooby.exception.RegistryException; + +class AvajeInjectRegistry implements Registry { + + private final BeanScope beanScope; + + public AvajeInjectRegistry(BeanScope beanScope) { + this.beanScope = beanScope; + } + + @Override + public T require(Class type) throws RegistryException { + try { + return beanScope.get(type); + } catch (NoSuchElementException e) { + ServiceKey key = ServiceKey.key(type); + throw new RegistryException("Provisioning of `" + key + "` resulted in exception", e); + } + } + + @Override + public T require(Class type, String name) throws RegistryException { + try { + return beanScope.get(type, name); + } catch (NoSuchElementException e) { + ServiceKey key = ServiceKey.key(type, name); + throw new RegistryException("Provisioning of `" + key + "` resulted in exception", e); + } + } + + @Override + public T require(ServiceKey key) throws RegistryException { + return require(key.getType(), key.getName()); + } +} diff --git a/modules/jooby-avaje-inject/src/main/java/io/jooby/avaje/inject/JoobyPropertyPlugin.java b/modules/jooby-avaje-inject/src/main/java/io/jooby/avaje/inject/JoobyPropertyPlugin.java new file mode 100644 index 0000000000..246ed35665 --- /dev/null +++ b/modules/jooby-avaje-inject/src/main/java/io/jooby/avaje/inject/JoobyPropertyPlugin.java @@ -0,0 +1,32 @@ +package io.jooby.avaje.inject; + +import java.util.Objects; +import java.util.Optional; + +import com.typesafe.config.Config; + +import io.avaje.inject.spi.PropertyRequiresPlugin; + +public class JoobyPropertyPlugin implements PropertyRequiresPlugin { + + private final Config config; + + public JoobyPropertyPlugin(Config config) { + this.config = config; + } + + @Override + public Optional get(String property) { + return Optional.ofNullable(config.getString(property)); + } + + @Override + public boolean contains(String property) { + return config.hasPath(property); + } + + @Override + public boolean equalTo(String property, String value) { + return config.hasPath(property) && Objects.equals(config.getString(property), value); + } +} diff --git a/modules/jooby-avaje-inject/src/main/java/module-info.java b/modules/jooby-avaje-inject/src/main/java/module-info.java new file mode 100644 index 0000000000..341614c740 --- /dev/null +++ b/modules/jooby-avaje-inject/src/main/java/module-info.java @@ -0,0 +1,13 @@ +/* + * Jooby https://jooby.io + * Apache License Version 2.0 https://jooby.io/LICENSE.txt + * Copyright 2014 Edgar Espina + */ +/** Avaje Inject module. */ +module io.jooby.avaje.inject { + exports io.jooby.avaje.inject; + + requires transitive io.jooby; + requires transitive typesafe.config; + requires transitive io.avaje.inject; +} diff --git a/modules/jooby-avaje-jsonb/pom.xml b/modules/jooby-avaje-jsonb/pom.xml index 6a81da793d..78a168ccfd 100644 --- a/modules/jooby-avaje-jsonb/pom.xml +++ b/modules/jooby-avaje-jsonb/pom.xml @@ -24,6 +24,12 @@ avaje-jsonb + + io.avaje + avaje-jsonb-generator + provided + + io.jooby diff --git a/modules/jooby-bom/pom.xml b/modules/jooby-bom/pom.xml index 88f73d14ff..97c5517d53 100644 --- a/modules/jooby-bom/pom.xml +++ b/modules/jooby-bom/pom.xml @@ -29,6 +29,11 @@ jooby ${project.version} + + io.jooby + jooby-avaje-inject + ${project.version} + io.jooby jooby-avaje-jsonb diff --git a/modules/pom.xml b/modules/pom.xml index b1144713ea..9438c2db27 100644 --- a/modules/pom.xml +++ b/modules/pom.xml @@ -64,6 +64,7 @@ jooby-quartz jooby-awssdk-v1 + jooby-avaje-inject jooby-guice jooby-commons-email diff --git a/pom.xml b/pom.xml index 1c4d768073..4d543bc7d0 100644 --- a/pom.xml +++ b/pom.xml @@ -47,6 +47,7 @@ 1.4.3 + 9.12 7.0.0 @@ -673,6 +674,19 @@ jackson-datatype-hibernate5 ${jackson.version} + + + + io.avaje + avaje-inject + ${avaje-inject.version} + + + + io.avaje + avaje-inject-generator + ${avaje-inject.version} + @@ -681,6 +695,12 @@ ${avaje-jsonb.version} + + io.avaje + avaje-jsonb-generator + ${avaje-jsonb.version} + + org.freemarker