http://sishuok.com/forum/blogPost/list/7798.html

在之前的《跟我学SpringMVC》中的《第七章 注解式控制器的数据验证、类型转换及格式化》中已经介绍过SpringMVC集成Bean Validation 1.0(JSR-303),目前Bean Validation最新版本是Bean Validation 1.1(JSR-349),新特性可以到官网查看,笔者最喜欢的两个特性是:跨参数验证(比如密码和确认密码的验证)和支持在消息中使用EL表达式,其他的还有如方法参数/返回值验证、CDI和依赖注入、分组转换等。对于方法参数/返回值验证,大家可以参阅《Spring3.1 对Bean Validation规范的新支持(方法级别验证) 》。

Bean Validation 1.1当前实现是Hibernate validator 5,且spring4才支持。接下来我们从以下几个方法讲解Bean Validation 1.1,当然不一定是新特性:

  1. 集成Bean Validation 1.1到SpringMVC
  2. 分组验证及分组顺序
  3. 消息中使用EL表达式
  4. 方法参数/返回值验证
  5. 自定义验证规则
  6. 类级别验证器
  7. 脚本验证器
  8. cross-parameter,跨参数验证
  9. 混合类级别验证器和跨参数验证器

因为大多数时候验证都配合web框架使用,而且很多朋友都咨询过如分组/跨参数验证,所以本文介绍下这些,且是和SpringMVC框架集成的例子,其他使用方式(比如集成到JPA中)可以参考其官方文档:

规范:http://beanvalidation.org/1.1/spec/

hibernate validator文档:http://hibernate.org/validator/

1、集成Bean Validation 1.1到SpringMVC

1.1、项目搭建

首先添加hibernate validator 5依赖:

查看复制到剪贴板打印
  1. <dependency>
  2. <groupId>org.hibernate</groupId>
  3. <artifactId>hibernate-validator</artifactId>
  4. <version>5.0.2.Final</version>
  5. </dependency>

如果想在消息中使用EL表达式,请确保EL表达式版本是 2.2或以上,如使用Tomcat6,请到Tomcat7中拷贝相应的EL jar包到Tomcat6中。

查看复制到剪贴板打印
  1. <dependency>
  2. <groupId>javax.el</groupId>
  3. <artifactId>javax.el-api</artifactId>
  4. <version>2.2.4</version>
  5. <scope>provided</scope>
  6. </dependency>

请确保您使用的Web容器有相应版本的el jar包。

对于其他POM依赖请下载附件中的项目参考。

1.2、Spring MVC配置文件(spring-mvc.xml):

查看复制到剪贴板打印
  1. <!-- 指定自己定义的validator -->
  2. <mvc:annotation-driven validator="validator"/>
  3. <!-- 以下 validator  ConversionService 在使用 mvc:annotation-driven 会 自动注册-->
  4. <bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
  5. <property name="providerClass" value="org.hibernate.validator.HibernateValidator"/>
  6. <!-- 如果不加默认到 使用classpath下的 ValidationMessages.properties -->
  7. <property name="validationMessageSource" ref="messageSource"/>
  8. </bean>
  9. <!-- 国际化的消息资源文件(本系统中主要用于显示/错误消息定制) -->
  10. <bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
  11. <property name="basenames">
  12. <list>
  13. <!-- 在web环境中一定要定位到classpath 否则默认到当前web应用下找  -->
  14. <value>classpath:messages</value>
  15. <value>classpath:org/hibernate/validator/ValidationMessages</value>
  16. </list>
  17. </property>
  18. <property name="useCodeAsDefaultMessage" value="false"/>
  19. <property name="defaultEncoding" value="UTF-8"/>
  20. <property name="cacheSeconds" value="60"/>
  21. </bean>

此处主要把bean validation的消息查找委托给spring的messageSource。

1.3、实体验证注解:

查看复制到剪贴板打印
  1. public class User implements Serializable {
  2. @NotNull(message = "{user.id.null}")
  3. private Long id;
  4. @NotEmpty(message = "{user.name.null}")
  5. @Length(min = 5, max = 20, message = "{user.name.length.illegal}")
  6. @Pattern(regexp = "[a-zA-Z]{5,20}", message = "{user.name.illegal}")
  7. private String name;
  8. @NotNull(message = "{user.password.null}")
  9. private String password;
  10. }

对于验证规则可以参考官方文档,或者《第七章 注解式控制器的数据验证、类型转换及格式化》。

1.4、错误消息文件messages.properties:

查看复制到剪贴板打印
  1. user.id.null=用户编号不能为空
  2. user.name.null=用户名不能为空
  3. user.name.length.illegal=用户名长度必须在5到20之间
  4. user.name.illegal=用户名必须是字母
  5. user.password.null=密码不能为空

1.5、控制器

查看复制到剪贴板打印
  1. @Controller
  2. public class UserController {
  3. @RequestMapping("/save")
  4. public String save(@Valid User user, BindingResult result) {
  5. if(result.hasErrors()) {
  6. return "error";
  7. }
  8. return "success";
  9. }
  10. }

1.6、错误页面:

查看复制到剪贴板打印
  1. <spring:hasBindErrors name="user">
  2. <c:if test="${errors.fieldErrorCount > 0}">
  3. 字段错误:<br/>
  4. <c:forEach items="${errors.fieldErrors}" var="error">
  5. <spring:message var="message" code="${error.code}" arguments="${error.arguments}" text="${error.defaultMessage}"/>
  6. ${error.field}------${message}<br/>
  7. </c:forEach>
  8. </c:if>
  9. <c:if test="${errors.globalErrorCount > 0}">
  10. 全局错误:<br/>
  11. <c:forEach items="${errors.globalErrors}" var="error">
  12. <spring:message var="message" code="${error.code}" arguments="${error.arguments}" text="${error.defaultMessage}"/>
  13. <c:if test="${not empty message}">
  14. ${message}<br/>
  15. </c:if>
  16. </c:forEach>
  17. </c:if>
  18. </spring:hasBindErrors>

大家以后可以根据这个做通用的错误消息显示规则。比如我前端页面使用validationEngine显示错误消息,那么我可以定义一个tag来通用化错误消息的显示:showFieldError.tag。

1.7、测试

输入如:http://localhost:9080/spring4/save?name=123 , 我们得到如下错误:

查看复制到剪贴板打印
  1. name------用户名必须是字母
  2. name------用户名长度必须在5到20之间
  3. password------密码不能为空
  4. id------用户编号不能为空

基本的集成就完成了。

如上测试有几个小问题:

1、错误消息顺序,大家可以看到name的错误消息顺序不是按照书写顺序的,即不确定;

2、我想显示如:用户名【zhangsan】必须在5到20之间;其中我们想动态显示:用户名、min,max;而不是写死了;

3、我想在修改的时候只验证用户名,其他的不验证怎么办。

接下来我们挨着试试吧。

2、分组验证及分组顺序

如果我们想在新增的情况验证id和name,而修改的情况验证name和password,怎么办? 那么就需要分组了。

首先定义分组接口:

查看复制到剪贴板打印
  1. public interface First {
  2. }
  3. public interface Second {
  4. }

分组接口就是两个普通的接口,用于标识,类似于java.io.Serializable。

接着我们使用分组接口标识实体:

查看复制到剪贴板打印
  1. public class User implements Serializable {
  2. @NotNull(message = "{user.id.null}", groups = {First.class})
  3. private Long id;
  4. @Length(min = 5, max = 20, message = "{user.name.length.illegal}", groups = {Second.class})
  5. @Pattern(regexp = "[a-zA-Z]{5,20}", message = "{user.name.illegal}", groups = {Second.class})
  6. private String name;
  7. @NotNull(message = "{user.password.null}", groups = {First.class, Second.class})
  8. private String password;
  9. }

验证时使用如:

查看复制到剪贴板打印
  1. @RequestMapping("/save")
  2. public String save(@Validated({Second.class}) User user, BindingResult result) {
  3. if(result.hasErrors()) {
  4. return "error";
  5. }
  6. return "success";
  7. }

即通过@Validate注解标识要验证的分组;如果要验证两个的话,可以这样@Validated({First.class, Second.class})。

接下来我们来看看通过分组来指定顺序;还记得之前的错误消息吗? user.name会显示两个错误消息,而且顺序不确定;如果我们先验证一个消息;如果不通过再验证另一个怎么办?可以通过@GroupSequence指定分组验证顺序:

查看复制到剪贴板打印
  1. @GroupSequence({First.class, Second.class, User.class})
  2. public class User implements Serializable {
  3. private Long id;
  4. @Length(min = 5, max = 20, message = "{user.name.length.illegal}", groups = {First.class})
  5. @Pattern(regexp = "[a-zA-Z]{5,20}", message = "{user.name.illegal}", groups = {Second.class})
  6. private String name;
  7. private String password;
  8. }

通过@GroupSequence指定验证顺序:先验证First分组,如果有错误立即返回而不会验证Second分组,接着如果First分组验证通过了,那么才去验证Second分组,最后指定User.class表示那些没有分组的在最后。这样我们就可以实现按顺序验证分组了。

3、消息中使用EL表达式

假设我们需要显示如:用户名[NAME]长度必须在[MIN]到[MAX]之间,此处大家可以看到,我们不想把一些数据写死,如NAME、MIN、MAX;此时我们可以使用EL表达式。

如:

查看复制到剪贴板打印
  1. @Length(min = 5, max = 20, message = "{user.name.length.illegal}", groups = {First.class})

错误消息:

查看复制到剪贴板打印
  1. user.name.length.illegal=用户名长度必须在{min}到{max}之间

其中我们可以使用{验证注解的属性}得到这些值;如{min}得到@Length中的min值;其他的也是类似的。

到此,我们还是无法得到出错的那个输入值,如name=zhangsan。此时就需要EL表达式的支持,首先确定引入EL jar包且版本正确。然后使用如:

查看复制到剪贴板打印
  1. user.name.length.illegal=用户名[${validatedValue}]长度必须在5到20之间

使用如EL表达式:${validatedValue}得到输入的值,如zhangsan。当然我们还可以使用如${min > 1 ? '大于1' : '小于等于1'},及在EL表达式中也能拿到如@Length的min等数据。

另外我们还可以拿到一个java.util.Formatter类型的formatter变量进行格式化:

查看复制到剪贴板打印
  1. ${formatter.format("%04d", min)}

4、方法参数/返回值验证

这个可以参考《Spring3.1 对Bean Validation规范的新支持(方法级别验证) 》,概念是类似的,具体可以参考Bean Validation 文档。

5、自定义验证规则

有时候默认的规则可能还不够,有时候还需要自定义规则,比如屏蔽关键词验证是非常常见的一个功能,比如在发帖时帖子中不允许出现admin等关键词。

1、定义验证注解

查看复制到剪贴板打印
  1. package com.sishuok.spring4.validator;
  2. import javax.validation.Constraint;
  3. import javax.validation.Payload;
  4. import java.lang.annotation.Documented;
  5. import java.lang.annotation.Retention;
  6. import java.lang.annotation.Target;
  7. import static java.lang.annotation.ElementType.*;
  8. import static java.lang.annotation.RetentionPolicy.*;
  9. /**
  10. * <p>User: Zhang Kaitao
  11. * <p>Date: 13-12-15
  12. * <p>Version: 1.0
  13. */
  14. @Target({ FIELD, METHOD, PARAMETER, ANNOTATION_TYPE })
  15. @Retention(RUNTIME)
  16. //指定验证器
  17. @Constraint(validatedBy = ForbiddenValidator.class)
  18. @Documented
  19. public @interface Forbidden {
  20. //默认错误消息
  21. String message() default "{forbidden.word}";
  22. //分组
  23. Class<?>[] groups() default { };
  24. //负载
  25. Class<? extends Payload>[] payload() default { };
  26. //指定多个时使用
  27. @Target({ FIELD, METHOD, PARAMETER, ANNOTATION_TYPE })
  28. @Retention(RUNTIME)
  29. @Documented
  30. @interface List {
  31. Forbidden[] value();
  32. }
  33. }

2、 定义验证器

查看复制到剪贴板打印
  1. package com.sishuok.spring4.validator;
  2. import org.hibernate.validator.internal.engine.constraintvalidation.ConstraintValidatorContextImpl;
  3. import org.springframework.beans.factory.annotation.Autowired;
  4. import org.springframework.context.ApplicationContext;
  5. import org.springframework.util.StringUtils;
  6. import javax.validation.ConstraintValidator;
  7. import javax.validation.ConstraintValidatorContext;
  8. import java.io.Serializable;
  9. /**
  10. * <p>User: Zhang Kaitao
  11. * <p>Date: 13-12-15
  12. * <p>Version: 1.0
  13. */
  14. public class ForbiddenValidator implements ConstraintValidator<Forbidden, String> {
  15. private String[] forbiddenWords = {"admin"};
  16. @Override
  17. public void initialize(Forbidden constraintAnnotation) {
  18. //初始化,得到注解数据
  19. }
  20. @Override
  21. public boolean isValid(String value, ConstraintValidatorContext context) {
  22. if(StringUtils.isEmpty(value)) {
  23. return true;
  24. }
  25. for(String word : forbiddenWords) {
  26. if(value.contains(word)) {
  27. return false;//验证失败
  28. }
  29. }
  30. return true;
  31. }
  32. }

验证器中可以使用spring的依赖注入,如注入:@Autowired  private ApplicationContext ctx;

3、使用

查看复制到剪贴板打印
  1. public class User implements Serializable {
  2. @Forbidden()
  3. private String name;
  4. }

4、当我们在提交name中含有admin的时候会输出错误消息:

查看复制到剪贴板打印
  1. forbidden.word=您输入的数据中有非法关键词

问题来了,哪个词是非法的呢?bean validation 和 hibernate validator都没有提供相应的api提供这个数据,怎么办呢?通过跟踪代码,发现一种不是特别好的方法:我们可以覆盖org.hibernate.validator.internal.metadata.descriptor.ConstraintDescriptorImpl实现(即复制一份代码放到我们的src中),然后覆盖buildAnnotationParameterMap方法;

查看复制到剪贴板打印
  1. private Map<String, Object> buildAnnotationParameterMap(Annotation annotation) {
  2. ……
  3. //将Collections.unmodifiableMap( parameters );替换为如下语句
  4. return parameters;
  5. }

即允许这个数据可以修改;然后在ForbiddenValidator中:

查看复制到剪贴板打印
  1. for(String word : forbiddenWords) {
  2. if(value.contains(word)) {
  3. ((ConstraintValidatorContextImpl)context).getConstraintDescriptor().getAttributes().put("word", word);
  4. return false;//验证失败
  5. }
  6. }

通过((ConstraintValidatorContextImpl)context).getConstraintDescriptor().getAttributes().put("word", word);添加自己的属性;放到attributes中的数据可以通过${} 获取。然后消息就可以变成:

查看复制到剪贴板打印
  1. forbidden.word=您输入的数据中有非法关键词【{word}】

这种方式不是很友好,但是可以解决我们的问题。

典型的如密码、确认密码的场景,非常常用;如果没有这个功能我们需要自己写代码来完成;而且经常重复自己。接下来看看bean validation 1.1如何实现的。

6、类级别验证器

6.1、定义验证注解

查看复制到剪贴板打印
  1. package com.sishuok.spring4.validator;
  2. import javax.validation.Constraint;
  3. import javax.validation.Payload;
  4. import javax.validation.constraints.NotNull;
  5. import java.lang.annotation.Documented;
  6. import java.lang.annotation.Retention;
  7. import java.lang.annotation.Target;
  8. import static java.lang.annotation.ElementType.*;
  9. import static java.lang.annotation.RetentionPolicy.*;
  10. /**
  11. * <p>User: Zhang Kaitao
  12. * <p>Date: 13-12-15
  13. * <p>Version: 1.0
  14. */
  15. @Target({ TYPE, ANNOTATION_TYPE})
  16. @Retention(RUNTIME)
  17. //指定验证器
  18. @Constraint(validatedBy = CheckPasswordValidator.class)
  19. @Documented
  20. public @interface CheckPassword {
  21. //默认错误消息
  22. String message() default "";
  23. //分组
  24. Class<?>[] groups() default { };
  25. //负载
  26. Class<? extends Payload>[] payload() default { };
  27. //指定多个时使用
  28. @Target({ FIELD, METHOD, PARAMETER, ANNOTATION_TYPE })
  29. @Retention(RUNTIME)
  30. @Documented
  31. @interface List {
  32. CheckPassword[] value();
  33. }
  34. }

6.2、 定义验证器

查看复制到剪贴板打印
  1. package com.sishuok.spring4.validator;
  2. import com.sishuok.spring4.entity.User;
  3. import org.springframework.util.StringUtils;
  4. import javax.validation.ConstraintValidator;
  5. import javax.validation.ConstraintValidatorContext;
  6. /**
  7. * <p>User: Zhang Kaitao
  8. * <p>Date: 13-12-15
  9. * <p>Version: 1.0
  10. */
  11. public class CheckPasswordValidator implements ConstraintValidator<CheckPassword, User> {
  12. @Override
  13. public void initialize(CheckPassword constraintAnnotation) {
  14. }
  15. @Override
  16. public boolean isValid(User user, ConstraintValidatorContext context) {
  17. if(user == null) {
  18. return true;
  19. }
  20. //没有填密码
  21. if(!StringUtils.hasText(user.getPassword())) {
  22. context.disableDefaultConstraintViolation();
  23. context.buildConstraintViolationWithTemplate("{password.null}")
  24. .addPropertyNode("password")
  25. .addConstraintViolation();
  26. return false;
  27. }
  28. if(!StringUtils.hasText(user.getConfirmation())) {
  29. context.disableDefaultConstraintViolation();
  30. context.buildConstraintViolationWithTemplate("{password.confirmation.null}")
  31. .addPropertyNode("confirmation")
  32. .addConstraintViolation();
  33. return false;
  34. }
  35. //两次密码不一样
  36. if (!user.getPassword().trim().equals(user.getConfirmation().trim())) {
  37. context.disableDefaultConstraintViolation();
  38. context.buildConstraintViolationWithTemplate("{password.confirmation.error}")
  39. .addPropertyNode("confirmation")
  40. .addConstraintViolation();
  41. return false;
  42. }
  43. return true;
  44. }
  45. }

其中我们通过disableDefaultConstraintViolation禁用默认的约束;然后通过buildConstraintViolationWithTemplate(消息模板)/addPropertyNode(所属属性)/addConstraintViolation定义我们自己的约束。

6.3、使用

查看复制到剪贴板打印
  1. @CheckPassword()
  2. public class User implements Serializable {
  3. }

放到类头上即可。

7、通过脚本验证

查看复制到剪贴板打印
  1. @ScriptAssert(script = "_this.password==_this.confirmation", lang = "javascript", alias = "_this", message = "{password.confirmation.error}")
  2. public class User implements Serializable {
  3. }

通过脚本验证是非常简单而且强大的,lang指定脚本语言(请参考javax.script.ScriptEngineManager JSR-223),alias是在脚本验证中User对象的名字,但是大家会发现一个问题:错误消息怎么显示呢? 在springmvc 中会添加到全局错误消息中,这肯定不是我们想要的,我们改造下吧。

7.1、定义验证注解

查看复制到剪贴板打印
  1. package com.sishuok.spring4.validator;
  2. import org.hibernate.validator.internal.constraintvalidators.ScriptAssertValidator;
  3. import java.lang.annotation.Documented;
  4. import java.lang.annotation.Retention;
  5. import java.lang.annotation.Target;
  6. import javax.validation.Constraint;
  7. import javax.validation.Payload;
  8. import static java.lang.annotation.ElementType.TYPE;
  9. import static java.lang.annotation.RetentionPolicy.RUNTIME;
  10. @Target({ TYPE })
  11. @Retention(RUNTIME)
  12. @Constraint(validatedBy = {PropertyScriptAssertValidator.class})
  13. @Documented
  14. public @interface PropertyScriptAssert {
  15. String message() default "{org.hibernate.validator.constraints.ScriptAssert.message}";
  16. Class<?>[] groups() default { };
  17. Class<? extends Payload>[] payload() default { };
  18. String lang();
  19. String script();
  20. String alias() default "_this";
  21. String property();
  22. @Target({ TYPE })
  23. @Retention(RUNTIME)
  24. @Documented
  25. public @interface List {
  26. PropertyScriptAssert[] value();
  27. }
  28. }

和ScriptAssert没什么区别,只是多了个property用来指定出错后给实体的哪个属性。

7.2、验证器

查看复制到剪贴板打印
  1. package com.sishuok.spring4.validator;
  2. import javax.script.ScriptException;
  3. import javax.validation.ConstraintDeclarationException;
  4. import javax.validation.ConstraintValidator;
  5. import javax.validation.ConstraintValidatorContext;
  6. import com.sishuok.spring4.validator.PropertyScriptAssert;
  7. import org.hibernate.validator.constraints.ScriptAssert;
  8. import org.hibernate.validator.internal.util.Contracts;
  9. import org.hibernate.validator.internal.util.logging.Log;
  10. import org.hibernate.validator.internal.util.logging.LoggerFactory;
  11. import org.hibernate.validator.internal.util.scriptengine.ScriptEvaluator;
  12. import org.hibernate.validator.internal.util.scriptengine.ScriptEvaluatorFactory;
  13. import static org.hibernate.validator.internal.util.logging.Messages.MESSAGES;
  14. public class PropertyScriptAssertValidator implements ConstraintValidator<PropertyScriptAssert, Object> {
  15. private static final Log log = LoggerFactory.make();
  16. private String script;
  17. private String languageName;
  18. private String alias;
  19. private String property;
  20. private String message;
  21. public void initialize(PropertyScriptAssert constraintAnnotation) {
  22. validateParameters( constraintAnnotation );
  23. this.script = constraintAnnotation.script();
  24. this.languageName = constraintAnnotation.lang();
  25. this.alias = constraintAnnotation.alias();
  26. this.property = constraintAnnotation.property();
  27. this.message = constraintAnnotation.message();
  28. }
  29. public boolean isValid(Object value, ConstraintValidatorContext constraintValidatorContext) {
  30. Object evaluationResult;
  31. ScriptEvaluator scriptEvaluator;
  32. try {
  33. ScriptEvaluatorFactory evaluatorFactory = ScriptEvaluatorFactory.getInstance();
  34. scriptEvaluator = evaluatorFactory.getScriptEvaluatorByLanguageName( languageName );
  35. }
  36. catch ( ScriptException e ) {
  37. throw new ConstraintDeclarationException( e );
  38. }
  39. try {
  40. evaluationResult = scriptEvaluator.evaluate( script, value, alias );
  41. }
  42. catch ( ScriptException e ) {
  43. throw log.getErrorDuringScriptExecutionException( script, e );
  44. }
  45. if ( evaluationResult == null ) {
  46. throw log.getScriptMustReturnTrueOrFalseException( script );
  47. }
  48. if ( !( evaluationResult instanceof Boolean ) ) {
  49. throw log.getScriptMustReturnTrueOrFalseException(
  50. script,
  51. evaluationResult,
  52. evaluationResult.getClass().getCanonicalName()
  53. );
  54. }
  55. if(Boolean.FALSE.equals(evaluationResult)) {
  56. constraintValidatorContext.disableDefaultConstraintViolation();
  57. constraintValidatorContext
  58. .buildConstraintViolationWithTemplate(message)
  59. .addPropertyNode(property)
  60. .addConstraintViolation();
  61. }
  62. return Boolean.TRUE.equals( evaluationResult );
  63. }
  64. private void validateParameters(PropertyScriptAssert constraintAnnotation) {
  65. Contracts.assertNotEmpty( constraintAnnotation.script(), MESSAGES.parameterMustNotBeEmpty( "script" ) );
  66. Contracts.assertNotEmpty( constraintAnnotation.lang(), MESSAGES.parameterMustNotBeEmpty( "lang" ) );
  67. Contracts.assertNotEmpty( constraintAnnotation.alias(), MESSAGES.parameterMustNotBeEmpty( "alias" ) );
  68. Contracts.assertNotEmpty( constraintAnnotation.property(), MESSAGES.parameterMustNotBeEmpty( "property" ) );
  69. Contracts.assertNotEmpty( constraintAnnotation.message(), MESSAGES.parameterMustNotBeEmpty( "message" ) );
  70. }
  71. }

和之前的类级别验证器类似,就不多解释了,其他代码全部拷贝自org.hibernate.validator.internal.constraintvalidators.ScriptAssertValidator。

7.3、使用

查看复制到剪贴板打印
  1. @PropertyScriptAssert(property = "confirmation", script = "_this.password==_this.confirmation", lang = "javascript", alias = "_this", message = "{password.confirmation.error}")

和之前的区别就是多了个property,用来指定出错时给哪个字段。 这个相对之前的类级别验证器更通用一点。

8、cross-parameter,跨参数验证

直接看示例;

8.1、首先注册MethodValidationPostProcessor,起作用请参考《Spring3.1 对Bean Validation规范的新支持(方法级别验证) 》

查看复制到剪贴板打印
  1. <bean class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor">
  2. <property name="validator" ref="validator"/>
  3. </bean>

8.2、Service

查看复制到剪贴板打印
  1. @Validated
  2. @Service
  3. public class UserService {
  4. @CrossParameter
  5. public void changePassword(String password, String confirmation) {
  6. }
  7. }

通过@Validated注解UserService表示该类中有需要进行方法参数/返回值验证;   @CrossParameter注解方法表示要进行跨参数验证;即验证password和confirmation是否相等。

8.3、验证注解

查看复制到剪贴板打印
  1. package com.sishuok.spring4.validator;
  2. //省略import
  3. @Constraint(validatedBy = CrossParameterValidator.class)
  4. @Target({ METHOD, CONSTRUCTOR, ANNOTATION_TYPE })
  5. @Retention(RUNTIME)
  6. @Documented
  7. public @interface CrossParameter {
  8. String message() default "{password.confirmation.error}";
  9. Class<?>[] groups() default { };
  10. Class<? extends Payload>[] payload() default { };
  11. }

8.4、验证器

查看复制到剪贴板打印
  1. package com.sishuok.spring4.validator;
  2. //省略import
  3. @SupportedValidationTarget(ValidationTarget.PARAMETERS)
  4. public class CrossParameterValidator implements ConstraintValidator<CrossParameter, Object[]> {
  5. @Override
  6. public void initialize(CrossParameter constraintAnnotation) {
  7. }
  8. @Override
  9. public boolean isValid(Object[] value, ConstraintValidatorContext context) {
  10. if(value == null || value.length != 2) {
  11. throw new IllegalArgumentException("must have two args");
  12. }
  13. if(value[0] == null || value[1] == null) {
  14. return true;
  15. }
  16. if(value[0].equals(value[1])) {
  17. return true;
  18. }
  19. return false;
  20. }
  21. }

其中@SupportedValidationTarget(ValidationTarget.PARAMETERS)表示验证参数; value将是参数列表。

8.5、使用

查看复制到剪贴板打印
  1. @RequestMapping("/changePassword")
  2. public String changePassword(
  3. @RequestParam("password") String password,
  4. @RequestParam("confirmation") String confirmation, Model model) {
  5. try {
  6. userService.changePassword(password, confirmation);
  7. } catch (ConstraintViolationException e) {
  8. for(ConstraintViolation violation : e.getConstraintViolations()) {
  9. System.out.println(violation.getMessage());
  10. }
  11. }
  12. return "success";
  13. }

调用userService.changePassword方法,如果验证失败将抛出ConstraintViolationException异常,然后得到ConstraintViolation,调用getMessage即可得到错误消息;然后到前台显示即可。

从以上来看,不如之前的使用方便,需要自己对错误消息进行处理。 下一节我们也写个脚本方式的跨参数验证器。

9、混合类级别验证器和跨参数验证器

9.1、验证注解

查看复制到剪贴板打印
  1. package com.sishuok.spring4.validator;
  2. //省略import
  3. @Constraint(validatedBy = {
  4. CrossParameterScriptAssertClassValidator.class,
  5. CrossParameterScriptAssertParameterValidator.class
  6. })
  7. @Target({ TYPE, FIELD, PARAMETER, METHOD, CONSTRUCTOR, ANNOTATION_TYPE })
  8. @Retention(RUNTIME)
  9. @Documented
  10. public @interface CrossParameterScriptAssert {
  11. String message() default "error";
  12. Class<?>[] groups() default { };
  13. Class<? extends Payload>[] payload() default { };
  14. String script();
  15. String lang();
  16. String alias() default "_this";
  17. String property() default "";
  18. ConstraintTarget validationAppliesTo() default ConstraintTarget.IMPLICIT;
  19. }

此处我们通过@Constraint指定了两个验证器,一个类级别的,一个跨参数的。validationAppliesTo指定为ConstraintTarget.IMPLICIT,表示隐式自动判断。

9.2、验证器

请下载源码查看

9.3、使用

9.3.1、类级别使用

查看复制到剪贴板打印
  1. @CrossParameterScriptAssert(property = "confirmation", script = "_this.password==_this.confirmation", lang = "javascript", alias = "_this", message = "{password.confirmation.error}")

指定property即可,其他和之前的一样。

9.3.2、跨参数验证

查看复制到剪贴板打印
  1. @CrossParameterScriptAssert(script = "args[0] == args[1]", lang = "javascript", alias = "args", message = "{password.confirmation.error}")
  2. public void changePassword(String password, String confirmation) {
  3. }

通过args[0]==args[1] 来判断是否相等。

这样,我们的验证注解就自动适应两种验证规则了。

到此,我们已经完成大部分Bean Validation的功能实验了。对于如XML配置、编程式验证API的使用等对于我们使用SpringMVC这种web环境用处不大,所以就不多介绍了,有兴趣可以自己下载官方文档学习。

Spring4新特性——集成Bean Validation 1.1(JSR-349)到SpringMVC相关推荐

  1. Spring4新特性——Web开发的增强

    2019独角兽企业重金招聘Python工程师标准>>> Spring4新特性--泛型限定式依赖注入 Spring4新特性--核心容器的其他改进 Spring4新特性--Web开发的增 ...

  2. Spring4新特性——核心容器的其他改进

    2019独角兽企业重金招聘Python工程师标准>>> Spring4新特性--泛型限定式依赖注入 Spring4新特性--核心容器的其他改进 Spring4新特性--Web开发的增 ...

  3. Spring4.1新特性——Spring MVC增强

    2019独角兽企业重金招聘Python工程师标准>>> 1.GroovyWebApplicationContext  在Spring 4.1之前没有提供Web集成的Applicati ...

  4. Java Bean Validation 最佳实践

    <h1 class="postTitle"><a id="cb_post_title_url" class="postTitle2& ...

  5. Jakarta Bean Validation,Constrain once, validate everywhere!

    前言 Jakarta Bean Validation到底是什么?我们不妨先看看其官网的相关介绍. Bean Validation 2.0 is a spec! It is done - after o ...

  6. iOS新特性框架、仿微信图片浏览、视频监控、爱心动画、文字适配等源码

    iOS精选源码 iOS一个看电影.电视剧集合 HDCinema 一个非常简易的新特性集成框架NewFeatures 全自动化的文字适配 仿微信朋友圈图片浏览器 iOS你的爱心❤️动画源码 一个类似系统 ...

  7. 详解Bean Validation

    理性阅读,欢迎探讨 1. Bean Validation是什么? Bean Validation 是一个数据验证规范,属于Java EE 6的子规范,详情参考维基百科,这里不做赘述. 既然是规范,那么 ...

  8. Spring4 对Bean Validation规范的新支持(方法级别验证)

    Bean Validation standardizes constraint definition, declaration and validation for the Java platform ...

  9. Spring 4.x框架中的新特性---Spring4.0框架的新功能和改善

    2004年Spring框架首次发布,然后陆续发布了一些重要的版本:Spring2.0提供XML命名空间和AspectJ的支持:Spring2.5包含了注释驱动配置:Spring3.0在框架基础代码中引 ...

最新文章

  1. Behavior Trees
  2. 2020 操作系统第二次习题
  3. [pytorch、学习] - 5.1 二维卷积层
  4. python 2x可以打么_Python打基础一定要吃透这68个内置函数
  5. 山东大学2016-2017校历
  6. python怎么读发音百度翻译-用python实现百度翻译的示例代码
  7. 移动Web开发之流式布局笔记
  8. tf卡可以自己裁剪成nm卡_这些年Surface 3用过的TF卡与购买心得
  9. linux用户态定时器,一种基于linux用户态调用定时器的方法及系统的制作方法
  10. MAC操作之Finder显示、隐藏 文件或文件夹
  11. 【修真院java小课堂】HashMap浅析
  12. 邓俊辉 数据结构 习题4-18 Fermat-Lagrange定理代码实现
  13. Excel - VBA的隔行拷贝功能
  14. 北航2012年软件工程硕士自主招生简章
  15. python二十行代码教你批量采集超高清 jpg
  16. 基于python的个人博客系统的设计开题报告_基于SSM的个人博客系统设计开题报告...
  17. 语言模型 Probability Based: Language Model
  18. loopback端口作用
  19. 1103zxx学习日报
  20. python双星号什么运算_Python3基础 双星号 求一个数的几次幂

热门文章

  1. python比c语言好学吗-对于初学者而言,python和 c语言先学哪个好
  2. 零基础python书籍推荐-非IT行业,零基础自学Python,选什么书?
  3. python编程培训多少钱-python培训费用多少?
  4. python笔记基础-Python入门基础知识学习笔记之一
  5. python turtle画圣诞树-Python画一棵漂亮的樱花树(不同种樱花+玫瑰+圣诞树喔)
  6. python基础语法第10关作业-【python基础语法】第11天作业练习题
  7. python语言if语句-python的if语句
  8. python真的这么厉害吗-Python为什么这么厉害?——Python ,能用来做什么
  9. python列表按照指定顺序排序-python列表排序、字典排序、列表中字典排序
  10. python单下划线和双下线的区别