Skip to content

Commit bcffa15

Browse files
committed
Reinstate qualifier support for JSR-330 @⁠javax.inject.Named
This commit revises QualifierAnnotationAutowireCandidateResolver to reinstate "qualifier" support for the legacy JSR-330 @⁠javax.inject.Named annotation. See gh-31090 Closes gh-33345
1 parent f9d2641 commit bcffa15

File tree

5 files changed

+155
-10
lines changed

5 files changed

+155
-10
lines changed

spring-beans/spring-beans.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,5 @@ dependencies {
1616
testImplementation(project(":spring-core-test"))
1717
testImplementation(testFixtures(project(":spring-core")))
1818
testImplementation("jakarta.annotation:jakarta.annotation-api")
19+
testImplementation("javax.inject:javax.inject")
1920
}

spring-beans/src/main/java/org/springframework/beans/factory/annotation/JakartaAnnotationsRuntimeHints.java

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2023 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -24,16 +24,24 @@
2424
import org.springframework.lang.Nullable;
2525

2626
/**
27-
* {@link RuntimeHintsRegistrar} for Jakarta annotations.
27+
* {@link RuntimeHintsRegistrar} for Jakarta annotations and their pre-Jakarta equivalents.
2828
*
2929
* @author Brian Clozel
30+
* @author Sam Brannen
3031
*/
3132
class JakartaAnnotationsRuntimeHints implements RuntimeHintsRegistrar {
3233

3334
@Override
3435
public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) {
35-
Stream.of("jakarta.inject.Inject", "jakarta.inject.Provider", "jakarta.inject.Qualifier").forEach(typeName ->
36-
hints.reflection().registerType(TypeReference.of(typeName)));
36+
// javax.inject.Provider is omitted from the list, since we do not currently load
37+
// it via reflection.
38+
Stream.of(
39+
"jakarta.inject.Inject",
40+
"jakarta.inject.Provider",
41+
"jakarta.inject.Qualifier",
42+
"javax.inject.Inject",
43+
"javax.inject.Qualifier"
44+
).forEach(typeName -> hints.reflection().registerType(TypeReference.of(typeName)));
3745
}
3846

3947
}

spring-beans/src/main/java/org/springframework/beans/factory/annotation/QualifierAnnotationAutowireCandidateResolver.java

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,13 @@
4747
* against {@link Qualifier qualifier annotations} on the field or parameter to be autowired.
4848
* Also supports suggested expression values through a {@link Value value} annotation.
4949
*
50-
* <p>Also supports JSR-330's {@link jakarta.inject.Qualifier} annotation, if available.
50+
* <p>Also supports JSR-330's {@link jakarta.inject.Qualifier} annotation (as well as its
51+
* pre-Jakarta {@code javax.inject.Qualifier} equivalent), if available.
5152
*
5253
* @author Mark Fisher
5354
* @author Juergen Hoeller
5455
* @author Stephane Nicoll
56+
* @author Sam Brannen
5557
* @since 2.5
5658
* @see AutowireCandidateQualifier
5759
* @see Qualifier
@@ -65,9 +67,10 @@ public class QualifierAnnotationAutowireCandidateResolver extends GenericTypeAwa
6567

6668

6769
/**
68-
* Create a new QualifierAnnotationAutowireCandidateResolver
69-
* for Spring's standard {@link Qualifier} annotation.
70-
* <p>Also supports JSR-330's {@link jakarta.inject.Qualifier} annotation, if available.
70+
* Create a new {@code QualifierAnnotationAutowireCandidateResolver} for Spring's
71+
* standard {@link Qualifier} annotation.
72+
* <p>Also supports JSR-330's {@link jakarta.inject.Qualifier} annotation (as well as
73+
* its pre-Jakarta {@code javax.inject.Qualifier} equivalent), if available.
7174
*/
7275
@SuppressWarnings("unchecked")
7376
public QualifierAnnotationAutowireCandidateResolver() {
@@ -76,6 +79,13 @@ public QualifierAnnotationAutowireCandidateResolver() {
7679
this.qualifierTypes.add((Class<? extends Annotation>) ClassUtils.forName("jakarta.inject.Qualifier",
7780
QualifierAnnotationAutowireCandidateResolver.class.getClassLoader()));
7881
}
82+
catch (ClassNotFoundException ex) {
83+
// JSR-330 API (as included in Jakarta EE) not available - simply skip.
84+
}
85+
try {
86+
this.qualifierTypes.add((Class<? extends Annotation>) ClassUtils.forName("javax.inject.Qualifier",
87+
QualifierAnnotationAutowireCandidateResolver.class.getClassLoader()));
88+
}
7989
catch (ClassNotFoundException ex) {
8090
// JSR-330 API not available - simply skip.
8191
}

spring-beans/src/test/java/org/springframework/beans/factory/annotation/JakartaAnnotationsRuntimeHintsTests.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2023 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -35,6 +35,7 @@
3535
* Tests for {@link JakartaAnnotationsRuntimeHints}.
3636
*
3737
* @author Brian Clozel
38+
* @author Sam Brannen
3839
*/
3940
class JakartaAnnotationsRuntimeHintsTests {
4041

@@ -62,4 +63,14 @@ void jakartaQualifierAnnotationHasHints() {
6263
assertThat(RuntimeHintsPredicates.reflection().onType(Qualifier.class)).accepts(this.hints);
6364
}
6465

66+
@Test // gh-33345
67+
void javaxInjectAnnotationHasHints() {
68+
assertThat(RuntimeHintsPredicates.reflection().onType(javax.inject.Inject.class)).accepts(this.hints);
69+
}
70+
71+
@Test // gh-33345
72+
void javaxQualifierAnnotationHasHints() {
73+
assertThat(RuntimeHintsPredicates.reflection().onType(javax.inject.Qualifier.class)).accepts(this.hints);
74+
}
75+
6576
}

spring-context/src/test/java/org/springframework/beans/factory/support/InjectAnnotationAutowireContextTests.java

Lines changed: 116 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,16 +32,19 @@
3232
import org.springframework.beans.factory.UnsatisfiedDependencyException;
3333
import org.springframework.beans.factory.config.BeanDefinitionHolder;
3434
import org.springframework.beans.factory.config.ConstructorArgumentValues;
35+
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
3536
import org.springframework.context.annotation.AnnotationConfigUtils;
3637
import org.springframework.context.support.GenericApplicationContext;
3738

3839
import static org.assertj.core.api.Assertions.assertThat;
3940
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
4041

4142
/**
42-
* Integration tests for handling JSR-303 {@link jakarta.inject.Qualifier} annotations.
43+
* Integration tests for handling JSR-330 {@link jakarta.inject.Qualifier} and
44+
* {@link javax.inject.Qualifier} annotations.
4345
*
4446
* @author Juergen Hoeller
47+
* @author Sam Brannen
4548
* @since 3.0
4649
*/
4750
class InjectAnnotationAutowireContextTests {
@@ -304,6 +307,26 @@ void autowiredConstructorArgumentResolvesQualifiedCandidate() {
304307
assertThat(bean.getPerson().getName()).isEqualTo(JUERGEN);
305308
}
306309

310+
@Test // gh-33345
311+
void autowiredConstructorArgumentResolvesJakartaNamedCandidate() {
312+
Class<JakartaNamedConstructorArgumentTestBean> testBeanClass = JakartaNamedConstructorArgumentTestBean.class;
313+
AnnotationConfigApplicationContext context =
314+
new AnnotationConfigApplicationContext(testBeanClass, JakartaCat.class, JakartaDog.class);
315+
JakartaNamedConstructorArgumentTestBean bean = context.getBean(testBeanClass);
316+
assertThat(bean.getAnimal1().getName()).isEqualTo("Jakarta Tiger");
317+
assertThat(bean.getAnimal2().getName()).isEqualTo("Jakarta Fido");
318+
}
319+
320+
@Test // gh-33345
321+
void autowiredConstructorArgumentResolvesJavaxNamedCandidate() {
322+
Class<JavaxNamedConstructorArgumentTestBean> testBeanClass = JavaxNamedConstructorArgumentTestBean.class;
323+
AnnotationConfigApplicationContext context =
324+
new AnnotationConfigApplicationContext(testBeanClass, JavaxCat.class, JavaxDog.class);
325+
JavaxNamedConstructorArgumentTestBean bean = context.getBean(testBeanClass);
326+
assertThat(bean.getAnimal1().getName()).isEqualTo("Javax Tiger");
327+
assertThat(bean.getAnimal2().getName()).isEqualTo("Javax Fido");
328+
}
329+
307330
@Test
308331
void autowiredFieldResolvesQualifiedCandidateWithDefaultValueAndNoValueOnBeanDefinition() {
309332
GenericApplicationContext context = new GenericApplicationContext();
@@ -541,6 +564,52 @@ public Person getPerson() {
541564
}
542565

543566

567+
static class JakartaNamedConstructorArgumentTestBean {
568+
569+
private final Animal animal1;
570+
private final Animal animal2;
571+
572+
@jakarta.inject.Inject
573+
public JakartaNamedConstructorArgumentTestBean(@jakarta.inject.Named("Cat") Animal animal1,
574+
@jakarta.inject.Named("Dog") Animal animal2) {
575+
576+
this.animal1 = animal1;
577+
this.animal2 = animal2;
578+
}
579+
580+
public Animal getAnimal1() {
581+
return this.animal1;
582+
}
583+
584+
public Animal getAnimal2() {
585+
return this.animal2;
586+
}
587+
}
588+
589+
590+
static class JavaxNamedConstructorArgumentTestBean {
591+
592+
private final Animal animal1;
593+
private final Animal animal2;
594+
595+
@javax.inject.Inject
596+
public JavaxNamedConstructorArgumentTestBean(@javax.inject.Named("Cat") Animal animal1,
597+
@javax.inject.Named("Dog") Animal animal2) {
598+
599+
this.animal1 = animal1;
600+
this.animal2 = animal2;
601+
}
602+
603+
public Animal getAnimal1() {
604+
return this.animal1;
605+
}
606+
607+
public Animal getAnimal2() {
608+
return this.animal2;
609+
}
610+
}
611+
612+
544613
public static class QualifiedFieldWithDefaultValueTestBean {
545614

546615
@Inject
@@ -620,6 +689,52 @@ public QualifiedPerson(String name) {
620689
}
621690

622691

692+
interface Animal {
693+
694+
String getName();
695+
}
696+
697+
698+
@jakarta.inject.Named("Cat")
699+
static class JakartaCat implements Animal {
700+
701+
@Override
702+
public String getName() {
703+
return "Jakarta Tiger";
704+
}
705+
}
706+
707+
708+
@javax.inject.Named("Cat")
709+
static class JavaxCat implements Animal {
710+
711+
@Override
712+
public String getName() {
713+
return "Javax Tiger";
714+
}
715+
}
716+
717+
718+
@jakarta.inject.Named("Dog")
719+
static class JakartaDog implements Animal {
720+
721+
@Override
722+
public String getName() {
723+
return "Jakarta Fido";
724+
}
725+
}
726+
727+
728+
@javax.inject.Named("Dog")
729+
static class JavaxDog implements Animal {
730+
731+
@Override
732+
public String getName() {
733+
return "Javax Fido";
734+
}
735+
}
736+
737+
623738
@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE})
624739
@Retention(RetentionPolicy.RUNTIME)
625740
@Qualifier

0 commit comments

Comments
 (0)