Browse Source

Add AOT support for Qualifiers

This commit handles AutowiredCandidateQualifier instances, rather than
relying on qualifiers being statically defined and meta-annotated with
`@Qualifier`.

Closes gh-30410
pull/30420/head
Stephane Nicoll 1 year ago committed by Stephane Nicoll
parent
commit
44b427c3e1
  1. 25
      spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanDefinitionPropertiesCodeGenerator.java
  2. 43
      spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanDefinitionPropertiesCodeGeneratorTests.java
  3. 14
      spring-context/src/test/java/org/springframework/context/aot/ApplicationContextAotGeneratorTests.java
  4. 51
      spring-context/src/testFixtures/java/org/springframework/context/testfixture/context/annotation/QualifierConfiguration.java

25
spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanDefinitionPropertiesCodeGenerator.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -19,11 +19,14 @@ package org.springframework.beans.factory.aot; @@ -19,11 +19,14 @@ package org.springframework.beans.factory.aot;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.BiPredicate;
import java.util.function.Function;
@ -40,6 +43,7 @@ import org.springframework.beans.factory.config.BeanDefinition; @@ -40,6 +43,7 @@ import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.AutowireCandidateQualifier;
import org.springframework.beans.factory.support.InstanceSupplier;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.javapoet.CodeBlock;
@ -119,6 +123,7 @@ class BeanDefinitionPropertiesCodeGenerator { @@ -119,6 +123,7 @@ class BeanDefinitionPropertiesCodeGenerator {
addConstructorArgumentValues(code, beanDefinition);
addPropertyValues(code, beanDefinition);
addAttributes(code, beanDefinition);
addQualifiers(code, beanDefinition);
return code.build();
}
@ -180,6 +185,24 @@ class BeanDefinitionPropertiesCodeGenerator { @@ -180,6 +185,24 @@ class BeanDefinitionPropertiesCodeGenerator {
}
}
private void addQualifiers(CodeBlock.Builder code,
RootBeanDefinition beanDefinition) {
Set<AutowireCandidateQualifier> qualifiers = beanDefinition.getQualifiers();
if (!qualifiers.isEmpty()) {
for (AutowireCandidateQualifier qualifier : qualifiers) {
Collection<CodeBlock> arguments = new ArrayList<>();
arguments.add(CodeBlock.of("$S", qualifier.getTypeName()));
Object qualifierValue = qualifier.getAttribute(AutowireCandidateQualifier.VALUE_KEY);
if (qualifierValue != null) {
arguments.add(generateValue("value", qualifierValue));
}
code.addStatement("$L.addQualifier(new $T($L))", BEAN_DEFINITION_VARIABLE,
AutowireCandidateQualifier.class, CodeBlock.join(arguments, ", "));
}
}
}
private CodeBlock generateValue(@Nullable String name, @Nullable Object value) {
try {
PropertyNamesStack.push(name);

43
spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanDefinitionPropertiesCodeGeneratorTests.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,10 +16,13 @@ @@ -16,10 +16,13 @@
package org.springframework.beans.factory.aot;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.function.Supplier;
@ -36,6 +39,7 @@ import org.springframework.beans.factory.config.BeanReference; @@ -36,6 +39,7 @@ import org.springframework.beans.factory.config.BeanReference;
import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder;
import org.springframework.beans.factory.config.RuntimeBeanNameReference;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.AutowireCandidateQualifier;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.ManagedList;
import org.springframework.beans.factory.support.ManagedMap;
@ -392,6 +396,43 @@ class BeanDefinitionPropertiesCodeGeneratorTests { @@ -392,6 +396,43 @@ class BeanDefinitionPropertiesCodeGeneratorTests {
});
}
@Test
void qualifiersWhenQualifierHasNoValue() {
this.beanDefinition.addQualifier(new AutowireCandidateQualifier("com.example.Qualifier"));
compile((actual, compiled) -> {
assertThat(actual.getQualifiers()).singleElement().satisfies(isQualifierFor("com.example.Qualifier", null));
assertThat(this.beanDefinition.getQualifiers()).isEqualTo(actual.getQualifiers());
});
}
@Test
void qualifiersWhenQualifierHasStringValue() {
this.beanDefinition.addQualifier(new AutowireCandidateQualifier("com.example.Qualifier", "id"));
compile((actual, compiled) -> {
assertThat(actual.getQualifiers()).singleElement().satisfies(isQualifierFor("com.example.Qualifier", "id"));
assertThat(this.beanDefinition.getQualifiers()).isEqualTo(actual.getQualifiers());
});
}
@Test
void qualifiersWhenMultipleQualifiers() {
this.beanDefinition.addQualifier(new AutowireCandidateQualifier("com.example.Qualifier", "id"));
this.beanDefinition.addQualifier(new AutowireCandidateQualifier("com.example.Another", ChronoUnit.SECONDS));
compile((actual, compiled) -> {
List<AutowireCandidateQualifier> qualifiers = new ArrayList<>(actual.getQualifiers());
assertThat(qualifiers.get(0)).satisfies(isQualifierFor("com.example.Qualifier", "id"));
assertThat(qualifiers.get(1)).satisfies(isQualifierFor("com.example.Another", ChronoUnit.SECONDS));
assertThat(qualifiers).hasSize(2);
});
}
private Consumer<AutowireCandidateQualifier> isQualifierFor(String typeName, Object value) {
return qualifier -> {
assertThat(qualifier.getTypeName()).isEqualTo(typeName);
assertThat(qualifier.getAttribute(AutowireCandidateQualifier.VALUE_KEY)).isEqualTo(value);
};
}
@Test
void multipleItems() {
this.beanDefinition.setPrimary(true);

14
spring-context/src/test/java/org/springframework/context/aot/ApplicationContextAotGeneratorTests.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -64,6 +64,7 @@ import org.springframework.context.testfixture.context.annotation.LazyAutowiredM @@ -64,6 +64,7 @@ import org.springframework.context.testfixture.context.annotation.LazyAutowiredM
import org.springframework.context.testfixture.context.annotation.LazyConstructorArgumentComponent;
import org.springframework.context.testfixture.context.annotation.LazyFactoryMethodArgumentComponent;
import org.springframework.context.testfixture.context.annotation.PropertySourceConfiguration;
import org.springframework.context.testfixture.context.annotation.QualifierConfiguration;
import org.springframework.context.testfixture.context.generator.SimpleComponent;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
@ -303,6 +304,17 @@ class ApplicationContextAotGeneratorTests { @@ -303,6 +304,17 @@ class ApplicationContextAotGeneratorTests {
});
}
@Test
void processAheadOfTimeWithQualifier() {
GenericApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.registerBean(QualifierConfiguration.class);
testCompiledResult(applicationContext, (initializer, compiled) -> {
GenericApplicationContext freshApplicationContext = toFreshApplicationContext(initializer);
QualifierConfiguration configuration = freshApplicationContext.getBean(QualifierConfiguration.class);
assertThat(configuration).hasFieldOrPropertyWithValue("bean", "one");
});
}
@Nested
@CompileWithForkedClassLoader
class ConfigurationClassCglibProxy {

51
spring-context/src/testFixtures/java/org/springframework/context/testfixture/context/annotation/QualifierConfiguration.java

@ -0,0 +1,51 @@ @@ -0,0 +1,51 @@
/*
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.testfixture.context.annotation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration(proxyBeanMethods = false)
public class QualifierConfiguration {
private String bean;
@Autowired
@Qualifier("1")
public void setBean(String bean) {
this.bean = bean;
}
public static class BeansConfiguration {
@Bean
@Qualifier("1")
public String one() {
return "one";
}
@Bean
@Qualifier("2")
public String two() {
return "one";
}
}
}
Loading…
Cancel
Save