Skip to content

Commit b4f4dba

Browse files
authored
Detect clash with multiple @bean methods returning same type (#781)
Given a @factory with 2 or more bean methods that return the same type, and effectively clash on qualifier name (typically missing) then make this a compilation error. The error suggests to add a @nAmed or qualifier annotation to resolve the issue. Example: ```java @factory class MyFactory { @bean BFace one() { ... } @bean BFace two() { ... } ```
1 parent 241097e commit b4f4dba

File tree

6 files changed

+111
-1
lines changed

6 files changed

+111
-1
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package org.example.myapp.config;
2+
3+
public interface BFace {
4+
5+
String hi();
6+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package org.example.myapp.config;
2+
3+
import io.avaje.inject.Bean;
4+
import io.avaje.inject.Factory;
5+
import jakarta.inject.Named;
6+
7+
import java.util.Optional;
8+
9+
@Factory
10+
public class BFactory {
11+
12+
@Named
13+
@Bean
14+
BFace one() {
15+
return new TheBFace("one");
16+
}
17+
18+
@Named
19+
@Bean
20+
BFace two() {
21+
return new TheBFace("two");
22+
}
23+
24+
@Named
25+
@Bean
26+
Optional<BFace> three() {
27+
return Optional.of(new TheBFace("three"));
28+
}
29+
30+
31+
static class TheBFace implements BFace {
32+
33+
private final String msg;
34+
35+
TheBFace(String msg) {
36+
this.msg = msg;
37+
}
38+
39+
@Override
40+
public String hi() {
41+
return msg;
42+
}
43+
}
44+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package org.example.myapp.config;
2+
3+
import io.avaje.inject.BeanScope;
4+
import io.avaje.inject.test.TestBeanScope;
5+
import org.junit.jupiter.api.Test;
6+
7+
import java.util.List;
8+
import java.util.stream.Collectors;
9+
10+
import static org.assertj.core.api.Assertions.assertThat;
11+
12+
class BFactoryTest {
13+
14+
@Test
15+
void beanMethodsOfSameType() {
16+
try (BeanScope scope = TestBeanScope.builder().build()) {
17+
BFace one = scope.get(BFace.class, "one");
18+
BFace two = scope.get(BFace.class, "two");
19+
BFace three = scope.get(BFace.class, "three");
20+
assertThat(one).isNotNull();
21+
assertThat(two).isNotNull();
22+
assertThat(three).isNotNull();
23+
24+
List<BFace> list = scope.list(BFace.class);
25+
assertThat(list).hasSize(3);
26+
27+
List<String> hi = list.stream().map(BFace::hi).collect(Collectors.toList());
28+
assertThat(hi).containsOnly("one", "two", "three");
29+
}
30+
}
31+
}

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -499,6 +499,14 @@ MethodParam observeParam() {
499499
return observeParameter;
500500
}
501501

502+
String qualifiedKey() {
503+
return name + ':' + genericType.full();
504+
}
505+
506+
boolean isVoid() {
507+
return isVoid;
508+
}
509+
502510
static class MethodParam {
503511

504512
private final VariableElement element;

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,8 +253,24 @@ DestroyMethods.DestroyMethod matchPreDestroy(String returnTypeRaw) {
253253
}
254254

255255
void validate() {
256+
validateFactoryMethodDuplicates();
256257
for (DestroyMethods.DestroyMethod destroyMethod : factoryPreDestroyMethods.unmatched()) {
257258
logError(destroyMethod.element(), "Unused @PreDestroy method, no matching @Bean method for type " + destroyMethod.matchType());
258259
}
259260
}
261+
262+
void validateFactoryMethodDuplicates() {
263+
var map = new HashMap<String, MethodReader>();
264+
for (MethodReader method : factoryMethods) {
265+
if (!method.isVoid()) {
266+
var clashMethod = map.put(method.qualifiedKey(), method);
267+
if (clashMethod != null) {
268+
var msg = String.format("@Bean method %s() returns the same type as with method %s() without a unique name qualifier." +
269+
" Add @Named or a qualifier annotation to allow both @Bean methods.",
270+
method.name(), clashMethod.name());
271+
logError(method.element(), msg);
272+
}
273+
}
274+
}
275+
}
260276
}

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,12 @@ static String commonParent(String currentTop, String aPackage) {
294294
static String named(Element p) {
295295
final NamedPrism named = NamedPrism.getInstanceOn(p);
296296
if (named != null) {
297-
return named.value().replace("\"", "\\\"");
297+
String raw = named.value();
298+
if (raw.isEmpty()) {
299+
// default to the method name
300+
raw = p.getSimpleName().toString();
301+
}
302+
return raw.replace("\"", "\\\"");
298303
}
299304
for (final AnnotationMirror annotationMirror : p.getAnnotationMirrors()) {
300305
final DeclaredType annotationType = annotationMirror.getAnnotationType();

0 commit comments

Comments
 (0)