spring-projects / spring-boot

Spring Boot helps you to create Spring-powered, production-grade applications and services with absolute minimum fuss.
https://spring.io/projects/spring-boot
Apache License 2.0
75.23k stars 40.7k forks source link

MethodValidationPostProcessor causes further validation failure #11249

Closed bashnesnos closed 6 years ago

bashnesnos commented 6 years ago

I'm trying to add Spring Cloud Consul into an existing Spring Boot app with spring validation enabled. It's all good until I run the app, and it fails on the validation of a properties bean annotated with @Validated

As far as I got it, here's what happens:

  1. MethodValidationPostProcessor creates a proxy for the properties bean
  2. CustomBeanValidationPostProcessor kicks in and tries to validate the proxy fields and fails with the following:
    org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'heartbeatProperties' defined in issues.validation.Application: Initialization of bean failed; nested exception is org.springframework.beans.factory.BeanInitializationException: Bean state is invalid: ttlUnit - may not be null; ttlValue - must be greater than or equal to 1; intervalRatio - must be greater than or equal to 0.1
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:564) ~[spring-beans-4.3.13.RELEASE.jar:4.3.13.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:483) ~[spring-beans-4.3.13.RELEASE.jar:4.3.13.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306) ~[spring-beans-4.3.13.RELEASE.jar:4.3.13.RELEASE]
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230) ~[spring-beans-4.3.13.RELEASE.jar:4.3.13.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302) ~[spring-beans-4.3.13.RELEASE.jar:4.3.13.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197) ~[spring-beans-4.3.13.RELEASE.jar:4.3.13.RELEASE]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:761) ~[spring-beans-4.3.13.RELEASE.jar:4.3.13.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:867) ~[spring-context-4.3.13.RELEASE.jar:4.3.13.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:543) ~[spring-context-4.3.13.RELEASE.jar:4.3.13.RELEASE]
    at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.refresh(EmbeddedWebApplicationContext.java:122) ~[spring-boot-1.5.9.RELEASE.jar:1.5.9.RELEASE]
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:693) [spring-boot-1.5.9.RELEASE.jar:1.5.9.RELEASE]
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:360) [spring-boot-1.5.9.RELEASE.jar:1.5.9.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:303) [spring-boot-1.5.9.RELEASE.jar:1.5.9.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1118) [spring-boot-1.5.9.RELEASE.jar:1.5.9.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1107) [spring-boot-1.5.9.RELEASE.jar:1.5.9.RELEASE]
    at issues.validation.Application.main(Application.java:25) [classes/:na]
    Caused by: org.springframework.beans.factory.BeanInitializationException: Bean state is invalid: ttlUnit - may not be null; ttlValue - must be greater than or equal to 1; intervalRatio - must be greater than or equal to 0.1
    at org.springframework.validation.beanvalidation.BeanValidationPostProcessor.doValidate(BeanValidationPostProcessor.java:116) ~[spring-context-4.3.13.RELEASE.jar:4.3.13.RELEASE]
    at org.springframework.validation.beanvalidation.BeanValidationPostProcessor.postProcessAfterInitialization(BeanValidationPostProcessor.java:94) ~[spring-context-4.3.13.RELEASE.jar:4.3.13.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsAfterInitialization(AbstractAutowireCapableBeanFactory.java:423) ~[spring-beans-4.3.13.RELEASE.jar:4.3.13.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1633) ~[spring-beans-4.3.13.RELEASE.jar:4.3.13.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:555) ~[spring-beans-4.3.13.RELEASE.jar:4.3.13.RELEASE]
    ... 15 common frames omitted

Here is a minimum project reproducing the issue: https://github.com/bashnesnos/cas-spring-cloud-consul-issue

Questions:

  1. Is there any work-around to avoid MethodValidationPostProcessor for a particular bean?
  2. Could MethodValidationPostProcessor be ordered after any properties validator? or
  3. Could MethodValidationPostProcessor check if a bean is even eligible for any method validation? In current example, heartbeatProperties doesn't have any method-scoped validation at all.
wilkinsona commented 6 years ago

MethodValidationPostProcessor is ordered with lowest precedence. Your custom post-processor is unordered so it defaults to lowest precedence. If you want to ensure that it is called before the method validation you can do so by giving it an order with higher precedence, for example:

diff --git a/src/main/java/issues/validation/CustomBeanValidationPostProcessor.java b/src/main/java/issues/validation/CustomBeanValidationPostProcessor.java
index 81daa6a..79dc576 100644
--- a/src/main/java/issues/validation/CustomBeanValidationPostProcessor.java
+++ b/src/main/java/issues/validation/CustomBeanValidationPostProcessor.java
@@ -1,5 +1,6 @@
 package issues.validation;

+import org.springframework.core.Ordered;
 import org.springframework.validation.beanvalidation.BeanValidationPostProcessor;

 import java.lang.annotation.ElementType;
@@ -15,7 +16,7 @@ import javax.validation.Validator;
  *
  * Taken from CAS as is https://github.com/apereo/cas/blob/master/core/cas-server-core-util-api/src/main/java/org/apereo/cas/util/spring/CustomBeanValidationPostProcessor.java
  */
-public class CustomBeanValidationPostProcessor extends BeanValidationPostProcessor {
+public class CustomBeanValidationPostProcessor extends BeanValidationPostProcessor implements Ordered {

     /**
      * Instantiates a new custom bean validation post processor.
@@ -44,4 +45,9 @@ public class CustomBeanValidationPostProcessor extends BeanValidationPostProcess
         setValidator(validator);

     }
+
+       @Override
+       public int getOrder() {
+               return 0;
+       }
 }