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 a9512bc9e..eb55c4fcb 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 @@ -27,4 +27,6 @@ void init(BeanScope scope) { void something() {} public void other() {} + + protected void other2() {} } diff --git a/blackbox-test-inject/src/main/java/org/example/myapp/lazy/RandomFactory.java b/blackbox-test-inject/src/main/java/org/example/myapp/lazy/RandomFactory.java new file mode 100644 index 000000000..0db0918e8 --- /dev/null +++ b/blackbox-test-inject/src/main/java/org/example/myapp/lazy/RandomFactory.java @@ -0,0 +1,18 @@ +package org.example.myapp.lazy; + +import java.security.SecureRandom; +import java.util.Random; + +import io.avaje.inject.Bean; +import io.avaje.inject.Factory; +import io.avaje.inject.Lazy; +import io.avaje.inject.Lazy.Kind; + +@Lazy(Kind.PROVIDER) +@Factory +public class RandomFactory { + @Bean + public Random secureRandom() { + return new SecureRandom(); + } +} diff --git a/blackbox-test-inject/src/main/java/org/example/myapp/lazy2/package-info.java b/blackbox-test-inject/src/main/java/org/example/myapp/lazy2/package-info.java index 133f6dc74..d06a97643 100644 --- a/blackbox-test-inject/src/main/java/org/example/myapp/lazy2/package-info.java +++ b/blackbox-test-inject/src/main/java/org/example/myapp/lazy2/package-info.java @@ -1,9 +1,11 @@ /** * Use Lazy for all the beans in this package. - *

- * Use {@code enforceProxy = true} to fail compilation if there is no default constructor/lazy not supported. + * + *

Use {@code enforceProxy = true} to fail compilation if there is no default constructor/lazy + * not supported. */ -@Lazy(enforceProxy = true) +@Lazy(Kind.FORCE_PROXY) package org.example.myapp.lazy2; import io.avaje.inject.Lazy; +import io.avaje.inject.Lazy.Kind; diff --git a/blackbox-test-inject/src/test/java/org/example/myapp/beantypes/BeanTypeComponentTest.java b/blackbox-test-inject/src/test/java/org/example/myapp/beantypes/BeanTypeComponentTest.java index 5ca5a05f3..cba899a80 100644 --- a/blackbox-test-inject/src/test/java/org/example/myapp/beantypes/BeanTypeComponentTest.java +++ b/blackbox-test-inject/src/test/java/org/example/myapp/beantypes/BeanTypeComponentTest.java @@ -7,8 +7,6 @@ import io.avaje.inject.BeanScope; -import java.io.Serializable; - class BeanTypesTest { @Test @@ -18,7 +16,6 @@ void testBeanTypesRestrictingInjection() { assertFalse(scope.contains(BeanTypeComponent.class)); assertThat(scope.get(AbstractSuperClass.class)).isNotNull(); assertThat(scope.get(LimitedInterface.class)).isNotNull(); - assertThat(scope.get(Serializable.class)).isEqualTo("IAmSerializable"); assertThat(scope.get(CharSequence.class)).isEqualTo("IAmNullable"); } } 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 6ed4a6e3d..e7bfab5f9 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 @@ -103,10 +103,13 @@ final class BeanReader { this.observerMethods = typeReader.observerMethods(); this.importedComponent = importedComponent && constructor != null && constructor.isPublic(); this.delayed = shouldDelay(); - this.lazyProxyType = !lazy || delayed ? null : Util.lazyProxy(actualType); + String lazyKind = Optional.ofNullable(lazyPrism).map(LazyPrism::value).orElse(""); + boolean useProxy = !"PROVIDER".equals(lazyKind); + this.lazyProxyType = !lazy || !useProxy ? null : Util.lazyProxy(actualType); this.proxyLazy = lazy && lazyProxyType != null; - if (lazy && !proxyLazy) { - if (lazyPrism != null && lazyPrism.enforceProxy()) { + + if (lazy && !proxyLazy && useProxy) { + if ("FORCE_PROXY".equals(lazyKind)) { logError(beanType, "Lazy beans must have an additional no-arg constructor"); } else { logWarn(beanType, "Lazy beans should have an additional no-arg constructor"); 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 e092d96e9..025096149 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 @@ -10,6 +10,7 @@ import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.function.Supplier; @@ -75,10 +76,14 @@ final class MethodReader { var lazyPrism = Util.isLazy(element); lazy = lazyPrism != null; conditions.readAll(element); - this.lazyProxyType = lazy ? Util.lazyProxy(element) : null; + + String lazyKind = Optional.ofNullable(lazyPrism).map(LazyPrism::value).orElse(""); + boolean useProxy = !"PROVIDER".equals(lazyKind); + this.lazyProxyType = !lazy || !useProxy ? null : Util.lazyProxy(element); this.proxyLazy = lazy && lazyProxyType != null; - if (lazy && !proxyLazy) { - if (lazyPrism.enforceProxy()) { + + if (lazy && !proxyLazy && useProxy) { + if ("FORCE_PROXY".equals(lazyKind)) { logError(element, "Lazy return type must be abstract or have a no-arg constructor"); } else { logWarn(element, "Lazy return type should be abstract or have a no-arg constructor"); 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 01d9a9b32..d220d1582 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 @@ -163,19 +163,24 @@ private String methods() { } sb.append(" {\n "); - if (!"void".equals(returnType.full())) { - sb.append("return "); - } + if (modifiers.contains(Modifier.PROTECTED)) { + sb.append("// @Lazy proxy does not support protected methods, instead use @Lazy(useProxy = false)"); + sb.append("\n throw new UnsupportedOperationException();\n"); + } else { + 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()); - if (i < parameters.size() - 1) { - sb.append(", "); + 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"); sb.append(" }\n\n"); } return sb.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 232a7f322..d9d700374 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 @@ -400,9 +400,10 @@ static TypeElement lazyProxy(Element element) { element instanceof TypeElement ? (TypeElement) element : APContext.asTypeElement(((ExecutableElement) element).getReturnType()); - + var notInterface = !type.getKind().isInterface(); if (type.getModifiers().contains(Modifier.FINAL) - || !type.getKind().isInterface() && !Util.hasNoArgConstructor(type)) { + || notInterface && !Util.hasNoArgConstructor(type) + || notInterface && Util.hasFinalMethods(type)) { return BeanTypesPrism.getOptionalOn(element) .map(BeanTypesPrism::value) @@ -417,6 +418,14 @@ static TypeElement lazyProxy(Element element) { return type; } + private static boolean hasFinalMethods(TypeElement type) { + return ElementFilter.methodsIn(type.getEnclosedElements()).stream() + .filter(x -> !x.getModifiers().contains(Modifier.STATIC)) + .filter(x -> !x.getModifiers().contains(Modifier.PRIVATE)) + .filter(x -> !x.getModifiers().contains(Modifier.PROTECTED)) + .anyMatch(m -> m.getModifiers().contains(Modifier.FINAL)); + } + 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/RandomFactory.java b/inject-generator/src/test/java/io/avaje/inject/generator/models/valid/lazy/RandomFactory.java new file mode 100644 index 000000000..cdbd71b6a --- /dev/null +++ b/inject-generator/src/test/java/io/avaje/inject/generator/models/valid/lazy/RandomFactory.java @@ -0,0 +1,16 @@ +package io.avaje.inject.generator.models.valid.lazy; + +import java.security.SecureRandom; + +import io.avaje.inject.Bean; +import io.avaje.inject.Factory; +import io.avaje.inject.Lazy; + +@Lazy +@Factory +public class RandomFactory { + @Bean + public SecureRandom secureRandom() { + return new SecureRandom(); + } +} diff --git a/inject/src/main/java/io/avaje/inject/Lazy.java b/inject/src/main/java/io/avaje/inject/Lazy.java index f5766e064..6c8bd3b69 100644 --- a/inject/src/main/java/io/avaje/inject/Lazy.java +++ b/inject/src/main/java/io/avaje/inject/Lazy.java @@ -17,10 +17,32 @@ @Retention(RetentionPolicy.SOURCE) @Target({ElementType.METHOD, ElementType.TYPE, ElementType.PACKAGE, ElementType.MODULE}) public @interface Lazy { + /** Determine the kind of lazy initialization. */ + Kind value() default Kind.AUTO_PROXY; /** - * Ensures that a compile-time proxy is generated, will fail compilation if missing conditions for - * generation + * Control whether a compile-time proxy is generated to support lazy initialization. + * + *

When using {@link Kind#FORCE_PROXY} a compile-time error will occur if the conditions for + * generating a proxy are not met (for example the class is final or has no no-args constructor). + * When using {@link Kind#AUTO_PROXY} a warning will be issued and lazy initialization will fall + * back to provider based lazy initialization. + * + *

When using {@link Kind#PROVIDER} no proxy is generated and lazy initialization is done via a + * provider. */ - boolean enforceProxy() default false; + enum Kind { + /** + * Ensures that a compile-time proxy is generated, will fail compilation if missing conditions + * for generation + */ + FORCE_PROXY, + /** + * Attempt compile-time proxy, will warn and fallback to provider compilation if missing + * conditions for generation + */ + AUTO_PROXY, + /** No proxy, use a provider based lazy initialization */ + PROVIDER + } }