Browse Source

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
pull/33365/head
Sam Brannen 1 year ago
parent
commit
bcffa15c7d
  1. 1
      spring-beans/spring-beans.gradle
  2. 16
      spring-beans/src/main/java/org/springframework/beans/factory/annotation/JakartaAnnotationsRuntimeHints.java
  3. 18
      spring-beans/src/main/java/org/springframework/beans/factory/annotation/QualifierAnnotationAutowireCandidateResolver.java
  4. 13
      spring-beans/src/test/java/org/springframework/beans/factory/annotation/JakartaAnnotationsRuntimeHintsTests.java
  5. 117
      spring-context/src/test/java/org/springframework/beans/factory/support/InjectAnnotationAutowireContextTests.java

1
spring-beans/spring-beans.gradle

@ -16,4 +16,5 @@ dependencies {
testImplementation(project(":spring-core-test")) testImplementation(project(":spring-core-test"))
testImplementation(testFixtures(project(":spring-core"))) testImplementation(testFixtures(project(":spring-core")))
testImplementation("jakarta.annotation:jakarta.annotation-api") testImplementation("jakarta.annotation:jakarta.annotation-api")
testImplementation("javax.inject:javax.inject")
} }

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

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2023 the original author or authors. * Copyright 2002-2024 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -24,16 +24,24 @@ import org.springframework.aot.hint.TypeReference;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
/** /**
* {@link RuntimeHintsRegistrar} for Jakarta annotations. * {@link RuntimeHintsRegistrar} for Jakarta annotations and their pre-Jakarta equivalents.
* *
* @author Brian Clozel * @author Brian Clozel
* @author Sam Brannen
*/ */
class JakartaAnnotationsRuntimeHints implements RuntimeHintsRegistrar { class JakartaAnnotationsRuntimeHints implements RuntimeHintsRegistrar {
@Override @Override
public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) { public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) {
Stream.of("jakarta.inject.Inject", "jakarta.inject.Provider", "jakarta.inject.Qualifier").forEach(typeName -> // javax.inject.Provider is omitted from the list, since we do not currently load
hints.reflection().registerType(TypeReference.of(typeName))); // it via reflection.
Stream.of(
"jakarta.inject.Inject",
"jakarta.inject.Provider",
"jakarta.inject.Qualifier",
"javax.inject.Inject",
"javax.inject.Qualifier"
).forEach(typeName -> hints.reflection().registerType(TypeReference.of(typeName)));
} }
} }

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

@ -47,11 +47,13 @@ import org.springframework.util.ObjectUtils;
* against {@link Qualifier qualifier annotations} on the field or parameter to be autowired. * against {@link Qualifier qualifier annotations} on the field or parameter to be autowired.
* Also supports suggested expression values through a {@link Value value} annotation. * Also supports suggested expression values through a {@link Value value} annotation.
* *
* <p>Also supports JSR-330's {@link jakarta.inject.Qualifier} annotation, if available. * <p>Also supports JSR-330's {@link jakarta.inject.Qualifier} annotation (as well as its
* pre-Jakarta {@code javax.inject.Qualifier} equivalent), if available.
* *
* @author Mark Fisher * @author Mark Fisher
* @author Juergen Hoeller * @author Juergen Hoeller
* @author Stephane Nicoll * @author Stephane Nicoll
* @author Sam Brannen
* @since 2.5 * @since 2.5
* @see AutowireCandidateQualifier * @see AutowireCandidateQualifier
* @see Qualifier * @see Qualifier
@ -65,9 +67,10 @@ public class QualifierAnnotationAutowireCandidateResolver extends GenericTypeAwa
/** /**
* Create a new QualifierAnnotationAutowireCandidateResolver * Create a new {@code QualifierAnnotationAutowireCandidateResolver} for Spring's
* for Spring's standard {@link Qualifier} annotation. * standard {@link Qualifier} annotation.
* <p>Also supports JSR-330's {@link jakarta.inject.Qualifier} annotation, if available. * <p>Also supports JSR-330's {@link jakarta.inject.Qualifier} annotation (as well as
* its pre-Jakarta {@code javax.inject.Qualifier} equivalent), if available.
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public QualifierAnnotationAutowireCandidateResolver() { public QualifierAnnotationAutowireCandidateResolver() {
@ -76,6 +79,13 @@ public class QualifierAnnotationAutowireCandidateResolver extends GenericTypeAwa
this.qualifierTypes.add((Class<? extends Annotation>) ClassUtils.forName("jakarta.inject.Qualifier", this.qualifierTypes.add((Class<? extends Annotation>) ClassUtils.forName("jakarta.inject.Qualifier",
QualifierAnnotationAutowireCandidateResolver.class.getClassLoader())); QualifierAnnotationAutowireCandidateResolver.class.getClassLoader()));
} }
catch (ClassNotFoundException ex) {
// JSR-330 API (as included in Jakarta EE) not available - simply skip.
}
try {
this.qualifierTypes.add((Class<? extends Annotation>) ClassUtils.forName("javax.inject.Qualifier",
QualifierAnnotationAutowireCandidateResolver.class.getClassLoader()));
}
catch (ClassNotFoundException ex) { catch (ClassNotFoundException ex) {
// JSR-330 API not available - simply skip. // JSR-330 API not available - simply skip.
} }

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

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2023 the original author or authors. * Copyright 2002-2024 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -35,6 +35,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* Tests for {@link JakartaAnnotationsRuntimeHints}. * Tests for {@link JakartaAnnotationsRuntimeHints}.
* *
* @author Brian Clozel * @author Brian Clozel
* @author Sam Brannen
*/ */
class JakartaAnnotationsRuntimeHintsTests { class JakartaAnnotationsRuntimeHintsTests {
@ -62,4 +63,14 @@ class JakartaAnnotationsRuntimeHintsTests {
assertThat(RuntimeHintsPredicates.reflection().onType(Qualifier.class)).accepts(this.hints); assertThat(RuntimeHintsPredicates.reflection().onType(Qualifier.class)).accepts(this.hints);
} }
@Test // gh-33345
void javaxInjectAnnotationHasHints() {
assertThat(RuntimeHintsPredicates.reflection().onType(javax.inject.Inject.class)).accepts(this.hints);
}
@Test // gh-33345
void javaxQualifierAnnotationHasHints() {
assertThat(RuntimeHintsPredicates.reflection().onType(javax.inject.Qualifier.class)).accepts(this.hints);
}
} }

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

@ -32,6 +32,7 @@ import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.UnsatisfiedDependencyException; import org.springframework.beans.factory.UnsatisfiedDependencyException;
import org.springframework.beans.factory.config.BeanDefinitionHolder; import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.ConstructorArgumentValues; import org.springframework.beans.factory.config.ConstructorArgumentValues;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.AnnotationConfigUtils; import org.springframework.context.annotation.AnnotationConfigUtils;
import org.springframework.context.support.GenericApplicationContext; import org.springframework.context.support.GenericApplicationContext;
@ -39,9 +40,11 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/** /**
* Integration tests for handling JSR-303 {@link jakarta.inject.Qualifier} annotations. * Integration tests for handling JSR-330 {@link jakarta.inject.Qualifier} and
* {@link javax.inject.Qualifier} annotations.
* *
* @author Juergen Hoeller * @author Juergen Hoeller
* @author Sam Brannen
* @since 3.0 * @since 3.0
*/ */
class InjectAnnotationAutowireContextTests { class InjectAnnotationAutowireContextTests {
@ -304,6 +307,26 @@ class InjectAnnotationAutowireContextTests {
assertThat(bean.getPerson().getName()).isEqualTo(JUERGEN); assertThat(bean.getPerson().getName()).isEqualTo(JUERGEN);
} }
@Test // gh-33345
void autowiredConstructorArgumentResolvesJakartaNamedCandidate() {
Class<JakartaNamedConstructorArgumentTestBean> testBeanClass = JakartaNamedConstructorArgumentTestBean.class;
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(testBeanClass, JakartaCat.class, JakartaDog.class);
JakartaNamedConstructorArgumentTestBean bean = context.getBean(testBeanClass);
assertThat(bean.getAnimal1().getName()).isEqualTo("Jakarta Tiger");
assertThat(bean.getAnimal2().getName()).isEqualTo("Jakarta Fido");
}
@Test // gh-33345
void autowiredConstructorArgumentResolvesJavaxNamedCandidate() {
Class<JavaxNamedConstructorArgumentTestBean> testBeanClass = JavaxNamedConstructorArgumentTestBean.class;
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(testBeanClass, JavaxCat.class, JavaxDog.class);
JavaxNamedConstructorArgumentTestBean bean = context.getBean(testBeanClass);
assertThat(bean.getAnimal1().getName()).isEqualTo("Javax Tiger");
assertThat(bean.getAnimal2().getName()).isEqualTo("Javax Fido");
}
@Test @Test
void autowiredFieldResolvesQualifiedCandidateWithDefaultValueAndNoValueOnBeanDefinition() { void autowiredFieldResolvesQualifiedCandidateWithDefaultValueAndNoValueOnBeanDefinition() {
GenericApplicationContext context = new GenericApplicationContext(); GenericApplicationContext context = new GenericApplicationContext();
@ -541,6 +564,52 @@ class InjectAnnotationAutowireContextTests {
} }
static class JakartaNamedConstructorArgumentTestBean {
private final Animal animal1;
private final Animal animal2;
@jakarta.inject.Inject
public JakartaNamedConstructorArgumentTestBean(@jakarta.inject.Named("Cat") Animal animal1,
@jakarta.inject.Named("Dog") Animal animal2) {
this.animal1 = animal1;
this.animal2 = animal2;
}
public Animal getAnimal1() {
return this.animal1;
}
public Animal getAnimal2() {
return this.animal2;
}
}
static class JavaxNamedConstructorArgumentTestBean {
private final Animal animal1;
private final Animal animal2;
@javax.inject.Inject
public JavaxNamedConstructorArgumentTestBean(@javax.inject.Named("Cat") Animal animal1,
@javax.inject.Named("Dog") Animal animal2) {
this.animal1 = animal1;
this.animal2 = animal2;
}
public Animal getAnimal1() {
return this.animal1;
}
public Animal getAnimal2() {
return this.animal2;
}
}
public static class QualifiedFieldWithDefaultValueTestBean { public static class QualifiedFieldWithDefaultValueTestBean {
@Inject @Inject
@ -620,6 +689,52 @@ class InjectAnnotationAutowireContextTests {
} }
interface Animal {
String getName();
}
@jakarta.inject.Named("Cat")
static class JakartaCat implements Animal {
@Override
public String getName() {
return "Jakarta Tiger";
}
}
@javax.inject.Named("Cat")
static class JavaxCat implements Animal {
@Override
public String getName() {
return "Javax Tiger";
}
}
@jakarta.inject.Named("Dog")
static class JakartaDog implements Animal {
@Override
public String getName() {
return "Jakarta Fido";
}
}
@javax.inject.Named("Dog")
static class JavaxDog implements Animal {
@Override
public String getName() {
return "Javax Fido";
}
}
@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE}) @Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Qualifier @Qualifier

Loading…
Cancel
Save