Skip to content

Commit 44b427c

Browse files
committed
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
1 parent aabd685 commit 44b427c

File tree

4 files changed

+130
-3
lines changed

4 files changed

+130
-3
lines changed

Diff for: spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanDefinitionPropertiesCodeGenerator.java

+24-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2023 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.
@@ -19,11 +19,14 @@
1919
import java.beans.PropertyDescriptor;
2020
import java.lang.reflect.Method;
2121
import java.util.ArrayDeque;
22+
import java.util.ArrayList;
2223
import java.util.Arrays;
24+
import java.util.Collection;
2325
import java.util.Collections;
2426
import java.util.HashMap;
2527
import java.util.Map;
2628
import java.util.Objects;
29+
import java.util.Set;
2730
import java.util.function.BiFunction;
2831
import java.util.function.BiPredicate;
2932
import java.util.function.Function;
@@ -40,6 +43,7 @@
4043
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
4144
import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder;
4245
import org.springframework.beans.factory.support.AbstractBeanDefinition;
46+
import org.springframework.beans.factory.support.AutowireCandidateQualifier;
4347
import org.springframework.beans.factory.support.InstanceSupplier;
4448
import org.springframework.beans.factory.support.RootBeanDefinition;
4549
import org.springframework.javapoet.CodeBlock;
@@ -119,6 +123,7 @@ CodeBlock generateCode(RootBeanDefinition beanDefinition) {
119123
addConstructorArgumentValues(code, beanDefinition);
120124
addPropertyValues(code, beanDefinition);
121125
addAttributes(code, beanDefinition);
126+
addQualifiers(code, beanDefinition);
122127
return code.build();
123128
}
124129

@@ -180,6 +185,24 @@ private void addPropertyValues(CodeBlock.Builder code,
180185
}
181186
}
182187

188+
private void addQualifiers(CodeBlock.Builder code,
189+
RootBeanDefinition beanDefinition) {
190+
191+
Set<AutowireCandidateQualifier> qualifiers = beanDefinition.getQualifiers();
192+
if (!qualifiers.isEmpty()) {
193+
for (AutowireCandidateQualifier qualifier : qualifiers) {
194+
Collection<CodeBlock> arguments = new ArrayList<>();
195+
arguments.add(CodeBlock.of("$S", qualifier.getTypeName()));
196+
Object qualifierValue = qualifier.getAttribute(AutowireCandidateQualifier.VALUE_KEY);
197+
if (qualifierValue != null) {
198+
arguments.add(generateValue("value", qualifierValue));
199+
}
200+
code.addStatement("$L.addQualifier(new $T($L))", BEAN_DEFINITION_VARIABLE,
201+
AutowireCandidateQualifier.class, CodeBlock.join(arguments, ", "));
202+
}
203+
}
204+
}
205+
183206
private CodeBlock generateValue(@Nullable String name, @Nullable Object value) {
184207
try {
185208
PropertyNamesStack.push(name);

Diff for: spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanDefinitionPropertiesCodeGeneratorTests.java

+42-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2023 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.
@@ -16,10 +16,13 @@
1616

1717
package org.springframework.beans.factory.aot;
1818

19+
import java.time.temporal.ChronoUnit;
20+
import java.util.ArrayList;
1921
import java.util.List;
2022
import java.util.Map;
2123
import java.util.Set;
2224
import java.util.function.BiConsumer;
25+
import java.util.function.Consumer;
2326
import java.util.function.Predicate;
2427
import java.util.function.Supplier;
2528

@@ -36,6 +39,7 @@
3639
import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder;
3740
import org.springframework.beans.factory.config.RuntimeBeanNameReference;
3841
import org.springframework.beans.factory.config.RuntimeBeanReference;
42+
import org.springframework.beans.factory.support.AutowireCandidateQualifier;
3943
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
4044
import org.springframework.beans.factory.support.ManagedList;
4145
import org.springframework.beans.factory.support.ManagedMap;
@@ -392,6 +396,43 @@ void attributesWhenSomeFiltered() {
392396
});
393397
}
394398

399+
@Test
400+
void qualifiersWhenQualifierHasNoValue() {
401+
this.beanDefinition.addQualifier(new AutowireCandidateQualifier("com.example.Qualifier"));
402+
compile((actual, compiled) -> {
403+
assertThat(actual.getQualifiers()).singleElement().satisfies(isQualifierFor("com.example.Qualifier", null));
404+
assertThat(this.beanDefinition.getQualifiers()).isEqualTo(actual.getQualifiers());
405+
});
406+
}
407+
408+
@Test
409+
void qualifiersWhenQualifierHasStringValue() {
410+
this.beanDefinition.addQualifier(new AutowireCandidateQualifier("com.example.Qualifier", "id"));
411+
compile((actual, compiled) -> {
412+
assertThat(actual.getQualifiers()).singleElement().satisfies(isQualifierFor("com.example.Qualifier", "id"));
413+
assertThat(this.beanDefinition.getQualifiers()).isEqualTo(actual.getQualifiers());
414+
});
415+
}
416+
417+
@Test
418+
void qualifiersWhenMultipleQualifiers() {
419+
this.beanDefinition.addQualifier(new AutowireCandidateQualifier("com.example.Qualifier", "id"));
420+
this.beanDefinition.addQualifier(new AutowireCandidateQualifier("com.example.Another", ChronoUnit.SECONDS));
421+
compile((actual, compiled) -> {
422+
List<AutowireCandidateQualifier> qualifiers = new ArrayList<>(actual.getQualifiers());
423+
assertThat(qualifiers.get(0)).satisfies(isQualifierFor("com.example.Qualifier", "id"));
424+
assertThat(qualifiers.get(1)).satisfies(isQualifierFor("com.example.Another", ChronoUnit.SECONDS));
425+
assertThat(qualifiers).hasSize(2);
426+
});
427+
}
428+
429+
private Consumer<AutowireCandidateQualifier> isQualifierFor(String typeName, Object value) {
430+
return qualifier -> {
431+
assertThat(qualifier.getTypeName()).isEqualTo(typeName);
432+
assertThat(qualifier.getAttribute(AutowireCandidateQualifier.VALUE_KEY)).isEqualTo(value);
433+
};
434+
}
435+
395436
@Test
396437
void multipleItems() {
397438
this.beanDefinition.setPrimary(true);

Diff for: spring-context/src/test/java/org/springframework/context/aot/ApplicationContextAotGeneratorTests.java

+13-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2023 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.
@@ -64,6 +64,7 @@
6464
import org.springframework.context.testfixture.context.annotation.LazyConstructorArgumentComponent;
6565
import org.springframework.context.testfixture.context.annotation.LazyFactoryMethodArgumentComponent;
6666
import org.springframework.context.testfixture.context.annotation.PropertySourceConfiguration;
67+
import org.springframework.context.testfixture.context.annotation.QualifierConfiguration;
6768
import org.springframework.context.testfixture.context.generator.SimpleComponent;
6869
import org.springframework.core.env.ConfigurableEnvironment;
6970
import org.springframework.core.env.Environment;
@@ -303,6 +304,17 @@ void processAheadOfTimeWithPropertySource() {
303304
});
304305
}
305306

307+
@Test
308+
void processAheadOfTimeWithQualifier() {
309+
GenericApplicationContext applicationContext = new AnnotationConfigApplicationContext();
310+
applicationContext.registerBean(QualifierConfiguration.class);
311+
testCompiledResult(applicationContext, (initializer, compiled) -> {
312+
GenericApplicationContext freshApplicationContext = toFreshApplicationContext(initializer);
313+
QualifierConfiguration configuration = freshApplicationContext.getBean(QualifierConfiguration.class);
314+
assertThat(configuration).hasFieldOrPropertyWithValue("bean", "one");
315+
});
316+
}
317+
306318
@Nested
307319
@CompileWithForkedClassLoader
308320
class ConfigurationClassCglibProxy {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* Copyright 2002-2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.context.testfixture.context.annotation;
18+
19+
import org.springframework.beans.factory.annotation.Autowired;
20+
import org.springframework.beans.factory.annotation.Qualifier;
21+
import org.springframework.context.annotation.Bean;
22+
import org.springframework.context.annotation.Configuration;
23+
24+
@Configuration(proxyBeanMethods = false)
25+
public class QualifierConfiguration {
26+
27+
private String bean;
28+
29+
@Autowired
30+
@Qualifier("1")
31+
public void setBean(String bean) {
32+
this.bean = bean;
33+
}
34+
35+
36+
public static class BeansConfiguration {
37+
38+
@Bean
39+
@Qualifier("1")
40+
public String one() {
41+
return "one";
42+
}
43+
44+
@Bean
45+
@Qualifier("2")
46+
public String two() {
47+
return "one";
48+
}
49+
50+
}
51+
}

0 commit comments

Comments
 (0)