From 4190209ead126442c6670de5101c7741a76fe055 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Tue, 24 Jun 2025 21:58:31 +0200 Subject: [PATCH] Add missing AOT support for method overrides (including @Lookup) Closes gh-34642 --- ...BeanDefinitionPropertiesCodeGenerator.java | 34 +++ .../factory/aot/BeanInstanceSupplier.java | 6 + .../aot/InstanceSupplierCodeGenerator.java | 4 +- .../factory/support/ReplaceOverride.java | 24 ++ .../ApplicationContextAotGeneratorTests.java | 288 +++++++++++------- .../annotation/AutowiredComponent.java | 16 +- .../context/annotation/LookupComponent.java | 43 +++ 7 files changed, 305 insertions(+), 110 deletions(-) create mode 100644 spring-context/src/testFixtures/java/org/springframework/context/testfixture/context/annotation/LookupComponent.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 42aba59edf5..cbb49023f66 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 @@ -52,6 +52,9 @@ import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueH 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.LookupOverride; +import org.springframework.beans.factory.support.MethodOverride; +import org.springframework.beans.factory.support.ReplaceOverride; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.javapoet.CodeBlock; import org.springframework.javapoet.CodeBlock.Builder; @@ -145,6 +148,7 @@ class BeanDefinitionPropertiesCodeGenerator { addPropertyValues(code, beanDefinition); addAttributes(code, beanDefinition); addQualifiers(code, beanDefinition); + addMethodOverrides(code, beanDefinition); return code.build(); } @@ -274,6 +278,36 @@ class BeanDefinitionPropertiesCodeGenerator { } } + private void addMethodOverrides(CodeBlock.Builder code, RootBeanDefinition beanDefinition) { + if (beanDefinition.hasMethodOverrides()) { + for (MethodOverride methodOverride : beanDefinition.getMethodOverrides().getOverrides()) { + if (methodOverride instanceof LookupOverride lookupOverride) { + Collection arguments = new ArrayList<>(); + arguments.add(CodeBlock.of("$S", lookupOverride.getMethodName())); + arguments.add(CodeBlock.of("$S", lookupOverride.getBeanName())); + code.addStatement("$L.getMethodOverrides().addOverride(new $T($L))", BEAN_DEFINITION_VARIABLE, + LookupOverride.class, CodeBlock.join(arguments, ", ")); + } + else if (methodOverride instanceof ReplaceOverride replaceOverride) { + Collection arguments = new ArrayList<>(); + arguments.add(CodeBlock.of("$S", replaceOverride.getMethodName())); + arguments.add(CodeBlock.of("$S", replaceOverride.getMethodReplacerBeanName())); + List typeIdentifiers = replaceOverride.getTypeIdentifiers(); + if (!typeIdentifiers.isEmpty()) { + arguments.add(CodeBlock.of("java.util.List.of($S)", + StringUtils.collectionToDelimitedString(typeIdentifiers, ", "))); + } + code.addStatement("$L.getMethodOverrides().addOverride(new $T($L))", BEAN_DEFINITION_VARIABLE, + ReplaceOverride.class, CodeBlock.join(arguments, ", ")); + } + else { + throw new UnsupportedOperationException("Unexpected MethodOverride subclass: " + + methodOverride.getClass().getName()); + } + } + } + } + private CodeBlock generateValue(@Nullable String name, @Nullable Object value) { PropertyNamesStack.push(name); try { diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanInstanceSupplier.java b/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanInstanceSupplier.java index d94181b6c78..817a1038217 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanInstanceSupplier.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanInstanceSupplier.java @@ -38,6 +38,7 @@ import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueH import org.springframework.beans.factory.config.DependencyDescriptor; import org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory; import org.springframework.beans.factory.support.BeanDefinitionValueResolver; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.InstanceSupplier; import org.springframework.beans.factory.support.RegisteredBean; import org.springframework.beans.factory.support.RootBeanDefinition; @@ -372,6 +373,11 @@ public final class BeanInstanceSupplier extends AutowiredElementResolver impl private Object instantiate(RegisteredBean registeredBean, Executable executable, Object[] args) { if (executable instanceof Constructor constructor) { + if (registeredBean.getBeanFactory() instanceof DefaultListableBeanFactory dlbf && + registeredBean.getMergedBeanDefinition().hasMethodOverrides()) { + return dlbf.getInstantiationStrategy().instantiate(registeredBean.getMergedBeanDefinition(), + registeredBean.getBeanName(), registeredBean.getBeanFactory()); + } return BeanUtils.instantiateClass(constructor, args); } if (executable instanceof Method method) { diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/aot/InstanceSupplierCodeGenerator.java b/spring-beans/src/main/java/org/springframework/beans/factory/aot/InstanceSupplierCodeGenerator.java index c0129973325..91a84acf0c2 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/aot/InstanceSupplierCodeGenerator.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/aot/InstanceSupplierCodeGenerator.java @@ -115,6 +115,7 @@ public class InstanceSupplierCodeGenerator { this.allowDirectSupplierShortcut = allowDirectSupplierShortcut; } + /** * Generate the instance supplier code. * @param registeredBean the bean to handle @@ -165,7 +166,8 @@ public class InstanceSupplierCodeGenerator { hints -> hints.registerType(publicType, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)); } - if (!isVisible(constructor, constructor.getDeclaringClass())) { + if (!isVisible(constructor, constructor.getDeclaringClass()) || + registeredBean.getMergedBeanDefinition().hasMethodOverrides()) { return generateCodeForInaccessibleConstructor(descriptor, hints -> hints.registerConstructor(constructor, ExecutableMode.INVOKE)); } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/ReplaceOverride.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/ReplaceOverride.java index 3e1110e72d8..f0fe4d1cf8a 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/ReplaceOverride.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/ReplaceOverride.java @@ -18,6 +18,7 @@ package org.springframework.beans.factory.support; import java.lang.reflect.Method; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Objects; @@ -53,6 +54,20 @@ public class ReplaceOverride extends MethodOverride { this.methodReplacerBeanName = methodReplacerBeanName; } + /** + * Construct a new ReplaceOverride. + * @param methodName the name of the method to override + * @param methodReplacerBeanName the bean name of the {@link MethodReplacer} + * @param typeIdentifiers a list of type identifiers for parameter types + * @since 6.2.9 + */ + public ReplaceOverride(String methodName, String methodReplacerBeanName, List typeIdentifiers) { + super(methodName); + Assert.notNull(methodReplacerBeanName, "Method replacer bean name must not be null"); + this.methodReplacerBeanName = methodReplacerBeanName; + this.typeIdentifiers.addAll(typeIdentifiers); + } + /** * Return the name of the bean implementing MethodReplacer. @@ -70,6 +85,15 @@ public class ReplaceOverride extends MethodOverride { this.typeIdentifiers.add(identifier); } + /** + * Return the list of registered type identifiers (fragments of a class string). + * @since 6.2.9 + * @see #addTypeIdentifier + */ + public List getTypeIdentifiers() { + return Collections.unmodifiableList(this.typeIdentifiers); + } + @Override public boolean matches(Method method) { 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 a391d60192d..f35a6e46e8c 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 @@ -18,6 +18,7 @@ package org.springframework.context.aot; import java.io.IOException; import java.lang.reflect.Constructor; +import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.List; import java.util.function.BiConsumer; @@ -51,7 +52,9 @@ import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.beans.factory.support.MethodReplacer; import org.springframework.beans.factory.support.RegisteredBean; +import org.springframework.beans.factory.support.ReplaceOverride; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.testfixture.beans.Employee; import org.springframework.beans.testfixture.beans.Pet; @@ -81,6 +84,7 @@ import org.springframework.context.testfixture.context.annotation.LazyConstructo import org.springframework.context.testfixture.context.annotation.LazyFactoryMethodArgumentComponent; import org.springframework.context.testfixture.context.annotation.LazyResourceFieldComponent; import org.springframework.context.testfixture.context.annotation.LazyResourceMethodComponent; +import org.springframework.context.testfixture.context.annotation.LookupComponent; import org.springframework.context.testfixture.context.annotation.PropertySourceConfiguration; import org.springframework.context.testfixture.context.annotation.QualifierConfiguration; import org.springframework.context.testfixture.context.annotation.ResourceComponent; @@ -112,6 +116,7 @@ class ApplicationContextAotGeneratorTests { void processAheadOfTimeWhenHasSimpleBean() { GenericApplicationContext applicationContext = new GenericApplicationContext(); applicationContext.registerBeanDefinition("test", new RootBeanDefinition(SimpleComponent.class)); + testCompiledResult(applicationContext, (initializer, compiled) -> { GenericApplicationContext freshApplicationContext = toFreshApplicationContext(initializer); assertThat(freshApplicationContext.getBeanDefinitionNames()).containsOnly("test"); @@ -119,6 +124,99 @@ class ApplicationContextAotGeneratorTests { }); } + @Test + void processAheadOfTimeWhenHasNoAotContributions() { + GenericApplicationContext applicationContext = new GenericApplicationContext(); + + testCompiledResult(applicationContext, (initializer, compiled) -> { + GenericApplicationContext freshApplicationContext = toFreshApplicationContext(initializer); + assertThat(freshApplicationContext.getBeanDefinitionNames()).isEmpty(); + assertThat(compiled.getSourceFile()) + .contains("beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver())") + .contains("beanFactory.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE)"); + }); + } + + @Test + void processAheadOfTimeWhenHasBeanFactoryInitializationAotProcessorExcludesProcessor() { + GenericApplicationContext applicationContext = new GenericApplicationContext(); + applicationContext.registerBeanDefinition("test", + new RootBeanDefinition(NoOpBeanFactoryInitializationAotProcessor.class)); + + testCompiledResult(applicationContext, (initializer, compiled) -> { + GenericApplicationContext freshApplicationContext = toFreshApplicationContext(initializer); + assertThat(freshApplicationContext.getBeanDefinitionNames()).isEmpty(); + }); + } + + @Test + void processAheadOfTimeWhenHasBeanRegistrationAotProcessorExcludesProcessor() { + GenericApplicationContext applicationContext = new GenericApplicationContext(); + applicationContext.registerBeanDefinition("test", + new RootBeanDefinition(NoOpBeanRegistrationAotProcessor.class)); + + testCompiledResult(applicationContext, (initializer, compiled) -> { + GenericApplicationContext freshApplicationContext = toFreshApplicationContext(initializer); + assertThat(freshApplicationContext.getBeanDefinitionNames()).isEmpty(); + }); + } + + @Test + void processAheadOfTimeWithPropertySource() { + GenericApplicationContext applicationContext = new AnnotationConfigApplicationContext(); + applicationContext.registerBean(PropertySourceConfiguration.class); + + testCompiledResult(applicationContext, (initializer, compiled) -> { + GenericApplicationContext freshApplicationContext = toFreshApplicationContext(initializer); + ConfigurableEnvironment environment = freshApplicationContext.getEnvironment(); + PropertySource propertySource = environment.getPropertySources().get("testp1"); + assertThat(propertySource).isNotNull(); + assertThat(propertySource.getProperty("from.p1")).isEqualTo("p1Value"); + }); + } + + @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"); + }); + } + + @Test + void processAheadOfTimeWithInjectionPoint() { + GenericApplicationContext applicationContext = new AnnotationConfigApplicationContext(); + applicationContext.registerBean(InjectionPointConfiguration.class); + + testCompiledResult(applicationContext, (initializer, compiled) -> { + GenericApplicationContext freshApplicationContext = toFreshApplicationContext(initializer); + assertThat(freshApplicationContext.getBean("classToString")) + .isEqualTo(InjectionPointConfiguration.class.getName()); + }); + } + + @Test // gh-30689 + void processAheadOfTimeWithExplicitResolvableType() { + GenericApplicationContext applicationContext = new GenericApplicationContext(); + DefaultListableBeanFactory beanFactory = applicationContext.getDefaultListableBeanFactory(); + RootBeanDefinition beanDefinition = new RootBeanDefinition(One.class); + beanDefinition.setResolvedFactoryMethod(ReflectionUtils.findMethod(TestHierarchy.class, "oneBean")); + // Override target type + beanDefinition.setTargetType(Two.class); + beanFactory.registerBeanDefinition("hierarchyBean", beanDefinition); + + testCompiledResult(applicationContext, (initializer, compiled) -> { + GenericApplicationContext freshApplicationContext = toFreshApplicationContext(initializer); + assertThat(freshApplicationContext.getBean(Two.class)) + .isInstanceOf(Implementation.class); + }); + } + + @Nested class Autowiring { @@ -129,6 +227,7 @@ class ApplicationContextAotGeneratorTests { AnnotationConfigUtils.AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME, AutowiredAnnotationBeanPostProcessor.class); applicationContext.registerBeanDefinition("autowiredComponent", new RootBeanDefinition(AutowiredComponent.class)); registerIntegerBean(applicationContext, "number", 42); + testCompiledResult(applicationContext, (initializer, compiled) -> { GenericApplicationContext freshApplicationContext = toFreshApplicationContext(initializer); assertThat(freshApplicationContext.getBeanDefinitionNames()).containsOnly("autowiredComponent", "number"); @@ -138,11 +237,56 @@ class ApplicationContextAotGeneratorTests { }); } + @Test + void processAheadOfTimeWhenHasReplacer() { + GenericApplicationContext applicationContext = new GenericApplicationContext(); + registerBeanPostProcessor(applicationContext, + AnnotationConfigUtils.AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME, AutowiredAnnotationBeanPostProcessor.class); + RootBeanDefinition rbd = new RootBeanDefinition(AutowiredComponent.class); + rbd.getMethodOverrides().addOverride( + new ReplaceOverride("getCounter", "replacer")); + applicationContext.registerBeanDefinition("autowiredComponent", rbd); + registerIntegerBean(applicationContext, "number", 42); + applicationContext.registerBean("replacer", DummyReplacer.class); + + testCompiledResult(applicationContext, (initializer, compiled) -> { + GenericApplicationContext freshApplicationContext = toFreshApplicationContext(initializer); + assertThat(freshApplicationContext.getBeanDefinitionNames()).containsOnly("autowiredComponent", "number", "replacer"); + AutowiredComponent bean = freshApplicationContext.getBean(AutowiredComponent.class); + assertThat(bean.getEnvironment()).isSameAs(freshApplicationContext.getEnvironment()); + assertThat(bean.getCounter()).isEqualTo(44); + assertThat(bean.getCounter(0)).isEqualTo(42); + }); + } + + @Test + void processAheadOfTimeWhenHasLookup() { + GenericApplicationContext applicationContext = new GenericApplicationContext(); + registerBeanPostProcessor(applicationContext, + AnnotationConfigUtils.AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME, AutowiredAnnotationBeanPostProcessor.class); + RootBeanDefinition rbd = new RootBeanDefinition(LookupComponent.class); + rbd.getMethodOverrides().addOverride( + new ReplaceOverride("getCounter", "replacer", List.of( "Integer"))); + applicationContext.registerBeanDefinition("autowiredComponent", rbd); + registerIntegerBean(applicationContext, "number", 42); + applicationContext.registerBean("replacer", DummyReplacer.class); + + testCompiledResult(applicationContext, (initializer, compiled) -> { + GenericApplicationContext freshApplicationContext = toFreshApplicationContext(initializer); + assertThat(freshApplicationContext.getBeanDefinitionNames()).containsOnly("autowiredComponent", "number", "replacer"); + LookupComponent bean = freshApplicationContext.getBean(LookupComponent.class); + assertThat(bean.getEnvironment()).isSameAs(freshApplicationContext.getEnvironment()); + assertThat(bean.getCounter()).isEqualTo(42); + assertThat(bean.getCounter(0)).isEqualTo(44); + }); + } + @Test void processAheadOfTimeWhenHasAutowiringOnUnresolvedGeneric() { GenericApplicationContext applicationContext = new AnnotationConfigApplicationContext(); applicationContext.registerBean(GenericTemplateConfiguration.class); applicationContext.registerBean("autowiredComponent", AutowiredGenericTemplate.class); + testCompiledResult(applicationContext, (initializer, compiled) -> { GenericApplicationContext freshApplicationContext = toFreshApplicationContext(initializer); AutowiredGenericTemplate bean = freshApplicationContext.getBean(AutowiredGenericTemplate.class); @@ -162,7 +306,6 @@ class ApplicationContextAotGeneratorTests { assertThat(runtimeHints.proxies().jdkProxyHints()).anySatisfy(proxyHint -> assertThat(proxyHint.getProxiedInterfaces()).isEqualTo(TypeReference.listOf( environment.getClass().getInterfaces()))); - }); } @@ -226,15 +369,16 @@ class ApplicationContextAotGeneratorTests { AnnotationConfigUtils.AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME, AutowiredAnnotationBeanPostProcessor.class); applicationContext.registerBeanDefinition("testComponent", beanDefinition); TestGenerationContext generationContext = processAheadOfTime(applicationContext); + testCompiledResult(generationContext, (initializer, compiled) -> { GenericApplicationContext freshApplicationContext = toFreshApplicationContext(initializer); assertThat(freshApplicationContext.getBeanDefinitionNames()).containsOnly("testComponent"); assertions.accept(freshApplicationContext.getBean("testComponent", type), generationContext); }); } - } + @Nested class ResourceAutowiring { @@ -247,6 +391,7 @@ class ApplicationContextAotGeneratorTests { registerStringBean(applicationContext, "text2", "hello2"); registerIntegerBean(applicationContext, "number", 42); applicationContext.registerBeanDefinition("resourceComponent", new RootBeanDefinition(ResourceComponent.class)); + testCompiledResult(applicationContext, (initializer, compiled) -> { GenericApplicationContext freshApplicationContext = toFreshApplicationContext(initializer); assertThat(freshApplicationContext.getBeanDefinitionNames()).containsOnly("resourceComponent", "text", "text2", "number"); @@ -300,6 +445,7 @@ class ApplicationContextAotGeneratorTests { AnnotationConfigUtils.COMMON_ANNOTATION_PROCESSOR_BEAN_NAME, CommonAnnotationBeanPostProcessor.class); applicationContext.registerBeanDefinition("testComponent", beanDefinition); TestGenerationContext generationContext = processAheadOfTime(applicationContext); + testCompiledResult(generationContext, (initializer, compiled) -> { GenericApplicationContext freshApplicationContext = toFreshApplicationContext(initializer); assertThat(freshApplicationContext.getBeanDefinitionNames()).containsOnly("testComponent"); @@ -308,6 +454,7 @@ class ApplicationContextAotGeneratorTests { } } + @Nested class InitDestroy { @@ -318,6 +465,7 @@ class ApplicationContextAotGeneratorTests { AnnotationConfigUtils.COMMON_ANNOTATION_PROCESSOR_BEAN_NAME, CommonAnnotationBeanPostProcessor.class); applicationContext.registerBeanDefinition("initDestroyComponent", new RootBeanDefinition(InitDestroyComponent.class)); + testCompiledResult(applicationContext, (initializer, compiled) -> { GenericApplicationContext freshApplicationContext = toFreshApplicationContext(initializer); assertThat(freshApplicationContext.getBeanDefinitionNames()).containsOnly("initDestroyComponent"); @@ -337,6 +485,7 @@ class ApplicationContextAotGeneratorTests { beanDefinition.setInitMethodName("customInit"); beanDefinition.setDestroyMethodName("customDestroy"); applicationContext.registerBeanDefinition("initDestroyComponent", beanDefinition); + testCompiledResult(applicationContext, (initializer, compiled) -> { GenericApplicationContext freshApplicationContext = toFreshApplicationContext(initializer); assertThat(freshApplicationContext.getBeanDefinitionNames()).containsOnly("initDestroyComponent"); @@ -346,94 +495,8 @@ class ApplicationContextAotGeneratorTests { assertThat(bean.events).containsExactly("init", "customInit", "destroy", "customDestroy"); }); } - } - @Test - void processAheadOfTimeWhenHasNoAotContributions() { - GenericApplicationContext applicationContext = new GenericApplicationContext(); - testCompiledResult(applicationContext, (initializer, compiled) -> { - GenericApplicationContext freshApplicationContext = toFreshApplicationContext(initializer); - assertThat(freshApplicationContext.getBeanDefinitionNames()).isEmpty(); - assertThat(compiled.getSourceFile()) - .contains("beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver())") - .contains("beanFactory.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE)"); - }); - } - - @Test - void processAheadOfTimeWhenHasBeanFactoryInitializationAotProcessorExcludesProcessor() { - GenericApplicationContext applicationContext = new GenericApplicationContext(); - applicationContext.registerBeanDefinition("test", - new RootBeanDefinition(NoOpBeanFactoryInitializationAotProcessor.class)); - testCompiledResult(applicationContext, (initializer, compiled) -> { - GenericApplicationContext freshApplicationContext = toFreshApplicationContext(initializer); - assertThat(freshApplicationContext.getBeanDefinitionNames()).isEmpty(); - }); - } - - @Test - void processAheadOfTimeWhenHasBeanRegistrationAotProcessorExcludesProcessor() { - GenericApplicationContext applicationContext = new GenericApplicationContext(); - applicationContext.registerBeanDefinition("test", - new RootBeanDefinition(NoOpBeanRegistrationAotProcessor.class)); - testCompiledResult(applicationContext, (initializer, compiled) -> { - GenericApplicationContext freshApplicationContext = toFreshApplicationContext(initializer); - assertThat(freshApplicationContext.getBeanDefinitionNames()).isEmpty(); - }); - } - - - @Test - void processAheadOfTimeWithPropertySource() { - GenericApplicationContext applicationContext = new AnnotationConfigApplicationContext(); - applicationContext.registerBean(PropertySourceConfiguration.class); - testCompiledResult(applicationContext, (initializer, compiled) -> { - GenericApplicationContext freshApplicationContext = toFreshApplicationContext(initializer); - ConfigurableEnvironment environment = freshApplicationContext.getEnvironment(); - PropertySource propertySource = environment.getPropertySources().get("testp1"); - assertThat(propertySource).isNotNull(); - assertThat(propertySource.getProperty("from.p1")).isEqualTo("p1Value"); - }); - } - - @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"); - }); - } - - @Test - void processAheadOfTimeWithInjectionPoint() { - GenericApplicationContext applicationContext = new AnnotationConfigApplicationContext(); - applicationContext.registerBean(InjectionPointConfiguration.class); - testCompiledResult(applicationContext, (initializer, compiled) -> { - GenericApplicationContext freshApplicationContext = toFreshApplicationContext(initializer); - assertThat(freshApplicationContext.getBean("classToString")) - .isEqualTo(InjectionPointConfiguration.class.getName()); - }); - } - - @Test // gh-30689 - void processAheadOfTimeWithExplicitResolvableType() { - GenericApplicationContext applicationContext = new GenericApplicationContext(); - DefaultListableBeanFactory beanFactory = applicationContext.getDefaultListableBeanFactory(); - RootBeanDefinition beanDefinition = new RootBeanDefinition(One.class); - beanDefinition.setResolvedFactoryMethod(ReflectionUtils.findMethod(TestHierarchy.class, "oneBean")); - // Override target type - beanDefinition.setTargetType(Two.class); - beanFactory.registerBeanDefinition("hierarchyBean", beanDefinition); - testCompiledResult(applicationContext, (initializer, compiled) -> { - GenericApplicationContext freshApplicationContext = toFreshApplicationContext(initializer); - assertThat(freshApplicationContext.getBean(Two.class)) - .isInstanceOf(Implementation.class); - }); - } @Nested @CompileWithForkedClassLoader @@ -499,6 +562,7 @@ class ApplicationContextAotGeneratorTests { void processAheadOfTimeWhenHasCglibProxyUseProxy() { GenericApplicationContext applicationContext = new AnnotationConfigApplicationContext(); applicationContext.registerBean(CglibConfiguration.class); + testCompiledResult(applicationContext, (initializer, compiled) -> { GenericApplicationContext freshApplicationContext = toFreshApplicationContext(initializer); assertThat(freshApplicationContext.getBean("prefix", String.class)).isEqualTo("Hello0"); @@ -510,6 +574,7 @@ class ApplicationContextAotGeneratorTests { void processAheadOfTimeWhenHasCglibProxyAndAutowiring() { GenericApplicationContext applicationContext = new AnnotationConfigApplicationContext(); applicationContext.registerBean(AutowiredCglibConfiguration.class); + testCompiledResult(applicationContext, (initializer, compiled) -> { GenericApplicationContext freshApplicationContext = toFreshApplicationContext(context -> { context.setEnvironment(new MockEnvironment().withProperty("hello", "Hi")); @@ -523,6 +588,7 @@ class ApplicationContextAotGeneratorTests { void processAheadOfTimeWhenHasCglibProxyAndMixedAutowiring() { GenericApplicationContext applicationContext = new AnnotationConfigApplicationContext(); applicationContext.registerBean(AutowiredMixedCglibConfiguration.class); + testCompiledResult(applicationContext, (initializer, compiled) -> { GenericApplicationContext freshApplicationContext = toFreshApplicationContext(context -> { context.setEnvironment(new MockEnvironment().withProperty("hello", "Hi") @@ -537,6 +603,7 @@ class ApplicationContextAotGeneratorTests { void processAheadOfTimeWhenHasCglibProxyWithAnnotationsOnTheUserClasConstructor() { GenericApplicationContext applicationContext = new AnnotationConfigApplicationContext(); applicationContext.registerBean("config", ValueCglibConfiguration.class); + testCompiledResult(applicationContext, (initializer, compiled) -> { GenericApplicationContext freshApplicationContext = toFreshApplicationContext(context -> { context.setEnvironment(new MockEnvironment().withProperty("name", "AOT World")); @@ -551,6 +618,7 @@ class ApplicationContextAotGeneratorTests { void processAheadOfTimeWhenHasCglibProxyWithArgumentsUseProxy() { GenericApplicationContext applicationContext = new AnnotationConfigApplicationContext(); applicationContext.registerBean(ConfigurableCglibConfiguration.class); + testCompiledResult(applicationContext, (initializer, compiled) -> { GenericApplicationContext freshApplicationContext = createFreshApplicationContext(initializer); freshApplicationContext.setEnvironment(new MockEnvironment().withProperty("test.prefix", "Hi")); @@ -573,9 +641,9 @@ class ApplicationContextAotGeneratorTests { private String toCglibClassSimpleName(Class configClass) { return configClass.getSimpleName() + CGLIB_CONFIGURATION_CLASS_SUFFIX; } - } + @Nested class ActiveProfile { @@ -586,6 +654,7 @@ class ApplicationContextAotGeneratorTests { if (aotProfiles.length != 0) { applicationContext.getEnvironment().setActiveProfiles(aotProfiles); } + testCompiledResult(applicationContext, (initializer, compiled) -> { GenericApplicationContext freshApplicationContext = new GenericApplicationContext(); if (runtimeProfiles.length != 0) { @@ -604,17 +673,18 @@ class ApplicationContextAotGeneratorTests { Arguments.of(new String[] { "aot", "prod" }, new String[] { "aot", "prod" }, new String[] { "aot", "prod" }), Arguments.of(new String[] { "default" }, new String[] {}, new String[] {})); } - } + @Nested class XmlSupport { @Test void processAheadOfTimeWhenHasTypedStringValue() { GenericXmlApplicationContext applicationContext = new GenericXmlApplicationContext(); - applicationContext - .load(new ClassPathResource("applicationContextAotGeneratorTests-values.xml", getClass())); + applicationContext.load( + new ClassPathResource("applicationContextAotGeneratorTests-values.xml", getClass())); + testCompiledResult(applicationContext, (initializer, compiled) -> { GenericApplicationContext freshApplicationContext = toFreshApplicationContext(initializer); Employee employee = freshApplicationContext.getBean(Employee.class); @@ -631,8 +701,9 @@ class ApplicationContextAotGeneratorTests { @Test void processAheadOfTimeWhenHasTypedStringValueWithType() { GenericXmlApplicationContext applicationContext = new GenericXmlApplicationContext(); - applicationContext - .load(new ClassPathResource("applicationContextAotGeneratorTests-values-types.xml", getClass())); + applicationContext.load( + new ClassPathResource("applicationContextAotGeneratorTests-values-types.xml", getClass())); + testCompiledResult(applicationContext, (initializer, compiled) -> { GenericApplicationContext freshApplicationContext = toFreshApplicationContext(initializer); Employee employee = freshApplicationContext.getBean(Employee.class); @@ -647,8 +718,9 @@ class ApplicationContextAotGeneratorTests { @Test void processAheadOfTimeWhenHasTypedStringValueWithExpression() { GenericXmlApplicationContext applicationContext = new GenericXmlApplicationContext(); - applicationContext - .load(new ClassPathResource("applicationContextAotGeneratorTests-values-expressions.xml", getClass())); + applicationContext.load( + new ClassPathResource("applicationContextAotGeneratorTests-values-expressions.xml", getClass())); + testCompiledResult(applicationContext, (initializer, compiled) -> { GenericApplicationContext freshApplicationContext = toFreshApplicationContext(initializer); Employee employee = freshApplicationContext.getBean(Employee.class); @@ -663,8 +735,9 @@ class ApplicationContextAotGeneratorTests { @Test void processAheadOfTimeWhenXmlHasBeanReferences() { GenericXmlApplicationContext applicationContext = new GenericXmlApplicationContext(); - applicationContext - .load(new ClassPathResource("applicationContextAotGeneratorTests-references.xml", getClass())); + applicationContext.load( + new ClassPathResource("applicationContextAotGeneratorTests-references.xml", getClass())); + testCompiledResult(applicationContext, (initializer, compiled) -> { GenericApplicationContext freshApplicationContext = toFreshApplicationContext(initializer); assertThat(freshApplicationContext.getBean("petInnerBean", Pet.class) @@ -673,11 +746,11 @@ class ApplicationContextAotGeneratorTests { .getName()).isEqualTo("Dofi"); }); } - } + @Nested - class ExceptionHanding { + class ExceptionHandling { @Test void failureProcessingBeanFactoryAotContribution() { @@ -692,6 +765,7 @@ class ApplicationContextAotGeneratorTests { } } + private static void registerBeanPostProcessor(GenericApplicationContext applicationContext, String beanName, Class beanPostProcessorClass) { @@ -716,7 +790,7 @@ class ApplicationContextAotGeneratorTests { .getBeanDefinition()); } - private Consumer> doesNotHaveProxyFor(Class target) { + private static Consumer> doesNotHaveProxyFor(Class target) { return hints -> assertThat(hints).noneMatch(hint -> hint.getProxiedInterfaces().get(0).equals(TypeReference.of(target))); } @@ -771,7 +845,6 @@ class ApplicationContextAotGeneratorTests { public BeanFactoryInitializationAotContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) { return null; } - } @@ -782,9 +855,9 @@ class ApplicationContextAotGeneratorTests { public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) { return null; } - } + static class FailingBeanFactoryInitializationAotContribution implements BeanFactoryInitializationAotProcessor { @Override @@ -793,4 +866,13 @@ class ApplicationContextAotGeneratorTests { } } + + public static class DummyReplacer implements MethodReplacer { + + @Override + public Object reimplement(Object obj, Method method, Object[] args) throws Throwable { + return 44; + } + } + } diff --git a/spring-context/src/testFixtures/java/org/springframework/context/testfixture/context/annotation/AutowiredComponent.java b/spring-context/src/testFixtures/java/org/springframework/context/testfixture/context/annotation/AutowiredComponent.java index 4104c7f6414..fdc9d03f216 100644 --- a/spring-context/src/testFixtures/java/org/springframework/context/testfixture/context/annotation/AutowiredComponent.java +++ b/spring-context/src/testFixtures/java/org/springframework/context/testfixture/context/annotation/AutowiredComponent.java @@ -25,17 +25,13 @@ public class AutowiredComponent { private Integer counter; - public Environment getEnvironment() { - return this.environment; - } - @Autowired public void setEnvironment(Environment environment) { this.environment = environment; } - public Integer getCounter() { - return this.counter; + public Environment getEnvironment() { + return this.environment; } @Autowired @@ -43,4 +39,12 @@ public class AutowiredComponent { this.counter = counter; } + public Integer getCounter() { + return this.counter; + } + + public Integer getCounter(Integer ignored) { + return this.counter; + } + } diff --git a/spring-context/src/testFixtures/java/org/springframework/context/testfixture/context/annotation/LookupComponent.java b/spring-context/src/testFixtures/java/org/springframework/context/testfixture/context/annotation/LookupComponent.java new file mode 100644 index 00000000000..4b4281f8f47 --- /dev/null +++ b/spring-context/src/testFixtures/java/org/springframework/context/testfixture/context/annotation/LookupComponent.java @@ -0,0 +1,43 @@ +/* + * Copyright 2002-present 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.Lookup; +import org.springframework.core.env.Environment; + +public abstract class LookupComponent { + + private Environment environment; + + @Autowired + public void setEnvironment(Environment environment) { + this.environment = environment; + } + + public Environment getEnvironment() { + return this.environment; + } + + @Lookup + public abstract Integer getCounter(); + + public Integer getCounter(Integer ignored) { + return 0; + } + +}