diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/validation/ValidatorAdapter.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/validation/ValidatorAdapter.java index 2f2e5e4283e..e6c792838d8 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/validation/ValidatorAdapter.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/validation/ValidatorAdapter.java @@ -157,14 +157,10 @@ public class ValidatorAdapter implements SmartValidator, ApplicationContextAware @Override @SuppressWarnings("unchecked") public T unwrap(Class type) { - if (type.isAssignableFrom(this.target.getClass())) { - if (this.target instanceof SpringValidatorAdapter adapter) { - return adapter.unwrap(type); - } + if (type.isInstance(this.target)) { return (T) this.target; } - - throw new IllegalArgumentException("Cannot unwrap " + this.target + " to " + type.getName()); + return this.target.unwrap(type); } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/validation/ValidatorAdapterTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/validation/ValidatorAdapterTests.java index 2230b94d612..641c45dc220 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/validation/ValidatorAdapterTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/validation/ValidatorAdapterTests.java @@ -21,7 +21,6 @@ import java.util.HashMap; import jakarta.validation.Validator; import jakarta.validation.constraints.Min; import org.hibernate.validator.HibernateValidator; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.FilteredClassLoader; @@ -30,10 +29,13 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.ClassPathResource; +import org.springframework.validation.Errors; import org.springframework.validation.MapBindingResult; +import org.springframework.validation.SmartValidator; import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatRuntimeException; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.then; import static org.mockito.Mockito.mock; @@ -95,11 +97,26 @@ class ValidatorAdapterTests { } @Test - void unwrapValidatorInstanceOfJakartaTypeAndExceptionThrownWhenTypeNotSupported() { + void unwrapToJakartaValidatorShouldReturnJakartaValidator() { this.contextRunner.withUserConfiguration(LocalValidatorFactoryBeanConfig.class).run((context) -> { ValidatorAdapter wrapper = context.getBean(ValidatorAdapter.class); assertThat(wrapper.unwrap(Validator.class)).isInstanceOf(Validator.class); - Assertions.assertThrows(IllegalArgumentException.class, () -> wrapper.unwrap(HibernateValidator.class)); + }); + } + + @Test + void whenJakartaValidatorIsWrappedMultipleTimesUnwrapToJakartaValidatorShouldReturnJakartaValidator() { + this.contextRunner.withUserConfiguration(DoubleWrappedConfig.class).run((context) -> { + ValidatorAdapter wrapper = context.getBean(ValidatorAdapter.class); + assertThat(wrapper.unwrap(Validator.class)).isInstanceOf(Validator.class); + }); + } + + @Test + void unwrapToUnsupportedTypeShouldThrow() { + this.contextRunner.withUserConfiguration(LocalValidatorFactoryBeanConfig.class).run((context) -> { + ValidatorAdapter wrapper = context.getBean(ValidatorAdapter.class); + assertThatRuntimeException().isThrownBy(() -> wrapper.unwrap(HibernateValidator.class)); }); } @@ -118,6 +135,55 @@ class ValidatorAdapterTests { } + @Configuration(proxyBeanMethods = false) + static class DoubleWrappedConfig { + + @Bean + LocalValidatorFactoryBean validator() { + return new LocalValidatorFactoryBean(); + } + + @Bean + ValidatorAdapter wrapper(LocalValidatorFactoryBean validator) { + return new ValidatorAdapter(new Wrapper(validator), true); + } + + static class Wrapper implements SmartValidator { + + private final SmartValidator delegate; + + Wrapper(SmartValidator delegate) { + this.delegate = delegate; + } + + @Override + public boolean supports(Class clazz) { + return this.delegate.supports(clazz); + } + + @Override + public void validate(Object target, Errors errors) { + this.delegate.validate(target, errors); + } + + @Override + public void validate(Object target, Errors errors, Object... validationHints) { + this.delegate.validate(target, errors, validationHints); + } + + @Override + @SuppressWarnings("unchecked") + public T unwrap(Class type) { + if (type.isInstance(this.delegate)) { + return (T) this.delegate; + } + return this.delegate.unwrap(type); + } + + } + + } + @Configuration(proxyBeanMethods = false) static class NonManagedBeanConfig {