From d6f4f4c7b4d06dc3653d74c492645242f8eb76dc Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Tue, 15 Dec 2009 18:04:44 +0000 Subject: [PATCH] BeanValidationPostProcessor runs in before-initialization phase by default (SPR-6565) --- .../BeanValidationPostProcessor.java | 49 +++++- .../BeanValidationPostProcessorTests.java | 156 ++++++++++++++++++ 2 files changed, 202 insertions(+), 3 deletions(-) create mode 100644 org.springframework.context/src/test/java/org/springframework/validation/beanvalidation/BeanValidationPostProcessorTests.java diff --git a/org.springframework.context/src/main/java/org/springframework/validation/beanvalidation/BeanValidationPostProcessor.java b/org.springframework.context/src/main/java/org/springframework/validation/beanvalidation/BeanValidationPostProcessor.java index be9d84edfa3..3edc4581a49 100644 --- a/org.springframework.context/src/main/java/org/springframework/validation/beanvalidation/BeanValidationPostProcessor.java +++ b/org.springframework.context/src/main/java/org/springframework/validation/beanvalidation/BeanValidationPostProcessor.java @@ -29,33 +29,77 @@ import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.config.BeanPostProcessor; /** + * Simple {@link BeanPostProcessor} that checks JSR-303 constraint annotations + * in Spring-managed beans, throwing an initialization exception in case of + * constraint violations right before calling the bean's init method (if any). + * * @author Juergen Hoeller * @since 3.0 */ public class BeanValidationPostProcessor implements BeanPostProcessor, InitializingBean { - private javax.validation.Validator validator; + private Validator validator; + + private boolean afterInitialization = false; + /** + * Set the JSR-303 Validator to delegate to for validating beans. + *

Default is the default ValidatorFactory's default Validator. + */ public void setValidator(Validator validator) { this.validator = validator; } + /** + * Set the JSR-303 ValidatorFactory to delegate to for validating beans, + * using its default Validator. + *

Default is the default ValidatorFactory's default Validator. + * @see javax.validation.ValidatorFactory#getValidator() + */ public void setValidatorFactory(ValidatorFactory validatorFactory) { this.validator = validatorFactory.getValidator(); } + /** + * Choose whether to perform validation after bean initialization + * (i.e. after init methods) instead of before (which is the default). + *

Default is "false" (before initialization). Switch this to "true" + * (after initialization) if you would like to give init methods a chance + * to populate constrained fields before they get validated. + */ + public void setAfterInitialization(boolean afterInitialization) { + this.afterInitialization = afterInitialization; + } + public void afterPropertiesSet() { if (this.validator == null) { - Validation.buildDefaultValidatorFactory().getValidator(); + this.validator = Validation.buildDefaultValidatorFactory().getValidator(); } } + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + if (!this.afterInitialization) { + doValidate(bean); + } return bean; } public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + if (this.afterInitialization) { + doValidate(bean); + } + return bean; + } + + + /** + * Perform validation of the given bean. + * @param bean the bean instance to validate + * @see javax.validation.Validator#validate + */ + protected void doValidate(Object bean) { Set> result = this.validator.validate(bean); if (!result.isEmpty()) { StringBuilder sb = new StringBuilder("Bean state is invalid: "); @@ -68,7 +112,6 @@ public class BeanValidationPostProcessor implements BeanPostProcessor, Initializ } throw new BeanInitializationException(sb.toString()); } - return bean; } } diff --git a/org.springframework.context/src/test/java/org/springframework/validation/beanvalidation/BeanValidationPostProcessorTests.java b/org.springframework.context/src/test/java/org/springframework/validation/beanvalidation/BeanValidationPostProcessorTests.java new file mode 100644 index 00000000000..42ca119e736 --- /dev/null +++ b/org.springframework.context/src/test/java/org/springframework/validation/beanvalidation/BeanValidationPostProcessorTests.java @@ -0,0 +1,156 @@ +/* + * Copyright 2002-2009 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 + * + * http://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.validation.beanvalidation; + +import javax.annotation.PostConstruct; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; + +import static org.junit.Assert.*; +import org.junit.Test; + +import org.springframework.beans.TestBean; +import org.springframework.beans.factory.BeanCreationException; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.context.annotation.CommonAnnotationBeanPostProcessor; +import org.springframework.context.support.GenericApplicationContext; + +/** + * @author Juergen Hoeller + * @since 3.0 + */ +public class BeanValidationPostProcessorTests { + + @Test + public void testNotNullConstraint() { + GenericApplicationContext ac = new GenericApplicationContext(); + ac.registerBeanDefinition("bvpp", new RootBeanDefinition(BeanValidationPostProcessor.class)); + ac.registerBeanDefinition("capp", new RootBeanDefinition(CommonAnnotationBeanPostProcessor.class)); + ac.registerBeanDefinition("bean", new RootBeanDefinition(NotNullConstrainedBean.class)); + try { + ac.refresh(); + fail("Should have thrown BeanCreationException"); + } + catch (BeanCreationException ex) { + assertTrue(ex.getRootCause().getMessage().contains("testBean")); + assertTrue(ex.getRootCause().getMessage().contains("invalid")); + } + } + + @Test + public void testNotNullConstraintSatisfied() { + GenericApplicationContext ac = new GenericApplicationContext(); + ac.registerBeanDefinition("bvpp", new RootBeanDefinition(BeanValidationPostProcessor.class)); + ac.registerBeanDefinition("capp", new RootBeanDefinition(CommonAnnotationBeanPostProcessor.class)); + RootBeanDefinition bd = new RootBeanDefinition(NotNullConstrainedBean.class); + bd.getPropertyValues().add("testBean", new TestBean()); + ac.registerBeanDefinition("bean", bd); + ac.refresh(); + } + + @Test + public void testNotNullConstraintAfterInitialization() { + GenericApplicationContext ac = new GenericApplicationContext(); + RootBeanDefinition bvpp = new RootBeanDefinition(BeanValidationPostProcessor.class); + bvpp.getPropertyValues().add("afterInitialization", true); + ac.registerBeanDefinition("bvpp", bvpp); + ac.registerBeanDefinition("capp", new RootBeanDefinition(CommonAnnotationBeanPostProcessor.class)); + ac.registerBeanDefinition("bean", new RootBeanDefinition(AfterInitConstraintBean.class)); + ac.refresh(); + } + + @Test + public void testSizeConstraint() { + GenericApplicationContext ac = new GenericApplicationContext(); + ac.registerBeanDefinition("bvpp", new RootBeanDefinition(BeanValidationPostProcessor.class)); + RootBeanDefinition bd = new RootBeanDefinition(NotNullConstrainedBean.class); + bd.getPropertyValues().add("testBean", new TestBean()); + bd.getPropertyValues().add("stringValue", "s"); + ac.registerBeanDefinition("bean", bd); + try { + ac.refresh(); + fail("Should have thrown BeanCreationException"); + } + catch (BeanCreationException ex) { + assertTrue(ex.getRootCause().getMessage().contains("stringValue")); + assertTrue(ex.getRootCause().getMessage().contains("invalid")); + } + } + + @Test + public void testSizeConstraintSatisfied() { + GenericApplicationContext ac = new GenericApplicationContext(); + ac.registerBeanDefinition("bvpp", new RootBeanDefinition(BeanValidationPostProcessor.class)); + RootBeanDefinition bd = new RootBeanDefinition(NotNullConstrainedBean.class); + bd.getPropertyValues().add("testBean", new TestBean()); + bd.getPropertyValues().add("stringValue", "ss"); + ac.registerBeanDefinition("bean", bd); + ac.refresh(); + } + + + public static class NotNullConstrainedBean { + + @NotNull + private TestBean testBean; + + @Size(min = 2) + private String stringValue; + + public TestBean getTestBean() { + return testBean; + } + + public void setTestBean(TestBean testBean) { + this.testBean = testBean; + } + + public String getStringValue() { + return stringValue; + } + + public void setStringValue(String stringValue) { + this.stringValue = stringValue; + } + + @PostConstruct + public void init() { + assertNotNull("Shouldn't be here after constraint checking", this.testBean); + } + } + + + public static class AfterInitConstraintBean { + + @NotNull + private TestBean testBean; + + public TestBean getTestBean() { + return testBean; + } + + public void setTestBean(TestBean testBean) { + this.testBean = testBean; + } + + @PostConstruct + public void init() { + this.testBean = new TestBean(); + } + } + +}