diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/aot/AotBeanProcessingException.java b/spring-beans/src/main/java/org/springframework/beans/factory/aot/AotBeanProcessingException.java new file mode 100644 index 0000000000..03173bf9a9 --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/aot/AotBeanProcessingException.java @@ -0,0 +1,74 @@ +/* + * Copyright 2002-2024 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.beans.factory.aot; + +import org.springframework.beans.factory.support.RegisteredBean; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.lang.Nullable; + +/** + * Thrown when AOT fails to process a bean. + * + * @author Stephane Nicoll + * @since 6.2 + */ +@SuppressWarnings("serial") +public class AotBeanProcessingException extends AotProcessingException { + + private final RootBeanDefinition beanDefinition; + + /** + * Create an instance with the {@link RegisteredBean} that fails to be + * processed, a detail message, and an optional root cause. + * @param registeredBean the registered bean that fails to be processed + * @param msg the detail message + * @param cause the root cause, if any + */ + public AotBeanProcessingException(RegisteredBean registeredBean, String msg, @Nullable Throwable cause) { + super(createErrorMessage(registeredBean, msg), cause); + this.beanDefinition = registeredBean.getMergedBeanDefinition(); + } + + /** + * Shortcut to create an instance with the {@link RegisteredBean} that fails + * to be processed with only a detail message. + * @param registeredBean the registered bean that fails to be processed + * @param msg the detail message + */ + public AotBeanProcessingException(RegisteredBean registeredBean, String msg) { + this(registeredBean, msg, null); + } + + private static String createErrorMessage(RegisteredBean registeredBean, String msg) { + StringBuilder sb = new StringBuilder("Error processing bean with name '"); + sb.append(registeredBean.getBeanName()).append("'"); + String resourceDescription = registeredBean.getMergedBeanDefinition().getResourceDescription(); + if (resourceDescription != null) { + sb.append(" defined in ").append(resourceDescription); + } + sb.append(": ").append(msg); + return sb.toString(); + } + + /** + * Return the bean definition of the bean that failed to be processed. + */ + public RootBeanDefinition getBeanDefinition() { + return this.beanDefinition; + } + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/aot/AotException.java b/spring-beans/src/main/java/org/springframework/beans/factory/aot/AotException.java new file mode 100644 index 0000000000..0b1f810726 --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/aot/AotException.java @@ -0,0 +1,39 @@ +/* + * Copyright 2002-2024 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.beans.factory.aot; + +import org.springframework.lang.Nullable; + +/** + * Abstract superclass for all exceptions thrown by ahead-of-time processing. + * + * @author Stephane Nicoll + * @since 6.2 + */ +@SuppressWarnings("serial") +public abstract class AotException extends RuntimeException { + + /** + * Create an instance with the specified message and root cause. + * @param msg the detail message + * @param cause the root cause + */ + protected AotException(@Nullable String msg, @Nullable Throwable cause) { + super(msg, cause); + } + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/aot/AotProcessingException.java b/spring-beans/src/main/java/org/springframework/beans/factory/aot/AotProcessingException.java new file mode 100644 index 0000000000..87613d63da --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/aot/AotProcessingException.java @@ -0,0 +1,39 @@ +/* + * Copyright 2002-2024 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.beans.factory.aot; + +import org.springframework.lang.Nullable; + +/** + * Throw when an AOT processor failed. + * + * @author Stephane Nicoll + * @since 6.2 + */ +@SuppressWarnings("serial") +public class AotProcessingException extends AotException { + + /** + * Create a new instance with the detail message and a root cause, if any. + * @param msg the detail message + * @param cause the root cause, if any + */ + public AotProcessingException(String msg, @Nullable Throwable cause) { + super(msg, cause); + } + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanRegistrationsAotContribution.java b/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanRegistrationsAotContribution.java index b588dd8171..8b1066060d 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanRegistrationsAotContribution.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanRegistrationsAotContribution.java @@ -86,12 +86,21 @@ class BeanRegistrationsAotContribution method.addParameter(DefaultListableBeanFactory.class, BEAN_FACTORY_PARAMETER_NAME); CodeBlock.Builder code = CodeBlock.builder(); this.registrations.forEach(registration -> { - MethodReference beanDefinitionMethod = registration.methodGenerator - .generateBeanDefinitionMethod(generationContext, beanRegistrationsCode); - CodeBlock methodInvocation = beanDefinitionMethod.toInvokeCodeBlock( - ArgumentCodeGenerator.none(), beanRegistrationsCode.getClassName()); - code.addStatement("$L.registerBeanDefinition($S, $L)", - BEAN_FACTORY_PARAMETER_NAME, registration.beanName(), methodInvocation); + try { + MethodReference beanDefinitionMethod = registration.methodGenerator + .generateBeanDefinitionMethod(generationContext, beanRegistrationsCode); + CodeBlock methodInvocation = beanDefinitionMethod.toInvokeCodeBlock( + ArgumentCodeGenerator.none(), beanRegistrationsCode.getClassName()); + code.addStatement("$L.registerBeanDefinition($S, $L)", + BEAN_FACTORY_PARAMETER_NAME, registration.beanName(), methodInvocation); + } + catch (AotException ex) { + throw ex; + } + catch (Exception ex) { + throw new AotBeanProcessingException(registration.registeredBean, + "failed to generate code for bean definition", ex); + } }); method.addCode(code.build()); } 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 50564ee23a..01f006c0f8 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 @@ -79,9 +79,7 @@ class DefaultBeanRegistrationCodeFragments implements BeanRegistrationCodeFragme @Override public ClassName getTarget(RegisteredBean registeredBean) { if (hasInstanceSupplier()) { - String resourceDescription = registeredBean.getMergedBeanDefinition().getResourceDescription(); - throw new IllegalStateException("Error processing bean with name '" + registeredBean.getBeanName() + "'" + - (resourceDescription != null ? " defined in " + resourceDescription : "") + ": instance supplier is not supported"); + throw new AotBeanProcessingException(registeredBean, "instance supplier is not supported"); } Class target = extractDeclaringClass(registeredBean, this.instantiationDescriptor.get()); while (target.getName().startsWith("java.") && registeredBean.isInnerBean()) { @@ -236,8 +234,7 @@ class DefaultBeanRegistrationCodeFragments implements BeanRegistrationCodeFragme public CodeBlock generateInstanceSupplierCode(GenerationContext generationContext, BeanRegistrationCode beanRegistrationCode, boolean allowDirectSupplierShortcut) { if (hasInstanceSupplier()) { - throw new IllegalStateException("Default code generation is not supported for bean definitions declaring " - + "an instance supplier callback: " + this.registeredBean.getMergedBeanDefinition()); + throw new AotBeanProcessingException(this.registeredBean, "instance supplier is not supported"); } return new InstanceSupplierCodeGenerator(generationContext, beanRegistrationCode.getClassName(), beanRegistrationCode.getMethods(), allowDirectSupplierShortcut).generateCode( 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 c65ab4ed1f..d6ee4124db 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 @@ -145,8 +145,7 @@ public class InstanceSupplierCodeGenerator { if (constructorOrFactoryMethod instanceof Method method && !KotlinDetector.isSuspendingFunction(method)) { return generateCodeForFactoryMethod(registeredBean, method, instantiationDescriptor.targetClass()); } - throw new IllegalStateException( - "No suitable executor found for " + registeredBean.getBeanName()); + throw new AotBeanProcessingException(registeredBean, "no suitable constructor or factory method found"); } private void registerRuntimeHintsIfNecessary(RegisteredBean registeredBean, Executable constructorOrFactoryMethod) { diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanDefinitionMethodGeneratorTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanDefinitionMethodGeneratorTests.java index e7c986925a..0aa194ff9a 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanDefinitionMethodGeneratorTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanDefinitionMethodGeneratorTests.java @@ -69,7 +69,7 @@ import org.springframework.javapoet.ParameterizedTypeName; import org.springframework.util.ReflectionUtils; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalStateException; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatNoException; /** @@ -691,9 +691,10 @@ class BeanDefinitionMethodGeneratorTests { BeanDefinitionMethodGenerator generator = new BeanDefinitionMethodGenerator( this.methodGeneratorFactory, registeredBean, null, List.of()); - assertThatIllegalStateException().isThrownBy(() -> generator.generateBeanDefinitionMethod( - this.generationContext, this.beanRegistrationsCode)).withMessage( - "Error processing bean with name 'testBean': instance supplier is not supported"); + assertThatExceptionOfType(AotBeanProcessingException.class) + .isThrownBy(() -> generator.generateBeanDefinitionMethod( + this.generationContext, this.beanRegistrationsCode)) + .withMessage("Error processing bean with name 'testBean': instance supplier is not supported"); } @Test @@ -709,9 +710,10 @@ class BeanDefinitionMethodGeneratorTests { BeanDefinitionMethodGenerator generator = new BeanDefinitionMethodGenerator( this.methodGeneratorFactory, registeredBean, null, List.of(aotContribution)); - assertThatIllegalStateException().isThrownBy(() -> generator.generateBeanDefinitionMethod( - this.generationContext, this.beanRegistrationsCode)).withMessageStartingWith( - "Default code generation is not supported for bean definitions declaring an instance supplier callback"); + assertThatExceptionOfType(AotBeanProcessingException.class) + .isThrownBy(() -> generator.generateBeanDefinitionMethod( + this.generationContext, this.beanRegistrationsCode)) + .withMessage("Error processing bean with name 'testBean': instance supplier is not supported"); } @Test @@ -728,9 +730,10 @@ class BeanDefinitionMethodGeneratorTests { BeanDefinitionMethodGenerator generator = new BeanDefinitionMethodGenerator( this.methodGeneratorFactory, registeredBean, null, List.of(aotContribution)); - assertThatIllegalStateException().isThrownBy(() -> generator.generateBeanDefinitionMethod( - this.generationContext, this.beanRegistrationsCode)).withMessage( - "Error processing bean with name 'testBean': instance supplier is not supported"); + assertThatExceptionOfType(AotBeanProcessingException.class) + .isThrownBy(() -> generator.generateBeanDefinitionMethod( + this.generationContext, this.beanRegistrationsCode)) + .withMessage("Error processing bean with name 'testBean': instance supplier is not supported"); } @Test diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanRegistrationsAotContributionTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanRegistrationsAotContributionTests.java index 6b7ff497e6..68a3d68c68 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanRegistrationsAotContributionTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanRegistrationsAotContributionTests.java @@ -29,6 +29,7 @@ import org.springframework.aot.generate.ClassNameGenerator; import org.springframework.aot.generate.GenerationContext; import org.springframework.aot.generate.MethodReference; import org.springframework.aot.generate.MethodReference.ArgumentCodeGenerator; +import org.springframework.aot.generate.ValueCodeGenerationException; import org.springframework.aot.hint.MemberCategory; import org.springframework.aot.test.generate.TestGenerationContext; import org.springframework.beans.factory.aot.BeanRegistrationsAotContribution.Registration; @@ -38,6 +39,7 @@ import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.testfixture.beans.AgeHolder; import org.springframework.beans.testfixture.beans.Employee; import org.springframework.beans.testfixture.beans.ITestBean; +import org.springframework.beans.testfixture.beans.NestedTestBean; import org.springframework.beans.testfixture.beans.TestBean; import org.springframework.beans.testfixture.beans.factory.aot.MockBeanFactoryInitializationCode; import org.springframework.core.test.io.support.MockSpringFactoriesLoader; @@ -50,6 +52,7 @@ import org.springframework.javapoet.MethodSpec; import org.springframework.javapoet.ParameterizedTypeName; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.springframework.aot.hint.predicate.RuntimeHintsPredicates.reflection; /** @@ -156,6 +159,57 @@ class BeanRegistrationsAotContributionTests { .accepts(this.generationContext.getRuntimeHints()); } + @Test + void applyToFailingDoesNotWrapAotException() { + RootBeanDefinition beanDefinition = new RootBeanDefinition(TestBean.class); + beanDefinition.setInstanceSupplier(TestBean::new); + RegisteredBean registeredBean = registerBean(beanDefinition); + + BeanDefinitionMethodGenerator generator = new BeanDefinitionMethodGenerator(this.methodGeneratorFactory, + registeredBean, null, List.of()); + BeanRegistrationsAotContribution contribution = createContribution(registeredBean, generator, "testAlias"); + assertThatExceptionOfType(AotProcessingException.class) + .isThrownBy(() -> contribution.applyTo(this.generationContext, this.beanFactoryInitializationCode)) + .withMessage("Error processing bean with name 'testBean': instance supplier is not supported") + .withNoCause(); + } + + @Test + void applyToFailingWrapsValueCodeGeneration() { + RootBeanDefinition beanDefinition = new RootBeanDefinition(TestBean.class); + beanDefinition.getPropertyValues().addPropertyValue("doctor", new NestedTestBean()); + RegisteredBean registeredBean = registerBean(beanDefinition); + + BeanDefinitionMethodGenerator generator = new BeanDefinitionMethodGenerator(this.methodGeneratorFactory, + registeredBean, null, List.of()); + BeanRegistrationsAotContribution contribution = createContribution(registeredBean, generator, "testAlias"); + assertThatExceptionOfType(AotProcessingException.class) + .isThrownBy(() -> contribution.applyTo(this.generationContext, this.beanFactoryInitializationCode)) + .withMessage("Error processing bean with name 'testBean': failed to generate code for bean definition") + .havingCause().isInstanceOf(ValueCodeGenerationException.class) + .withMessageContaining("Failed to generate code for") + .withMessageContaining(NestedTestBean.class.getName()); + } + + @Test + void applyToFailingProvidesDedicatedException() { + RegisteredBean registeredBean = registerBean(new RootBeanDefinition(TestBean.class)); + + BeanDefinitionMethodGenerator generator = new BeanDefinitionMethodGenerator(this.methodGeneratorFactory, + registeredBean, null, List.of()) { + @Override + MethodReference generateBeanDefinitionMethod(GenerationContext generationContext, + BeanRegistrationsCode beanRegistrationsCode) { + throw new IllegalStateException("Test exception"); + } + }; + BeanRegistrationsAotContribution contribution = createContribution(registeredBean, generator, "testAlias"); + assertThatExceptionOfType(AotProcessingException.class) + .isThrownBy(() -> contribution.applyTo(this.generationContext, this.beanFactoryInitializationCode)) + .withMessage("Error processing bean with name 'testBean': failed to generate code for bean definition") + .havingCause().isInstanceOf(IllegalStateException.class).withMessage("Test exception"); + } + private RegisteredBean registerBean(RootBeanDefinition rootBeanDefinition) { String beanName = "testBean"; this.beanFactory.registerBeanDefinition(beanName, rootBeanDefinition); diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/aot/DefaultBeanRegistrationCodeFragmentsTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/aot/DefaultBeanRegistrationCodeFragmentsTests.java index 1333b2f160..7379fc2226 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/aot/DefaultBeanRegistrationCodeFragmentsTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/aot/DefaultBeanRegistrationCodeFragmentsTests.java @@ -48,7 +48,7 @@ import org.springframework.lang.Nullable; import org.springframework.util.ReflectionUtils; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalStateException; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; @@ -72,7 +72,8 @@ class DefaultBeanRegistrationCodeFragmentsTests { beanDefinition.setInstanceSupplier(SimpleBean::new); RegisteredBean registeredBean = registerTestBean(beanDefinition); BeanRegistrationCodeFragments codeFragments = createInstance(registeredBean); - assertThatIllegalStateException().isThrownBy(() -> codeFragments.getTarget(registeredBean)) + assertThatExceptionOfType(AotBeanProcessingException.class) + .isThrownBy(() -> codeFragments.getTarget(registeredBean)) .withMessageContaining("Error processing bean with name 'testBean': instance supplier is not supported"); } @@ -83,7 +84,8 @@ class DefaultBeanRegistrationCodeFragmentsTests { beanDefinition.setResourceDescription("my test resource"); RegisteredBean registeredBean = registerTestBean(beanDefinition); BeanRegistrationCodeFragments codeFragments = createInstance(registeredBean); - assertThatIllegalStateException().isThrownBy(() -> codeFragments.getTarget(registeredBean)) + assertThatExceptionOfType(AotBeanProcessingException.class) + .isThrownBy(() -> codeFragments.getTarget(registeredBean)) .withMessageContaining("Error processing bean with name 'testBean' defined in my test resource: " + "instance supplier is not supported"); } diff --git a/spring-beans/src/test/kotlin/org/springframework/beans/factory/aot/InstanceSupplierCodeGeneratorKotlinTests.kt b/spring-beans/src/test/kotlin/org/springframework/beans/factory/aot/InstanceSupplierCodeGeneratorKotlinTests.kt index 63501a601c..451c77b37a 100644 --- a/spring-beans/src/test/kotlin/org/springframework/beans/factory/aot/InstanceSupplierCodeGeneratorKotlinTests.kt +++ b/spring-beans/src/test/kotlin/org/springframework/beans/factory/aot/InstanceSupplierCodeGeneratorKotlinTests.kt @@ -102,7 +102,7 @@ class InstanceSupplierCodeGeneratorKotlinTests { this.beanFactory.registerBeanDefinition("config", BeanDefinitionBuilder .genericBeanDefinition(KotlinConfiguration::class.java).beanDefinition ) - Assertions.assertThatIllegalStateException().isThrownBy { + Assertions.assertThatExceptionOfType(AotBeanProcessingException::class.java).isThrownBy { compile(beanFactory, beanDefinition) { _, _ -> } } } 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 878ed204c0..d2d5ec73be 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 @@ -21,11 +21,14 @@ import java.util.Collections; import java.util.List; import org.springframework.aot.generate.GenerationContext; +import org.springframework.beans.factory.aot.AotException; +import org.springframework.beans.factory.aot.AotProcessingException; import org.springframework.beans.factory.aot.AotServices; import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution; import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor; import org.springframework.beans.factory.aot.BeanFactoryInitializationCode; import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.lang.Nullable; /** * A collection of {@link BeanFactoryInitializationAotContribution AOT @@ -63,8 +66,7 @@ class BeanFactoryInitializationAotContributions { List processors) { List contributions = new ArrayList<>(); for (BeanFactoryInitializationAotProcessor processor : processors) { - BeanFactoryInitializationAotContribution contribution = processor - .processAheadOfTime(beanFactory); + BeanFactoryInitializationAotContribution contribution = processAheadOfTime(processor, beanFactory); if (contribution != null) { contributions.add(contribution); } @@ -72,6 +74,22 @@ class BeanFactoryInitializationAotContributions { return Collections.unmodifiableList(contributions); } + @Nullable + private BeanFactoryInitializationAotContribution processAheadOfTime(BeanFactoryInitializationAotProcessor processor, + DefaultListableBeanFactory beanFactory) { + + try { + return processor.processAheadOfTime(beanFactory); + } + catch (AotException ex) { + throw ex; + } + catch (Exception ex) { + throw new AotProcessingException("Error executing '" + + processor.getClass().getName() + "': " + ex.getMessage(), ex); + } + } + void applyTo(GenerationContext generationContext, BeanFactoryInitializationCode beanFactoryInitializationCode) { for (BeanFactoryInitializationAotContribution contribution : this.contributions) { 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 d3d67fd9b6..104d6b0761 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 @@ -40,6 +40,7 @@ import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; import org.springframework.aot.test.generate.TestGenerationContext; import org.springframework.beans.BeansException; import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor; +import org.springframework.beans.factory.aot.AotProcessingException; import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution; import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor; import org.springframework.beans.factory.aot.BeanRegistrationAotContribution; @@ -94,6 +95,7 @@ import org.springframework.mock.env.MockEnvironment; import org.springframework.util.ReflectionUtils; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; /** * Tests for {@link ApplicationContextAotGenerator}. @@ -587,6 +589,22 @@ class ApplicationContextAotGeneratorTests { } + @Nested + class ExceptionHanding { + + @Test + void failureProcessingBeanFactoryAotContribution() { + GenericApplicationContext applicationContext = new GenericApplicationContext(); + applicationContext.registerBeanDefinition("test", + new RootBeanDefinition(FailingBeanFactoryInitializationAotContribution.class)); + assertThatExceptionOfType(AotProcessingException.class) + .isThrownBy(() -> processAheadOfTime(applicationContext)) + .withMessageStartingWith("Error executing '") + .withMessageContaining(FailingBeanFactoryInitializationAotContribution.class.getName()) + .withMessageContaining("Test exception"); + } + } + private static void registerBeanPostProcessor(GenericApplicationContext applicationContext, String beanName, Class beanPostProcessorClass) { @@ -676,4 +694,12 @@ class ApplicationContextAotGeneratorTests { } + static class FailingBeanFactoryInitializationAotContribution implements BeanFactoryInitializationAotProcessor { + + @Override + public BeanFactoryInitializationAotContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) { + throw new IllegalStateException("Test exception"); + } + } + } diff --git a/spring-context/src/test/java/org/springframework/context/aot/RuntimeHintsBeanFactoryInitializationAotProcessorTests.java b/spring-context/src/test/java/org/springframework/context/aot/RuntimeHintsBeanFactoryInitializationAotProcessorTests.java index c3542b630f..bb4aa59ff8 100644 --- a/spring-context/src/test/java/org/springframework/context/aot/RuntimeHintsBeanFactoryInitializationAotProcessorTests.java +++ b/spring-context/src/test/java/org/springframework/context/aot/RuntimeHintsBeanFactoryInitializationAotProcessorTests.java @@ -31,6 +31,7 @@ import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.RuntimeHintsRegistrar; import org.springframework.aot.test.generate.TestGenerationContext; import org.springframework.beans.BeanInstantiationException; +import org.springframework.beans.factory.aot.AotProcessingException; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.context.annotation.AnnotationConfigUtils; import org.springframework.context.annotation.Bean; @@ -40,7 +41,7 @@ import org.springframework.context.support.GenericApplicationContext; import org.springframework.lang.Nullable; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; /** * Tests for {@link RuntimeHintsBeanFactoryInitializationAotProcessor}. @@ -119,9 +120,9 @@ class RuntimeHintsBeanFactoryInitializationAotProcessorTests { void shouldRejectRuntimeHintsRegistrarWithoutDefaultConstructor() { GenericApplicationContext applicationContext = createApplicationContext( ConfigurationWithIllegalRegistrar.class); - assertThatThrownBy(() -> this.generator.processAheadOfTime( - applicationContext, this.generationContext)) - .isInstanceOf(BeanInstantiationException.class); + assertThatExceptionOfType(AotProcessingException.class) + .isThrownBy(() -> this.generator.processAheadOfTime(applicationContext, this.generationContext)) + .havingCause().isInstanceOf(BeanInstantiationException.class); } private void assertThatSampleRegistrarContributed() {