Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,6 @@ void init(BeanScope scope) {
void something() {}

public void other() {}

protected void other2() {}
}
Original file line number Diff line number Diff line change
@@ -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();
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
/**
* Use Lazy for all the beans in this package.
* <p>
* Use {@code enforceProxy = true} to fail compilation if there is no default constructor/lazy not supported.
*
* <p>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;
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@

import io.avaje.inject.BeanScope;

import java.io.Serializable;

class BeanTypesTest {

@Test
Expand All @@ -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");
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
13 changes: 11 additions & 2 deletions inject-generator/src/main/java/io/avaje/inject/generator/Util.java
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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));
Expand Down
Original file line number Diff line number Diff line change
@@ -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();
}
}
28 changes: 25 additions & 3 deletions inject/src/main/java/io/avaje/inject/Lazy.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
* <p>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.
*
* <p>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
}
}