Repository: avaje/avaje-inject
Pull request #751 (2025-01-04 22:54:09)

Now can import `Prototype`/`Lazy` Beans
Adds an attribute to the `Component.Import` annotation to make imported types have the prototype/lazy scope


Patch:
diff --git a/.gitignore b/.gitignore
index 92ed340a..9e1f2cd7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,3 +13,5 @@ build/
 inject-generator/avaje-inject
 inject-generator/avaje-inject-generator
 inject-generator/avaje-processors.txt
+inject-generator/avaje-module-dependencies.csv
+inject-generator/avaje-plugins.csv
diff --git a/blackbox-other/src/main/java/org/other/one/OtherComponent3.java b/blackbox-other/src/main/java/org/other/one/OtherComponent3.java
new file mode 100644
index 00000000..4fced6e7
--- /dev/null
+++ b/blackbox-other/src/main/java/org/other/one/OtherComponent3.java
@@ -0,0 +1,3 @@
+package org.other.one;
+
+public class OtherComponent3 {}
diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/BeanReader.java b/inject-generator/src/main/java/io/avaje/inject/generator/BeanReader.java
index 58cb8f23..d261f4fd 100644
--- a/inject-generator/src/main/java/io/avaje/inject/generator/BeanReader.java
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/BeanReader.java
@@ -1,6 +1,7 @@
 package io.avaje.inject.generator;
 
 import static io.avaje.inject.generator.APContext.logError;
+
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.LinkedHashSet;
@@ -54,10 +55,15 @@ final class BeanReader {
     this.beanType = beanType;
     this.type = beanType.getQualifiedName().toString();
     this.shortName = shortName(beanType);
-    this.prototype = PrototypePrism.isPresent(beanType);
+    this.prototype =
+      PrototypePrism.isPresent(beanType)
+        || importedComponent && ProcessingContext.isImportedPrototype(beanType);
     this.primary = PrimaryPrism.isPresent(beanType);
     this.secondary = !primary && SecondaryPrism.isPresent(beanType);
-    this.lazy = !FactoryPrism.isPresent(beanType) && LazyPrism.isPresent(beanType);
+    this.lazy =
+      !FactoryPrism.isPresent(beanType)
+        && (LazyPrism.isPresent(beanType)
+          || importedComponent && ProcessingContext.isImportedLazy(beanType));
     final var beantypes = BeanTypesPrism.getOptionalOn(beanType);
     beantypes.ifPresent(p -> Util.validateBeanTypes(beanType, p.value()));
     this.typeReader =
@@ -371,7 +377,7 @@ final class BeanReader {
       }
     }
     checkImports();
-    if (!suppressGeneratedImport){
+    if (!suppressGeneratedImport) {
       importTypes.add(Constants.GENERATED);
     }
     if (!suppressBuilderImport && !isGenerateProxy()) {
diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/InjectProcessor.java b/inject-generator/src/main/java/io/avaje/inject/generator/InjectProcessor.java
index 2f362bd8..7132c888 100644
--- a/inject-generator/src/main/java/io/avaje/inject/generator/InjectProcessor.java
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/InjectProcessor.java
@@ -211,12 +211,23 @@ public final class InjectProcessor extends AbstractProcessor {
     return maybeElements(roundEnv, ImportPrism.PRISM_TYPE).stream()
       .flatMap(Set::stream)
       .map(ImportPrism::getInstanceOn)
-      .flatMap(p -> p.value().stream())
-      .map(ProcessingContext::asElement)
-      .filter(this::notAlreadyProvided)
+      .flatMap(p -> {
+        var kind = p.kind();
+        return p.value().stream()
+          .map(ProcessingContext::asElement)
+          .filter(this::notAlreadyProvided)
+          .map(e -> registerImportedKind(e, kind));
+      })
       .collect(Collectors.toSet());
   }
 
+  private static TypeElement registerImportedKind(TypeElement e, String kind) {
+    if (!"SINGLETON".equals(kind)) {
+      ProcessingContext.addImportedKind(e, kind);
+    }
+    return e;
+  }
+
   private boolean notAlreadyProvided(TypeElement e) {
     final String type = e.getQualifiedName().toString();
     return !moduleFileProvided.contains(type) && !pluginFileProvided.contains(type);
diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/ProcessingContext.java b/inject-generator/src/main/java/io/avaje/inject/generator/ProcessingContext.java
index ef445771..86fcc431 100644
--- a/inject-generator/src/main/java/io/avaje/inject/generator/ProcessingContext.java
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/ProcessingContext.java
@@ -27,6 +27,7 @@ final class ProcessingContext {
   static final class Ctx {
     private final Set<String> uniqueModuleNames = new HashSet<>();
     private final Set<String> providedTypes = new HashSet<>();
+    private final Map<String, String> importedProtoTypes = new HashMap<>();
     private final Set<String> optionalTypes = new LinkedHashSet<>();
     private final Map<String, AspectImportPrism> aspectImportPrisms = new HashMap<>();
     private final List<ModuleData> modules = new ArrayList<>();
@@ -154,6 +155,22 @@ final class ProcessingContext {
     }
   }
 
+  static void addImportedKind(TypeElement element, String kind) {
+    CTX.get().importedProtoTypes.put(element.getQualifiedName().toString(), kind);
+  }
+
+  static boolean isImportedPrototype(TypeElement element) {
+    return "prototype".equalsIgnoreCase(importedTypeKind(element));
+  }
+
+  static boolean isImportedLazy(TypeElement element) {
+    return "lazy".equalsIgnoreCase(importedTypeKind(element));
+  }
+
+  private static String importedTypeKind(TypeElement element) {
+    return CTX.get().importedProtoTypes.get(element.getQualifiedName().toString());
+  }
+
   static void addImportedAspects(Map<String, AspectImportPrism> importedMap) {
     CTX.get().aspectImportPrisms.putAll(importedMap);
   }
diff --git a/inject/src/main/java/io/avaje/inject/Component.java b/inject/src/main/java/io/avaje/inject/Component.java
index 9b5af85b..43069b5f 100644
--- a/inject/src/main/java/io/avaje/inject/Component.java
+++ b/inject/src/main/java/io/avaje/inject/Component.java
@@ -1,11 +1,12 @@
 package io.avaje.inject;
 
+import java.lang.annotation.Repeatable;
 import java.lang.annotation.Retention;
 import java.lang.annotation.Target;
 
 import static java.lang.annotation.ElementType.*;
-import static java.lang.annotation.RetentionPolicy.CLASS;
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
 
 /**
  * Identify a bean as component with singleton scope that avaje-inject will use.
@@ -63,13 +64,26 @@ public @interface Component {
    *
    * }</pre>
    */
-  @Retention(CLASS)
+  @Retention(SOURCE)
   @Target({TYPE, PACKAGE, MODULE})
+  @Repeatable(Imports.class)
   @interface Import {
 
-    /**
-     * Types to generate DI classes for.
-     */
+    /** Types to generate DI classes for. */
     Class<?>[] value();
+
+    /** What kind of bean */
+    Kind kind() default Kind.SINGLETON;
+
+    enum Kind { SINGLETON, PROTOTYPE, LAZY }
+  }
+
+  /**
+   * @see Import
+   */
+  @Retention(SOURCE)
+  @Target({TYPE, PACKAGE, MODULE})
+  @interface Imports {
+    Import[] value();
   }
 }


Test patch:
diff --git a/blackbox-test-inject/src/main/java/org/example/myapp/ConfigPropertiesPlugin.java b/blackbox-test-inject/src/main/java/org/example/myapp/ConfigPropertiesPlugin.java
index 2485b157..ab757b12 100644
--- a/blackbox-test-inject/src/main/java/org/example/myapp/ConfigPropertiesPlugin.java
+++ b/blackbox-test-inject/src/main/java/org/example/myapp/ConfigPropertiesPlugin.java
@@ -2,15 +2,18 @@ package org.example.myapp;
 
 import io.avaje.config.Config;
 import io.avaje.inject.Component;
+import io.avaje.inject.Component.Import.Kind;
 import io.avaje.inject.spi.ConfigPropertyPlugin;
 
 import io.avaje.spi.ServiceProvider;
 import org.other.one.OtherComponent2;
+import org.other.one.OtherComponent3;
 
 import java.util.Optional;
 
 @ServiceProvider
 @Component.Import(value = OtherComponent2.class)
+@Component.Import(value = OtherComponent3.class, kind = Kind.LAZY)
 public class ConfigPropertiesPlugin implements ConfigPropertyPlugin {
 
   @Override
diff --git a/inject-generator/src/test/java/io/avaje/inject/generator/models/valid/imported/ImportedProtoType.java b/inject-generator/src/test/java/io/avaje/inject/generator/models/valid/imported/ImportedProtoType.java
new file mode 100644
index 00000000..ac4a0aaf
--- /dev/null
+++ b/inject-generator/src/test/java/io/avaje/inject/generator/models/valid/imported/ImportedProtoType.java
@@ -0,0 +1,7 @@
+package io.avaje.inject.generator.models.valid.imported;
+
+import io.avaje.inject.Component;
+import io.avaje.inject.Component.Import.Kind;
+
+@Component.Import(value = ImportedProtoType.class, kind =  Kind.PROTOTYPE)
+public class ImportedProtoType {}