From 44b427c3e1e2e9025d1574d23adb9790d01a3b32 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Wed, 3 May 2023 11:41:16 +0200 Subject: [PATCH] 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 --- ...BeanDefinitionPropertiesCodeGenerator.java | 25 ++++++++- ...efinitionPropertiesCodeGeneratorTests.java | 43 +++++++++++++++- .../ApplicationContextAotGeneratorTests.java | 14 ++++- .../annotation/QualifierConfiguration.java | 51 +++++++++++++++++++ 4 files changed, 130 insertions(+), 3 deletions(-) create mode 100644 spring-context/src/testFixtures/java/org/springframework/context/testfixture/context/annotation/QualifierConfiguration.java diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanDefinitionPropertiesCodeGenerator.java b/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanDefinitionPropertiesCodeGenerator.java index f4dc48e2ed81..648f1d7f37fd 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanDefinitionPropertiesCodeGenerator.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanDefinitionPropertiesCodeGenerator.java @@ -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 @@ 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.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 @@ CodeBlock generateCode(RootBeanDefinition beanDefinition) { addConstructorArgumentValues(code, beanDefinition); addPropertyValues(code, beanDefinition); addAttributes(code, beanDefinition); + addQualifiers(code, beanDefinition); return code.build(); } @@ -180,6 +185,24 @@ private void addPropertyValues(CodeBlock.Builder code, } } + private void addQualifiers(CodeBlock.Builder code, + RootBeanDefinition beanDefinition) { + + Set qualifiers = beanDefinition.getQualifiers(); + if (!qualifiers.isEmpty()) { + for (AutowireCandidateQualifier qualifier : qualifiers) { + Collection 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); diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanDefinitionPropertiesCodeGeneratorTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanDefinitionPropertiesCodeGeneratorTests.java index 2fb541a9696c..60093ff23061 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanDefinitionPropertiesCodeGeneratorTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanDefinitionPropertiesCodeGeneratorTests.java @@ -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 @@ 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.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 @@ void attributesWhenSomeFiltered() { }); } + @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 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 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); diff --git a/spring-context/src/test/java/org/springframework/context/aot/ApplicationContextAotGeneratorTests.java b/spring-context/src/test/java/org/springframework/context/aot/ApplicationContextAotGeneratorTests.java index bbd4a1effdab..e3b0df915384 100644 --- a/spring-context/src/test/java/org/springframework/context/aot/ApplicationContextAotGeneratorTests.java +++ b/spring-context/src/test/java/org/springframework/context/aot/ApplicationContextAotGeneratorTests.java @@ -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.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 @@ void processAheadOfTimeWithPropertySource() { }); } + @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 { diff --git a/spring-context/src/testFixtures/java/org/springframework/context/testfixture/context/annotation/QualifierConfiguration.java b/spring-context/src/testFixtures/java/org/springframework/context/testfixture/context/annotation/QualifierConfiguration.java new file mode 100644 index 000000000000..e40e86c52e59 --- /dev/null +++ b/spring-context/src/testFixtures/java/org/springframework/context/testfixture/context/annotation/QualifierConfiguration.java @@ -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"; + } + + } +}