Skip to content

Commit 1abf790

Browse files
authored
Make @Lazy work on a package/module level (#884)
Given that we generate proxies now, this technically works.
1 parent 59232b0 commit 1abf790

File tree

9 files changed

+201
-11
lines changed

9 files changed

+201
-11
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package org.example.myapp.lazy2;
2+
3+
import io.avaje.inject.PostConstruct;
4+
import jakarta.inject.Singleton;
5+
6+
import java.util.concurrent.atomic.AtomicBoolean;
7+
8+
@Singleton
9+
public class LazyOneA {
10+
11+
public static final AtomicBoolean AINIT = new AtomicBoolean();
12+
public static final AtomicBoolean A_POST_CONSTRUCT = new AtomicBoolean();
13+
14+
LazyOneA() {
15+
AINIT.set(true);
16+
}
17+
18+
@PostConstruct
19+
void postConstruct() {
20+
A_POST_CONSTRUCT.set(true);
21+
}
22+
23+
public String oneA() {
24+
return "oneA";
25+
}
26+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package org.example.myapp.lazy2;
2+
3+
import jakarta.inject.Inject;
4+
import jakarta.inject.Singleton;
5+
import org.example.myapp.HelloService;
6+
7+
import java.util.concurrent.atomic.AtomicBoolean;
8+
9+
@Singleton
10+
public class LazyOneB {
11+
12+
public static final AtomicBoolean BINIT = new AtomicBoolean();
13+
14+
final HelloService helloService;
15+
16+
@Inject
17+
LazyOneB(HelloService helloService) {
18+
this.helloService = helloService; // non-lazy dependency
19+
BINIT.set(true);
20+
}
21+
22+
/** Required by Lazy proxy */
23+
LazyOneB() {
24+
this.helloService = null;
25+
}
26+
27+
public String oneB() {
28+
return "oneB";
29+
}
30+
31+
public HelloService helloService() {
32+
return helloService;
33+
}
34+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package org.example.myapp.lazy2;
2+
3+
import jakarta.inject.Inject;
4+
import jakarta.inject.Singleton;
5+
6+
import java.util.concurrent.atomic.AtomicBoolean;
7+
8+
@Singleton
9+
public class LazyTwo {
10+
11+
public static final AtomicBoolean INIT = new AtomicBoolean();
12+
13+
private final LazyOneB oneB;
14+
final LazyOneA oneA;
15+
16+
@Inject
17+
LazyTwo(LazyOneA oneA, LazyOneB oneB) {
18+
this.oneA = oneA;
19+
this.oneB = oneB;
20+
INIT.set(true);
21+
}
22+
23+
/** Required by Lazy proxy */
24+
LazyTwo() {
25+
this.oneA = null;
26+
this.oneB = null;
27+
}
28+
29+
String something() {
30+
return "two-" + oneA.oneA() + "-" + oneB.oneB();
31+
}
32+
33+
String description() {
34+
return this.getClass() + "|" + oneA.getClass() + "|" + oneB.getClass();
35+
}
36+
37+
public LazyOneA oneA() {
38+
return oneA;
39+
}
40+
41+
public LazyOneB oneB() {
42+
return oneB;
43+
}
44+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/**
2+
* Use Lazy for all the beans in this package.
3+
* <p>
4+
* Use {@code enforceProxy = true} to fail compilation if there is no default constructor/lazy not supported.
5+
*/
6+
@Lazy(enforceProxy = true)
7+
package org.example.myapp.lazy2;
8+
9+
import io.avaje.inject.Lazy;
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package org.example.myapp.lazy2;
2+
3+
import io.avaje.inject.BeanScope;
4+
import org.example.myapp.HelloService;
5+
import org.junit.jupiter.api.Test;
6+
7+
import static org.assertj.core.api.Assertions.assertThat;
8+
9+
class LazyTwoTest {
10+
11+
@Test
12+
void test() {
13+
try (var scope = BeanScope.builder().build()) {
14+
assertThat(LazyTwo.INIT).isFalse();
15+
assertThat(LazyOneA.AINIT).describedAs("Only 1 constructor, so <init> called by LazyOneA$$Lazy()").isTrue();
16+
assertThat(LazyOneA.A_POST_CONSTRUCT).isFalse();
17+
assertThat(LazyOneB.BINIT).isFalse();
18+
19+
var lazyOneA = scope.get(LazyOneA.class);
20+
assertThat(LazyOneA.A_POST_CONSTRUCT).describedAs("Only got the proxy").isFalse();
21+
22+
var lazy = scope.get(LazyTwo.class);
23+
assertThat(lazy.getClass().toString()).describedAs("got the proxy").contains("LazyTwo$Lazy");
24+
assertThat(LazyTwo.INIT).isFalse();
25+
assertThat(LazyOneB.BINIT).isFalse();
26+
assertThat(LazyOneA.A_POST_CONSTRUCT).describedAs("Only got the proxy").isFalse();
27+
28+
assertThat(lazy.oneA()).describedAs("same proxy instance").isSameAs(lazyOneA);
29+
30+
// invocation will initialize the lazy beans
31+
String value = lazy.something();
32+
assertThat(value).isEqualTo("two-oneA-oneB");
33+
assertThat(LazyTwo.INIT).isTrue();
34+
assertThat(LazyOneA.A_POST_CONSTRUCT).isTrue();
35+
assertThat(LazyOneB.BINIT).isTrue();
36+
37+
// the graph is of Lazy beans
38+
String description = lazy.description();
39+
assertThat(description).describedAs("this is the underlying real instance").doesNotContain("LazyTwo$Lazy");
40+
assertThat(description).contains("LazyOneA$Lazy");
41+
assertThat(description).contains("LazyOneB$Lazy");
42+
43+
assertThat(scope.get(LazyTwo.class)).isSameAs(lazy);
44+
45+
HelloService nonLazyDependency = lazy.oneB().helloService();
46+
HelloService helloService = scope.get(HelloService.class);
47+
assertThat(nonLazyDependency).isSameAs(helloService);
48+
}
49+
}
50+
}

inject-generator/src/main/java/io/avaje/inject/generator/BeanReader.java

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -85,10 +85,10 @@ final class BeanReader {
8585
factory);
8686

8787
typeReader.process();
88-
this.lazy =
89-
!FactoryPrism.isPresent(actualType)
90-
&& (LazyPrism.isPresent(actualType)
91-
|| importedComponent && ProcessingContext.isImportedLazy(actualType));
88+
var lazyPrism = Util.isLazy(actualType);
89+
this.lazy = !FactoryPrism.isPresent(actualType)
90+
&& (lazyPrism != null
91+
|| importedComponent && ProcessingContext.isImportedLazy(actualType));
9292

9393
this.requestParams = new BeanRequestParams(type);
9494
this.name = typeReader.name();
@@ -105,6 +105,14 @@ final class BeanReader {
105105
this.delayed = shouldDelay();
106106
this.lazyProxyType = !lazy || delayed ? null : Util.lazyProxy(actualType);
107107
this.proxyLazy = lazy && lazyProxyType != null;
108+
if (lazy && !proxyLazy) {
109+
if (lazyPrism != null && lazyPrism.enforceProxy()) {
110+
logError(beanType, "Lazy beans must have an additional no-arg constructor");
111+
} else {
112+
logWarn(beanType, "Lazy beans should have an additional no-arg constructor");
113+
}
114+
}
115+
108116
conditions.readAll(actualType);
109117
}
110118

@@ -197,8 +205,6 @@ BeanReader read() {
197205
conditions.addImports(importTypes);
198206
if (proxyLazy) {
199207
SimpleBeanLazyWriter.write(APContext.elements().getPackageOf(beanType), lazyProxyType);
200-
} else if (lazy) {
201-
logWarn(beanType, "Lazy beans should have a no-arg constructor");
202208
}
203209
return this;
204210
}

inject-generator/src/main/java/io/avaje/inject/generator/MethodReader.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.avaje.inject.generator;
22

3+
import static io.avaje.inject.generator.APContext.logError;
34
import static io.avaje.inject.generator.APContext.logWarn;
45
import static io.avaje.inject.generator.Constants.CONDITIONAL_DEPENDENCY;
56
import static io.avaje.inject.generator.ProcessingContext.asElement;
@@ -71,10 +72,18 @@ final class MethodReader {
7172
primary = PrimaryPrism.isPresent(element);
7273
secondary = SecondaryPrism.isPresent(element);
7374
priority = Util.priority(element);
74-
lazy = LazyPrism.isPresent(element) || LazyPrism.isPresent(element.getEnclosingElement());
75+
var lazyPrism = Util.isLazy(element);
76+
lazy = lazyPrism != null;
7577
conditions.readAll(element);
7678
this.lazyProxyType = lazy ? Util.lazyProxy(element) : null;
7779
this.proxyLazy = lazy && lazyProxyType != null;
80+
if (lazy && !proxyLazy) {
81+
if (lazyPrism.enforceProxy()) {
82+
logError(element, "Lazy return type must be abstract or have a no-arg constructor");
83+
} else {
84+
logWarn(element, "Lazy return type should be abstract or have a no-arg constructor");
85+
}
86+
}
7887
} else {
7988
prototype = false;
8089
primary = false;
@@ -181,8 +190,6 @@ MethodReader read() {
181190
observeParameter = params.stream().filter(MethodParam::observeEvent).findFirst().orElse(null);
182191
if (proxyLazy) {
183192
SimpleBeanLazyWriter.write(APContext.elements().getPackageOf(element), lazyProxyType);
184-
} else if (lazy) {
185-
logWarn(element, "Lazy return types should be abstract or have a no-arg constructor");
186193
}
187194
return this;
188195
}

inject-generator/src/main/java/io/avaje/inject/generator/Util.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,4 +442,11 @@ static Integer priority(Element element) {
442442
private static boolean isPriorityAnnotation(AnnotationMirror mirror) {
443443
return mirror.getAnnotationType().asElement().getSimpleName().toString().contains("Priority");
444444
}
445+
446+
static LazyPrism isLazy(Element element) {
447+
if (element == null) {
448+
return null;
449+
}
450+
return LazyPrism.getOptionalOn(element).orElseGet(() -> isLazy(element.getEnclosingElement()));
451+
}
445452
}

inject/src/main/java/io/avaje/inject/Lazy.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,12 @@
1515
* constructor, a generated proxy bean will be wired for ultimate laziness.
1616
*/
1717
@Retention(RetentionPolicy.SOURCE)
18-
@Target({ElementType.METHOD, ElementType.TYPE})
19-
public @interface Lazy {}
18+
@Target({ElementType.METHOD, ElementType.TYPE, ElementType.PACKAGE, ElementType.MODULE})
19+
public @interface Lazy {
20+
21+
/**
22+
* Ensures that a compile-time proxy is generated, will fail compilation if missing conditions for
23+
* generation
24+
*/
25+
boolean enforceProxy() default false;
26+
}

0 commit comments

Comments
 (0)