From 6077896f3f36de199f92a296a9af1e3f3759f1e1 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Thu, 12 Jun 2025 16:06:00 -0400 Subject: [PATCH 01/11] start --- .../io/avaje/inject/generator/BeanReader.java | 27 ++- .../inject/generator/InjectProcessor.java | 46 +++-- .../generator/SimpleBeanLazyWriter.java | 172 ++++++++++++++++++ .../generator/SimpleBeanProxyWriter.java | 2 + .../inject/generator/SimpleBeanWriter.java | 23 ++- .../java/io/avaje/inject/generator/Util.java | 39 +++- .../generator/models/valid/lazy/LazyBean.java | 12 +- .../models/valid/lazy/LazyBeanTypes.java | 23 +++ .../models/valid/lazy/LazyFactory.java | 5 + .../models/valid/lazy/LazyInterface.java | 6 + .../java/io/avaje/inject/spi/Builder.java | 12 +- .../avaje/inject/spi/DContextEntryBean.java | 23 +-- .../io/avaje/inject/spi/OnceProvider.java | 34 ++++ 13 files changed, 372 insertions(+), 52 deletions(-) create mode 100644 inject-generator/src/main/java/io/avaje/inject/generator/SimpleBeanLazyWriter.java create mode 100644 inject-generator/src/test/java/io/avaje/inject/generator/models/valid/lazy/LazyBeanTypes.java create mode 100644 inject-generator/src/test/java/io/avaje/inject/generator/models/valid/lazy/LazyInterface.java create mode 100644 inject/src/main/java/io/avaje/inject/spi/OnceProvider.java 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 0fd9f0f3d..6b3a34fa6 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 @@ -2,7 +2,12 @@ import static io.avaje.inject.generator.APContext.logError; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; import java.util.stream.Stream; import javax.lang.model.element.Element; @@ -36,6 +41,9 @@ final class BeanReader { private final boolean secondary; private final boolean lazy; private final boolean proxy; + private final boolean proxyLazy; + private final TypeElement lazyProxyType; + private final BeanAspects aspects; private final BeanConditions conditions = new BeanConditions(); private final boolean importedComponent; @@ -71,6 +79,8 @@ final class BeanReader { typeReader.process(); + this.lazyProxyType = Util.lazyProxy(beanType); + this.proxyLazy = lazy && lazyProxyType != null; this.requestParams = new BeanRequestParams(type); this.name = typeReader.name(); this.aspects = typeReader.hasAspects(); @@ -82,7 +92,7 @@ final class BeanReader { this.preDestroyPriority = typeReader.preDestroyPriority(); this.constructor = typeReader.constructor(); this.observerMethods = typeReader.observerMethods(); - this.importedComponent = importedComponent && (constructor != null && constructor.isPublic()); + this.importedComponent = importedComponent && constructor != null && constructor.isPublic(); if (ProxyPrism.isPresent(beanType)) { this.proxy = true; @@ -151,6 +161,10 @@ boolean lazy() { return lazy; } + boolean proxyLazy() { + return proxyLazy; + } + boolean importedComponent() { return importedComponent; } @@ -178,6 +192,9 @@ BeanReader read() { postConstructMethod.ifPresent(m -> m.addImports(importTypes)); conditions.addImports(importTypes); + if (proxyLazy) { + SimpleBeanLazyWriter.write(APContext.elements().getPackageOf(beanType), lazyProxyType); + } return this; } @@ -284,7 +301,7 @@ String metaKey() { * Return true if lifecycle via annotated methods is required. */ boolean hasLifecycleMethods() { - return (postConstructMethod.isPresent() || preDestroyMethod != null || typeReader.isClosable()); + return postConstructMethod.isPresent() || preDestroyMethod != null || typeReader.isClosable(); } List createFactoryMethodMeta() { @@ -599,4 +616,8 @@ boolean isDelayed() { void validate() { typeReader.validate(); } + + public TypeElement getLazyProxyType() { + return lazyProxyType; + } } 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 962944c43..410f12ca6 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 @@ -1,10 +1,32 @@ package io.avaje.inject.generator; -import io.avaje.prism.GenerateAPContext; -import io.avaje.prism.GenerateModuleInfoReader; -import io.avaje.prism.GenerateUtils; +import static io.avaje.inject.generator.APContext.logError; +import static io.avaje.inject.generator.APContext.typeElement; +import static io.avaje.inject.generator.ProcessingContext.addImportedAspects; +import static io.avaje.inject.generator.ProcessingContext.delayedElements; +import static io.avaje.inject.generator.ProcessingContext.loadMetaInfCustom; +import static io.avaje.inject.generator.ProcessingContext.loadMetaInfServices; +import static java.util.stream.Collectors.joining; +import static java.util.stream.Collectors.toList; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.StandardOpenOption; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; -import javax.annotation.processing.*; +import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.ProcessingEnvironment; +import javax.annotation.processing.RoundEnvironment; +import javax.annotation.processing.SupportedAnnotationTypes; +import javax.annotation.processing.SupportedOptions; import javax.lang.model.SourceVersion; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; @@ -14,18 +36,9 @@ import javax.lang.model.util.ElementFilter; import javax.lang.model.util.Elements; -import static java.util.stream.Collectors.joining; -import static java.util.stream.Collectors.toList; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.StandardOpenOption; -import java.util.*; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import static io.avaje.inject.generator.APContext.*; -import static io.avaje.inject.generator.ProcessingContext.*; +import io.avaje.prism.GenerateAPContext; +import io.avaje.prism.GenerateModuleInfoReader; +import io.avaje.prism.GenerateUtils; @GenerateUtils @GenerateAPContext @@ -158,7 +171,6 @@ public boolean process(Set annotations, RoundEnvironment readImported(importedElements(roundEnv)); maybeElements(roundEnv, ControllerPrism.PRISM_TYPE).ifPresent(this::readBeans); - maybeElements(roundEnv, ProxyPrism.PRISM_TYPE).ifPresent(this::readBeans); maybeElements(roundEnv, AssistFactoryPrism.PRISM_TYPE).ifPresent(this::readAssisted); maybeElements(roundEnv, ExternalPrism.PRISM_TYPE).stream() diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/SimpleBeanLazyWriter.java b/inject-generator/src/main/java/io/avaje/inject/generator/SimpleBeanLazyWriter.java new file mode 100644 index 000000000..70e383475 --- /dev/null +++ b/inject-generator/src/main/java/io/avaje/inject/generator/SimpleBeanLazyWriter.java @@ -0,0 +1,172 @@ +package io.avaje.inject.generator; + +import static io.avaje.inject.generator.APContext.logError; + +import java.text.MessageFormat; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import javax.lang.model.element.Modifier; +import javax.lang.model.element.PackageElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.TypeParameterElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.util.ElementFilter; + +final class SimpleBeanLazyWriter { + private static final Set GENERATED_PUBLISHERS = new HashSet<>(); + private static final String TEMPLATE = + "package {0};\n\n" + + "{1}" + + "@Proxy\n" + + "@Generated(\"avaje-inject-generator\")\n" + + "public final class {2}$Lazy {3} {2} '{'\n" + + "\n" + + " private final Provider<{2}> onceProvider;\n" + + "\n" + + " public {2}$Lazy(Provider<{2}> onceProvider) '{'\n" + + " this.onceProvider = onceProvider;\n" + + " '}'\n\n" + + " {4}" + + "'}'\n"; + + private final String originName; + private final ImportTypeMap importTypes = new ImportTypeMap(); + private final String packageName; + + static void write(PackageElement pkg, TypeElement element) { + new SimpleBeanLazyWriter(pkg, element); + } + + private final String shortName; + private boolean isInterface; + private TypeElement element; + + SimpleBeanLazyWriter(PackageElement pkg, TypeElement element) { + this.element = element; + this.isInterface = element.getKind().isInterface(); + this.shortName = Util.shortName(element.getQualifiedName().toString()).replace(".", "_"); + this.packageName = pkg.getQualifiedName().toString(); + this.originName = packageName + "." + shortName + "$Lazy"; + + if (GENERATED_PUBLISHERS.contains(originName)) { + return; + } + + importTypes.addAll(UType.parse(element.asType()).importTypes()); + write(); + GENERATED_PUBLISHERS.add(originName); + } + + void write() { + try { + var writer = new Append(APContext.createSourceFile(originName, element).openWriter()); + + var typeString = isInterface ? "implements" : "extends"; + + String methodString = methods(); + writer.append( + MessageFormat.format( + TEMPLATE, packageName, imports(), shortName, typeString, methodString)); + writer.close(); + } catch (Exception e) { + logError("Failed to write Proxy class %s", e); + } + } + + String imports() { + importTypes.add("io.avaje.inject.spi.Proxy"); + importTypes.add("io.avaje.inject.spi.Generated"); + importTypes.add("jakarta.inject.Provider"); + + StringBuilder writer = new StringBuilder(); + for (String importType : importTypes.forImport()) { + if (Util.validImportType(importType, packageName)) { + writer.append(String.format("import %s;\n", Util.sanitizeImports(importType))); + } + } + return writer.append("\n").toString(); + } + + private String methods() { + + var sb = new StringBuilder(); + + for (var methodElement : ElementFilter.methodsIn(APContext.elements().getAllMembers(element))) { + + Set modifiers = methodElement.getModifiers(); + if (modifiers.contains(Modifier.PRIVATE) + || modifiers.contains(Modifier.STATIC) + || methodElement.getEnclosingElement().getSimpleName().contentEquals("Object")) continue; + sb.append(""); + // Access modifiers + if (modifiers.contains(Modifier.PUBLIC)) { + sb.append(" public "); + } else if (modifiers.contains(Modifier.PROTECTED)) { + sb.append(" protected "); + } else { + sb.append(" "); + } + // Generic type parameters + List typeParameters = methodElement.getTypeParameters(); + if (!typeParameters.isEmpty()) { + sb.append("<"); + sb.append( + typeParameters.stream() + .map(tp -> tp.getSimpleName().toString()) + .collect(Collectors.joining(", "))); + sb.append("> "); + } + var returnType = UType.parse(methodElement.getReturnType()); + importTypes.addAll(returnType.importTypes()); + sb.append(returnType.shortType()).append(" "); + + // Method name + String methodName = methodElement.getSimpleName().toString(); + sb.append(methodName); + + // Parameters + sb.append("("); + var parameters = methodElement.getParameters(); + for (int i = 0; i < parameters.size(); i++) { + VariableElement param = parameters.get(i); + + var type = UType.parse(param.asType()); + importTypes.addAll(type.importTypes()); + sb.append(type.shortType()); + sb.append(" "); + sb.append(param.getSimpleName().toString()); + if (i < parameters.size() - 1) { + sb.append(", "); + } + } + sb.append(")"); + + // Thrown exceptions + var thrownTypes = methodElement.getThrownTypes(); + if (!thrownTypes.isEmpty()) { + sb.append(" throws "); + sb.append( + thrownTypes.stream() + .map(t -> UType.parse(t).shortType()) + .collect(Collectors.joining(", "))); + } + + sb.append(" {\n"); + sb.append(" onceProvider.get().").append(methodName); + sb.append("("); + for (int i = 0; i < parameters.size(); i++) { + sb.append(parameters.get(i).getSimpleName().toString()); + if (i < parameters.size() - 1) { + sb.append(", "); + } + } + sb.append(");\n"); + sb.append(" }\n\n"); + } + + return sb.toString(); + } +} 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 d66cebf9b..a8a7ca152 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 @@ -94,6 +94,7 @@ private void writeImports() { writer.append("import %s;", Constants.INVOCATION).eol(); writer.append("import %s;", Constants.INVOCATION_EXCEPTION).eol(); writer.append("import %s;", Constants.METHOD_INTERCEPTOR).eol(); + writer.append("import %s;", Constants.COMPONENT).eol(); writer.append("import %s;", Constants.PROXY).eol(); beanReader.writeImports(writer, packageName); } @@ -104,6 +105,7 @@ private void writeClassEnd() { private void writeClassStart() { writer.append(Constants.AT_PROXY).eol(); + writer.append("@Component").eol(); writer.append(Constants.AT_GENERATED).eol(); writer.append("public final class %s%s extends %s {", shortName, suffix, shortName).eol().eol(); } 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 3aa8f5b5e..128d3c450 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 @@ -178,7 +178,9 @@ private void writeAddFor(MethodReader constructor) { indent += " "; final String registerProvider; - if (beanReader.lazy()) { + if (beanReader.proxyLazy()) { + registerProvider = "registerLazy"; + } else if (beanReader.lazy()) { registerProvider = "registerProvider"; } else { registerProvider = "asPrototype().registerProvider"; @@ -197,7 +199,16 @@ private void writeAddFor(MethodReader constructor) { beanReader.prototypePostConstruct(writer, indent); writer.indent(" return bean;").eol(); if (!constructor.methodThrows()) { - writer.indent(" });").eol(); + writer.append(" }"); + if (beanReader.proxyLazy()) { + writer + .append(", ") + .append( + "%s$Lazy::new", + Util.shortName(beanReader.getLazyProxyType().getQualifiedName().toString()) + .replace(".", "_")); + } + writer.append(");").eol(); } } writeObserveMethods(); @@ -205,6 +216,14 @@ private void writeAddFor(MethodReader constructor) { if (beanReader.registerProvider() && constructor.methodThrows()) { writer.append(" }"); + if (beanReader.proxyLazy()) { + writer + .append(", ") + .append( + " %s$Lazy::new", + Util.shortName(beanReader.getLazyProxyType().getQualifiedName().toString()) + .replace(".", "_")); + } writer.append(");").eol(); } 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 1221ee56b..e6cd1ed63 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 @@ -16,6 +16,7 @@ import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.ElementFilter; final class Util { static final String ASPECT_PROVIDER_PREFIX = "io.avaje.inject.aop.AspectProvider<"; @@ -391,13 +392,35 @@ static String valhalla() { static void validateBeanTypes(Element origin, List beanType) { TypeMirror targetType = - origin instanceof TypeElement - ? origin.asType() - : ((ExecutableElement) origin).getReturnType(); - beanType.forEach(type -> { - if (!APContext.types().isAssignable(targetType, type)) { - APContext.logError(origin, "%s does not extend type %s", targetType, beanType); - } - }); + origin instanceof TypeElement + ? origin.asType() + : ((ExecutableElement) origin).getReturnType(); + beanType.forEach( + type -> { + if (!APContext.types().isAssignable(targetType, type)) { + APContext.logError(origin, "%s does not extend type %s", targetType, beanType); + } + }); + } + + static TypeElement lazyProxy(TypeElement beanType) { + + if (beanType.getModifiers().contains(Modifier.FINAL) + || !beanType.getKind().isInterface() && !Util.hasNoArgConstructor(beanType)) { + + return BeanTypesPrism.getOptionalOn(beanType) + .map(BeanTypesPrism::value) + .filter(v -> v.size() == 1) + .map(v -> APContext.asTypeElement(v.get(0))) + .filter(v -> v.getKind().isInterface() || hasNoArgConstructor(v)) + .orElse(null); + } + + return beanType; + } + + static boolean hasNoArgConstructor(TypeElement beanType) { + return ElementFilter.constructorsIn(beanType.getEnclosedElements()).stream() + .anyMatch(e -> e.getParameters().isEmpty() && !e.getModifiers().contains(Modifier.PRIVATE)); } } diff --git a/inject-generator/src/test/java/io/avaje/inject/generator/models/valid/lazy/LazyBean.java b/inject-generator/src/test/java/io/avaje/inject/generator/models/valid/lazy/LazyBean.java index 2919b2c8f..2035b4c16 100644 --- a/inject-generator/src/test/java/io/avaje/inject/generator/models/valid/lazy/LazyBean.java +++ b/inject-generator/src/test/java/io/avaje/inject/generator/models/valid/lazy/LazyBean.java @@ -8,7 +8,15 @@ @Lazy @Singleton public class LazyBean { - @Inject Provider intProvider; - public LazyBean() throws Exception {} + Provider intProvider; + + @Inject + public LazyBean(Provider intProvider) { + this.intProvider = intProvider; + } + + public LazyBean() {} + + void something() {} } diff --git a/inject-generator/src/test/java/io/avaje/inject/generator/models/valid/lazy/LazyBeanTypes.java b/inject-generator/src/test/java/io/avaje/inject/generator/models/valid/lazy/LazyBeanTypes.java new file mode 100644 index 000000000..0e8abf7d5 --- /dev/null +++ b/inject-generator/src/test/java/io/avaje/inject/generator/models/valid/lazy/LazyBeanTypes.java @@ -0,0 +1,23 @@ +package io.avaje.inject.generator.models.valid.lazy; + +import io.avaje.inject.BeanTypes; +import io.avaje.inject.Lazy; +import jakarta.inject.Inject; +import jakarta.inject.Provider; +import jakarta.inject.Singleton; + +@Lazy +@Singleton +@BeanTypes(LazyInterface.class) +public class LazyBeanTypes implements LazyInterface { + + Provider intProvider; + + @Inject + public LazyBeanTypes(Provider intProvider) { + this.intProvider = intProvider; + } + + @Override + public void something() {} +} diff --git a/inject-generator/src/test/java/io/avaje/inject/generator/models/valid/lazy/LazyFactory.java b/inject-generator/src/test/java/io/avaje/inject/generator/models/valid/lazy/LazyFactory.java index 931e67237..6afc4a35e 100644 --- a/inject-generator/src/test/java/io/avaje/inject/generator/models/valid/lazy/LazyFactory.java +++ b/inject-generator/src/test/java/io/avaje/inject/generator/models/valid/lazy/LazyFactory.java @@ -12,4 +12,9 @@ public class LazyFactory { Integer lazyInt() { return 0; } + + @Bean + LazyInterface lazyInterface() { + return null; + } } diff --git a/inject-generator/src/test/java/io/avaje/inject/generator/models/valid/lazy/LazyInterface.java b/inject-generator/src/test/java/io/avaje/inject/generator/models/valid/lazy/LazyInterface.java new file mode 100644 index 000000000..753b2d88a --- /dev/null +++ b/inject-generator/src/test/java/io/avaje/inject/generator/models/valid/lazy/LazyInterface.java @@ -0,0 +1,6 @@ +package io.avaje.inject.generator.models.valid.lazy; + +public interface LazyInterface { + + void something(); +} 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 d81c21f21..2088d7b54 100644 --- a/inject/src/main/java/io/avaje/inject/spi/Builder.java +++ b/inject/src/main/java/io/avaje/inject/spi/Builder.java @@ -1,14 +1,15 @@ package io.avaje.inject.spi; -import io.avaje.inject.BeanScope; -import jakarta.inject.Provider; - import java.lang.reflect.Type; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.function.Consumer; +import java.util.function.Function; + +import io.avaje.inject.BeanScope; +import jakarta.inject.Provider; /** * Mutable builder object used when building a bean scope. @@ -78,6 +79,11 @@ default boolean isBeanAbsent(Type... types) { */ void registerProvider(Provider provider); + /** Register the lazy provider into the context. */ + default void registerLazy( + Provider provider, Function, T> proxyClassConstructor) { + register(proxyClassConstructor.apply(new OnceProvider<>(provider))); + } /** * Register the bean instance into the context. 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 3aaf1e265..16d4afa69 100644 --- a/inject/src/main/java/io/avaje/inject/spi/DContextEntryBean.java +++ b/inject/src/main/java/io/avaje/inject/spi/DContextEntryBean.java @@ -1,9 +1,7 @@ package io.avaje.inject.spi; import io.avaje.inject.BeanEntry; - import jakarta.inject.Provider; -import java.util.concurrent.locks.ReentrantLock; /** * Holds either the bean itself or a provider of the bean. @@ -26,14 +24,14 @@ static DContextEntryBean of(Object source, String name, int flag, Class)source, name, flag, null); + return new OnceBeanProvider((Provider)source, name, flag, null); } else { return new DContextEntryBean(source, name, flag, null); } } static DContextEntryBean provider(boolean prototype, Provider provider, String name, int flag, Class currentModule) { - return prototype ? new ProtoProvider(provider, name, flag, currentModule) : new OnceProvider(provider, name, flag, currentModule); + return prototype ? new ProtoProvider(provider, name, flag, currentModule) : new OnceBeanProvider(provider, name, flag, currentModule); } protected final Object source; @@ -143,26 +141,17 @@ Object bean() { */ static final class OnceProvider extends DContextEntryBean { - private final ReentrantLock lock = new ReentrantLock(); private final Provider provider; - private Object bean; - private OnceProvider(Provider provider, String name, int flag, Class currentModule) { + private OnceBeanProvider( + Provider provider, String name, int flag, Class currentModule) { super(provider, name, flag, currentModule); - this.provider = provider; + this.provider = new OnceProvider<>(provider); } @Override Object bean() { - lock.lock(); - try { - if (bean == null) { - bean = provider.get(); - } - return bean; - } finally { - lock.unlock(); - } + return provider.get(); } } } diff --git a/inject/src/main/java/io/avaje/inject/spi/OnceProvider.java b/inject/src/main/java/io/avaje/inject/spi/OnceProvider.java new file mode 100644 index 000000000..aa73707c4 --- /dev/null +++ b/inject/src/main/java/io/avaje/inject/spi/OnceProvider.java @@ -0,0 +1,34 @@ +package io.avaje.inject.spi; + +import java.util.Objects; +import java.util.concurrent.locks.ReentrantLock; + +import jakarta.inject.Provider; + +/** Single instance Lazy Provider. {@link #get()} will return the same instance every time */ +final class OnceProvider implements Provider { + + private final ReentrantLock lock = new ReentrantLock(); + private final Provider provider; + private T bean; + + public OnceProvider(Provider provider) { + this.provider = Objects.requireNonNull(provider); + } + + @Override + public T get() { + if (bean != null) { + return bean; + } + lock.lock(); + try { + if (bean == null) { + bean = provider.get(); + } + return bean; + } finally { + lock.unlock(); + } + } +} From a03c11741e06cd8a2aeb3e647c85e7dd12a09a8c Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Thu, 12 Jun 2025 17:15:31 -0400 Subject: [PATCH 02/11] factory --- .../avaje/inject/generator/MethodReader.java | 23 +++++++++++++++++-- .../java/io/avaje/inject/generator/Util.java | 14 +++++++---- .../src/main/java/io/avaje/inject/Lazy.java | 14 ++++++----- 3 files changed, 38 insertions(+), 13 deletions(-) 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 dfc31234e..b58aa1b46 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 @@ -27,6 +27,8 @@ final class MethodReader { private final boolean primary; private final boolean secondary; private final boolean lazy; + private final boolean proxyLazy; + private final TypeElement lazyProxyType; private final String returnTypeRaw; private final UType genericType; private final String shortName; @@ -57,11 +59,15 @@ final class MethodReader { secondary = SecondaryPrism.isPresent(element); lazy = LazyPrism.isPresent(element) || LazyPrism.isPresent(element.getEnclosingElement()); conditions.readAll(element); + this.lazyProxyType = Util.lazyProxy(element); + this.proxyLazy = lazy && lazyProxyType != null; } else { prototype = false; primary = false; secondary = false; lazy = false; + this.proxyLazy = false; + this.lazyProxyType = null; } this.methodName = element.getSimpleName().toString(); TypeMirror returnMirror = element.getReturnType(); @@ -252,7 +258,11 @@ void builderAddBeanProvider(Append writer) { writer.append(".asSecondary()"); } - writer.indent(".registerProvider(() -> {").eol(); + if (proxyLazy) { + writer.indent(".registerLazy(() -> {").eol(); + } else { + writer.indent(".registerProvider(() -> {").eol(); + } startTry(writer, " "); writer.indent(indent).append(" return "); @@ -265,7 +275,16 @@ void builderAddBeanProvider(Append writer) { } writer.append(");").eol(); endTry(writer, " "); - writer.indent(indent).append(" });").eol(); + writer.indent(indent); + if (proxyLazy) { + writer + .append( + " }, %s$Lazy::new);", + Util.shortName(lazyProxyType.getQualifiedName().toString()).replace(".", "_")) + .eol(); + } else { + writer.indent(indent).append(" });").eol(); + } writer.indent(indent).append("}").eol(); } 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 e6cd1ed63..715fd1fea 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 @@ -403,12 +403,16 @@ static void validateBeanTypes(Element origin, List beanType) { }); } - static TypeElement lazyProxy(TypeElement beanType) { + static TypeElement lazyProxy(Element element) { + TypeElement type = + element instanceof TypeElement + ? (TypeElement) element + : APContext.asTypeElement(((ExecutableElement) element).getReturnType()); - if (beanType.getModifiers().contains(Modifier.FINAL) - || !beanType.getKind().isInterface() && !Util.hasNoArgConstructor(beanType)) { + if (type.getModifiers().contains(Modifier.FINAL) + || !type.getKind().isInterface() && !Util.hasNoArgConstructor(type)) { - return BeanTypesPrism.getOptionalOn(beanType) + return BeanTypesPrism.getOptionalOn(element) .map(BeanTypesPrism::value) .filter(v -> v.size() == 1) .map(v -> APContext.asTypeElement(v.get(0))) @@ -416,7 +420,7 @@ static TypeElement lazyProxy(TypeElement beanType) { .orElse(null); } - return beanType; + return type; } static boolean hasNoArgConstructor(TypeElement beanType) { diff --git a/inject/src/main/java/io/avaje/inject/Lazy.java b/inject/src/main/java/io/avaje/inject/Lazy.java index b75140be0..b1d66fee4 100644 --- a/inject/src/main/java/io/avaje/inject/Lazy.java +++ b/inject/src/main/java/io/avaje/inject/Lazy.java @@ -6,12 +6,14 @@ import java.lang.annotation.Target; /** - * Marks a Singleton, Component or Factory method beans to be initialised lazily. - *

- * When annotating a {@link Factory} as {@code @Lazy} it means that the factory - * itself is not lazy but all beans that it provides will have lazy initialisation. + * Marks a Singleton, Component or Factory method beans to be initialized lazily. + * + *

When annotating a {@link Factory} as {@code @Lazy} it means that the factory itself is not + * lazy but all beans that it provides will have lazy initialization. + * + * @apiNote If the annotated class is an interface or has an additional no-args constructor, a + * generated proxy bean will be wired for ultimate laziness. */ @Retention(RetentionPolicy.SOURCE) @Target({ElementType.METHOD, ElementType.TYPE}) -public @interface Lazy { -} +public @interface Lazy {} From 20939543427daa1e362c8aac3b66316d28fa6925 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Thu, 12 Jun 2025 18:24:17 -0400 Subject: [PATCH 03/11] aop --- .../avaje/inject/generator/AspectMethod.java | 7 +-- .../io/avaje/inject/generator/BeanReader.java | 46 ++++++++----------- .../avaje/inject/generator/MethodReader.java | 2 +- .../generator/SimpleBeanProxyWriter.java | 7 ++- .../models/valid/lazy/LazyBeanAOP.java | 23 ++++++++++ .../avaje/inject/spi/DContextEntryBean.java | 6 +-- 6 files changed, 56 insertions(+), 35 deletions(-) create mode 100644 inject-generator/src/test/java/io/avaje/inject/generator/models/valid/lazy/LazyBeanAOP.java 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 357a9b766..0507bb44a 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 @@ -168,11 +168,12 @@ private void invokeSuper(Append writer, String simpleName) { writer.append(")"); } - void writeSetupFields(Append writer) { - writer.append(" private final Method %s;", localName).eol(); + void writeSetupFields(Append writer, boolean lazy) { + var isFinal = lazy ? "" : "final "; + writer.append(" private %sMethod %s;", isFinal, localName).eol(); for (AspectPair aspectPair : aspectPairs) { String sn = aspectPair.annotationShortName(); - writer.append(" private final MethodInterceptor %s%s;", localName, sn).eol(); + writer.append(" private %sMethodInterceptor %s%s;", isFinal, localName, sn).eol(); } } 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 6b3a34fa6..58a4572ef 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 @@ -58,29 +58,28 @@ final class BeanReader { this.beanType = beanType; this.type = beanType.getQualifiedName().toString(); this.shortName = shortName(beanType); + this.proxy = ProxyPrism.isPresent(beanType); + + // if proxy read annotations from actual type + TypeElement actualType = proxy ? APContext.asTypeElement(beanType.getSuperclass()) : 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) - || importedComponent && ProcessingContext.isImportedLazy(beanType)); - final var beantypes = BeanTypesPrism.getOptionalOn(beanType); - beantypes.ifPresent(p -> Util.validateBeanTypes(beanType, p.value())); + PrototypePrism.isPresent(actualType) + || importedComponent && ProcessingContext.isImportedPrototype(actualType); + this.primary = PrimaryPrism.isPresent(actualType); + this.secondary = !primary && SecondaryPrism.isPresent(actualType); + final var beantypes = BeanTypesPrism.getOptionalOn(actualType); + beantypes.ifPresent(p -> Util.validateBeanTypes(actualType, p.value())); this.typeReader = - new TypeReader( - beantypes, - UType.parse(beanType.asType()), - beanType, - importTypes, - factory); + new TypeReader(beantypes, UType.parse(beanType.asType()), beanType, importTypes, factory); typeReader.process(); - this.lazyProxyType = Util.lazyProxy(beanType); - this.proxyLazy = lazy && lazyProxyType != null; + this.lazy = + !FactoryPrism.isPresent(actualType) + && (LazyPrism.isPresent(actualType) + || importedComponent && ProcessingContext.isImportedLazy(actualType)); + this.requestParams = new BeanRequestParams(type); this.name = typeReader.name(); this.aspects = typeReader.hasAspects(); @@ -93,15 +92,10 @@ final class BeanReader { this.constructor = typeReader.constructor(); this.observerMethods = typeReader.observerMethods(); this.importedComponent = importedComponent && constructor != null && constructor.isPublic(); - - if (ProxyPrism.isPresent(beanType)) { - this.proxy = true; - conditions.readAll(APContext.asTypeElement(beanType.getSuperclass())); - } else { - conditions.readAll(beanType); - this.proxy = false; - } this.delayed = shouldDelay(); + this.lazyProxyType = !lazy || delayed ? null : Util.lazyProxy(actualType); + this.proxyLazy = lazy && lazyProxyType != null; + conditions.readAll(actualType); } /** 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 b58aa1b46..c268f03ba 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 @@ -59,7 +59,7 @@ final class MethodReader { secondary = SecondaryPrism.isPresent(element); lazy = LazyPrism.isPresent(element) || LazyPrism.isPresent(element.getEnclosingElement()); conditions.readAll(element); - this.lazyProxyType = Util.lazyProxy(element); + this.lazyProxyType = lazy ? Util.lazyProxy(element) : null; this.proxyLazy = lazy && lazyProxyType != null; } else { prototype = false; 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 a8a7ca152..8a73ad5b3 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 @@ -49,12 +49,16 @@ private void writeMethods() { private void writeFields() { for (AspectMethod method : aspects.methods()) { - method.writeSetupFields(writer); + method.writeSetupFields(writer, beanReader.proxyLazy()); } writer.eol(); } private void writeConstructor() { + if (beanReader.proxyLazy()) { + writer.append(" public %s%s(){}", shortName, suffix).eol().eol(); + } + writer.append(" @Inject\n"); writer.append(" public %s%s(", shortName, suffix); int count = 0; for (final String aspectName : aspects.aspectNames()) { @@ -95,6 +99,7 @@ private void writeImports() { writer.append("import %s;", Constants.INVOCATION_EXCEPTION).eol(); writer.append("import %s;", Constants.METHOD_INTERCEPTOR).eol(); writer.append("import %s;", Constants.COMPONENT).eol(); + writer.append("import %s;", Constants.INJECT).eol(); writer.append("import %s;", Constants.PROXY).eol(); beanReader.writeImports(writer, packageName); } diff --git a/inject-generator/src/test/java/io/avaje/inject/generator/models/valid/lazy/LazyBeanAOP.java b/inject-generator/src/test/java/io/avaje/inject/generator/models/valid/lazy/LazyBeanAOP.java new file mode 100644 index 000000000..e32d1ded7 --- /dev/null +++ b/inject-generator/src/test/java/io/avaje/inject/generator/models/valid/lazy/LazyBeanAOP.java @@ -0,0 +1,23 @@ +package io.avaje.inject.generator.models.valid.lazy; + +import io.avaje.inject.Lazy; +import io.avaje.inject.generator.models.valid.Timed; +import jakarta.inject.Inject; +import jakarta.inject.Singleton; + +@Lazy +@Timed +@Singleton +public class LazyBeanAOP { + + Integer intProvider; + + @Inject + public LazyBeanAOP(Integer intProvider) { + this.intProvider = intProvider; + } + + public LazyBeanAOP() {} + + void something() {} +} 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 16d4afa69..be63270df 100644 --- a/inject/src/main/java/io/avaje/inject/spi/DContextEntryBean.java +++ b/inject/src/main/java/io/avaje/inject/spi/DContextEntryBean.java @@ -136,10 +136,8 @@ Object bean() { } } - /** - * Single instance scoped Provider based entry. - */ - static final class OnceProvider extends DContextEntryBean { + /** Single instance scoped Provider based entry. */ + static final class OnceBeanProvider extends DContextEntryBean { private final Provider provider; From 98172dae023d540014f8aad466e6559e81c58f0c Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Thu, 12 Jun 2025 19:12:42 -0400 Subject: [PATCH 04/11] test --- .../java/org/example/myapp/lazy/LazyBean.java | 4 +- .../org/example/myapp/lazy/LazyBeanAOP.java | 30 +++++++ .../org/example/myapp/lazy/LazyFactory.java | 20 +++++ .../java/org/example/myapp/lazy/LazyImpl.java | 33 +++++++ .../org/example/myapp/lazy/LazyInterface.java | 6 ++ .../java/org/example/myapp/lazy/OldLazy.java | 30 +++++++ .../java/org/example/myapp/lazy/LazyTest.java | 85 ++++++++++++++++++- .../inject/generator/AssistBeanReader.java | 24 +++--- .../io/avaje/inject/generator/BeanReader.java | 14 ++- .../avaje/inject/generator/MethodReader.java | 28 ++++-- .../io/avaje/inject/generator/TypeReader.java | 13 ++- 11 files changed, 249 insertions(+), 38 deletions(-) create mode 100644 blackbox-test-inject/src/main/java/org/example/myapp/lazy/LazyBeanAOP.java create mode 100644 blackbox-test-inject/src/main/java/org/example/myapp/lazy/LazyImpl.java create mode 100644 blackbox-test-inject/src/main/java/org/example/myapp/lazy/LazyInterface.java create mode 100644 blackbox-test-inject/src/main/java/org/example/myapp/lazy/OldLazy.java diff --git a/blackbox-test-inject/src/main/java/org/example/myapp/lazy/LazyBean.java b/blackbox-test-inject/src/main/java/org/example/myapp/lazy/LazyBean.java index fcba81c95..383452d18 100644 --- a/blackbox-test-inject/src/main/java/org/example/myapp/lazy/LazyBean.java +++ b/blackbox-test-inject/src/main/java/org/example/myapp/lazy/LazyBean.java @@ -18,11 +18,11 @@ public class LazyBean { @Inject @Nullable AtomicBoolean initialized; - public LazyBean() throws Exception {} - @PostConstruct void init(BeanScope scope) { // note that nested test scopes will not be lazy if (initialized != null) initialized.set(true); } + + void something() {} } diff --git a/blackbox-test-inject/src/main/java/org/example/myapp/lazy/LazyBeanAOP.java b/blackbox-test-inject/src/main/java/org/example/myapp/lazy/LazyBeanAOP.java new file mode 100644 index 000000000..35f8e6256 --- /dev/null +++ b/blackbox-test-inject/src/main/java/org/example/myapp/lazy/LazyBeanAOP.java @@ -0,0 +1,30 @@ +package org.example.myapp.lazy; + +import java.util.concurrent.atomic.AtomicBoolean; + +import org.example.myapp.aspect.MyTimed; +import org.jspecify.annotations.Nullable; + +import io.avaje.inject.BeanScope; +import io.avaje.inject.Lazy; +import io.avaje.inject.PostConstruct; +import jakarta.inject.Inject; +import jakarta.inject.Named; +import jakarta.inject.Singleton; + +@Lazy +@Singleton +@Named("single") +@MyTimed(name = "AOP") +public class LazyBeanAOP { + + @Inject @Nullable AtomicBoolean initialized; + + @PostConstruct + void init(BeanScope scope) { + // note that nested test scopes will not be lazy + if (initialized != null) initialized.set(true); + } + + void something() {} +} diff --git a/blackbox-test-inject/src/main/java/org/example/myapp/lazy/LazyFactory.java b/blackbox-test-inject/src/main/java/org/example/myapp/lazy/LazyFactory.java index 5302a7439..774dae5ff 100644 --- a/blackbox-test-inject/src/main/java/org/example/myapp/lazy/LazyFactory.java +++ b/blackbox-test-inject/src/main/java/org/example/myapp/lazy/LazyFactory.java @@ -5,6 +5,7 @@ import org.jspecify.annotations.Nullable; import io.avaje.inject.Bean; +import io.avaje.inject.BeanTypes; import io.avaje.inject.Factory; import io.avaje.inject.Lazy; import jakarta.inject.Named; @@ -22,6 +23,25 @@ LazyBean lazyInt(@Nullable AtomicBoolean initialized) throws Exception { return new LazyBean(); } + @Bean + @Named("factory") + LazyInterface lazyInterFace(@Nullable AtomicBoolean initialized) throws Exception { + + // note that nested test scopes will not be lazy + if (initialized != null) initialized.set(true); + return new LazyImpl(initialized); + } + + @Bean + @BeanTypes(LazyInterface.class) + @Named("factoryBeanType") + LazyImpl factoryBeanType(@Nullable AtomicBoolean initialized) throws Exception { + + // note that nested test scopes will not be lazy + if (initialized != null) initialized.set(true); + return new LazyImpl(initialized); + } + @Bean @Named("factoryThrows") LazyBean lazyIntThrows(@Nullable AtomicBoolean initialized) throws Exception { diff --git a/blackbox-test-inject/src/main/java/org/example/myapp/lazy/LazyImpl.java b/blackbox-test-inject/src/main/java/org/example/myapp/lazy/LazyImpl.java new file mode 100644 index 000000000..4b41d220d --- /dev/null +++ b/blackbox-test-inject/src/main/java/org/example/myapp/lazy/LazyImpl.java @@ -0,0 +1,33 @@ +package org.example.myapp.lazy; + +import java.util.concurrent.atomic.AtomicBoolean; + +import io.avaje.inject.BeanScope; +import io.avaje.inject.BeanTypes; +import io.avaje.inject.Lazy; +import io.avaje.inject.PostConstruct; +import io.github.resilience4j.core.lang.Nullable; +import jakarta.inject.Named; +import jakarta.inject.Singleton; + +@Lazy +@Singleton +@Named("single") +@BeanTypes(LazyInterface.class) +public class LazyImpl implements LazyInterface { + + AtomicBoolean initialized; + + public LazyImpl(@Nullable AtomicBoolean initialized) { + this.initialized = initialized; + } + + @PostConstruct + void init(BeanScope scope) { + // note that nested test scopes will not be lazy + if (initialized != null) initialized.set(true); + } + + @Override + public void something() {} +} diff --git a/blackbox-test-inject/src/main/java/org/example/myapp/lazy/LazyInterface.java b/blackbox-test-inject/src/main/java/org/example/myapp/lazy/LazyInterface.java new file mode 100644 index 000000000..d5ba84e34 --- /dev/null +++ b/blackbox-test-inject/src/main/java/org/example/myapp/lazy/LazyInterface.java @@ -0,0 +1,6 @@ +package org.example.myapp.lazy; + +public interface LazyInterface { + + void something(); +} diff --git a/blackbox-test-inject/src/main/java/org/example/myapp/lazy/OldLazy.java b/blackbox-test-inject/src/main/java/org/example/myapp/lazy/OldLazy.java new file mode 100644 index 000000000..ec063bb6d --- /dev/null +++ b/blackbox-test-inject/src/main/java/org/example/myapp/lazy/OldLazy.java @@ -0,0 +1,30 @@ +package org.example.myapp.lazy; + +import java.util.concurrent.atomic.AtomicBoolean; + +import io.avaje.inject.BeanScope; +import io.avaje.inject.Lazy; +import io.avaje.inject.PostConstruct; +import io.github.resilience4j.core.lang.Nullable; +import jakarta.inject.Named; +import jakarta.inject.Singleton; + +@Lazy +@Singleton +@Named("single") +public class OldLazy { + + AtomicBoolean initialized; + + public OldLazy(@Nullable AtomicBoolean initialized) { + this.initialized = initialized; + } + + @PostConstruct + void init(BeanScope scope) { + // note that nested test scopes will not be lazy + if (initialized != null) initialized.set(true); + } + + void something() {} +} diff --git a/blackbox-test-inject/src/test/java/org/example/myapp/lazy/LazyTest.java b/blackbox-test-inject/src/test/java/org/example/myapp/lazy/LazyTest.java index f5c9a8d4e..bbdcbba24 100644 --- a/blackbox-test-inject/src/test/java/org/example/myapp/lazy/LazyTest.java +++ b/blackbox-test-inject/src/test/java/org/example/myapp/lazy/LazyTest.java @@ -15,11 +15,13 @@ void test() { var initialized = new AtomicBoolean(); try (var scope = BeanScope.builder().beans(initialized).build()) { assertThat(initialized).isFalse(); - LazyBean lazy = scope.get(LazyBean.class, "single"); - assertThat(initialized).isTrue(); + var lazy = scope.get(LazyBean.class, "single"); assertThat(lazy).isNotNull(); + assertThat(initialized).isFalse(); + lazy.something(); + assertThat(initialized).isTrue(); - LazyBean lazyAgain = scope.get(LazyBean.class, "single"); + var lazyAgain = scope.get(LazyBean.class, "single"); assertThat(lazyAgain).isSameAs(lazy); } } @@ -29,9 +31,84 @@ void testFactory() { var initialized = new AtomicBoolean(); try (var scope = BeanScope.builder().beans(initialized).build()) { assertThat(initialized).isFalse(); - LazyBean prov = scope.get(LazyBean.class, "factory"); + var prov = scope.get(LazyBean.class, "factory"); + assertThat(initialized).isFalse(); + prov.something(); + assertThat(initialized).isTrue(); + assertThat(prov).isNotNull(); + } + } + + @Test + void testInterface() { + var initialized = new AtomicBoolean(); + try (var scope = BeanScope.builder().beans(initialized).build()) { + assertThat(initialized).isFalse(); + var lazy = scope.get(LazyInterface.class, "single"); + assertThat(lazy).isNotNull(); + assertThat(initialized).isFalse(); + lazy.something(); + assertThat(initialized).isTrue(); + + var lazyAgain = scope.get(LazyInterface.class, "single"); + assertThat(lazyAgain).isSameAs(lazy); + } + } + + @Test + void testFactoryInterface() { + var initialized = new AtomicBoolean(); + try (var scope = BeanScope.builder().beans(initialized).build()) { + assertThat(initialized).isFalse(); + var prov = scope.get(LazyInterface.class, "factory"); + assertThat(initialized).isFalse(); + prov.something(); assertThat(initialized).isTrue(); assertThat(prov).isNotNull(); } } + + @Test + void factoryBeanType() { + var initialized = new AtomicBoolean(); + try (var scope = BeanScope.builder().beans(initialized).build()) { + assertThat(initialized).isFalse(); + var prov = scope.get(LazyInterface.class, "factoryBeanType"); + assertThat(initialized).isFalse(); + prov.something(); + assertThat(initialized).isTrue(); + assertThat(prov).isNotNull(); + } + } + + @Test + void testAOP() { + var initialized = new AtomicBoolean(); + try (var scope = BeanScope.builder().beans(initialized).build()) { + assertThat(initialized).isFalse(); + var lazy = scope.get(LazyBeanAOP.class, "single"); + assertThat(lazy).isNotNull(); + assertThat(initialized).isFalse(); + lazy.something(); + assertThat(initialized).isTrue(); + + var lazyAgain = scope.get(LazyBeanAOP.class, "single"); + assertThat(lazyAgain).isSameAs(lazy); + } + } + + @Test + void testOldLazyBehavior() { + var initialized = new AtomicBoolean(); + try (var scope = BeanScope.builder().beans(initialized).build()) { + assertThat(initialized).isFalse(); + var lazy = scope.get(OldLazy.class, "single"); + assertThat(lazy).isNotNull(); + assertThat(initialized).isTrue(); + lazy.something(); + + var lazyAgain = scope.get(OldLazy.class, "single"); + assertThat(lazyAgain).isSameAs(lazy); + } + } } diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/AssistBeanReader.java b/inject-generator/src/main/java/io/avaje/inject/generator/AssistBeanReader.java index 6e8049858..9c4250786 100644 --- a/inject-generator/src/main/java/io/avaje/inject/generator/AssistBeanReader.java +++ b/inject-generator/src/main/java/io/avaje/inject/generator/AssistBeanReader.java @@ -1,17 +1,22 @@ package io.avaje.inject.generator; -import io.avaje.inject.generator.MethodReader.MethodParam; +import static java.util.function.Predicate.not; +import static java.util.stream.Collectors.joining; +import static java.util.stream.Collectors.toSet; -import javax.lang.model.element.*; -import javax.lang.model.util.ElementFilter; import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.Set; -import static java.util.function.Predicate.not; -import static java.util.stream.Collectors.joining; -import static java.util.stream.Collectors.toSet; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.util.ElementFilter; + +import io.avaje.inject.generator.MethodReader.MethodParam; final class AssistBeanReader { @@ -34,12 +39,7 @@ final class AssistBeanReader { this.beanType = beanType; this.type = beanType.getQualifiedName().toString(); this.typeReader = - new TypeReader( - Optional.empty(), - UType.parse(beanType.asType()), - beanType, - importTypes, - false); + new TypeReader(List.of(), UType.parse(beanType.asType()), beanType, importTypes, false); typeReader.process(); qualifierName = typeReader.name(); 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 58a4572ef..a2ccd530a 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 @@ -68,10 +68,18 @@ final class BeanReader { || importedComponent && ProcessingContext.isImportedPrototype(actualType); this.primary = PrimaryPrism.isPresent(actualType); this.secondary = !primary && SecondaryPrism.isPresent(actualType); - final var beantypes = BeanTypesPrism.getOptionalOn(actualType); - beantypes.ifPresent(p -> Util.validateBeanTypes(actualType, p.value())); + var beantypes = + BeanTypesPrism.getOptionalOn(actualType) + .map(BeanTypesPrism::value) + .or(() -> proxy ? Optional.of(List.of(actualType.asType())) : Optional.empty()); + beantypes.ifPresent(t -> Util.validateBeanTypes(actualType, t)); this.typeReader = - new TypeReader(beantypes, UType.parse(beanType.asType()), beanType, importTypes, factory); + new TypeReader( + beantypes.orElse(List.of()), + UType.parse(beanType.asType()), + beanType, + importTypes, + factory); typeReader.process(); 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 c268f03ba..26ce230e3 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,13 +1,22 @@ package io.avaje.inject.generator; -import javax.lang.model.element.*; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; -import java.util.*; - import static io.avaje.inject.generator.Constants.CONDITIONAL_DEPENDENCY; import static io.avaje.inject.generator.ProcessingContext.asElement; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; + final class MethodReader { private static final String CODE_COMMENT_BUILD_FACTORYBEAN = " /**\n * Create and register %s via factory bean method %s#%s().\n */"; @@ -110,9 +119,10 @@ final class MethodReader { this.initMethod = initMethod; this.destroyMethod = destroyMethod; } else { - final var beantypes = BeanTypesPrism.getOptionalOn(element); - beantypes.ifPresent(p -> Util.validateBeanTypes(element, p.value())); - this.typeReader = new TypeReader(beantypes, genericType, returnElement, importTypes); + var beantypes = BeanTypesPrism.getOptionalOn(element).map(BeanTypesPrism::value); + beantypes.ifPresent(t -> Util.validateBeanTypes(element, t)); + this.typeReader = + new TypeReader(beantypes.orElse(List.of()), genericType, returnElement, importTypes); typeReader.process(); MethodLifecycleReader lifecycleReader = new MethodLifecycleReader(returnElement, initMethod, destroyMethod); this.initMethod = lifecycleReader.initMethod(); @@ -388,7 +398,7 @@ static String addPreDestroy(String destroyMethod) { } private boolean hasLifecycleMethods() { - return notEmpty(initMethod) || notEmpty(destroyMethod) || (typeReader != null && typeReader.isClosable() || beanCloseable); + return notEmpty(initMethod) || notEmpty(destroyMethod) || typeReader != null && typeReader.isClosable() || beanCloseable; } private boolean notEmpty(String value) { 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 cffc9eba0..da2a024ca 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 @@ -9,6 +9,7 @@ import javax.lang.model.element.Element; import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; final class TypeReader { @@ -22,7 +23,7 @@ final class TypeReader { private final List injectsTypes; TypeReader( - Optional injectsTypes, + List injectsTypes, UType genericType, TypeElement beanType, ImportTypeMap importTypes, @@ -31,7 +32,7 @@ final class TypeReader { } TypeReader( - Optional injectsTypes, + List injectsTypes, UType genericType, TypeElement returnElement, ImportTypeMap importTypes) { @@ -39,17 +40,13 @@ final class TypeReader { } private TypeReader( - Optional injectsTypes, + List injectsTypes, UType genericType, boolean forBean, TypeElement beanType, ImportTypeMap importTypes, boolean factory) { - this.injectsTypes = - injectsTypes.map(BeanTypesPrism::value).stream() - .flatMap(List::stream) - .map(UType::parse) - .collect(toList()); + this.injectsTypes = injectsTypes.stream().map(UType::parse).collect(toList()); this.forBean = forBean; this.beanType = beanType; this.importTypes = importTypes; From 87950af93516c526bdadd1f5740d8ee7e781628d Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Thu, 12 Jun 2025 23:27:57 -0400 Subject: [PATCH 05/11] Update Util.java --- .../src/main/java/io/avaje/inject/generator/Util.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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 715fd1fea..011af3c74 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 @@ -416,7 +416,11 @@ static TypeElement lazyProxy(Element element) { .map(BeanTypesPrism::value) .filter(v -> v.size() == 1) .map(v -> APContext.asTypeElement(v.get(0))) - .filter(v -> v.getKind().isInterface() || hasNoArgConstructor(v)) + // figure out generics later + .filter( + v -> + v.getTypeParameters().isEmpty() + && (v.getKind().isInterface() || hasNoArgConstructor(v))) .orElse(null); } From 5a1543118d89bf94ec08c583aa370db574594a20 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Fri, 13 Jun 2025 13:47:45 -0400 Subject: [PATCH 06/11] fix return --- .../io/avaje/inject/generator/SimpleBeanLazyWriter.java | 8 ++++++-- .../src/main/java/io/avaje/inject/generator/Util.java | 3 ++- .../inject/generator/models/valid/lazy/LazyBeanTypes.java | 5 +++++ .../inject/generator/models/valid/lazy/LazyInterface.java | 2 ++ 4 files changed, 15 insertions(+), 3 deletions(-) diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/SimpleBeanLazyWriter.java b/inject-generator/src/main/java/io/avaje/inject/generator/SimpleBeanLazyWriter.java index 70e383475..1803af021 100644 --- a/inject-generator/src/main/java/io/avaje/inject/generator/SimpleBeanLazyWriter.java +++ b/inject-generator/src/main/java/io/avaje/inject/generator/SimpleBeanLazyWriter.java @@ -154,8 +154,12 @@ private String methods() { .collect(Collectors.joining(", "))); } - sb.append(" {\n"); - sb.append(" onceProvider.get().").append(methodName); + sb.append(" {\n "); + if (!"void".equals(returnType.full())) { + sb.append("return "); + } + + sb.append("onceProvider.get().").append(methodName); sb.append("("); for (int i = 0; i < parameters.size(); i++) { sb.append(parameters.get(i).getSimpleName().toString()); 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 011af3c74..e69a8c41f 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 @@ -409,7 +409,8 @@ static TypeElement lazyProxy(Element element) { ? (TypeElement) element : APContext.asTypeElement(((ExecutableElement) element).getReturnType()); - if (type.getModifiers().contains(Modifier.FINAL) + if (!type.getTypeParameters().isEmpty() + || type.getModifiers().contains(Modifier.FINAL) || !type.getKind().isInterface() && !Util.hasNoArgConstructor(type)) { return BeanTypesPrism.getOptionalOn(element) diff --git a/inject-generator/src/test/java/io/avaje/inject/generator/models/valid/lazy/LazyBeanTypes.java b/inject-generator/src/test/java/io/avaje/inject/generator/models/valid/lazy/LazyBeanTypes.java index 0e8abf7d5..c6afc3b34 100644 --- a/inject-generator/src/test/java/io/avaje/inject/generator/models/valid/lazy/LazyBeanTypes.java +++ b/inject-generator/src/test/java/io/avaje/inject/generator/models/valid/lazy/LazyBeanTypes.java @@ -20,4 +20,9 @@ public LazyBeanTypes(Provider intProvider) { @Override public void something() {} + + @Override + public String somethingElse() { // TODO Auto-generated method stub + return null; + } } diff --git a/inject-generator/src/test/java/io/avaje/inject/generator/models/valid/lazy/LazyInterface.java b/inject-generator/src/test/java/io/avaje/inject/generator/models/valid/lazy/LazyInterface.java index 753b2d88a..8335d171d 100644 --- a/inject-generator/src/test/java/io/avaje/inject/generator/models/valid/lazy/LazyInterface.java +++ b/inject-generator/src/test/java/io/avaje/inject/generator/models/valid/lazy/LazyInterface.java @@ -3,4 +3,6 @@ public interface LazyInterface { void something(); + + String somethingElse(); } From 037435458abe1f4b62a1aa829b4dbc1f1721a920 Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Mon, 16 Jun 2025 15:11:24 +1200 Subject: [PATCH 07/11] Final fields and some formatting only --- .../inject/generator/AssistBeanReader.java | 2 +- .../io/avaje/inject/generator/BeanReader.java | 36 +++++++++---------- .../avaje/inject/generator/MethodReader.java | 16 ++++----- .../generator/SimpleBeanLazyWriter.java | 26 ++++++-------- .../inject/generator/SimpleBeanWriter.java | 4 +-- 5 files changed, 38 insertions(+), 46 deletions(-) diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/AssistBeanReader.java b/inject-generator/src/main/java/io/avaje/inject/generator/AssistBeanReader.java index 9c4250786..a6a389f6d 100644 --- a/inject-generator/src/main/java/io/avaje/inject/generator/AssistBeanReader.java +++ b/inject-generator/src/main/java/io/avaje/inject/generator/AssistBeanReader.java @@ -39,7 +39,7 @@ final class AssistBeanReader { this.beanType = beanType; this.type = beanType.getQualifiedName().toString(); this.typeReader = - new TypeReader(List.of(), UType.parse(beanType.asType()), beanType, importTypes, false); + new TypeReader(List.of(), UType.parse(beanType.asType()), beanType, importTypes, false); typeReader.process(); qualifierName = typeReader.name(); 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 a2ccd530a..5b6df4591 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 @@ -64,29 +64,28 @@ final class BeanReader { TypeElement actualType = proxy ? APContext.asTypeElement(beanType.getSuperclass()) : beanType; this.prototype = - PrototypePrism.isPresent(actualType) - || importedComponent && ProcessingContext.isImportedPrototype(actualType); + PrototypePrism.isPresent(actualType) + || importedComponent && ProcessingContext.isImportedPrototype(actualType); this.primary = PrimaryPrism.isPresent(actualType); this.secondary = !primary && SecondaryPrism.isPresent(actualType); - var beantypes = - BeanTypesPrism.getOptionalOn(actualType) - .map(BeanTypesPrism::value) - .or(() -> proxy ? Optional.of(List.of(actualType.asType())) : Optional.empty()); - beantypes.ifPresent(t -> Util.validateBeanTypes(actualType, t)); + var beanTypes = + BeanTypesPrism.getOptionalOn(actualType) + .map(BeanTypesPrism::value) + .or(() -> proxy ? Optional.of(List.of(actualType.asType())) : Optional.empty()); + beanTypes.ifPresent(t -> Util.validateBeanTypes(actualType, t)); this.typeReader = - new TypeReader( - beantypes.orElse(List.of()), - UType.parse(beanType.asType()), - beanType, - importTypes, - factory); + new TypeReader( + beanTypes.orElse(List.of()), + UType.parse(beanType.asType()), + beanType, + importTypes, + factory); typeReader.process(); - this.lazy = - !FactoryPrism.isPresent(actualType) - && (LazyPrism.isPresent(actualType) - || importedComponent && ProcessingContext.isImportedLazy(actualType)); + !FactoryPrism.isPresent(actualType) + && (LazyPrism.isPresent(actualType) + || importedComponent && ProcessingContext.isImportedLazy(actualType)); this.requestParams = new BeanRequestParams(type); this.name = typeReader.name(); @@ -192,7 +191,6 @@ BeanReader read() { } postConstructMethod.ifPresent(m -> m.addImports(importTypes)); - conditions.addImports(importTypes); if (proxyLazy) { SimpleBeanLazyWriter.write(APContext.elements().getPackageOf(beanType), lazyProxyType); @@ -619,7 +617,7 @@ void validate() { typeReader.validate(); } - public TypeElement getLazyProxyType() { + TypeElement lazyProxyType() { return lazyProxyType; } } 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 4d489df65..239b090ad 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 @@ -119,10 +119,10 @@ final class MethodReader { this.initMethod = initMethod; this.destroyMethod = destroyMethod; } else { - var beantypes = BeanTypesPrism.getOptionalOn(element).map(BeanTypesPrism::value); - beantypes.ifPresent(t -> Util.validateBeanTypes(element, t)); + var beanTypes = BeanTypesPrism.getOptionalOn(element).map(BeanTypesPrism::value); + beanTypes.ifPresent(t -> Util.validateBeanTypes(element, t)); this.typeReader = - new TypeReader(beantypes.orElse(List.of()), genericType, returnElement, importTypes); + new TypeReader(beanTypes.orElse(List.of()), genericType, returnElement, importTypes); typeReader.process(); MethodLifecycleReader lifecycleReader = new MethodLifecycleReader(returnElement, initMethod, destroyMethod); this.initMethod = lifecycleReader.initMethod(); @@ -287,17 +287,17 @@ void builderAddBeanProvider(Append writer) { endTry(writer, " "); writer.indent(indent); if (proxyLazy) { - writer - .append( - " }, %s$Lazy::new);", - Util.shortName(lazyProxyType.getQualifiedName().toString()).replace(".", "_")) - .eol(); + writer.append(" }, %s$Lazy::new);", lazyProxyShortName()).eol(); } else { writer.indent(indent).append(" });").eol(); } writer.indent(indent).append("}").eol(); } + private String lazyProxyShortName() { + return Util.shortName(lazyProxyType.getQualifiedName().toString()).replace(".", "_"); + } + void builderBuildAddBean(Append writer) { if (!isVoid) { if (optionalType) { diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/SimpleBeanLazyWriter.java b/inject-generator/src/main/java/io/avaje/inject/generator/SimpleBeanLazyWriter.java index 1803af021..ea8073b74 100644 --- a/inject-generator/src/main/java/io/avaje/inject/generator/SimpleBeanLazyWriter.java +++ b/inject-generator/src/main/java/io/avaje/inject/generator/SimpleBeanLazyWriter.java @@ -41,8 +41,8 @@ static void write(PackageElement pkg, TypeElement element) { } private final String shortName; - private boolean isInterface; - private TypeElement element; + private final boolean isInterface; + private final TypeElement element; SimpleBeanLazyWriter(PackageElement pkg, TypeElement element) { this.element = element; @@ -62,14 +62,11 @@ static void write(PackageElement pkg, TypeElement element) { void write() { try { - var writer = new Append(APContext.createSourceFile(originName, element).openWriter()); + final var writer = new Append(APContext.createSourceFile(originName, element).openWriter()); var typeString = isInterface ? "implements" : "extends"; - String methodString = methods(); - writer.append( - MessageFormat.format( - TEMPLATE, packageName, imports(), shortName, typeString, methodString)); + writer.append(MessageFormat.format(TEMPLATE, packageName, imports(), shortName, typeString, methodString)); writer.close(); } catch (Exception e) { logError("Failed to write Proxy class %s", e); @@ -91,16 +88,13 @@ String imports() { } private String methods() { - var sb = new StringBuilder(); - for (var methodElement : ElementFilter.methodsIn(APContext.elements().getAllMembers(element))) { Set modifiers = methodElement.getModifiers(); if (modifiers.contains(Modifier.PRIVATE) || modifiers.contains(Modifier.STATIC) || methodElement.getEnclosingElement().getSimpleName().contentEquals("Object")) continue; - sb.append(""); // Access modifiers if (modifiers.contains(Modifier.PUBLIC)) { sb.append(" public "); @@ -114,9 +108,9 @@ private String methods() { if (!typeParameters.isEmpty()) { sb.append("<"); sb.append( - typeParameters.stream() - .map(tp -> tp.getSimpleName().toString()) - .collect(Collectors.joining(", "))); + typeParameters.stream() + .map(tp -> tp.getSimpleName().toString()) + .collect(Collectors.joining(", "))); sb.append("> "); } var returnType = UType.parse(methodElement.getReturnType()); @@ -149,9 +143,9 @@ private String methods() { if (!thrownTypes.isEmpty()) { sb.append(" throws "); sb.append( - thrownTypes.stream() - .map(t -> UType.parse(t).shortType()) - .collect(Collectors.joining(", "))); + thrownTypes.stream() + .map(t -> UType.parse(t).shortType()) + .collect(Collectors.joining(", "))); } sb.append(" {\n "); 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 128d3c450..906f48e15 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 @@ -205,7 +205,7 @@ private void writeAddFor(MethodReader constructor) { .append(", ") .append( "%s$Lazy::new", - Util.shortName(beanReader.getLazyProxyType().getQualifiedName().toString()) + Util.shortName(beanReader.lazyProxyType().getQualifiedName().toString()) .replace(".", "_")); } writer.append(");").eol(); @@ -221,7 +221,7 @@ private void writeAddFor(MethodReader constructor) { .append(", ") .append( " %s$Lazy::new", - Util.shortName(beanReader.getLazyProxyType().getQualifiedName().toString()) + Util.shortName(beanReader.lazyProxyType().getQualifiedName().toString()) .replace(".", "_")); } writer.append(");").eol(); From dedaae20b207797957fde1ded8a37f4cc7d3cc2e Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Mon, 16 Jun 2025 15:19:46 +1200 Subject: [PATCH 08/11] Refactor extract Util.shortNameLazyProxy() helper method --- .../io/avaje/inject/generator/MethodReader.java | 6 +----- .../avaje/inject/generator/SimpleBeanWriter.java | 14 ++------------ .../main/java/io/avaje/inject/generator/Util.java | 4 ++++ 3 files changed, 7 insertions(+), 17 deletions(-) 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 239b090ad..04788ce09 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 @@ -287,17 +287,13 @@ void builderAddBeanProvider(Append writer) { endTry(writer, " "); writer.indent(indent); if (proxyLazy) { - writer.append(" }, %s$Lazy::new);", lazyProxyShortName()).eol(); + writer.append(" }, %s$Lazy::new);", Util.shortNameLazyProxy(lazyProxyType)).eol(); } else { writer.indent(indent).append(" });").eol(); } writer.indent(indent).append("}").eol(); } - private String lazyProxyShortName() { - return Util.shortName(lazyProxyType.getQualifiedName().toString()).replace(".", "_"); - } - void builderBuildAddBean(Append writer) { if (!isVoid) { if (optionalType) { 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 906f48e15..6d7b9b86d 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 @@ -201,12 +201,7 @@ private void writeAddFor(MethodReader constructor) { if (!constructor.methodThrows()) { writer.append(" }"); if (beanReader.proxyLazy()) { - writer - .append(", ") - .append( - "%s$Lazy::new", - Util.shortName(beanReader.lazyProxyType().getQualifiedName().toString()) - .replace(".", "_")); + writer.append(", %s$Lazy::new", Util.shortNameLazyProxy(beanReader.lazyProxyType())); } writer.append(");").eol(); } @@ -217,12 +212,7 @@ private void writeAddFor(MethodReader constructor) { if (beanReader.registerProvider() && constructor.methodThrows()) { writer.append(" }"); if (beanReader.proxyLazy()) { - writer - .append(", ") - .append( - " %s$Lazy::new", - Util.shortName(beanReader.lazyProxyType().getQualifiedName().toString()) - .replace(".", "_")); + writer.append(", %s$Lazy::new", Util.shortNameLazyProxy(beanReader.lazyProxyType())); } writer.append(");").eol(); } 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 e69a8c41f..c92bed760 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 @@ -432,4 +432,8 @@ static boolean hasNoArgConstructor(TypeElement beanType) { return ElementFilter.constructorsIn(beanType.getEnclosedElements()).stream() .anyMatch(e -> e.getParameters().isEmpty() && !e.getModifiers().contains(Modifier.PRIVATE)); } + + public static String shortNameLazyProxy(TypeElement lazyProxyType) { + return shortName(lazyProxyType.getQualifiedName().toString()) + .replace(".", "_"); } } From c200e3c754e1cbee38d2000abdbe561ccae49dd4 Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Mon, 16 Jun 2025 15:26:01 +1200 Subject: [PATCH 09/11] Format, remove unrequired public modifier --- .../java/io/avaje/inject/generator/Util.java | 46 +++++++++---------- .../src/main/java/io/avaje/inject/Lazy.java | 4 +- .../java/io/avaje/inject/spi/Builder.java | 7 +-- .../avaje/inject/spi/DContextEntryBean.java | 3 +- .../io/avaje/inject/spi/OnceProvider.java | 2 +- 5 files changed, 30 insertions(+), 32 deletions(-) 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 c92bed760..bc67f26a2 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 @@ -392,37 +392,35 @@ static String valhalla() { static void validateBeanTypes(Element origin, List beanType) { TypeMirror targetType = - origin instanceof TypeElement - ? origin.asType() - : ((ExecutableElement) origin).getReturnType(); - beanType.forEach( - type -> { - if (!APContext.types().isAssignable(targetType, type)) { - APContext.logError(origin, "%s does not extend type %s", targetType, beanType); - } - }); + origin instanceof TypeElement + ? origin.asType() + : ((ExecutableElement) origin).getReturnType(); + beanType.forEach(type -> { + if (!APContext.types().isAssignable(targetType, type)) { + APContext.logError(origin, "%s does not extend type %s", targetType, beanType); + } + }); } static TypeElement lazyProxy(Element element) { TypeElement type = - element instanceof TypeElement - ? (TypeElement) element - : APContext.asTypeElement(((ExecutableElement) element).getReturnType()); + element instanceof TypeElement + ? (TypeElement) element + : APContext.asTypeElement(((ExecutableElement) element).getReturnType()); if (!type.getTypeParameters().isEmpty() - || type.getModifiers().contains(Modifier.FINAL) - || !type.getKind().isInterface() && !Util.hasNoArgConstructor(type)) { + || type.getModifiers().contains(Modifier.FINAL) + || !type.getKind().isInterface() && !Util.hasNoArgConstructor(type)) { return BeanTypesPrism.getOptionalOn(element) - .map(BeanTypesPrism::value) - .filter(v -> v.size() == 1) - .map(v -> APContext.asTypeElement(v.get(0))) - // figure out generics later - .filter( - v -> - v.getTypeParameters().isEmpty() - && (v.getKind().isInterface() || hasNoArgConstructor(v))) - .orElse(null); + .map(BeanTypesPrism::value) + .filter(v -> v.size() == 1) + .map(v -> APContext.asTypeElement(v.get(0))) + // figure out generics later + .filter(v -> + v.getTypeParameters().isEmpty() + && (v.getKind().isInterface() || hasNoArgConstructor(v))) + .orElse(null); } return type; @@ -430,7 +428,7 @@ static TypeElement lazyProxy(Element element) { static boolean hasNoArgConstructor(TypeElement beanType) { return ElementFilter.constructorsIn(beanType.getEnclosedElements()).stream() - .anyMatch(e -> e.getParameters().isEmpty() && !e.getModifiers().contains(Modifier.PRIVATE)); + .anyMatch(e -> e.getParameters().isEmpty() && !e.getModifiers().contains(Modifier.PRIVATE)); } public static String shortNameLazyProxy(TypeElement lazyProxyType) { diff --git a/inject/src/main/java/io/avaje/inject/Lazy.java b/inject/src/main/java/io/avaje/inject/Lazy.java index b1d66fee4..4e5665983 100644 --- a/inject/src/main/java/io/avaje/inject/Lazy.java +++ b/inject/src/main/java/io/avaje/inject/Lazy.java @@ -11,8 +11,8 @@ *

When annotating a {@link Factory} as {@code @Lazy} it means that the factory itself is not * lazy but all beans that it provides will have lazy initialization. * - * @apiNote If the annotated class is an interface or has an additional no-args constructor, a - * generated proxy bean will be wired for ultimate laziness. + *

If the annotated class is an interface or has an additional no-args constructor, a + * generated proxy bean will be wired for ultimate laziness. */ @Retention(RetentionPolicy.SOURCE) @Target({ElementType.METHOD, ElementType.TYPE}) 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 2088d7b54..56caf92fa 100644 --- a/inject/src/main/java/io/avaje/inject/spi/Builder.java +++ b/inject/src/main/java/io/avaje/inject/spi/Builder.java @@ -79,9 +79,10 @@ default boolean isBeanAbsent(Type... types) { */ void registerProvider(Provider provider); - /** Register the lazy provider into the context. */ - default void registerLazy( - Provider provider, Function, T> proxyClassConstructor) { + /** + * Register the lazy provider into the context. + */ + default void registerLazy(Provider provider, Function, T> proxyClassConstructor) { register(proxyClassConstructor.apply(new OnceProvider<>(provider))); } 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 be63270df..d1f2e7a79 100644 --- a/inject/src/main/java/io/avaje/inject/spi/DContextEntryBean.java +++ b/inject/src/main/java/io/avaje/inject/spi/DContextEntryBean.java @@ -141,8 +141,7 @@ static final class OnceBeanProvider extends DContextEntryBean { private final Provider provider; - private OnceBeanProvider( - Provider provider, String name, int flag, Class currentModule) { + private OnceBeanProvider(Provider provider, String name, int flag, Class currentModule) { super(provider, name, flag, currentModule); this.provider = new OnceProvider<>(provider); } diff --git a/inject/src/main/java/io/avaje/inject/spi/OnceProvider.java b/inject/src/main/java/io/avaje/inject/spi/OnceProvider.java index aa73707c4..38cc66f21 100644 --- a/inject/src/main/java/io/avaje/inject/spi/OnceProvider.java +++ b/inject/src/main/java/io/avaje/inject/spi/OnceProvider.java @@ -12,7 +12,7 @@ final class OnceProvider implements Provider { private final Provider provider; private T bean; - public OnceProvider(Provider provider) { + OnceProvider(Provider provider) { this.provider = Objects.requireNonNull(provider); } From 59a038c3128f9fb331c5e523248ef3bd37049b39 Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Mon, 16 Jun 2025 15:39:45 +1200 Subject: [PATCH 10/11] Modify SimpleBeanProxyWriter, removing the generation of the default constructor As that default constructor looks completely unused and not needed. --- .../java/io/avaje/inject/generator/SimpleBeanProxyWriter.java | 3 --- 1 file changed, 3 deletions(-) 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 8a73ad5b3..c835ecd1d 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 @@ -55,9 +55,6 @@ private void writeFields() { } private void writeConstructor() { - if (beanReader.proxyLazy()) { - writer.append(" public %s%s(){}", shortName, suffix).eol().eol(); - } writer.append(" @Inject\n"); writer.append(" public %s%s(", shortName, suffix); int count = 0; From 3a72ad8fa5d9aeb4a1d1dbd12e363c33048a50da Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Mon, 16 Jun 2025 15:52:20 +1200 Subject: [PATCH 11/11] Fix format of generated code for indentation plus add @Override --- .../src/main/java/org/example/myapp/lazy/LazyBean.java | 2 ++ .../src/main/java/org/example/myapp/lazy/LazyImpl.java | 3 +++ .../src/main/java/org/example/myapp/lazy/LazyInterface.java | 2 ++ .../java/io/avaje/inject/generator/SimpleBeanLazyWriter.java | 5 +++-- 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/blackbox-test-inject/src/main/java/org/example/myapp/lazy/LazyBean.java b/blackbox-test-inject/src/main/java/org/example/myapp/lazy/LazyBean.java index 383452d18..a9512bc9e 100644 --- a/blackbox-test-inject/src/main/java/org/example/myapp/lazy/LazyBean.java +++ b/blackbox-test-inject/src/main/java/org/example/myapp/lazy/LazyBean.java @@ -25,4 +25,6 @@ void init(BeanScope scope) { } void something() {} + + public void other() {} } diff --git a/blackbox-test-inject/src/main/java/org/example/myapp/lazy/LazyImpl.java b/blackbox-test-inject/src/main/java/org/example/myapp/lazy/LazyImpl.java index 4b41d220d..14c96c5d8 100644 --- a/blackbox-test-inject/src/main/java/org/example/myapp/lazy/LazyImpl.java +++ b/blackbox-test-inject/src/main/java/org/example/myapp/lazy/LazyImpl.java @@ -30,4 +30,7 @@ void init(BeanScope scope) { @Override public void something() {} + + @Override + public void otherThing() {} } diff --git a/blackbox-test-inject/src/main/java/org/example/myapp/lazy/LazyInterface.java b/blackbox-test-inject/src/main/java/org/example/myapp/lazy/LazyInterface.java index d5ba84e34..b61b2482b 100644 --- a/blackbox-test-inject/src/main/java/org/example/myapp/lazy/LazyInterface.java +++ b/blackbox-test-inject/src/main/java/org/example/myapp/lazy/LazyInterface.java @@ -3,4 +3,6 @@ public interface LazyInterface { void something(); + + void otherThing(); } diff --git a/inject-generator/src/main/java/io/avaje/inject/generator/SimpleBeanLazyWriter.java b/inject-generator/src/main/java/io/avaje/inject/generator/SimpleBeanLazyWriter.java index ea8073b74..26ddd3e97 100644 --- a/inject-generator/src/main/java/io/avaje/inject/generator/SimpleBeanLazyWriter.java +++ b/inject-generator/src/main/java/io/avaje/inject/generator/SimpleBeanLazyWriter.java @@ -29,7 +29,7 @@ final class SimpleBeanLazyWriter { + " public {2}$Lazy(Provider<{2}> onceProvider) '{'\n" + " this.onceProvider = onceProvider;\n" + " '}'\n\n" - + " {4}" + + "{4}" + "'}'\n"; private final String originName; @@ -96,12 +96,13 @@ private String methods() { || modifiers.contains(Modifier.STATIC) || methodElement.getEnclosingElement().getSimpleName().contentEquals("Object")) continue; // Access modifiers + sb.append(" @Override\n"); if (modifiers.contains(Modifier.PUBLIC)) { sb.append(" public "); } else if (modifiers.contains(Modifier.PROTECTED)) { sb.append(" protected "); } else { - sb.append(" "); + sb.append(" "); } // Generic type parameters List typeParameters = methodElement.getTypeParameters();