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 169134592c8..5cbf519a65c 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 @@ -53,6 +53,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 ab475c057bb..48a1fcca45c 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 @@ -40,6 +40,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; @@ -343,6 +344,11 @@ public final class BeanInstanceSupplier extends AutowiredElementResolver impl private Object instantiate(RegisteredBean registeredBean, Executable executable, @Nullable 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/BeanRegistrationAotContribution.java b/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanRegistrationAotContribution.java index 53748b1dfb9..fc76f514d10 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanRegistrationAotContribution.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanRegistrationAotContribution.java @@ -41,8 +41,7 @@ public interface BeanRegistrationAotContribution { * default code generation isn't suitable. * @param generationContext the generation context * @param codeFragments the existing code fragments - * @return the code fragments to use, may be the original instance or a - * wrapper + * @return the code fragments to use, may be the original instance or a wrapper */ default BeanRegistrationCodeFragments customizeBeanRegistrationCodeFragments( GenerationContext generationContext, BeanRegistrationCodeFragments codeFragments) { @@ -78,8 +77,7 @@ public interface BeanRegistrationAotContribution { return defaultCodeFragments.apply(codeFragments); } @Override - public void applyTo(GenerationContext generationContext, - BeanRegistrationCode beanRegistrationCode) { + public void applyTo(GenerationContext generationContext, BeanRegistrationCode beanRegistrationCode) { } }; } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanRegistrationCodeFragments.java b/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanRegistrationCodeFragments.java index 5d391c541c6..cc7dc90d8e4 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanRegistrationCodeFragments.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanRegistrationCodeFragments.java @@ -31,14 +31,15 @@ import org.springframework.javapoet.CodeBlock; /** * Generate the various fragments of code needed to register a bean. - *

- * A default implementation is provided that suits most needs and custom code + * + *

A default implementation is provided that suits most needs and custom code * fragments are only expected to be used by library authors having built custom * arrangement on top of the core container. - *

- * Users are not expected to implement this interface directly, but rather extends - * from {@link BeanRegistrationCodeFragmentsDecorator} and only override the - * necessary method(s). + * + *

Users are not expected to implement this interface directly, but rather + * extends from {@link BeanRegistrationCodeFragmentsDecorator} and only override + * the necessary method(s). + * * @author Phillip Webb * @author Stephane Nicoll * @since 6.0 @@ -48,12 +49,12 @@ import org.springframework.javapoet.CodeBlock; public interface BeanRegistrationCodeFragments { /** - * The variable name to used when creating the bean definition. + * The variable name used when creating the bean definition. */ String BEAN_DEFINITION_VARIABLE = "beanDefinition"; /** - * The variable name to used when creating the bean definition. + * The variable name used when creating the bean definition. */ String INSTANCE_SUPPLIER_VARIABLE = "instanceSupplier"; @@ -69,8 +70,7 @@ public interface BeanRegistrationCodeFragments { /** * Generate the code that defines the new bean definition instance. - *

- * This should declare a variable named {@value BEAN_DEFINITION_VARIABLE} + *

This should declare a variable named {@value BEAN_DEFINITION_VARIABLE} * so that further fragments can refer to the variable to further tune * the bean definition. * @param generationContext the generation context @@ -94,14 +94,13 @@ public interface BeanRegistrationCodeFragments { /** * Generate the code that sets the instance supplier on the bean definition. - *

- * The {@code postProcessors} represent methods to be exposed once the + *

The {@code postProcessors} represent methods to be exposed once the * instance has been created to further configure it. Each method should * accept two parameters, the {@link RegisteredBean} and the bean * instance, and should return the modified bean instance. * @param generationContext the generation context * @param beanRegistrationCode the bean registration code - * @param instanceSupplierCode the instance supplier code supplier code + * @param instanceSupplierCode the instance supplier code * @param postProcessors any instance post processors that should be applied * @return the generated code * @see #generateInstanceSupplierCode diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanRegistrationCodeGenerator.java b/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanRegistrationCodeGenerator.java index ee814c7b64e..5ef293f128b 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanRegistrationCodeGenerator.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanRegistrationCodeGenerator.java @@ -58,6 +58,7 @@ class BeanRegistrationCodeGenerator implements BeanRegistrationCode { this.codeFragments = codeFragments; } + @Override public ClassName getClassName() { return this.className; diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/aot/DefaultBeanRegistrationCodeFragments.java b/spring-beans/src/main/java/org/springframework/beans/factory/aot/DefaultBeanRegistrationCodeFragments.java index a17cc82b9ba..e11473e2971 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/aot/DefaultBeanRegistrationCodeFragments.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/aot/DefaultBeanRegistrationCodeFragments.java @@ -245,7 +245,7 @@ class DefaultBeanRegistrationCodeFragments implements BeanRegistrationCodeFragme } private boolean hasInstanceSupplier() { - return this.registeredBean.getMergedBeanDefinition().getInstanceSupplier() != null; + return (this.registeredBean.getMergedBeanDefinition().getInstanceSupplier() != null); } } 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 e85717629a5..10489b71b3a 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 ffeec95ce8d..e56369ac988 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; @@ -54,6 +55,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. @@ -71,6 +86,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/main/java/org/springframework/context/aot/BeanFactoryInitializationAotContributions.java b/spring-context/src/main/java/org/springframework/context/aot/BeanFactoryInitializationAotContributions.java index c387dd2d274..fd56de4ef15 100644 --- a/spring-context/src/main/java/org/springframework/context/aot/BeanFactoryInitializationAotContributions.java +++ b/spring-context/src/main/java/org/springframework/context/aot/BeanFactoryInitializationAotContributions.java @@ -32,9 +32,8 @@ import org.springframework.beans.factory.aot.BeanFactoryInitializationCode; import org.springframework.beans.factory.support.DefaultListableBeanFactory; /** - * A collection of {@link BeanFactoryInitializationAotContribution AOT - * contributions} obtained from {@link BeanFactoryInitializationAotProcessor AOT - * processors}. + * A collection of {@link BeanFactoryInitializationAotContribution AOT contributions} + * obtained from {@link BeanFactoryInitializationAotProcessor AOT processors}. * * @author Phillip Webb * @since 6.0 @@ -48,14 +47,12 @@ class BeanFactoryInitializationAotContributions { this(beanFactory, AotServices.factoriesAndBeans(beanFactory)); } - BeanFactoryInitializationAotContributions(DefaultListableBeanFactory beanFactory, - AotServices.Loader loader) { + BeanFactoryInitializationAotContributions(DefaultListableBeanFactory beanFactory, AotServices.Loader loader) { this.contributions = getContributions(beanFactory, getProcessors(loader)); } - private static List getProcessors( - AotServices.Loader loader) { + private static List getProcessors(AotServices.Loader loader) { List processors = new ArrayList<>( loader.load(BeanFactoryInitializationAotProcessor.class).asList()); processors.add(new RuntimeHintsBeanFactoryInitializationAotProcessor()); @@ -63,8 +60,8 @@ class BeanFactoryInitializationAotContributions { } private List getContributions( - DefaultListableBeanFactory beanFactory, - List processors) { + DefaultListableBeanFactory beanFactory, List processors) { + List contributions = new ArrayList<>(); for (BeanFactoryInitializationAotProcessor processor : processors) { BeanFactoryInitializationAotContribution contribution = processAheadOfTime(processor, beanFactory); @@ -75,8 +72,8 @@ class BeanFactoryInitializationAotContributions { return Collections.unmodifiableList(contributions); } - private @Nullable BeanFactoryInitializationAotContribution processAheadOfTime(BeanFactoryInitializationAotProcessor processor, - DefaultListableBeanFactory beanFactory) { + private @Nullable BeanFactoryInitializationAotContribution processAheadOfTime( + BeanFactoryInitializationAotProcessor processor, DefaultListableBeanFactory beanFactory) { try { return processor.processAheadOfTime(beanFactory); @@ -92,6 +89,7 @@ class BeanFactoryInitializationAotContributions { void applyTo(GenerationContext generationContext, BeanFactoryInitializationCode beanFactoryInitializationCode) { + for (BeanFactoryInitializationAotContribution contribution : this.contributions) { contribution.applyTo(generationContext, beanFactoryInitializationCode); } 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 557a6d1b1a4..96afaa990ec 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 @@ -17,6 +17,7 @@ package org.springframework.context.aot; import java.io.IOException; +import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.List; import java.util.function.BiConsumer; @@ -50,7 +51,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; @@ -80,6 +83,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; @@ -111,6 +115,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"); @@ -118,6 +123,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 { @@ -128,6 +226,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"); @@ -137,11 +236,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); @@ -161,7 +305,6 @@ class ApplicationContextAotGeneratorTests { assertThat(runtimeHints.proxies().jdkProxyHints()).anySatisfy(proxyHint -> assertThat(proxyHint.getProxiedInterfaces()).isEqualTo(TypeReference.listOf( environment.getClass().getInterfaces()))); - }); } @@ -225,15 +368,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 { @@ -246,6 +390,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"); @@ -299,6 +444,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"); @@ -307,6 +453,7 @@ class ApplicationContextAotGeneratorTests { } } + @Nested class InitDestroy { @@ -317,6 +464,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"); @@ -336,6 +484,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"); @@ -345,94 +494,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 @@ -498,6 +561,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"); @@ -509,6 +573,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")); @@ -522,6 +587,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") @@ -536,6 +602,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")); @@ -550,6 +617,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")); @@ -571,9 +639,9 @@ class ApplicationContextAotGeneratorTests { private String toCglibClassSimpleName(Class configClass) { return configClass.getSimpleName() + CGLIB_CONFIGURATION_CLASS_SUFFIX; } - } + @Nested class ActiveProfile { @@ -584,6 +652,7 @@ class ApplicationContextAotGeneratorTests { if (aotProfiles.length != 0) { applicationContext.getEnvironment().setActiveProfiles(aotProfiles); } + testCompiledResult(applicationContext, (initializer, compiled) -> { GenericApplicationContext freshApplicationContext = new GenericApplicationContext(); if (runtimeProfiles.length != 0) { @@ -602,17 +671,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); @@ -629,8 +699,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); @@ -645,8 +716,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); @@ -661,8 +733,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) @@ -671,11 +744,11 @@ class ApplicationContextAotGeneratorTests { .getName()).isEqualTo("Dofi"); }); } - } + @Nested - class ExceptionHanding { + class ExceptionHandling { @Test void failureProcessingBeanFactoryAotContribution() { @@ -690,6 +763,7 @@ class ApplicationContextAotGeneratorTests { } } + private static void registerBeanPostProcessor(GenericApplicationContext applicationContext, String beanName, Class beanPostProcessorClass) { @@ -714,7 +788,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))); } @@ -769,7 +843,6 @@ class ApplicationContextAotGeneratorTests { public BeanFactoryInitializationAotContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) { return null; } - } @@ -780,9 +853,9 @@ class ApplicationContextAotGeneratorTests { public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) { return null; } - } + static class FailingBeanFactoryInitializationAotContribution implements BeanFactoryInitializationAotProcessor { @Override @@ -791,4 +864,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; + } + +} diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate5/HibernateQueryException.java b/spring-orm/src/main/java/org/springframework/orm/hibernate5/HibernateQueryException.java index eac5222f177..c8b0b63f1d9 100644 --- a/spring-orm/src/main/java/org/springframework/orm/hibernate5/HibernateQueryException.java +++ b/spring-orm/src/main/java/org/springframework/orm/hibernate5/HibernateQueryException.java @@ -41,7 +41,7 @@ public class HibernateQueryException extends InvalidDataAccessResourceUsageExcep */ public @Nullable String getQueryString() { QueryException cause = (QueryException) getCause(); - return cause == null ? null : cause.getQueryString(); + return (cause != null ? cause.getQueryString() : null); } } diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate5/LocalSessionFactoryBuilder.java b/spring-orm/src/main/java/org/springframework/orm/hibernate5/LocalSessionFactoryBuilder.java index 65a4d00280c..d13839ce1bc 100644 --- a/spring-orm/src/main/java/org/springframework/orm/hibernate5/LocalSessionFactoryBuilder.java +++ b/spring-orm/src/main/java/org/springframework/orm/hibernate5/LocalSessionFactoryBuilder.java @@ -367,11 +367,9 @@ public class LocalSessionFactoryBuilder extends Configuration { * the current class descriptor contained in the metadata reader. */ private boolean matchesEntityTypeFilter(MetadataReader reader, MetadataReaderFactory readerFactory) throws IOException { - if (this.entityTypeFilters != null) { - for (TypeFilter filter : this.entityTypeFilters) { - if (filter.match(reader, readerFactory)) { - return true; - } + for (TypeFilter filter : this.entityTypeFilters) { + if (filter.match(reader, readerFactory)) { + return true; } } return false;