diff --git a/org.springframework.context/src/main/java/org/springframework/validation/annotation/Valid.java b/org.springframework.context/src/main/java/org/springframework/validation/annotation/Validated.java similarity index 72% rename from org.springframework.context/src/main/java/org/springframework/validation/annotation/Valid.java rename to org.springframework.context/src/main/java/org/springframework/validation/annotation/Validated.java index b7807ab912b..70da20211f5 100644 --- a/org.springframework.context/src/main/java/org/springframework/validation/annotation/Valid.java +++ b/org.springframework.context/src/main/java/org/springframework/validation/annotation/Validated.java @@ -1,56 +1,63 @@ -/* - * Copyright 2002-2011 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.annotation; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Extended variant of JSR-303's {@link javax.validation.Valid}, - * supporting the specification of validation groups. Designed for - * convenient use with Spring's JSR-303 support but not JSR-303 specific. - * - *

Can be used e.g. with Spring MVC handler methods arguments. - * Supported through {@link org.springframework.validation.SmartValidator}'s - * validation hint concept, with validation group classes acting as hint objects. - * - * @author Juergen Hoeller - * @since 3.1 - * @see javax.validation.Validator#validate(Object, Class[]) - * @see org.springframework.validation.SmartValidator#validate(Object, org.springframework.validation.Errors, Object...) - * @see org.springframework.validation.beanvalidation.SpringValidatorAdapter - */ -@Target(ElementType.PARAMETER) -@Retention(RetentionPolicy.RUNTIME) -@Documented -public @interface Valid { - - /** - * Specify one or more validation groups to apply to the validation step - * kicked off by this annotation. - *

JSR-303 defines validation groups as custom annotations which an application declares - * for the sole purpose of using them as type-safe group arguments, as implemented in - * {@link org.springframework.validation.beanvalidation.SpringValidatorAdapter}. - *

Other {@link org.springframework.validation.SmartValidator} implementations may - * support class arguments in other ways as well. - */ - Class[] value() default {}; - -} +/* + * Copyright 2002-2011 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.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Variant of JSR-303's {@link javax.validation.Valid}, supporting the + * specification of validation groups. Designed for convenient use with + * Spring's JSR-303 support but not JSR-303 specific. + * + *

Can be used e.g. with Spring MVC handler methods arguments. + * Supported through {@link org.springframework.validation.SmartValidator}'s + * validation hint concept, with validation group classes acting as hint objects. + * + *

Can also be used with method level validation, indicating that a specific + * class is supposed to be validated at the method level (acting as a pointcut + * for the corresponding validation interceptor), but also optionally specifying + * the validation groups for method-level validation in the annotated class. + * Can also be used as a meta-annotation on a custom stereotype annotation. + * + * @author Juergen Hoeller + * @since 3.1 + * @see javax.validation.Validator#validate(Object, Class[]) + * @see org.springframework.validation.SmartValidator#validate(Object, org.springframework.validation.Errors, Object...) + * @see org.springframework.validation.beanvalidation.SpringValidatorAdapter + * @see org.springframework.validation.beanvalidation.MethodValidationPostProcessor + */ +@Target({ElementType.TYPE, ElementType.PARAMETER}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface Validated { + + /** + * Specify one or more validation groups to apply to the validation step + * kicked off by this annotation. + *

JSR-303 defines validation groups as custom annotations which an application declares + * for the sole purpose of using them as type-safe group arguments, as implemented in + * {@link org.springframework.validation.beanvalidation.SpringValidatorAdapter}. + *

Other {@link org.springframework.validation.SmartValidator} implementations may + * support class arguments in other ways as well. + */ + Class[] value() default {}; + +} diff --git a/org.springframework.context/src/main/java/org/springframework/validation/beanvalidation/MethodValidationInterceptor.java b/org.springframework.context/src/main/java/org/springframework/validation/beanvalidation/MethodValidationInterceptor.java new file mode 100644 index 00000000000..b3e087c83b6 --- /dev/null +++ b/org.springframework.context/src/main/java/org/springframework/validation/beanvalidation/MethodValidationInterceptor.java @@ -0,0 +1,114 @@ +/* + * Copyright 2002-2011 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 java.util.Set; +import javax.validation.Validation; +import javax.validation.Validator; +import javax.validation.ValidatorFactory; + +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; +import org.hibernate.validator.HibernateValidator; +import org.hibernate.validator.method.MethodConstraintViolation; +import org.hibernate.validator.method.MethodConstraintViolationException; +import org.hibernate.validator.method.MethodValidator; + +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.validation.annotation.Validated; + +/** + * An AOP Alliance {@link MethodInterceptor} implementation that delegates to a + * JSR-303 provider for performing method-level validation on annotated methods. + * + *

Applicable methods have JSR-303 constraint annotations on their parameters + * and/or on their return value (in the latter case specified at the method level, + * typically as inline annotation). + * + *

E.g.: public @NotNull Object myValidMethod(@NotNull String arg1, @Max(10) int arg2) + * + *

Validation groups can be specified through Spring's {@link Validated} annotation + * at the type level of the containing target class, applying to all public service methods + * of that class. By default, JSR-303 will validate against its default group only. + * + *

As of Spring 3.1, this functionality requires Hibernate Validator 4.2 or higher. + * In Spring 3.2, this class will autodetect a Bean Validation 1.1 compliant provider + * and automatically use the standard method validation support there (once available). + * + * @author Juergen Hoeller + * @since 3.1 + * @see MethodValidationPostProcessor + * @see org.hibernate.validator.method.MethodValidator + */ +public class MethodValidationInterceptor implements MethodInterceptor { + + private final MethodValidator validator; + + + /** + * Create a new MethodValidationInterceptor using a default JSR-303 validator underneath. + */ + public MethodValidationInterceptor() { + this(Validation.byProvider(HibernateValidator.class).configure().buildValidatorFactory()); + } + + /** + * Create a new MethodValidationInterceptor using the given JSR-303 ValidatorFactory. + * @param validatorFactory the JSR-303 ValidatorFactory to use + */ + public MethodValidationInterceptor(ValidatorFactory validatorFactory) { + this(validatorFactory.getValidator()); + } + + /** + * Create a new MethodValidationInterceptor using the given JSR-303 Validator. + * @param validatorFactory the JSR-303 Validator to use + */ + public MethodValidationInterceptor(Validator validator) { + this.validator = validator.unwrap(MethodValidator.class); + } + + + public Object invoke(MethodInvocation invocation) throws Throwable { + Class[] groups = determineValidationGroups(invocation); + Set> result = this.validator.validateAllParameters( + invocation.getThis(), invocation.getMethod(), invocation.getArguments(), groups); + if (!result.isEmpty()) { + throw new MethodConstraintViolationException(result); + } + Object returnValue = invocation.proceed(); + result = this.validator.validateReturnValue( + invocation.getThis(), invocation.getMethod(), returnValue, groups); + if (!result.isEmpty()) { + throw new MethodConstraintViolationException(result); + } + return returnValue; + } + + /** + * Determine the validation groups to validate against for the given method invocation. + *

Default are the validation groups as specified in the {@link Validated} annotation + * on the containing target class of the method. + * @param invocation the current MethodInvocation + * @return the applicable validation groups as a Class array + */ + protected Class[] determineValidationGroups(MethodInvocation invocation) { + Validated valid = AnnotationUtils.findAnnotation(invocation.getThis().getClass(), Validated.class); + return (valid != null ? valid.value() : new Class[0]); + } + +} diff --git a/org.springframework.context/src/main/java/org/springframework/validation/beanvalidation/MethodValidationPostProcessor.java b/org.springframework.context/src/main/java/org/springframework/validation/beanvalidation/MethodValidationPostProcessor.java new file mode 100644 index 00000000000..3ccf295ad48 --- /dev/null +++ b/org.springframework.context/src/main/java/org/springframework/validation/beanvalidation/MethodValidationPostProcessor.java @@ -0,0 +1,158 @@ +/* + * Copyright 2002-2011 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 java.lang.annotation.Annotation; +import javax.validation.Validator; +import javax.validation.ValidatorFactory; + +import org.aopalliance.aop.Advice; + +import org.springframework.aop.Advisor; +import org.springframework.aop.Pointcut; +import org.springframework.aop.framework.Advised; +import org.springframework.aop.framework.AopInfrastructureBean; +import org.springframework.aop.framework.ProxyConfig; +import org.springframework.aop.framework.ProxyFactory; +import org.springframework.aop.support.AopUtils; +import org.springframework.aop.support.DefaultPointcutAdvisor; +import org.springframework.aop.support.annotation.AnnotationMatchingPointcut; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanClassLoaderAware; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.core.Ordered; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +import org.springframework.validation.annotation.Validated; + +/** + * A convenient {@link BeanPostProcessor} implementation that delegates to a + * JSR-303 provider for performing method-level validation on annotated methods. + * + *

Applicable methods have JSR-303 constraint annotations on their parameters + * and/or on their return value (in the latter case specified at the method level, + * typically as inline annotation). + * + *

E.g.: public @NotNull Object myValidMethod(@NotNull String arg1, @Max(10) int arg2) + * + *

Target classes with such annotated methods need to be annotated with Spring's + * {@link Validated} annotation at the type level, for their methods to be searched for + * inline constraint annotations. Validation groups can be specified through {@link Validated} + * as well. By default, JSR-303 will validate against its default group only. + * + *

As of Spring 3.1, this functionality requires Hibernate Validator 4.2 or higher. + * In Spring 3.2, this class will autodetect a Bean Validation 1.1 compliant provider + * and automatically use the standard method validation support there (once available). + * + * @author Juergen Hoeller + * @since 3.1 + * @see MethodValidationInterceptor + * @see org.hibernate.validator.method.MethodValidator + */ +public class MethodValidationPostProcessor extends ProxyConfig + implements BeanPostProcessor, BeanClassLoaderAware, Ordered, InitializingBean { + + private Class validatedAnnotationType = Validated.class; + + private Validator validator; + + private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader(); + + private Advisor advisor; + + + /** + * Set the 'validated' annotation type. + * The default validated annotation type is the {@link Validated} annotation. + *

This setter property exists so that developers can provide their own + * (non-Spring-specific) annotation type to indicate that a class is supposed + * to be validated in the sense of applying method validation. + * @param validatedAnnotationType the desired annotation type + */ + public void setValidatedAnnotationType(Class validatedAnnotationType) { + Assert.notNull(validatedAnnotationType, "'validatedAnnotationType' must not be null"); + this.validatedAnnotationType = validatedAnnotationType; + } + + /** + * Set the JSR-303 Validator to delegate to for validating methods. + *

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 methods, + * 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(); + } + + public void setBeanClassLoader(ClassLoader classLoader) { + this.beanClassLoader = classLoader; + } + + public int getOrder() { + // This should run after all other post-processors, so that it can just add + // an advisor to existing proxies rather than double-proxy. + return LOWEST_PRECEDENCE; + } + + + public void afterPropertiesSet() { + Pointcut pointcut = new AnnotationMatchingPointcut(this.validatedAnnotationType, true); + Advice advice = (this.validator != null ? new MethodValidationInterceptor(this.validator) : + new MethodValidationInterceptor()); + this.advisor = new DefaultPointcutAdvisor(pointcut, advice); + } + + + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + return bean; + } + + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + if (bean instanceof AopInfrastructureBean) { + // Ignore AOP infrastructure such as scoped proxies. + return bean; + } + Class targetClass = AopUtils.getTargetClass(bean); + if (AopUtils.canApply(this.advisor, targetClass)) { + if (bean instanceof Advised) { + ((Advised) bean).addAdvisor(this.advisor); + return bean; + } + else { + ProxyFactory proxyFactory = new ProxyFactory(bean); + // Copy our properties (proxyTargetClass etc) inherited from ProxyConfig. + proxyFactory.copyFrom(this); + proxyFactory.addAdvisor(this.advisor); + return proxyFactory.getProxy(this.beanClassLoader); + } + } + else { + // This is not a repository. + return bean; + } + } + +} diff --git a/org.springframework.context/src/main/java/org/springframework/validation/beanvalidation/SpringValidatorAdapter.java b/org.springframework.context/src/main/java/org/springframework/validation/beanvalidation/SpringValidatorAdapter.java index 7901a0d1db3..08ef9b89bde 100644 --- a/org.springframework.context/src/main/java/org/springframework/validation/beanvalidation/SpringValidatorAdapter.java +++ b/org.springframework.context/src/main/java/org/springframework/validation/beanvalidation/SpringValidatorAdapter.java @@ -89,7 +89,7 @@ public class SpringValidatorAdapter implements SmartValidator, javax.validation. processConstraintViolations(this.targetValidator.validate(target), errors); } - public void validate(Object target, Errors errors, Object[] validationHints) { + public void validate(Object target, Errors errors, Object... validationHints) { Set groups = new LinkedHashSet(); if (validationHints != null) { for (Object hint : validationHints) { diff --git a/org.springframework.context/src/test/java/org/springframework/validation/beanvalidation/MethodValidationTests.java b/org.springframework.context/src/test/java/org/springframework/validation/beanvalidation/MethodValidationTests.java new file mode 100644 index 00000000000..ce29597e0c7 --- /dev/null +++ b/org.springframework.context/src/test/java/org/springframework/validation/beanvalidation/MethodValidationTests.java @@ -0,0 +1,109 @@ +/* + * Copyright 2002-2011 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 java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import javax.validation.constraints.Max; +import javax.validation.constraints.NotNull; +import javax.validation.groups.Default; + +import org.hibernate.validator.method.MethodConstraintViolationException; +import org.junit.Test; + +import org.springframework.aop.framework.ProxyFactory; +import org.springframework.context.support.StaticApplicationContext; +import org.springframework.validation.annotation.Validated; + +import static org.junit.Assert.*; + +/** + * @author Juergen Hoeller + * @since 3.1 + */ +public class MethodValidationTests { + + @Test + public void testMethodValidationInterceptor() { + MyValidBean bean = new MyValidBean(); + ProxyFactory proxyFactory = new ProxyFactory(bean); + proxyFactory.addAdvice(new MethodValidationInterceptor()); + doTestProxyValidation((MyValidInterface) proxyFactory.getProxy()); + } + + @Test + public void testMethodValidationPostProcessor() { + StaticApplicationContext ac = new StaticApplicationContext(); + ac.registerSingleton("mvpp", MethodValidationPostProcessor.class); + ac.registerSingleton("bean", MyValidBean.class); + ac.refresh(); + doTestProxyValidation(ac.getBean("bean", MyValidInterface.class)); + } + + + private void doTestProxyValidation(MyValidInterface proxy) { + assertNotNull(proxy.myValidMethod("value", 5)); + try { + assertNotNull(proxy.myValidMethod("value", 15)); + fail("Should have thrown MethodConstraintViolationException"); + } + catch (MethodConstraintViolationException ex) { + // expected + } + try { + assertNotNull(proxy.myValidMethod(null, 5)); + fail("Should have thrown MethodConstraintViolationException"); + } + catch (MethodConstraintViolationException ex) { + // expected + } + try { + assertNotNull(proxy.myValidMethod("value", 0)); + fail("Should have thrown MethodConstraintViolationException"); + } + catch (MethodConstraintViolationException ex) { + // expected + } + } + + + @MyStereotype + public static class MyValidBean implements MyValidInterface { + + public Object myValidMethod(String arg1, int arg2) { + return (arg2 == 0 ? null : "value"); + } + } + + + public interface MyValidInterface { + + @NotNull Object myValidMethod(@NotNull(groups = MyGroup.class) String arg1, @Max(10) int arg2); + } + + + public interface MyGroup { + } + + + @Validated({MyGroup.class, Default.class}) + @Retention(RetentionPolicy.RUNTIME) + public @interface MyStereotype { + + } + +} diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/RequestPartMethodArgumentResolver.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/RequestPartMethodArgumentResolver.java index 70f4deed645..4d5b546287e 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/RequestPartMethodArgumentResolver.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/RequestPartMethodArgumentResolver.java @@ -138,7 +138,7 @@ public class RequestPartMethodArgumentResolver extends AbstractMessageConverterM if (arg != null) { Annotation[] annotations = parameter.getParameterAnnotations(); for (Annotation annot : annotations) { - if ("Valid".equals(annot.annotationType().getSimpleName())) { + if (annot.annotationType().getSimpleName().startsWith("Valid")) { WebDataBinder binder = binderFactory.createBinder(request, arg, partName); Object hints = AnnotationUtils.getValue(annot); binder.validate(hints instanceof Object[] ? (Object[]) hints : new Object[] {hints}); diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/RequestResponseBodyMethodProcessor.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/RequestResponseBodyMethodProcessor.java index 711b5ace4f4..a8f268213c1 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/RequestResponseBodyMethodProcessor.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/RequestResponseBodyMethodProcessor.java @@ -71,7 +71,7 @@ public class RequestResponseBodyMethodProcessor extends AbstractMessageConverter Object arg = readWithMessageConverters(webRequest, parameter, parameter.getParameterType()); Annotation[] annotations = parameter.getParameterAnnotations(); for (Annotation annot : annotations) { - if ("Valid".equals(annot.annotationType().getSimpleName())) { + if (annot.annotationType().getSimpleName().startsWith("Valid")) { String name = Conventions.getVariableNameForParameter(parameter); WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name); Object hints = AnnotationUtils.getValue(annot); diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java index a0ae6f79b92..08190d0a916 100644 --- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java @@ -47,7 +47,7 @@ import org.springframework.stereotype.Controller; import org.springframework.validation.BindingResult; import org.springframework.validation.Errors; import org.springframework.validation.Validator; -import org.springframework.validation.annotation.Valid; +import org.springframework.validation.annotation.Validated; import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; @@ -450,7 +450,7 @@ public class MvcNamespaceTests { private boolean recordedValidationError; @RequestMapping - public void testBind(@RequestParam @DateTimeFormat(iso=ISO.DATE) Date date, @Valid(MyGroup.class) TestBean bean, BindingResult result) { + public void testBind(@RequestParam @DateTimeFormat(iso=ISO.DATE) Date date, @Validated(MyGroup.class) TestBean bean, BindingResult result) { this.recordedValidationError = (result.getErrorCount() == 1); } } diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/ServletAnnotationControllerTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/ServletAnnotationControllerTests.java index 48799de1807..1187bf8ab85 100644 --- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/ServletAnnotationControllerTests.java +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/ServletAnnotationControllerTests.java @@ -103,7 +103,7 @@ import org.springframework.util.StringUtils; import org.springframework.validation.BindingResult; import org.springframework.validation.Errors; import org.springframework.validation.FieldError; -import org.springframework.validation.annotation.Valid; +import org.springframework.validation.annotation.Validated; import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.annotation.CookieValue; @@ -2325,7 +2325,7 @@ public class ServletAnnotationControllerTests { @Override @RequestMapping("/myPath.do") - public String myHandle(@ModelAttribute("myCommand") @Valid(MyGroup.class) TestBean tb, BindingResult errors, ModelMap model) { + public String myHandle(@ModelAttribute("myCommand") @Validated(MyGroup.class) TestBean tb, BindingResult errors, ModelMap model) { if (!errors.hasFieldErrors("validCountry")) { throw new IllegalStateException("Declarative validation not applied"); } @@ -2383,7 +2383,7 @@ public class ServletAnnotationControllerTests { @Override @RequestMapping("/myPath.do") - public String myHandle(@ModelAttribute("myCommand") @Valid(MyGroup.class) TestBean tb, BindingResult errors, ModelMap model) { + public String myHandle(@ModelAttribute("myCommand") @Validated(MyGroup.class) TestBean tb, BindingResult errors, ModelMap model) { if (!errors.hasFieldErrors("sex")) { throw new IllegalStateException("requiredFields not applied"); } @@ -2410,8 +2410,7 @@ public class ServletAnnotationControllerTests { } } - @Retention(RetentionPolicy.RUNTIME) - public @interface MyGroup { + public interface MyGroup { } private static class MyWebBindingInitializer implements WebBindingInitializer { diff --git a/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/support/HandlerMethodInvoker.java b/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/support/HandlerMethodInvoker.java index 42bc358a039..28b93fc403a 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/support/HandlerMethodInvoker.java +++ b/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/support/HandlerMethodInvoker.java @@ -295,7 +295,7 @@ public class HandlerMethodInvoker { else if (Value.class.isInstance(paramAnn)) { defaultValue = ((Value) paramAnn).value(); } - else if ("Valid".equals(paramAnn.annotationType().getSimpleName())) { + else if (paramAnn.annotationType().getSimpleName().startsWith("Valid")) { validate = true; Object value = AnnotationUtils.getValue(paramAnn); validationHints = (value instanceof Object[] ? (Object[]) value : new Object[] {value}); diff --git a/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/ModelAttributeMethodProcessor.java b/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/ModelAttributeMethodProcessor.java index 25fd19968b9..4dc2c7fe8aa 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/ModelAttributeMethodProcessor.java +++ b/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/ModelAttributeMethodProcessor.java @@ -149,7 +149,7 @@ public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResol protected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) { Annotation[] annotations = parameter.getParameterAnnotations(); for (Annotation annot : annotations) { - if ("Valid".equals(annot.annotationType().getSimpleName())) { + if (annot.annotationType().getSimpleName().startsWith("Valid")) { Object hints = AnnotationUtils.getValue(annot); binder.validate(hints instanceof Object[] ? (Object[]) hints : new Object[] {hints}); }