Skip to content

Commit 6481e1a

Browse files
authored
fix proxy generation with final methods (#896)
Fixes issue where we would try to generate proxies for classes with final methods
1 parent 7f8db7e commit 6481e1a

File tree

10 files changed

+106
-27
lines changed

10 files changed

+106
-27
lines changed

blackbox-test-inject/src/main/java/org/example/myapp/lazy/LazyBean.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,6 @@ void init(BeanScope scope) {
2727
void something() {}
2828

2929
public void other() {}
30+
31+
protected void other2() {}
3032
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package org.example.myapp.lazy;
2+
3+
import java.security.SecureRandom;
4+
import java.util.Random;
5+
6+
import io.avaje.inject.Bean;
7+
import io.avaje.inject.Factory;
8+
import io.avaje.inject.Lazy;
9+
import io.avaje.inject.Lazy.Kind;
10+
11+
@Lazy(Kind.PROVIDER)
12+
@Factory
13+
public class RandomFactory {
14+
@Bean
15+
public Random secureRandom() {
16+
return new SecureRandom();
17+
}
18+
}
Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
/**
22
* 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.
3+
*
4+
* <p>Use {@code enforceProxy = true} to fail compilation if there is no default constructor/lazy
5+
* not supported.
56
*/
6-
@Lazy(enforceProxy = true)
7+
@Lazy(Kind.FORCE_PROXY)
78
package org.example.myapp.lazy2;
89

910
import io.avaje.inject.Lazy;
11+
import io.avaje.inject.Lazy.Kind;

blackbox-test-inject/src/test/java/org/example/myapp/beantypes/BeanTypeComponentTest.java

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@
77

88
import io.avaje.inject.BeanScope;
99

10-
import java.io.Serializable;
11-
1210
class BeanTypesTest {
1311

1412
@Test
@@ -18,7 +16,6 @@ void testBeanTypesRestrictingInjection() {
1816
assertFalse(scope.contains(BeanTypeComponent.class));
1917
assertThat(scope.get(AbstractSuperClass.class)).isNotNull();
2018
assertThat(scope.get(LimitedInterface.class)).isNotNull();
21-
assertThat(scope.get(Serializable.class)).isEqualTo("IAmSerializable");
2219
assertThat(scope.get(CharSequence.class)).isEqualTo("IAmNullable");
2320
}
2421
}

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

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,10 +103,13 @@ final class BeanReader {
103103
this.observerMethods = typeReader.observerMethods();
104104
this.importedComponent = importedComponent && constructor != null && constructor.isPublic();
105105
this.delayed = shouldDelay();
106-
this.lazyProxyType = !lazy || delayed ? null : Util.lazyProxy(actualType);
106+
String lazyKind = Optional.ofNullable(lazyPrism).map(LazyPrism::value).orElse("");
107+
boolean useProxy = !"PROVIDER".equals(lazyKind);
108+
this.lazyProxyType = !lazy || !useProxy ? null : Util.lazyProxy(actualType);
107109
this.proxyLazy = lazy && lazyProxyType != null;
108-
if (lazy && !proxyLazy) {
109-
if (lazyPrism != null && lazyPrism.enforceProxy()) {
110+
111+
if (lazy && !proxyLazy && useProxy) {
112+
if ("FORCE_PROXY".equals(lazyKind)) {
110113
logError(beanType, "Lazy beans must have an additional no-arg constructor");
111114
} else {
112115
logWarn(beanType, "Lazy beans should have an additional no-arg constructor");

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

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import java.util.Collections;
1111
import java.util.List;
1212
import java.util.Map;
13+
import java.util.Optional;
1314
import java.util.Set;
1415
import java.util.function.Supplier;
1516

@@ -75,10 +76,14 @@ final class MethodReader {
7576
var lazyPrism = Util.isLazy(element);
7677
lazy = lazyPrism != null;
7778
conditions.readAll(element);
78-
this.lazyProxyType = lazy ? Util.lazyProxy(element) : null;
79+
80+
String lazyKind = Optional.ofNullable(lazyPrism).map(LazyPrism::value).orElse("");
81+
boolean useProxy = !"PROVIDER".equals(lazyKind);
82+
this.lazyProxyType = !lazy || !useProxy ? null : Util.lazyProxy(element);
7983
this.proxyLazy = lazy && lazyProxyType != null;
80-
if (lazy && !proxyLazy) {
81-
if (lazyPrism.enforceProxy()) {
84+
85+
if (lazy && !proxyLazy && useProxy) {
86+
if ("FORCE_PROXY".equals(lazyKind)) {
8287
logError(element, "Lazy return type must be abstract or have a no-arg constructor");
8388
} else {
8489
logWarn(element, "Lazy return type should be abstract or have a no-arg constructor");

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

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -163,19 +163,24 @@ private String methods() {
163163
}
164164

165165
sb.append(" {\n ");
166-
if (!"void".equals(returnType.full())) {
167-
sb.append("return ");
168-
}
166+
if (modifiers.contains(Modifier.PROTECTED)) {
167+
sb.append("// @Lazy proxy does not support protected methods, instead use @Lazy(useProxy = false)");
168+
sb.append("\n throw new UnsupportedOperationException();\n");
169+
} else {
170+
if (!"void".equals(returnType.full())) {
171+
sb.append("return ");
172+
}
169173

170-
sb.append("onceProvider.get().").append(methodName);
171-
sb.append("(");
172-
for (int i = 0; i < parameters.size(); i++) {
173-
sb.append(parameters.get(i).getSimpleName().toString());
174-
if (i < parameters.size() - 1) {
175-
sb.append(", ");
174+
sb.append("onceProvider.get().").append(methodName);
175+
sb.append("(");
176+
for (int i = 0; i < parameters.size(); i++) {
177+
sb.append(parameters.get(i).getSimpleName().toString());
178+
if (i < parameters.size() - 1) {
179+
sb.append(", ");
180+
}
176181
}
182+
sb.append(");\n");
177183
}
178-
sb.append(");\n");
179184
sb.append(" }\n\n");
180185
}
181186
return sb.toString();

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

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -400,9 +400,10 @@ static TypeElement lazyProxy(Element element) {
400400
element instanceof TypeElement
401401
? (TypeElement) element
402402
: APContext.asTypeElement(((ExecutableElement) element).getReturnType());
403-
403+
var notInterface = !type.getKind().isInterface();
404404
if (type.getModifiers().contains(Modifier.FINAL)
405-
|| !type.getKind().isInterface() && !Util.hasNoArgConstructor(type)) {
405+
|| notInterface && !Util.hasNoArgConstructor(type)
406+
|| notInterface && Util.hasFinalMethods(type)) {
406407

407408
return BeanTypesPrism.getOptionalOn(element)
408409
.map(BeanTypesPrism::value)
@@ -417,6 +418,14 @@ static TypeElement lazyProxy(Element element) {
417418
return type;
418419
}
419420

421+
private static boolean hasFinalMethods(TypeElement type) {
422+
return ElementFilter.methodsIn(type.getEnclosedElements()).stream()
423+
.filter(x -> !x.getModifiers().contains(Modifier.STATIC))
424+
.filter(x -> !x.getModifiers().contains(Modifier.PRIVATE))
425+
.filter(x -> !x.getModifiers().contains(Modifier.PROTECTED))
426+
.anyMatch(m -> m.getModifiers().contains(Modifier.FINAL));
427+
}
428+
420429
static boolean hasNoArgConstructor(TypeElement beanType) {
421430
return ElementFilter.constructorsIn(beanType.getEnclosedElements()).stream()
422431
.anyMatch(e -> e.getParameters().isEmpty() && !e.getModifiers().contains(Modifier.PRIVATE));
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package io.avaje.inject.generator.models.valid.lazy;
2+
3+
import java.security.SecureRandom;
4+
5+
import io.avaje.inject.Bean;
6+
import io.avaje.inject.Factory;
7+
import io.avaje.inject.Lazy;
8+
9+
@Lazy
10+
@Factory
11+
public class RandomFactory {
12+
@Bean
13+
public SecureRandom secureRandom() {
14+
return new SecureRandom();
15+
}
16+
}

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

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,32 @@
1717
@Retention(RetentionPolicy.SOURCE)
1818
@Target({ElementType.METHOD, ElementType.TYPE, ElementType.PACKAGE, ElementType.MODULE})
1919
public @interface Lazy {
20+
/** Determine the kind of lazy initialization. */
21+
Kind value() default Kind.AUTO_PROXY;
2022

2123
/**
22-
* Ensures that a compile-time proxy is generated, will fail compilation if missing conditions for
23-
* generation
24+
* Control whether a compile-time proxy is generated to support lazy initialization.
25+
*
26+
* <p>When using {@link Kind#FORCE_PROXY} a compile-time error will occur if the conditions for
27+
* generating a proxy are not met (for example the class is final or has no no-args constructor).
28+
* When using {@link Kind#AUTO_PROXY} a warning will be issued and lazy initialization will fall
29+
* back to provider based lazy initialization.
30+
*
31+
* <p>When using {@link Kind#PROVIDER} no proxy is generated and lazy initialization is done via a
32+
* provider.
2433
*/
25-
boolean enforceProxy() default false;
34+
enum Kind {
35+
/**
36+
* Ensures that a compile-time proxy is generated, will fail compilation if missing conditions
37+
* for generation
38+
*/
39+
FORCE_PROXY,
40+
/**
41+
* Attempt compile-time proxy, will warn and fallback to provider compilation if missing
42+
* conditions for generation
43+
*/
44+
AUTO_PROXY,
45+
/** No proxy, use a provider based lazy initialization */
46+
PROVIDER
47+
}
2648
}

0 commit comments

Comments
 (0)