Skip to content

Commit 28fe259

Browse files
committed
#92 - Fix/support circular dependency binding when using Provider interface
1 parent d743881 commit 28fe259

File tree

12 files changed

+160
-14
lines changed

12 files changed

+160
-14
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ String buildMethod(MetaDataOrdering ordering) {
169169
}
170170

171171
for (String depend : dependsOn) {
172-
if (GenericType.isGeneric(depend)) {
172+
if (GenericType.isGeneric(depend) && !Util.isProvider(depend)) {
173173
// provide implementation of generic interface as a parameter to the build method
174174
final MetaData providerMeta = findProviderOf(depend, ordering);
175175
String prov = (providerMeta == null) ? "UnknownProvider" : Util.shortName(providerMeta.type);

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

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -165,15 +165,17 @@ private int processQueueRound() {
165165
}
166166

167167
private boolean allDependenciesWired(MetaData queuedMeta) {
168-
169168
for (String dependency : queuedMeta.getDependsOn()) {
170-
ProviderList providerList = providers.get(dependency);
171-
if (providerList == null) {
172-
// missing dependencies - leave to end
173-
return false;
174-
} else {
175-
if (!providerList.isAllWired()) {
169+
if (!Util.isProvider(dependency)) {
170+
// check non-provider dependency is satisfied
171+
ProviderList providerList = providers.get(dependency);
172+
if (providerList == null) {
173+
// dependency not yet satisfied
176174
return false;
175+
} else {
176+
if (!providerList.isAllWired()) {
177+
return false;
178+
}
177179
}
178180
}
179181
}

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

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -281,14 +281,16 @@ static class MethodParam {
281281

282282
String builderGetDependency(String builderName) {
283283
StringBuilder sb = new StringBuilder();
284-
if (genericType != null) {
284+
if (isGenericParam()) {
285285
// passed as provider to build method
286286
sb.append("prov").append(providerIndex).append(".get(");
287287
} else {
288288
sb.append(builderName).append(".").append(utilType.getMethod());
289289
}
290290
if (genericType == null) {
291291
sb.append(Util.shortName(paramType)).append(".class");
292+
} else if (isProvider()) {
293+
sb.append(providerParam()).append(".class");
292294
}
293295
if (named != null) {
294296
sb.append(",\"").append(named).append("\"");
@@ -297,10 +299,22 @@ String builderGetDependency(String builderName) {
297299
return sb.toString();
298300
}
299301

302+
private String providerParam() {
303+
return Util.shortName(Util.unwrapProvider(paramType));
304+
}
305+
306+
boolean isProvider() {
307+
return Util.isProvider(paramType);
308+
}
309+
300310
boolean isGenericType() {
301311
return genericType != null;
302312
}
303313

314+
boolean isGenericParam() {
315+
return isGenericType() && !isProvider();
316+
}
317+
304318
String getDependsOn() {
305319
return paramType;
306320
}

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,13 +100,13 @@ private void writeStaticFactoryMethod() {
100100
writer.append(CODE_COMMENT_BUILD, shortName).eol();
101101
writer.append(" public static void build(Builder builder");
102102
for (MethodReader.MethodParam param : constructor.getParams()) {
103-
if (param.isGenericType()) {
103+
if (param.isGenericParam()) {
104104
param.addProviderParam(writer, providerIndex++);
105105
}
106106
}
107107
for (MethodReader methodReader : beanReader.getInjectMethods()) {
108108
for (MethodReader.MethodParam param : methodReader.getParams()) {
109-
if (param.isGenericType()) {
109+
if (param.isGenericParam()) {
110110
param.addProviderParam(writer, providerIndex++);
111111
}
112112
}
@@ -143,7 +143,7 @@ private void writeStaticFactoryMethod() {
143143
List<MethodReader.MethodParam> methodParams = methodReader.getParams();
144144
for (int i = 0; i < methodParams.size(); i++) {
145145
if (i > 0) {
146-
writer.append(",");
146+
writer.append(" ,");
147147
}
148148
writer.append(methodParams.get(i).builderGetDependency("b"));
149149
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ static UtilType determineType(TypeMirror rawType) {
8282
return UtilType.of(rawType.toString());
8383
}
8484

85-
private static boolean isProvider(String rawType) {
85+
static boolean isProvider(String rawType) {
8686
return rawType.startsWith(PROVIDER_PREFIX);
8787
}
8888

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ private enum Type {
66
LIST,
77
SET,
88
OPTIONAL,
9+
PROVIDER,
910
OTHER
1011
}
1112

@@ -24,6 +25,8 @@ static UtilType of(String rawType) {
2425
return new UtilType(Type.SET, rawType);
2526
} else if (rawType.startsWith("java.util.Optional<")) {
2627
return new UtilType(Type.OPTIONAL, rawType);
28+
} else if (Util.isProvider(rawType)) {
29+
return new UtilType(Type.PROVIDER, rawType);
2730
} else {
2831
return new UtilType(Type.OTHER, rawType);
2932
}
@@ -37,8 +40,9 @@ String rawType() {
3740
return Util.extractList(rawType);
3841
case OPTIONAL:
3942
return Util.extractOptionalType(rawType);
43+
default:
44+
return rawType;
4045
}
41-
return rawType;
4246
}
4347

4448
String getMethod() {
@@ -49,6 +53,8 @@ String getMethod() {
4953
return "getList(";
5054
case OPTIONAL:
5155
return "getOptional(";
56+
case PROVIDER:
57+
return "getProvider(";
5258
}
5359
return "get(";
5460
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package org.example.circular;
2+
3+
import javax.inject.Inject;
4+
import javax.inject.Provider;
5+
import javax.inject.Singleton;
6+
7+
@Singleton
8+
public class Cupholder {
9+
10+
public final Provider<Seat> seatProvider;
11+
12+
/**
13+
* Resolve circular dependency via Provider interface rather than field injection.
14+
*/
15+
@Inject
16+
public Cupholder(Provider<Seat> seatProvider) {
17+
this.seatProvider = seatProvider;
18+
}
19+
20+
public String hello() {
21+
return "CupHello" + seatProvider.get().hello();
22+
}
23+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package org.example.circular;
2+
3+
import io.avaje.inject.SystemContext;
4+
import org.junit.jupiter.api.Test;
5+
6+
import static org.assertj.core.api.Assertions.assertThat;
7+
8+
public class CupholderTest {
9+
10+
@Test
11+
void circularDependency_via_providerInterface() {
12+
13+
Cupholder cupholder = SystemContext.getBean(Cupholder.class);
14+
String hello = cupholder.hello();
15+
16+
assertThat(hello).isEqualTo("CupHelloSeatHello");
17+
18+
// check circular binding
19+
Seat seat = SystemContext.getBean(Seat.class);
20+
Cupholder seatCupholder = seat.getCupholder();
21+
22+
assertThat(seatCupholder).isSameAs(cupholder);
23+
}
24+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package org.example.circular;
2+
3+
import javax.inject.Inject;
4+
import javax.inject.Singleton;
5+
6+
@Singleton
7+
public class Seat {
8+
9+
private final Cupholder cupholder;
10+
11+
@Inject
12+
Seat(Cupholder cupholder) {
13+
this.cupholder = cupholder;
14+
}
15+
16+
public Cupholder getCupholder() {
17+
return cupholder;
18+
}
19+
20+
public String hello() {
21+
return "SeatHello";
22+
}
23+
}

inject/src/main/java/io/avaje/inject/spi/Builder.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import io.avaje.inject.BeanContext;
44
import io.avaje.inject.BeanEntry;
55

6+
import javax.inject.Provider;
67
import java.util.List;
78
import java.util.Optional;
89
import java.util.Set;
@@ -127,6 +128,16 @@ static Builder newBuilder(String name, String[] provides, String[] dependsOn) {
127128
*/
128129
<T> Optional<T> getOptional(Class<T> cls, String name);
129130

131+
/**
132+
* Return Provider of T given the type.
133+
*/
134+
<T> Provider<T> getProvider(Class<T> cls);
135+
136+
/**
137+
* Return Provider of T given the type and name.
138+
*/
139+
<T> Provider<T> getProvider(Class<T> cls, String name);
140+
130141
/**
131142
* Get a dependency.
132143
*/

0 commit comments

Comments
 (0)