From 10f0017d691d76d865e926e6c0397351276e9557 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Fri, 29 Oct 2021 10:08:57 +0100 Subject: [PATCH] Fix method validation in child contexts Bean post-processors only apply to the context in which they're registered. ValidationAutoConfiguration will only auto-configure the MethodValidationPostProcessor if the post-processor is missing from the current context and any of its ancestors. If an ancestor context contains the post-processor it will not be auto-configured and the descendant context will not have method validation configured. This commit updates the auto-configuration to limit the search for an existing MethodValidationPostProcessor bean to the current context. Fixes gh-27890 --- .../ValidationAutoConfiguration.java | 5 +++-- .../ValidationAutoConfigurationTests.java | 20 +++++++++++++++---- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/validation/ValidationAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/validation/ValidationAutoConfiguration.java index 9c01dfc25254..04775648a618 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/validation/ValidationAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/validation/ValidationAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2021 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. @@ -25,6 +25,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnResource; +import org.springframework.boot.autoconfigure.condition.SearchStrategy; import org.springframework.boot.validation.MessageInterpolatorFactory; import org.springframework.boot.validation.beanvalidation.FilteredMethodValidationPostProcessor; import org.springframework.boot.validation.beanvalidation.MethodValidationExcludeFilter; @@ -62,7 +63,7 @@ public static LocalValidatorFactoryBean defaultValidator() { } @Bean - @ConditionalOnMissingBean + @ConditionalOnMissingBean(search = SearchStrategy.CURRENT) public static MethodValidationPostProcessor methodValidationPostProcessor(Environment environment, @Lazy Validator validator, ObjectProvider excludeFilters) { FilteredMethodValidationPostProcessor processor = new FilteredMethodValidationPostProcessor( diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/validation/ValidationAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/validation/ValidationAutoConfigurationTests.java index d0533993045f..30ce1a4bddb2 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/validation/ValidationAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/validation/ValidationAutoConfigurationTests.java @@ -34,7 +34,6 @@ import org.springframework.boot.test.context.assertj.AssertableApplicationContext; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.validation.beanvalidation.MethodValidationExcludeFilter; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; @@ -58,9 +57,8 @@ */ class ValidationAutoConfigurationTests { - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner( - AnnotationConfigApplicationContext::new) - .withConfiguration(AutoConfigurations.of(ValidationAutoConfiguration.class)); + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(ValidationAutoConfiguration.class)); @Test void validationAutoConfigurationShouldConfigureDefaultValidator() { @@ -224,6 +222,20 @@ void methodValidationPostProcessorValidatorDependencyDoesNotTriggerEarlyInitiali .contains("someService")); } + @Test + void validationIsEnabledInChildContext() { + this.contextRunner.run((parent) -> new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(ValidationAutoConfiguration.class)) + .withUserConfiguration(SampleService.class).withParent(parent).run((context) -> { + assertThat(context.getBeansOfType(Validator.class)).hasSize(0); + assertThat(parent.getBeansOfType(Validator.class)).hasSize(1); + SampleService service = context.getBean(SampleService.class); + service.doSomething("Valid"); + assertThatExceptionOfType(ConstraintViolationException.class) + .isThrownBy(() -> service.doSomething("KO")); + })); + } + private boolean isPrimaryBean(AssertableApplicationContext context, String beanName) { return ((BeanDefinitionRegistry) context.getSourceApplicationContext()).getBeanDefinition(beanName).isPrimary(); }