Repository: avaje/avaje-inject
Pull request #254 (2023-01-09 07:12:11)

#253 - Fix for @Secondary and @Primary not used with @Factory @Bean methods


Patch:
diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/AllScopes.java b/inject-generator/src/main/java/io/avaje/inject/generator/AllScopes.java
index 13433321..ca7c2464 100644
--- a/inject-generator/src/main/java/io/avaje/inject/generator/AllScopes.java
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/AllScopes.java
@@ -9,7 +9,7 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
-class AllScopes {
+final class AllScopes {
 
   private final Map<String, Data> scopeAnnotations = new HashMap<>();
   private final ProcessingContext context;
diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/AnnotationUtil.java b/inject-generator/src/main/java/io/avaje/inject/generator/AnnotationUtil.java
index 4238cebf..33d07229 100644
--- a/inject-generator/src/main/java/io/avaje/inject/generator/AnnotationUtil.java
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/AnnotationUtil.java
@@ -3,7 +3,7 @@ package io.avaje.inject.generator;
 import javax.lang.model.element.AnnotationMirror;
 import javax.lang.model.element.Element;
 
-class AnnotationUtil {
+final class AnnotationUtil {
 
   static boolean hasAnnotationWithName(Element element, String matchShortName) {
     for (AnnotationMirror mirror : element.getAnnotationMirrors()) {
diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/Append.java b/inject-generator/src/main/java/io/avaje/inject/generator/Append.java
index 53081560..665668e1 100644
--- a/inject-generator/src/main/java/io/avaje/inject/generator/Append.java
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/Append.java
@@ -6,7 +6,7 @@ import java.io.Writer;
 /**
  * Helper that wraps a writer with some useful methods to append content.
  */
-class Append {
+final class Append {
 
   private final Writer writer;
   private int nameIndex;
diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/AspectAnnotationReader.java b/inject-generator/src/main/java/io/avaje/inject/generator/AspectAnnotationReader.java
index 90da30d3..b8beae75 100644
--- a/inject-generator/src/main/java/io/avaje/inject/generator/AspectAnnotationReader.java
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/AspectAnnotationReader.java
@@ -10,7 +10,7 @@ import java.util.Map;
 /**
  * Read the annotations on the type.
  */
-class AspectAnnotationReader {
+final class AspectAnnotationReader {
 
   private static final String ASPECT = Constants.ASPECT;
   private final TypeElement baseType;
diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/AspectMethod.java b/inject-generator/src/main/java/io/avaje/inject/generator/AspectMethod.java
index 71e97db0..26d80078 100644
--- a/inject-generator/src/main/java/io/avaje/inject/generator/AspectMethod.java
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/AspectMethod.java
@@ -6,7 +6,7 @@ import javax.lang.model.element.VariableElement;
 import javax.lang.model.type.TypeMirror;
 import java.util.*;
 
-class AspectMethod {
+final class AspectMethod {
 
   private final List<AspectPair> aspectPairs;
   private final ExecutableElement method;
@@ -49,7 +49,7 @@ class AspectMethod {
     return rawReturn.equals("void");
   }
 
-  void addImports(Set<String> importTypes) {
+  void addImports(ImportTypeMap importTypes) {
     for (AspectPair aspect : aspectPairs) {
       aspect.addImports(importTypes);
     }
diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/AspectPair.java b/inject-generator/src/main/java/io/avaje/inject/generator/AspectPair.java
index 2a79b4f4..6c34349b 100644
--- a/inject-generator/src/main/java/io/avaje/inject/generator/AspectPair.java
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/AspectPair.java
@@ -1,9 +1,8 @@
 package io.avaje.inject.generator;
 
 import javax.lang.model.element.Element;
-import java.util.Set;
 
-class AspectPair implements Comparable<AspectPair> {
+final class AspectPair implements Comparable<AspectPair> {
 
   private final String target;
   private final int ordering;
@@ -21,7 +20,7 @@ class AspectPair implements Comparable<AspectPair> {
     return target;
   }
 
-  void addImports(Set<String> importTypes) {
+  void addImports(ImportTypeMap importTypes) {
     importTypes.add(target);
     importTypes.add(annotationFullName);
   }
diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/BeanAspects.java b/inject-generator/src/main/java/io/avaje/inject/generator/BeanAspects.java
index 4fde670c..80df5fda 100644
--- a/inject-generator/src/main/java/io/avaje/inject/generator/BeanAspects.java
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/BeanAspects.java
@@ -5,7 +5,7 @@ import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Set;
 
-class BeanAspects {
+final class BeanAspects {
 
   static final BeanAspects EMPTY = new BeanAspects();
 
@@ -30,7 +30,7 @@ class BeanAspects {
     return targets;
   }
 
-  void extraImports(Set<String> importTypes) {
+  void extraImports(ImportTypeMap importTypes) {
     for (AspectMethod aspectMethod : aspectMethods) {
       aspectMethod.addImports(importTypes);
     }
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 914871bd..71d70e67 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
@@ -9,7 +9,7 @@ import javax.lang.model.element.Element;
 import javax.lang.model.element.TypeElement;
 import java.util.*;
 
-class BeanReader {
+final class BeanReader {
 
   private final TypeElement beanType;
   private final String shortName;
@@ -23,7 +23,7 @@ class BeanReader {
   private final Element postConstructMethod;
   private final Element preDestroyMethod;
 
-  private final Set<String> importTypes = new TreeSet<>();
+  private final ImportTypeMap importTypes = new ImportTypeMap();
   private final BeanRequestParams requestParams;
   private final TypeReader typeReader;
   private final boolean prototype;
@@ -205,10 +205,15 @@ class BeanReader {
     }
     writer.append("      ");
     if (isExtraInjectionRequired() || hasLifecycleMethods()) {
-      writer.append("%s $bean = ", shortName);
+      writer.append("var $bean = ");
     }
-    String flags = primary ? "Primary" : secondary ? "Secondary" : "";
-    writer.append("builder.register%s(bean);", flags).eol();
+    writer.append("builder.");
+    if (primary) {
+      writer.append("asPrimary().");
+    } else if (secondary) {
+      writer.append("asSecondary().");
+    }
+    writer.append("register(bean);").eol();
   }
 
   void addLifecycleCallbacks(Append writer, String indent) {
@@ -248,10 +253,7 @@ class BeanReader {
       if (!genericTypes.isEmpty()) {
         importTypes.add(Constants.TYPE);
         importTypes.add(Constants.GENERICTYPE);
-        importTypes.add(Constants.PROVIDER);
-        for (GenericType genericType : genericTypes) {
-          genericType.addImports(importTypes);
-        }
+        // TYPE_ generic types are fully qualified
       }
     }
     checkImports();
@@ -261,17 +263,12 @@ class BeanReader {
     if (!suppressBuilderImport) {
       importTypes.add(Constants.BUILDER);
     }
-    return importTypes;
+    return importTypes.forImport();
   }
 
   private void checkImports() {
-    for (String type : importTypes) {
-      if (type.endsWith(".Builder")) {
-        suppressBuilderImport = true;
-      } else if (type.endsWith(".Generated")) {
-        suppressGeneratedImport = true;
-      }
-    }
+    suppressBuilderImport = importTypes.containsShortName("Builder");
+    suppressGeneratedImport = importTypes.containsShortName("Generated");
   }
 
   String builderType() {
@@ -343,7 +340,7 @@ class BeanReader {
     }
     requestParams.writeRequestCreate(writer);
     writer.resetNextName();
-    writer.append("    %s bean = new %s(", shortName, shortName);
+    writer.append("    var bean = new %s(", shortName);
     if (constructor != null) {
       constructor.writeRequestConstructor(writer);
     }
diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/BeanRequestParams.java b/inject-generator/src/main/java/io/avaje/inject/generator/BeanRequestParams.java
index 162c19e5..a6f5fba8 100644
--- a/inject-generator/src/main/java/io/avaje/inject/generator/BeanRequestParams.java
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/BeanRequestParams.java
@@ -1,12 +1,10 @@
 package io.avaje.inject.generator;
 
 
-import java.util.Set;
-
 /**
  * Holds detection and details of request scoped dependencies.
  */
-class BeanRequestParams {
+final class BeanRequestParams {
 
   private final String parentType;
   private RequestScope.Handler reqScopeHandler;
@@ -39,7 +37,7 @@ class BeanRequestParams {
     reqScopeHandler.factoryInterface(writer, nm(parentType));
   }
 
-  void addImports(Set<String> importTypes) {
+  void addImports(ImportTypeMap importTypes) {
     if (reqScopeHandler != null) {
       importTypes.add(Constants.SINGLETON);
       importTypes.add(Constants.INJECT);
diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/Constants.java b/inject-generator/src/main/java/io/avaje/inject/generator/Constants.java
index eabe9ee2..d8436788 100644
--- a/inject-generator/src/main/java/io/avaje/inject/generator/Constants.java
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/Constants.java
@@ -1,6 +1,6 @@
 package io.avaje.inject.generator;
 
-class Constants {
+final class Constants {
 
   static final int ORDERING_DEFAULT = 1000;
 
diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/FieldReader.java b/inject-generator/src/main/java/io/avaje/inject/generator/FieldReader.java
index 8b9a3996..1de3927d 100644
--- a/inject-generator/src/main/java/io/avaje/inject/generator/FieldReader.java
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/FieldReader.java
@@ -4,7 +4,7 @@ import javax.lang.model.element.Element;
 import java.util.List;
 import java.util.Set;
 
-class FieldReader {
+final class FieldReader {
 
   private final Element element;
   private final String name;
@@ -38,7 +38,7 @@ class FieldReader {
     return element.getSimpleName().toString();
   }
 
-  void addImports(Set<String> importTypes) {
+  void addImports(ImportTypeMap importTypes) {
     type.addImports(importTypes);
   }
 
diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/GenericType.java b/inject-generator/src/main/java/io/avaje/inject/generator/GenericType.java
index 319bacb8..62c0d0b3 100644
--- a/inject-generator/src/main/java/io/avaje/inject/generator/GenericType.java
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/GenericType.java
@@ -8,7 +8,7 @@ import java.util.Set;
 /**
  * A type with generic parameters and potentially nested.
  */
-class GenericType {
+final class GenericType {
 
   /**
    * Trim off generic wildcard from the raw type if present.
@@ -97,7 +97,7 @@ class GenericType {
   }
 
 
-  void addImports(Set<String> importTypes) {
+  void addImports(ImportTypeMap importTypes) {
     final String type = trimExtends();
     if (includeInImports(type)) {
       importTypes.add(type);
diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/ImportTypeMap.java b/inject-generator/src/main/java/io/avaje/inject/generator/ImportTypeMap.java
new file mode 100644
index 00000000..a0936ebe
--- /dev/null
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/ImportTypeMap.java
@@ -0,0 +1,47 @@
+package io.avaje.inject.generator;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+
+/**
+ * Registers types that we want to import - must have unique short names.
+ */
+final class ImportTypeMap {
+
+  private final Map<String, String> mapByShortName = new HashMap<>();
+
+  /**
+   * Return the full types that will be imported.
+   */
+  Set<String> forImport() {
+    return new TreeSet<>(mapByShortName.values());
+  }
+
+  /**
+   * Return true if there is a full type matching the given suffix.
+   */
+  boolean containsShortName(String suffix) {
+    return mapByShortName.containsKey(suffix);
+  }
+
+  /**
+   * Register the full type checking for unique short name and returning the short name to use.
+   */
+  String add(String fullType) {
+    String shortName = Util.shortName(fullType);
+    String existingFull = mapByShortName.get(shortName);
+    if (existingFull == null) {
+      mapByShortName.put(shortName, fullType);
+      return shortName;
+    } else if (existingFull.equals(fullType)) {
+      // already existing
+      return shortName;
+    } else {
+      // must use fully qualified type
+      return fullType;
+    }
+  }
+
+}
diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/IncludeAnnotations.java b/inject-generator/src/main/java/io/avaje/inject/generator/IncludeAnnotations.java
index 19c60ba2..cc865203 100644
--- a/inject-generator/src/main/java/io/avaje/inject/generator/IncludeAnnotations.java
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/IncludeAnnotations.java
@@ -3,7 +3,7 @@ package io.avaje.inject.generator;
 import java.util.HashSet;
 import java.util.Set;
 
-class IncludeAnnotations {
+final class IncludeAnnotations {
 
   private static final String[] EXCLUDED_PREFIX = {"javax.annotation.", "javax.inject.", "jakarta.annotation.", "jakarta.inject.", "io.avaje.inject.", "lombok."};
   private static final String[] EXCLUDED_SUFFIX = {".PostConstruct", ".PreDestroy", ".Generated"};
diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/MetaData.java b/inject-generator/src/main/java/io/avaje/inject/generator/MetaData.java
index 8a53b3fc..50192816 100644
--- a/inject-generator/src/main/java/io/avaje/inject/generator/MetaData.java
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/MetaData.java
@@ -12,7 +12,7 @@ import java.util.stream.Stream;
 /**
  * Holds the data as per <code>@DependencyMeta</code>
  */
-class MetaData {
+final class MetaData {
 
   private static final String NEWLINE = "\n";
 
diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/MetaDataOrdering.java b/inject-generator/src/main/java/io/avaje/inject/generator/MetaDataOrdering.java
index fec37edb..67f35b1a 100644
--- a/inject-generator/src/main/java/io/avaje/inject/generator/MetaDataOrdering.java
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/MetaDataOrdering.java
@@ -3,7 +3,7 @@ package io.avaje.inject.generator;
 import javax.lang.model.element.TypeElement;
 import java.util.*;
 
-class MetaDataOrdering {
+final class MetaDataOrdering {
 
   private static final String CIRC_ERR_MSG =
     "To handle circular dependencies consider using field injection " +
diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/MetaTopPackage.java b/inject-generator/src/main/java/io/avaje/inject/generator/MetaTopPackage.java
index 844154a9..c493e475 100644
--- a/inject-generator/src/main/java/io/avaje/inject/generator/MetaTopPackage.java
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/MetaTopPackage.java
@@ -2,7 +2,7 @@ package io.avaje.inject.generator;
 
 import java.util.Collection;
 
-class MetaTopPackage {
+final class MetaTopPackage {
 
   private String topPackage;
 
diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/MethodLifecycleReader.java b/inject-generator/src/main/java/io/avaje/inject/generator/MethodLifecycleReader.java
index 95e92593..293d5de1 100644
--- a/inject-generator/src/main/java/io/avaje/inject/generator/MethodLifecycleReader.java
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/MethodLifecycleReader.java
@@ -7,7 +7,7 @@ import javax.lang.model.element.TypeElement;
 /**
  * Looks for lifecycle annotations on methods.
  */
-class MethodLifecycleReader {
+final class MethodLifecycleReader {
 
   private final String initMethod;
   private final String destroyMethod;
diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/MethodReader.java b/inject-generator/src/main/java/io/avaje/inject/generator/MethodReader.java
index 2e1377e1..848f1e67 100644
--- a/inject-generator/src/main/java/io/avaje/inject/generator/MethodReader.java
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/MethodReader.java
@@ -1,6 +1,9 @@
 package io.avaje.inject.generator;
 
 import io.avaje.inject.Bean;
+import io.avaje.inject.Primary;
+import io.avaje.inject.Prototype;
+import io.avaje.inject.Secondary;
 
 import javax.lang.model.element.ExecutableElement;
 import javax.lang.model.element.Modifier;
@@ -8,12 +11,11 @@ import javax.lang.model.element.TypeElement;
 import javax.lang.model.element.VariableElement;
 import javax.lang.model.type.TypeMirror;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.Set;
 
-class MethodReader {
+final class MethodReader {
 
   private static final String CODE_COMMENT_BUILD_FACTORYBEAN = "  /**\n   * Create and register %s via factory bean method %s#%s().\n   */";
 
@@ -21,6 +23,8 @@ class MethodReader {
   private final String factoryType;
   private final String methodName;
   private final boolean prototype;
+  private final boolean primary;
+  private final boolean secondary;
   private final String returnTypeRaw;
   private final GenericType genericType;
   private final String shortName;
@@ -34,14 +38,22 @@ class MethodReader {
   private final TypeReader typeReader;
   private final boolean optionalType;
 
-  MethodReader(ProcessingContext context, ExecutableElement element, TypeElement beanType) {
-    this(context, element, beanType, null, null, false);
+  MethodReader(ProcessingContext context, ExecutableElement element, TypeElement beanType, ImportTypeMap importTypes) {
+    this(context, element, beanType, null, null, importTypes);
   }
 
-  MethodReader(ProcessingContext context, ExecutableElement element, TypeElement beanType, Bean bean, String qualifierName, boolean prototype) {
+  MethodReader(ProcessingContext context, ExecutableElement element, TypeElement beanType, Bean bean, String qualifierName, ImportTypeMap importTypes) {
     this.isFactory = bean != null;
-    this.prototype = prototype;
     this.element = element;
+    if (isFactory) {
+      prototype = element.getAnnotation(Prototype.class) != null;
+      primary = element.getAnnotation(Primary.class) != null;
+      secondary = element.getAnnotation(Secondary.class) != null;
+    } else {
+      prototype = false;
+      primary = false;
+      secondary = false;
+    }
     this.methodName = element.getSimpleName().toString();
     TypeMirror returnMirror = element.getReturnType();
     String raw = returnMirror.toString();
@@ -67,7 +79,7 @@ class MethodReader {
       this.initMethod = initMethod;
       this.destroyMethod = destroyMethod;
     } else {
-      this.typeReader = new TypeReader(genericType, returnElement, context);
+      this.typeReader = new TypeReader(genericType, returnElement, context, importTypes);
       typeReader.process();
       MethodLifecycleReader lifecycleReader = new MethodLifecycleReader(returnElement, initMethod, destroyMethod);
       this.initMethod = lifecycleReader.initMethod();
@@ -130,18 +142,16 @@ class MethodReader {
   }
 
   String builderGetFactory() {
-    return String.format("      %s factory = builder.get(%s.class);", factoryShortName, factoryShortName);
+    return String.format("      var factory = builder.get(%s.class);", factoryShortName);
   }
 
   void builderBuildBean(Append writer) {
     writer.append("      ");
     if (isVoid) {
-      writer.append(String.format("factory.%s(", methodName));
+      writer.append("factory.%s(", methodName);
     } else {
-      String beanType = optionalType ? String.format("Optional<%s>", shortName) : shortName;
       String beanName = optionalType ? "optionalBean" : "bean";
-      writer.append(beanType);
-      writer.append(String.format(" %s = factory.%s(", beanName, methodName));
+      writer.append("var %s = factory.%s(", beanName, methodName);
     }
     for (int i = 0; i < params.size(); i++) {
       if (i > 0) {
@@ -152,7 +162,7 @@ class MethodReader {
     writer.append(");").eol();
   }
 
-  public void builderAddProtoBean(Append writer) {
+  public void builderAddBeanProvider(Append writer) {
     if (isVoid) {
       writer.append("Error - void @Prototype method ?").eol();
       return;
@@ -162,8 +172,11 @@ class MethodReader {
       return;
     }
     String indent = "    ";
-    writer.append(indent).append("  // prototype scope bean method").eol();
-    writer.append(indent).append("  builder.registerProvider(() -> {").eol();
+    if (prototype) {
+      writer.append(indent).append("  builder.asPrototype().registerProvider(() -> {").eol();
+    } else {
+      writer.append(indent).append("  builder.asSecondary().registerProvider(() -> {").eol();
+    }
     writer.append("%s    return ", indent);
     writer.append(String.format("factory.%s(", methodName));
     for (int i = 0; i < params.size(); i++) {
@@ -182,13 +195,19 @@ class MethodReader {
       String indent = optionalType ? "        " : "      ";
       if (optionalType) {
         writer.append("      if (optionalBean.isPresent()) {").eol();
-        writer.append("        %s bean = optionalBean.get();", shortName).eol();
+        writer.append("        var bean = optionalBean.get();").eol();
       }
       writer.append(indent);
       if (hasLifecycleMethods()) {
-        writer.append("%s $bean = ", shortName);
+        writer.append("var $bean = ");
+      }
+      writer.append("builder");
+      if (primary) {
+        writer.append(".asPrimary()");
+      } else if (secondary) {
+        writer.append(".asSecondary()");
       }
-      writer.append("builder.register(bean);").eol();
+      writer.append(".register(bean);").eol();
       if (notEmpty(initMethod)) {
         writer.append(indent).append("builder.addPostConstruct($bean::%s);", initMethod).eol();
       }
@@ -211,19 +230,14 @@ class MethodReader {
     return value != null && !value.isEmpty();
   }
 
-  void addImports(Set<String> importTypes) {
+  void addImports(ImportTypeMap importTypes) {
     for (MethodParam param : params) {
       param.addImports(importTypes);
     }
-    if (isFactory) {
-      genericType.addImports(importTypes);
-    }
+    // TYPE_ generic types are fully qualified
     if (optionalType) {
       importTypes.add(Constants.OPTIONAL);
     }
-    if (typeReader != null) {
-      typeReader.addImports(importTypes);
-    }
   }
 
   Set<GenericType> getGenericTypes() {
@@ -280,6 +294,10 @@ class MethodReader {
     return prototype;
   }
 
+  boolean isUseProviderForSecondary() {
+    return secondary && !optionalType;
+  }
+
   boolean isPublic() {
     return element.getModifiers().contains(Modifier.PUBLIC);
   }
@@ -392,11 +410,8 @@ class MethodReader {
       return new Dependency(paramType, utilType.isCollection());
     }
 
-    void addImports(Set<String> importTypes) {
+    void addImports(ImportTypeMap importTypes) {
       fullGenericType.addImports(importTypes);
-      if (genericType.isGenericType()) {
-        importTypes.add(Constants.PROVIDER);
-      }
     }
 
     void checkRequest(BeanRequestParams requestParams) {
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 b2ab6654..0a6d9dfe 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
@@ -20,7 +20,7 @@ import java.io.Reader;
 import java.nio.file.NoSuchFileException;
 import java.util.*;
 
-class ProcessingContext {
+final class ProcessingContext {
 
   private final ProcessingEnvironment processingEnv;
   private final Messager messager;
diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/Processor.java b/inject-generator/src/main/java/io/avaje/inject/generator/Processor.java
index c0b5b639..3f64485b 100644
--- a/inject-generator/src/main/java/io/avaje/inject/generator/Processor.java
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/Processor.java
@@ -20,7 +20,7 @@ import javax.lang.model.util.Elements;
 import java.util.LinkedHashSet;
 import java.util.Set;
 
-public class Processor extends AbstractProcessor {
+public final class Processor extends AbstractProcessor {
 
   private ProcessingContext context;
   private Elements elementUtils;
diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/RequestScope.java b/inject-generator/src/main/java/io/avaje/inject/generator/RequestScope.java
index 68df095c..70bb984d 100644
--- a/inject-generator/src/main/java/io/avaje/inject/generator/RequestScope.java
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/RequestScope.java
@@ -7,7 +7,7 @@ import java.util.Set;
 /**
  * Handling detection of request scoped dependencies and appropriate BeanFactory generation.
  */
-class RequestScope {
+final class RequestScope {
 
   private static final String JEX_CONTEXT = "io.avaje.jex.Context";
   private static final String JAVALIN_CONTEXT = "io.javalin.http.Context";
@@ -61,7 +61,7 @@ class RequestScope {
     /**
      * Add appropriate imports.
      */
-    void addImports(Set<String> importTypes);
+    void addImports(ImportTypeMap importTypes);
 
     /**
      * Add dependencies and create method.
@@ -123,7 +123,7 @@ class RequestScope {
     }
 
     @Override
-    public void addImports(Set<String> importTypes) {
+    public void addImports(ImportTypeMap importTypes) {
       importTypes.add(Constants.BEAN_FACTORY);
       importTypes.add(contextType);
     }
@@ -158,7 +158,7 @@ class RequestScope {
     }
 
     @Override
-    public void addImports(Set<String> importTypes) {
+    public void addImports(ImportTypeMap importTypes) {
       importTypes.add(Constants.BEAN_FACTORY2);
       importTypes.add(reqType);
       importTypes.add(resType);
diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/ScopeInfo.java b/inject-generator/src/main/java/io/avaje/inject/generator/ScopeInfo.java
index 71967890..f965514b 100644
--- a/inject-generator/src/main/java/io/avaje/inject/generator/ScopeInfo.java
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/ScopeInfo.java
@@ -12,7 +12,7 @@ import javax.tools.JavaFileObject;
 import java.io.IOException;
 import java.util.*;
 
-class ScopeInfo {
+final class ScopeInfo {
 
   enum Type {
     /**
diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/ScopeUtil.java b/inject-generator/src/main/java/io/avaje/inject/generator/ScopeUtil.java
index 53cd2c91..03d55f5c 100644
--- a/inject-generator/src/main/java/io/avaje/inject/generator/ScopeUtil.java
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/ScopeUtil.java
@@ -6,7 +6,7 @@ import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 
-class ScopeUtil {
+final class ScopeUtil {
 
   private static final String INJECT_MODULE = "io.avaje.inject.InjectModule";
 
diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/SimpleBeanProxyWriter.java b/inject-generator/src/main/java/io/avaje/inject/generator/SimpleBeanProxyWriter.java
index 5ed7c73b..a9c13406 100644
--- a/inject-generator/src/main/java/io/avaje/inject/generator/SimpleBeanProxyWriter.java
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/SimpleBeanProxyWriter.java
@@ -5,7 +5,7 @@ import javax.tools.JavaFileObject;
 import java.io.IOException;
 import java.io.Writer;
 
-class SimpleBeanProxyWriter {
+final class SimpleBeanProxyWriter {
 
   private final BeanReader beanReader;
   private final ProcessingContext context;
@@ -108,7 +108,7 @@ class SimpleBeanProxyWriter {
   private void writeClassStart() {
     writer.append(Constants.AT_PROXY).eol();
     writer.append(Constants.AT_GENERATED).eol();
-    writer.append("public class %s%s extends %s {", shortName, suffix, shortName).eol().eol();
+    writer.append("public final class %s%s extends %s {", shortName, suffix, shortName).eol().eol();
   }
 
   private Writer createFileWriter() throws IOException {
diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/SimpleBeanWriter.java b/inject-generator/src/main/java/io/avaje/inject/generator/SimpleBeanWriter.java
index c6aa0ed9..77b72b8b 100644
--- a/inject-generator/src/main/java/io/avaje/inject/generator/SimpleBeanWriter.java
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/SimpleBeanWriter.java
@@ -10,7 +10,7 @@ import java.util.Set;
 /**
  * Write the source code for the bean.
  */
-class SimpleBeanWriter {
+final class SimpleBeanWriter {
 
   private static final String CODE_COMMENT = "/**\n * Generated source - dependency injection builder for %s.\n */";
   private static final String CODE_COMMENT_FACTORY = "/**\n * Generated source - dependency injection factory for request scoped %s.\n */";
@@ -73,7 +73,8 @@ class SimpleBeanWriter {
     if (!genericTypes.isEmpty()) {
       for (GenericType type : genericTypes) {
         writer.append("  public static final Type TYPE_%s = new GenericType<", type.shortName());
-        type.writeShort(writer);
+        // use fully qualified types here rather than use type.writeShort(writer)
+        writer.append(type.toString());
         writer.append(">(){}.type();").eol();
       }
       writer.eol();
@@ -100,7 +101,9 @@ class SimpleBeanWriter {
     method.buildAddFor(writer);
     writer.append(method.builderGetFactory()).eol();
     if (method.isProtoType()) {
-      method.builderAddProtoBean(writer);
+      method.builderAddBeanProvider(writer);
+    } else if (method.isUseProviderForSecondary()) {
+      method.builderAddBeanProvider(writer);
     } else {
       method.builderBuildBean(writer);
       method.builderBuildAddBean(writer);
@@ -128,8 +131,7 @@ class SimpleBeanWriter {
     beanReader.buildAddFor(writer);
     if (beanReader.prototype()) {
       indent += "  ";
-      writer.append("      // prototype scope so register provider").eol();
-      writer.append("      builder.registerProvider(() -> {", shortName, shortName).eol();
+      writer.append("      builder.asPrototype().registerProvider(() -> {", shortName, shortName).eol();
     }
     writeCreateBean(constructor);
     beanReader.buildRegister(writer);
@@ -156,7 +158,7 @@ class SimpleBeanWriter {
 
   String indent = "     ";
   private void writeCreateBean(MethodReader constructor) {
-    writer.append("%s %s bean = new %s(", indent, shortName, shortName);
+    writer.append(indent).append(" var bean = new %s(", shortName);
     // add constructor dependencies
     writeMethodParams("builder", constructor);
   }
@@ -225,7 +227,7 @@ class SimpleBeanWriter {
     if (beanReader.getBeanType().getNestingKind().isNested()) {
       shortName = shortName.replace(".", "$");
     }
-    writer.append("public class ").append(shortName).append(suffix).append(" ");
+    writer.append("public final class ").append(shortName).append(suffix).append(" ");
     if (beanReader.isRequestScopedController()) {
       writer.append("implements ");
       beanReader.factoryInterface(writer);
diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/SimpleModuleWriter.java b/inject-generator/src/main/java/io/avaje/inject/generator/SimpleModuleWriter.java
index 52fd9a9a..9835da20 100644
--- a/inject-generator/src/main/java/io/avaje/inject/generator/SimpleModuleWriter.java
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/SimpleModuleWriter.java
@@ -13,7 +13,7 @@ import java.util.TreeSet;
 /**
  * Write the source code for the factory.
  */
-class SimpleModuleWriter {
+final class SimpleModuleWriter {
 
   private static final String CODE_COMMENT_FACTORY =
     "/**\n" +
@@ -171,7 +171,7 @@ class SimpleModuleWriter {
     scopeInfo.buildAtInjectModule(writer);
 
     String interfaceType = scopeInfo.type().type();
-    writer.append("public class %s implements %s {", shortName, interfaceType).eol().eol();
+    writer.append("public final class %s implements %s {", shortName, interfaceType).eol().eol();
     scopeInfo.buildFields(writer);
     if (scopeInfo.addModuleConstructor()) {
       writeConstructor();
diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/TypeAnnotationReader.java b/inject-generator/src/main/java/io/avaje/inject/generator/TypeAnnotationReader.java
index 7daba57b..3ebc6276 100644
--- a/inject-generator/src/main/java/io/avaje/inject/generator/TypeAnnotationReader.java
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/TypeAnnotationReader.java
@@ -11,7 +11,7 @@ import java.util.List;
 /**
  * Read the annotations on the type.
  */
-class TypeAnnotationReader {
+final class TypeAnnotationReader {
 
   private final TypeElement beanType;
   private final ProcessingContext context;
diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/TypeAppender.java b/inject-generator/src/main/java/io/avaje/inject/generator/TypeAppender.java
index 56400333..e894dba8 100644
--- a/inject-generator/src/main/java/io/avaje/inject/generator/TypeAppender.java
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/TypeAppender.java
@@ -7,13 +7,13 @@ import java.util.Set;
 /**
  * Helper for building the registration types.
  */
-class TypeAppender {
+final class TypeAppender {
 
-  private final Set<String> importTypes;
+  private final ImportTypeMap importTypes;
   private final Set<String> types = new LinkedHashSet<>();
   private final Set<GenericType> genericTypes = new LinkedHashSet<>();
 
-  TypeAppender(Set<String> importTypes) {
+  TypeAppender(ImportTypeMap importTypes) {
     this.importTypes = importTypes;
   }
 
@@ -37,8 +37,8 @@ class TypeAppender {
   }
 
   void addSimpleType(String classType) {
-    importTypes.add(classType);
-    types.add(Util.shortName(classType) + ".class");
+    String shortName = importTypes.add(classType);
+    types.add(shortName + ".class");
   }
 
   private void addGenericType(GenericType genericType) {
diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/TypeExtendsInjection.java b/inject-generator/src/main/java/io/avaje/inject/generator/TypeExtendsInjection.java
index 9255d79b..77818e6d 100644
--- a/inject-generator/src/main/java/io/avaje/inject/generator/TypeExtendsInjection.java
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/TypeExtendsInjection.java
@@ -1,7 +1,6 @@
 package io.avaje.inject.generator;
 
 import io.avaje.inject.Bean;
-import io.avaje.inject.Prototype;
 import jakarta.inject.Inject;
 
 import javax.lang.model.element.Element;
@@ -14,7 +13,7 @@ import java.util.*;
  * Read points for field injection and method injection
  * on baseType plus inherited injection points.
  */
-class TypeExtendsInjection {
+final class TypeExtendsInjection {
 
   private MethodReader injectConstructor;
   private final List<MethodReader> otherConstructors = new ArrayList<>();
@@ -25,6 +24,7 @@ class TypeExtendsInjection {
   private final List<AspectMethod> aspectMethods = new ArrayList<>();
   private final Map<String, Integer> nameIndex = new HashMap<>();
 
+  private final ImportTypeMap importTypes;
   private final TypeElement baseType;
   private final ProcessingContext context;
   private final boolean factory;
@@ -32,7 +32,8 @@ class TypeExtendsInjection {
   private Element postConstructMethod;
   private Element preDestroyMethod;
 
-  TypeExtendsInjection(TypeElement baseType, ProcessingContext context, boolean factory) {
+  TypeExtendsInjection(TypeElement baseType, ProcessingContext context, boolean factory, ImportTypeMap importTypes) {
+    this.importTypes = importTypes;
     this.baseType = baseType;
     this.context = context;
     this.factory = factory;
@@ -71,7 +72,7 @@ class TypeExtendsInjection {
     }
 
     ExecutableElement ex = (ExecutableElement) element;
-    MethodReader methodReader = new MethodReader(context, ex, baseType).read();
+    MethodReader methodReader = new MethodReader(context, ex, baseType, importTypes).read();
     Inject inject = element.getAnnotation(Inject.class);
     if (inject != null) {
       injectConstructor = methodReader;
@@ -96,7 +97,7 @@ class TypeExtendsInjection {
     final String methodKey = methodElement.getSimpleName().toString();
     if (inject != null && !notInjectMethods.contains(methodKey)) {
       if (!injectMethods.containsKey(methodKey)) {
-        MethodReader methodReader = new MethodReader(context, methodElement, type).read();
+        MethodReader methodReader = new MethodReader(context, methodElement, type, importTypes).read();
         if (methodReader.isNotPrivate()) {
           injectMethods.put(methodKey, methodReader);
           checkAspect = false;
@@ -146,8 +147,7 @@ class TypeExtendsInjection {
 
   private void addFactoryMethod(ExecutableElement methodElement, Bean bean) {
     String qualifierName = Util.getNamed(methodElement);
-    boolean prototype = methodElement.getAnnotation(Prototype.class) != null;
-    factoryMethods.add(new MethodReader(context, methodElement, baseType, bean, qualifierName, prototype).read());
+    factoryMethods.add(new MethodReader(context, methodElement, baseType, bean, qualifierName, importTypes).read());
   }
 
   BeanAspects hasAspects() {
diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/TypeExtendsReader.java b/inject-generator/src/main/java/io/avaje/inject/generator/TypeExtendsReader.java
index ae5737ff..5c172619 100644
--- a/inject-generator/src/main/java/io/avaje/inject/generator/TypeExtendsReader.java
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/TypeExtendsReader.java
@@ -10,7 +10,7 @@ import java.util.List;
 /**
  * Read the inheritance types for a given bean type.
  */
-class TypeExtendsReader {
+final class TypeExtendsReader {
 
   private static final String JAVA_LANG_OBJECT = "java.lang.Object";
   private final GenericType baseGenericType;
@@ -28,11 +28,11 @@ class TypeExtendsReader {
    */
   private String qualifierName;
 
-  TypeExtendsReader(GenericType baseGenericType, TypeElement baseType, ProcessingContext context, boolean factory) {
+  TypeExtendsReader(GenericType baseGenericType, TypeElement baseType, ProcessingContext context, boolean factory, ImportTypeMap importTypes) {
     this.baseGenericType = baseGenericType;
     this.baseType = baseType;
     this.context = context;
-    this.extendsInjection = new TypeExtendsInjection(baseType, context, factory);
+    this.extendsInjection = new TypeExtendsInjection(baseType, context, factory, importTypes);
     this.beanSimpleName = baseType.getSimpleName().toString();
     this.baseTypeRaw = Util.unwrapProvider(baseGenericType.toString());
   }
diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/TypeReader.java b/inject-generator/src/main/java/io/avaje/inject/generator/TypeReader.java
index e2b9622a..d6ce68cd 100644
--- a/inject-generator/src/main/java/io/avaje/inject/generator/TypeReader.java
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/TypeReader.java
@@ -4,33 +4,32 @@ import jakarta.inject.Named;
 
 import javax.lang.model.element.Element;
 import javax.lang.model.element.TypeElement;
-import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Set;
 
-class TypeReader {
+final class TypeReader {
 
   private final boolean forBean;
   private final TypeElement beanType;
-  private final Set<String> importTypes;
+  private final ImportTypeMap importTypes;
   private final TypeExtendsReader extendsReader;
   private final TypeAnnotationReader annotationReader;
   private Set<GenericType> genericTypes;
   private String typesRegister;
 
-  TypeReader(GenericType genericType, TypeElement beanType, ProcessingContext context, Set<String> importTypes, boolean factory) {
+  TypeReader(GenericType genericType, TypeElement beanType, ProcessingContext context, ImportTypeMap importTypes, boolean factory) {
     this(genericType, true, beanType, context, importTypes, factory);
   }
 
-  TypeReader(GenericType genericType, TypeElement returnElement, ProcessingContext context) {
-    this(genericType, false, returnElement, context, new LinkedHashSet<>(), false);
+  TypeReader(GenericType genericType, TypeElement returnElement, ProcessingContext context, ImportTypeMap importTypes) {
+    this(genericType, false, returnElement, context, importTypes, false);
   }
 
-  private TypeReader(GenericType genericType, boolean forBean, TypeElement beanType, ProcessingContext context, Set<String> importTypes, boolean factory) {
+  private TypeReader(GenericType genericType, boolean forBean, TypeElement beanType, ProcessingContext context, ImportTypeMap importTypes, boolean factory) {
     this.forBean = forBean;
     this.beanType = beanType;
     this.importTypes = importTypes;
-    this.extendsReader = new TypeExtendsReader(genericType, beanType, context, factory);
+    this.extendsReader = new TypeExtendsReader(genericType, beanType, context, factory, importTypes);
     this.annotationReader = new TypeAnnotationReader(beanType, context);
   }
 
@@ -46,10 +45,6 @@ class TypeReader {
     return extendsReader.isCloseable();
   }
 
-  void addImports(Set<String> importTypes) {
-    importTypes.addAll(this.importTypes);
-  }
-
   BeanAspects hasAspects() {
     return extendsReader.hasAspects();
   }
@@ -112,14 +107,11 @@ class TypeReader {
     this.typesRegister = appender.asString();
   }
 
-  void extraImports(Set<String> importTypes) {
+  void extraImports(ImportTypeMap importTypes) {
     if (!genericTypes.isEmpty()) {
       importTypes.add(Constants.TYPE);
       importTypes.add(Constants.GENERICTYPE);
-      importTypes.add(Constants.PROVIDER);
-      for (GenericType type : genericTypes) {
-        type.addImports(importTypes);
-      }
+      // TYPE_ generic types are fully qualified
     }
   }
 }
diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/Util.java b/inject-generator/src/main/java/io/avaje/inject/generator/Util.java
index ca48dc13..da914cda 100644
--- a/inject-generator/src/main/java/io/avaje/inject/generator/Util.java
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/Util.java
@@ -7,7 +7,7 @@ import javax.lang.model.element.Element;
 import javax.lang.model.type.DeclaredType;
 import javax.lang.model.type.TypeMirror;
 
-class Util {
+final class Util {
 
   static final String PROVIDER_PREFIX = "jakarta.inject.Provider<";
   private static final String OPTIONAL_PREFIX = "java.util.Optional<";
diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/UtilType.java b/inject-generator/src/main/java/io/avaje/inject/generator/UtilType.java
index 6978f4ed..bf0c749a 100644
--- a/inject-generator/src/main/java/io/avaje/inject/generator/UtilType.java
+++ b/inject-generator/src/main/java/io/avaje/inject/generator/UtilType.java
@@ -1,6 +1,6 @@
 package io.avaje.inject.generator;
 
-class UtilType {
+final class UtilType {
 
   private enum Type {
     LIST,
diff --git a/inject/src/main/java/io/avaje/inject/DBeanScopeBuilder.java b/inject/src/main/java/io/avaje/inject/DBeanScopeBuilder.java
index a01febe4..841c6410 100644
--- a/inject/src/main/java/io/avaje/inject/DBeanScopeBuilder.java
+++ b/inject/src/main/java/io/avaje/inject/DBeanScopeBuilder.java
@@ -118,7 +118,7 @@ class DBeanScopeBuilder implements BeanScopeBuilder.ForTesting {
   }
 
   private <D> BeanScopeBuilder.ForTesting mock(Class<D> type, @Nullable String name, @Nullable Consumer<D> consumer) {
-    suppliedBeans.add(SuppliedBean.of(name, type, null, consumer));
+    suppliedBeans.add(SuppliedBean.of(name, type, consumer));
     return this;
   }
 
diff --git a/inject/src/main/java/io/avaje/inject/spi/Builder.java b/inject/src/main/java/io/avaje/inject/spi/Builder.java
index 6e5d27cd..d8bda077 100644
--- a/inject/src/main/java/io/avaje/inject/spi/Builder.java
+++ b/inject/src/main/java/io/avaje/inject/spi/Builder.java
@@ -54,26 +54,33 @@ public interface Builder {
   boolean isAddBeanFor(Type... types);
 
   /**
-   * Register the provider into the context.
+   * Register the next bean as having Primary priority.
+   * Highest priority, will be used over any other matching beans.
    */
-  <T> void registerProvider(Provider<T> provider);
+  Builder asPrimary();
 
   /**
-   * Register the bean instance into the context.
-   *
-   * @param bean The bean instance that has been created.
+   * Register the next bean as having Secondary priority.
+   * Lowest priority, only used if no other matching beans are available.
    */
-  <T> T register(T bean);
+  Builder asSecondary();
 
   /**
-   * Register the bean as a Primary bean.
+   * Register the next bean as having Prototype scope.
    */
-  <T> T registerPrimary(T bean);
+  Builder asPrototype();
 
   /**
-   * Register the bean as a secondary bean.
+   * Register the provider into the context.
    */
-  <T> T registerSecondary(T bean);
+  <T> void registerProvider(Provider<T> provider);
+
+  /**
+   * Register the bean instance into the context.
+   *
+   * @param bean The bean instance that has been created.
+   */
+  <T> T register(T bean);
 
   /**
    * Register the externally provided bean.
diff --git a/inject/src/main/java/io/avaje/inject/spi/DBeanMap.java b/inject/src/main/java/io/avaje/inject/spi/DBeanMap.java
index 607b88bb..6bce8a17 100644
--- a/inject/src/main/java/io/avaje/inject/spi/DBeanMap.java
+++ b/inject/src/main/java/io/avaje/inject/spi/DBeanMap.java
@@ -1,5 +1,6 @@
 package io.avaje.inject.spi;
 
+import io.avaje.inject.BeanEntry;
 import io.avaje.inject.BeanScope;
 import jakarta.inject.Provider;
 
@@ -10,13 +11,11 @@ import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 
-import static io.avaje.inject.BeanEntry.SUPPLIED;
-
 /**
  * Map of types (class types, interfaces and annotations) to a DContextEntry where the
  * entry holds a list of bean instances for that type.
  */
-class DBeanMap {
+final class DBeanMap {
 
   private final Map<String, DContextEntry> beans = new LinkedHashMap<>();
 
@@ -44,32 +43,30 @@ class DBeanMap {
   /**
    * Add test double supplied beans.
    */
-  @SuppressWarnings("rawtypes")
   void add(List<SuppliedBean> suppliedBeans) {
     for (SuppliedBean suppliedBean : suppliedBeans) {
       addSuppliedBean(suppliedBean);
     }
   }
 
-  @SuppressWarnings("rawtypes")
   private void addSuppliedBean(SuppliedBean supplied) {
     Type suppliedType = supplied.type();
-    DContextEntryBean entryBean = DContextEntryBean.of(supplied.bean(), supplied.name(), SUPPLIED);
+    DContextEntryBean entryBean = DContextEntryBean.of(supplied.source(), supplied.name(), supplied.priority());
     beans.computeIfAbsent(suppliedType.getTypeName(), s -> new DContextEntry()).add(entryBean);
     for (Class<?> anInterface : supplied.interfaces()) {
       beans.computeIfAbsent(anInterface.getTypeName(), s -> new DContextEntry()).add(entryBean);
     }
   }
 
-  void register(int flag, Object bean) {
-    DContextEntryBean entryBean = DContextEntryBean.of(bean, nextBean.name, flag);
+  void register(Object bean) {
+    DContextEntryBean entryBean = DContextEntryBean.of(bean, nextBean.name, nextBean.priority);
     for (Type type : nextBean.types) {
       beans.computeIfAbsent(type.getTypeName(), s -> new DContextEntry()).add(entryBean);
     }
   }
 
-  void register(int flag, Provider<?> provider) {
-    DContextEntryBean entryBean = DContextEntryBean.provider(provider, nextBean.name, flag);
+  void register(Provider<?> provider) {
+    DContextEntryBean entryBean = DContextEntryBean.provider(nextBean.prototype, provider, nextBean.name, nextBean.priority);
     for (Type type : nextBean.types) {
       beans.computeIfAbsent(type.getTypeName(), s -> new DContextEntry()).add(entryBean);
     }
@@ -177,6 +174,20 @@ class DBeanMap {
     nextBean = new NextBean(name, types);
   }
 
+  /**
+   * Set the priority for the next bean to register.
+   */
+  void nextPriority(int priority) {
+    nextBean.priority = priority;
+  }
+
+  /**
+   * Set the next bean to register as having Prototype scope.
+   */
+  void nextPrototype() {
+    nextBean.prototype = true;
+  }
+
   /**
    * Return the types of the bean being processed/registered.
    */
@@ -187,6 +198,8 @@ class DBeanMap {
   static class NextBean {
     final String name;
     final Type[] types;
+    int priority = BeanEntry.NORMAL;
+    boolean prototype;
 
     NextBean(String name, Type[] types) {
       this.name = name;
diff --git a/inject/src/main/java/io/avaje/inject/spi/DBuilder.java b/inject/src/main/java/io/avaje/inject/spi/DBuilder.java
index 5da8a591..c312f173 100644
--- a/inject/src/main/java/io/avaje/inject/spi/DBuilder.java
+++ b/inject/src/main/java/io/avaje/inject/spi/DBuilder.java
@@ -158,35 +158,40 @@ class DBuilder implements Builder {
 
   @Override
   public final <T> T register(T bean) {
-    return register(BeanEntry.NORMAL, bean);
+    bean = enrich(bean, beanMap.next());
+    beanMap.register(bean);
+    return bean;
   }
 
   @Override
-  public final <T> T registerPrimary(T bean) {
-    return register(BeanEntry.PRIMARY, bean);
+  public Builder asPrimary() {
+    beanMap.nextPriority(BeanEntry.PRIMARY);
+    return this;
   }
 
   @Override
-  public final <T> T registerSecondary(T bean) {
-    return register(BeanEntry.SECONDARY, bean);
+  public Builder asSecondary() {
+    beanMap.nextPriority(BeanEntry.SECONDARY);
+    return this;
   }
 
-  private <T> T register(int flag, T bean) {
-    bean = enrich(bean, beanMap.next());
-    beanMap.register(flag, bean);
-    return bean;
+  @Override
+  public Builder asPrototype() {
+    beanMap.nextPrototype();
+    return this;
   }
 
   @Override
   public final <T> void registerProvider(Provider<T> provider) {
     // no enrichment
-    beanMap.register(BeanEntry.NORMAL, provider);
+    beanMap.register(provider);
   }
 
   @Override
   public final <T> void withBean(Class<T> type, T bean) {
     next(null, type);
-    beanMap.register(BeanEntry.SUPPLIED, bean);
+    beanMap.nextPriority(BeanEntry.SUPPLIED);
+    beanMap.register(bean);
   }
 
   @Override
diff --git a/inject/src/main/java/io/avaje/inject/spi/DBuilderExtn.java b/inject/src/main/java/io/avaje/inject/spi/DBuilderExtn.java
index ef7d47e5..f6fbe709 100644
--- a/inject/src/main/java/io/avaje/inject/spi/DBuilderExtn.java
+++ b/inject/src/main/java/io/avaje/inject/spi/DBuilderExtn.java
@@ -52,7 +52,8 @@ final class DBuilderExtn extends DBuilder {
     if (parentMatch != null && !enrichMap.isEmpty()) {
       Object enrichedBean = enrich(parentMatch, beanMap.next());
       if (enrichedBean != parentMatch) {
-        beanMap.register(BeanEntry.SUPPLIED, enrichedBean);
+        beanMap.nextPriority(BeanEntry.SUPPLIED);
+        beanMap.register(enrichedBean);
       }
     }
   }
diff --git a/inject/src/main/java/io/avaje/inject/spi/DContextEntryBean.java b/inject/src/main/java/io/avaje/inject/spi/DContextEntryBean.java
index 68f29129..376d3c48 100644
--- a/inject/src/main/java/io/avaje/inject/spi/DContextEntryBean.java
+++ b/inject/src/main/java/io/avaje/inject/spi/DContextEntryBean.java
@@ -13,20 +13,20 @@ class DContextEntryBean {
   /**
    * Create taking into account if it is a Provider or the bean itself.
    *
-   * @param bean The bean itself or provider of the bean
+   * @param source The bean itself or provider of the bean
    * @param name The optional name for the bean
    * @param flag The flag for primary, secondary or normal
    */
-  static DContextEntryBean of(Object bean, String name, int flag) {
-    if (bean instanceof Provider) {
-      return new Prov((Provider<?>)bean, name, flag);
+  static DContextEntryBean of(Object source, String name, int flag) {
+    if (source instanceof Provider) {
+      return new ProtoProvider((Provider<?>)source, name, flag);
     } else {
-      return new DContextEntryBean(bean, name, flag);
+      return new DContextEntryBean(source, name, flag);
     }
   }
 
-  static DContextEntryBean provider(Provider<?> provider, String name, int flag) {
-      return new Prov(provider, name, flag);
+  static DContextEntryBean provider(boolean prototype, Provider<?> provider, String name, int flag) {
+    return prototype ? new ProtoProvider(provider, name, flag) : new OnceProvider(provider, name, flag);
   }
 
   protected final Object source;
@@ -102,13 +102,13 @@ class DContextEntryBean {
   }
 
   /**
-   * Provider based entry - provider controls the scope of the provided bean.
+   * Prototype scope Provider based entry.
    */
-  static final class Prov extends DContextEntryBean {
+  static final class ProtoProvider extends DContextEntryBean {
 
     private final Provider<?> provider;
 
-    private Prov(Provider<?> provider, String name, int flag) {
+    private ProtoProvider(Provider<?> provider, String name, int flag) {
       super(provider, name, flag);
       this.provider = provider;
     }
@@ -123,4 +123,28 @@ class DContextEntryBean {
       return provider.get();
     }
   }
+
+  /**
+   * Single instance scoped Provider based entry.
+   */
+  static final class OnceProvider extends DContextEntryBean {
+
+    private final Provider<?> provider;
+    private Object bean;
+
+    private OnceProvider(Provider<?> provider, String name, int flag) {
+      super(provider, name, flag);
+      this.provider = provider;
+    }
+
+    @Override
+    Object bean() {
+      synchronized (this) {
+        if (bean == null) {
+          bean = provider.get();
+        }
+        return bean;
+      }
+    }
+  }
 }
diff --git a/inject/src/main/java/io/avaje/inject/spi/SuppliedBean.java b/inject/src/main/java/io/avaje/inject/spi/SuppliedBean.java
index acc1bfb0..225dc267 100644
--- a/inject/src/main/java/io/avaje/inject/spi/SuppliedBean.java
+++ b/inject/src/main/java/io/avaje/inject/spi/SuppliedBean.java
@@ -1,8 +1,8 @@
 package io.avaje.inject.spi;
 
-import org.mockito.Mockito;
-
+import io.avaje.inject.BeanEntry;
 import jakarta.inject.Named;
+import org.mockito.Mockito;
 
 import java.lang.reflect.Type;
 import java.util.function.Consumer;
@@ -12,135 +12,120 @@ import java.util.function.Consumer;
  * <p>
  * These can be externally supplied dependencies or test doubles for testing purposes.
  */
-public abstract class SuppliedBean<B> {
+public class SuppliedBean {
 
-  protected final String name;
-  protected final Type type;
-  protected B bean;
+  private static final Class<?>[] NO_INTERFACES = new Class[0];
+
+  private final String name;
+  private final Type type;
+  private final int priority;
+  protected Object source;
 
   /**
    * Create with a class type and bean instance.
    */
   @SuppressWarnings({"rawtypes", "unchecked"})
-  public static SuppliedBean of(Class<?> type, Object bean) {
-    return new SuppliedBean.ForClass(null, type, bean, null);
+  public static SuppliedBean of(Class<?> type, Object source) {
+    return new SuppliedBean.ForClass(null, type, source, null);
   }
 
   /**
    * Create for a class type with a consumer that runs once when the bean is obtained.
    */
-  public static <B> SuppliedBean<B> of(String name, Class<B> type, B bean, Consumer<B> consumer) {
-    return new SuppliedBean.ForClass<>(name, type, bean, consumer);
+  public static <B> SuppliedBean of(String name, Class<B> type, Consumer<B> consumer) {
+    return new SuppliedBean.ForClass<>(name, type, null, consumer);
   }
 
   /**
    * Create for a class type with name.
    */
-  public static <B> SuppliedBean<B> of(String name, Class<B> type, B bean) {
-    return new SuppliedBean.ForClass<>(name, type, bean, null);
+  public static <B> SuppliedBean of(String name, Class<B> type, B source) {
+    return new SuppliedBean.ForClass<>(name, type, source, null);
   }
 
   /**
    * Create a supplied bean for a generic type.
    */
-  public static <B> SuppliedBean<B> ofType(String name, Type type, B bean) {
-    return new SuppliedBean.ForType<>(name, type, bean);
+  public static SuppliedBean ofType(String name, Type type, Object source) {
+    return new SuppliedBean(BeanEntry.SUPPLIED, name, type, source);
   }
 
-  private SuppliedBean(String name, Type type, B bean) {
+  private SuppliedBean(int priority, String name, Type type, Object source) {
+    this.priority = priority;
     this.name = name;
     this.type = type;
-    this.bean = bean;
+    this.source = source;
   }
 
-  private SuppliedBean(String name, Class<B> type, B bean) {
-    this.name = initName(name, type);
-    this.type = type;
-    this.bean = bean;
+  /**
+   * Return the bean instance or provider to use for injection.
+   */
+  public Object source() {
+    return source;
   }
 
-
-  String initName(String name, Class<?> type) {
-    if (name != null) {
-      return name;
-    }
-    Named annotation = type.getAnnotation(Named.class);
-    return (annotation == null) ? null : annotation.value();
+  /**
+   * Return the associated priority.
+   */
+  public final int priority() {
+    return priority;
   }
 
   /**
    * Return the dependency injection target type.
    */
-  public Type type() {
+  public final Type type() {
     return type;
   }
 
   /**
    * Return the qualifier name of the supplied bean.
    */
-  public String name() {
-    return name;
+  public final String name() {
+    if (name != null) {
+      return name;
+    }
+    if (type instanceof Class<?>) {
+      Named annotation = ((Class<?>) type).getAnnotation(Named.class);
+      return (annotation == null) ? null : annotation.value();
+    }
+    return null;
   }
 
-  /**
-   * Return the bean instance to use for injection.
-   */
-  public abstract B bean();
-
   /**
    * Return the interfaces to additionally register along with the type.
    */
-  public abstract Class<?>[] interfaces();
-
-  /**
-   * Type based supplied bean.
-   */
-  private static class ForType<B> extends SuppliedBean<B> {
-
-    private ForType(String name, Type type, B bean) {
-      super(name, type, bean);
-    }
-
-    @Override
-    public B bean() {
-      return bean;
-    }
-
-    @Override
-    public Class<?>[] interfaces() {
-      return new Class[0];
+  public final Class<?>[] interfaces() {
+    if (type instanceof Class<?>) {
+      return ((Class<?>) type).getInterfaces();
     }
+    return NO_INTERFACES;
   }
 
   /**
    * Class based supplied bean.
    */
-  private static class ForClass<B> extends SuppliedBean<B> {
+  private static final class ForClass<B> extends SuppliedBean {
 
     private final Consumer<B> consumer;
     private final Class<B> classType;
 
-    ForClass(String name, Class<B> type, B bean, Consumer<B> consumer) {
-      super(name, type, bean);
+    ForClass(String name, Class<B> type, Object source, Consumer<B> consumer) {
+      super(BeanEntry.SUPPLIED, name, type, source);
       this.classType = type;
       this.consumer = consumer;
     }
 
     @Override
-    public B bean() {
-      if (bean == null) {
-        // should extract a SPI for this
-        bean = Mockito.mock(classType);
+    public Object source() {
+      if (source == null) {
+        var mock = Mockito.mock(classType);
+        if (consumer != null) {
+          consumer.accept(mock);
+        }
+        source = mock;
       }
-      if (consumer != null) {
-        consumer.accept(bean);
-      }
-      return bean;
-    }
-
-    @Override
-    public Class<?>[] interfaces() {
-      return classType.getInterfaces();
+      return source;
     }
   }
 }


Test patch:
diff --git a/blackbox-test-inject/src/main/java/org/example/myapp/config/AppConfig.java b/blackbox-test-inject/src/main/java/org/example/myapp/config/AppConfig.java
index c485b53b..bb080824 100644
--- a/blackbox-test-inject/src/main/java/org/example/myapp/config/AppConfig.java
+++ b/blackbox-test-inject/src/main/java/org/example/myapp/config/AppConfig.java
@@ -1,13 +1,12 @@
 package org.example.myapp.config;
 
-import io.avaje.inject.Bean;
-import io.avaje.inject.Component;
-import io.avaje.inject.Factory;
-import io.avaje.inject.Prototype;
+import io.avaje.inject.*;
 import jakarta.inject.Inject;
-import jakarta.inject.Provider;
+import jakarta.inject.Named;
 import org.example.myapp.HelloData;
 
+import java.util.Optional;
+
 @Factory
 public class AppConfig {
 
@@ -35,19 +34,74 @@ public class AppConfig {
     return new Generated();
   }
 
+  @Secondary
+  @Bean
+  public MySecType generalSecondary() {
+    return new MySecType();
+  }
+
+  @Secondary
+  @Bean
+  public Optional<MySecOptType> optionalSecondary() {
+    return Optional.of(new MySecOptType());
+  }
+
+  @Primary
+  @Bean
+  public MyPrim primaryBean() {
+    return new MyPrim("prime");
+  }
+
+  @Named("notPrimary")
+  @Bean
+  public MyPrim notPrimaryBean() {
+    return new MyPrim("notPrimary");
+  }
+
+  @Bean
+  public Provider provider() {
+    return new Provider();
+  }
+
+  @Bean
+  public MyGen<String> myGen() {
+    return new MyGen<String>();
+  }
+
   public static class Builder {
   }
 
   public static class Generated {
   }
 
+  public static class MySecType {
+  }
+
+  public static class MySecOptType {
+  }
+
+  public static class Provider {
+
+  }
+
+  public static class MyGen<T> {
+
+  }
+
+  public static class MyPrim {
+    public final String val;
+    public MyPrim(String val) {
+      this.val = val;
+    }
+  }
+
   @Component
   public static class BuilderUser {
 
-    final Provider<Builder> builderProvider;
+    final jakarta.inject.Provider<Builder> builderProvider;
 
     @Inject
-    public BuilderUser(Provider<Builder> builderProvider) {
+    public BuilderUser(jakarta.inject.Provider<Builder> builderProvider) {
       this.builderProvider = builderProvider;
     }
 
diff --git a/blackbox-test-inject/src/main/java/org/example/myapp/config/DupShortNameFactory.java b/blackbox-test-inject/src/main/java/org/example/myapp/config/DupShortNameFactory.java
new file mode 100644
index 00000000..dc5d7f98
--- /dev/null
+++ b/blackbox-test-inject/src/main/java/org/example/myapp/config/DupShortNameFactory.java
@@ -0,0 +1,25 @@
+package org.example.myapp.config;
+
+import io.avaje.inject.Bean;
+import io.avaje.inject.Factory;
+
+@Factory
+public class DupShortNameFactory {
+
+  @Bean
+  MyDup one() {
+    return new MyDup();
+  }
+
+  /**
+   * Requires fully qualified name as short name of MyDup clashes.
+   */
+  @Bean
+  org.example.myapp.config.MyDup two() {
+    return new org.example.myapp.config.MyDup();
+  }
+
+  public static class MyDup {
+
+  }
+}
diff --git a/blackbox-test-inject/src/main/java/org/example/myapp/config/MyDup.java b/blackbox-test-inject/src/main/java/org/example/myapp/config/MyDup.java
new file mode 100644
index 00000000..5d0c502d
--- /dev/null
+++ b/blackbox-test-inject/src/main/java/org/example/myapp/config/MyDup.java
@@ -0,0 +1,4 @@
+package org.example.myapp.config;
+
+public class MyDup {
+}
diff --git a/blackbox-test-inject/src/test/java/org/example/myapp/config/AppConfigTest.java b/blackbox-test-inject/src/test/java/org/example/myapp/config/AppConfigTest.java
new file mode 100644
index 00000000..d283a746
--- /dev/null
+++ b/blackbox-test-inject/src/test/java/org/example/myapp/config/AppConfigTest.java
@@ -0,0 +1,36 @@
+package org.example.myapp.config;
+
+import io.avaje.inject.BeanScope;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+class AppConfigTest {
+
+  @Test
+  void factorySecondaryAsProvider() {
+    try (BeanScope testScope = BeanScope.builder().build()) {
+      assertNotNull(testScope.get(AppConfig.class));
+
+      var one = testScope.get(AppConfig.MySecType.class);
+      var two = testScope.get(AppConfig.MySecType.class);
+      assertThat(one).describedAs("Secondary Bean provided once").isSameAs(two);
+
+      var optOne = testScope.get(AppConfig.MySecOptType.class);
+      var optTwo = testScope.get(AppConfig.MySecOptType.class);
+      assertThat(optOne).describedAs("Secondary optional Bean provided once").isSameAs(optTwo);
+
+      var myPrim = testScope.get(AppConfig.MyPrim.class);
+      assertThat(myPrim.val).isEqualTo("prime");
+
+      var notPrimary = testScope.get(AppConfig.MyPrim.class, "notPrimary");
+      assertThat(notPrimary.val).isEqualTo("notPrimary");
+
+      List<AppConfig.MyPrim> list = testScope.list(AppConfig.MyPrim.class);
+      assertThat(list).hasSize(2);
+    }
+  }
+}
diff --git a/blackbox-test-inject/src/test/java/org/example/myapp/config/DupShortNameFactoryTest.java b/blackbox-test-inject/src/test/java/org/example/myapp/config/DupShortNameFactoryTest.java
new file mode 100644
index 00000000..72b41291
--- /dev/null
+++ b/blackbox-test-inject/src/test/java/org/example/myapp/config/DupShortNameFactoryTest.java
@@ -0,0 +1,23 @@
+package org.example.myapp.config;
+
+import io.avaje.inject.BeanScope;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.*;
+
+class DupShortNameFactoryTest {
+
+  @Test
+  void factoryBeanRequiringFullyQualifiedName() {
+    try (BeanScope testScope = BeanScope.builder().build()) {
+      // both have short name of MyDup
+      var one = testScope.get(org.example.myapp.config.MyDup.class);
+      var two = testScope.get(DupShortNameFactory.MyDup.class);
+
+      assertNotNull(one);
+      assertNotNull(two);
+      assertThat(one).isNotSameAs(two);
+    }
+  }
+}
diff --git a/inject-generator/src/test/java/io/avaje/inject/generator/GenericTypeTest.java b/inject-generator/src/test/java/io/avaje/inject/generator/GenericTypeTest.java
index a7de34df..4299b5c2 100644
--- a/inject-generator/src/test/java/io/avaje/inject/generator/GenericTypeTest.java
+++ b/inject-generator/src/test/java/io/avaje/inject/generator/GenericTypeTest.java
@@ -3,7 +3,6 @@ package io.avaje.inject.generator;
 import org.junit.jupiter.api.Test;
 
 import java.io.StringWriter;
-import java.util.HashSet;
 import java.util.LinkedHashSet;
 import java.util.Set;
 
@@ -60,10 +59,11 @@ class GenericTypeTest {
     assertThat(type.isGenericType()).isFalse();
     assertThat(type.isProviderType()).isFalse();
 
-    Set<String> importSet = new HashSet<>();
+    ImportTypeMap importSet = new ImportTypeMap();
     type.addImports(importSet);
-    assertThat(importSet).hasSize(1);
-    assertThat(importSet).containsOnly("my.exa.Repo");
+    Set<String> forImport = importSet.forImport();
+    assertThat(forImport).hasSize(1);
+    assertThat(forImport).containsOnly("my.exa.Repo");
   }
 
   @Test
@@ -96,10 +96,11 @@ class GenericTypeTest {
     assertThat(type.getParams()).isEmpty();
     assertThat(type.isGenericType()).isFalse();
 
-    Set<String> importSet = new HashSet<>();
+    ImportTypeMap importSet = new ImportTypeMap();
     type.addImports(importSet);
-    assertThat(importSet).hasSize(1);
-    assertThat(importSet).containsOnly("my.exa.Repo");
+    Set<String> forImport = importSet.forImport();
+    assertThat(forImport).hasSize(1);
+    assertThat(forImport).containsOnly("my.exa.Repo");
   }
 
   @Test
@@ -112,10 +113,11 @@ class GenericTypeTest {
     assertThat(type.getParams()).hasSize(1);
     assertThat(type.getParams().get(0).getMainType()).isEqualTo("my.Other");
 
-    Set<String> importSet = new HashSet<>();
+    ImportTypeMap importSet = new ImportTypeMap();
     type.addImports(importSet);
-    assertThat(importSet).hasSize(2);
-    assertThat(importSet).contains("my.exa.Repo", "my.Other");
+    Set<String> forImport = importSet.forImport();
+    assertThat(forImport).hasSize(2);
+    assertThat(forImport).contains("my.exa.Repo", "my.Other");
   }
 
   @Test
@@ -139,11 +141,11 @@ class GenericTypeTest {
     assertThat(type.getParams().get(0).getMainType()).isEqualTo("java.lang.String");
     assertThat(type.getParams().get(1).getMainType()).isEqualTo("my.d.Has");
 
-    LinkedHashSet<String> importTypes = new LinkedHashSet<>();
-    type.addImports(importTypes);
-
-    assertThat(importTypes).hasSize(2);
-    assertThat(importTypes).containsExactly("java.util.concurrent.ConcurrentMap", "my.d.Has");
+    ImportTypeMap importSet = new ImportTypeMap();
+    type.addImports(importSet);
+    Set<String> forImport = importSet.forImport();
+    assertThat(forImport).hasSize(2);
+    assertThat(forImport).containsExactly("java.util.concurrent.ConcurrentMap", "my.d.Has");
   }
 
   @Test
diff --git a/inject-generator/src/test/java/io/avaje/inject/generator/ImportTypeMapTest.java b/inject-generator/src/test/java/io/avaje/inject/generator/ImportTypeMapTest.java
new file mode 100644
index 00000000..6c1ac26e
--- /dev/null
+++ b/inject-generator/src/test/java/io/avaje/inject/generator/ImportTypeMapTest.java
@@ -0,0 +1,28 @@
+package io.avaje.inject.generator;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.Set;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class ImportTypeMapTest {
+
+  @Test
+  void add() {
+    ImportTypeMap map = new ImportTypeMap();
+
+    String sn = map.add("org.foo.Bax");
+    assertThat(sn).isEqualTo("Bax");
+
+    String sn1 = map.add("org.foo.Bazz");
+    assertThat(sn1).isEqualTo("Bazz");
+
+    String sn2 = map.add("org.other.Bax");
+    assertThat(sn2).isEqualTo("org.other.Bax");
+
+    Set<String> forImports = map.forImport();
+    assertThat(forImports).hasSize(2);
+    assertThat(forImports).containsExactly("org.foo.Bax", "org.foo.Bazz");
+  }
+}
diff --git a/inject-test/src/test/java/org/example/coffee/BeanScope_Builder_mockitoSpyTest.java b/inject-test/src/test/java/org/example/coffee/BeanScope_Builder_mockitoSpyTest.java
index 470d3fe4..f61f1203 100644
--- a/inject-test/src/test/java/org/example/coffee/BeanScope_Builder_mockitoSpyTest.java
+++ b/inject-test/src/test/java/org/example/coffee/BeanScope_Builder_mockitoSpyTest.java
@@ -209,6 +209,9 @@ class BeanScope_Builder_mockitoSpyTest {
       Grinder grinder = context.get(Grinder.class);
       assertThat(grinder).isSameAs(mock.get());
 
+      Grinder grinderByName = context.get(Grinder.class, "theGrinder");
+      assertThat(grinderByName).isSameAs(grinder);
+
       CoffeeMaker coffeeMaker = context.get(CoffeeMaker.class);
       assertThat(coffeeMaker).isNotNull();
       coffeeMaker.makeIt();
diff --git a/inject-test/src/test/java/org/example/coffee/grind/Grinder.java b/inject-test/src/test/java/org/example/coffee/grind/Grinder.java
index a77bac75..7585ad20 100644
--- a/inject-test/src/test/java/org/example/coffee/grind/Grinder.java
+++ b/inject-test/src/test/java/org/example/coffee/grind/Grinder.java
@@ -1,5 +1,6 @@
 package org.example.coffee.grind;
 
+import jakarta.inject.Named;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
 import lombok.Getter;
@@ -19,6 +20,7 @@ import jakarta.inject.Singleton;
 @Setter
 @Slf4j
 @Singleton
+@Named("theGrinder")
 public class Grinder {
 
   final AMusher aMusher;