集成Bean Validation 1.1(JSR-349)到 SpringMVC
Spring4新特性——集成Bean Validation 1.1(JSR-349)到 SpringMVC
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. 混合类级别验证器和跨参数验证器
10. 组合多个验证注解
11. 本地化
因为大多数时候验证都配合web框架使用,而且很多朋友都咨询过如分组/跨参数验证,所以本文介绍下
1、集成Bean Validation 1.1到SpringMVC
1.1、项目搭建
首先添加hibernate validator 5依赖:
<bean
class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMa
pping"/>
<bean
class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAda
pter">
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.0.2.Final</version>
</dependency>如果想在消息中使用EL表达式,请确保EL表达式版本是 2.2或以上,如使用Tomcat6,请到Tomcat7中
拷贝相应的EL jar包到Tomcat6中。
<dependency>
<groupId>javax.el</groupId>
<artifactId>javax.el-api</artifactId>
<version>2.2.4</version>
<scope>provided</scope>
</dependency>
请确保您使用的Web容器有相应版本的el jar包。
对于其他POM依赖请下载附件中的项目参考。
1.2、Spring MVC配置文件(spring-mvc.xml):
<!-- 指定自己定义的validator -->
<mvc:annotation-driven validator="validator"/>
<!-- 以下 validator ConversionService 在使用 mvc:annotation-driven 会 自动注册-
->
<bean id="validator"
class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
<property name="providerClass"
value="org.hibernate.validator.HibernateValidator"/>
<!-- 如果不加默认到 使用classpath下的 ValidationMessages.properties -->
<property name="validationMessageSource" ref="messageSource"/>
</bean>
<!-- 国际化的消息资源文件(本系统中主要用于显示/错误消息定制) -->
<bean id="messageSource"
class="org.springframework.context.support.ReloadableResourceBundleMessageSource
">
<property name="basenames">
<list>
<!-- 在web环境中一定要定位到classpath 否则默认到当前web应用下找 -->
<value>classpath:messages</value>
<value>classpath:org/hibernate/validator/ValidationMessages</value>
</list>
</property>
<property name="useCodeAsDefaultMessage" value="false"/>
<property name="defaultEncoding" value="UTF-8"/>
<property name="cacheSeconds" value="60"/>
</bean>
此处主要把bean validation的消息查找委托给spring的messageSource。
1.3、实体验证注解:public class User implements Serializable {
@NotNull(message = "{user.id.null}")
private Long id;
@NotEmpty(message = "{user.name.null}")
@Length(min = 5, max = 20, message = "{user.name.length.illegal}")
@Pattern(regexp = "[a-zA-Z]{5,20}", message = "{user.name.illegal}")
private String name;
@NotNull(message = "{user.password.null}")
private String password;
}
对于验证规则可以参考官方文档,或者《第七章 注解式控制器的数据验证、类型转换及格式化》。
1.4、错误消息文件messages.properties:
user.id.null=用户编号不能为空
user.name.null=用户名不能为空
user.name.length.illegal=用户名长度必须在5到20之间
user.name.illegal=用户名必须是字母
user.password.null=密码不能为空
1.5、控制器
@Controller
public class UserController {
@RequestMapping("/save")
public String save(@Valid User user, BindingResult result) {
if(result.hasErrors()) {
return "error";
}
return "success";
}
}
1.6、错误页面:
<spring:hasBindErrors name="user">
<c:if test="${errors.fieldErrorCount > 0}">
字段错误:<br/>
<c:forEach items="${errors.fieldErrors}" var="error">
<spring:message var="message" code="${error.code}"
arguments="${error.arguments}" text="${error.defaultMessage}"/>
${error.field}------${message}<br/>
</c:forEach>
</c:if>
<c:if test="${errors.globalErrorCount > 0}">大家以后可以根据这个做通用的错误消息显示规则。比如我前端页面使用validationEngine显示错误消
息,那么我可以定义一个tag来通用化错误消息的显示:showFieldError.tag。
1.7、测试
输入如:http://localhost:9080/spring4/save?name=123 , 我们得到如下错误:
基本的集成就完成了。
如上测试有几个小问题:
1、错误消息顺序,大家可以看到name的错误消息顺序不是按照书写顺序的,即不确定;
2、我想显示如:用户名【zhangsan】必须在5到20之间;其中我们想动态显示:用户名、min,max;
而不是写死了;
3、我想在修改的时候只验证用户名,其他的不验证怎么办。
接下来我们挨着试试吧。
2、分组验证及分组顺序
如果我们想在新增的情况验证id和name,而修改的情况验证name和password,怎么办? 那么就需要
分组了。
首先定义分组接口:
分组接口就是两个普通的接口,用于标识,类似于java.io.Serializable。
全局错误:<br/>
<c:forEach items="${errors.globalErrors}" var="error">
<spring:message var="message" code="${error.code}"
arguments="${error.arguments}" text="${error.defaultMessage}"/>
<c:if test="${not empty message}">
${message}<br/>
</c:if>
</c:forEach>
</c:if>
</spring:hasBindErrors>
name------用户名必须是字母
name------用户名长度必须在5到20之间
password------密码不能为空
id------用户编号不能为空
public interface First {
}
public interface Second {
}接着我们使用分组接口标识实体:
public class User implements Serializable {
@NotNull(message = "{user.id.null}", groups = {First.class})
private Long id;
@Length(min = 5, max = 20, message = "{user.name.length.illegal}", groups =
{Second.class})
@Pattern(regexp = "[a-zA-Z]{5,20}", message = "{user.name.illegal}", groups =
{Second.class})
private String name;
@NotNull(message = "{user.password.null}", groups = {First.class,
Second.class})
private String password;
}
验证时使用如:
@RequestMapping("/save")
public String save(@Validated({Second.class}) User user, BindingResult
result) {
if(result.hasErrors()) {
return "error";
}
return "success";
}
即通过@Validate注解标识要验证的分组;如果要验证两个的话,可以这样@Validated({First.class,
Second.class})。
接下来我们来看看通过分组来指定顺序;还记得之前的错误消息吗? user.name会显示两个错误消息,
而且顺序不确定;如果我们先验证一个消息;如果不通过再验证另一个怎么办?可以通过
@GroupSequence指定分组验证顺序:
@GroupSequence({First.class, Second.class, User.class})
public class User implements Serializable {
private Long id;
@Length(min = 5, max = 20, message = "{user.name.length.illegal}", groups =
{First.class})
@Pattern(regexp = "[a-zA-Z]{5,20}", message = "{user.name.illegal}", groups =
{Second.class})
private String name;
private String password;
}通过@GroupSequence指定验证顺序:先验证First分组,如果有错误立即返回而不会验证Second分
组,接着如果First分组验证通过了,那么才去验证Second分组,最后指定User.class表示那些没有分组
的在最后。这样我们就可以实现按顺序验证分组了。
另一个比较常见的就是级联验证:
如:
1、级联验证只要在相应的字段上加@Valid即可,会进行级联验证;@ConvertGroup的作用是当验证o
的分组是First时,那么验证o的分组是Second,即分组验证的转换。
3、消息中使用EL表达式
假设我们需要显示如:用户名[NAME]长度必须在[MIN]到[MAX]之间,此处大家可以看到,我们不想把
一些数据写死,如NAME、MIN、MAX;此时我们可以使用EL表达式。
如:
错误消息:
其中我们可以使用{验证注解的属性}得到这些值;如{min}得到@Length中的min值;其他的也是类似
的。
到此,我们还是无法得到出错的那个输入值,如name=zhangsan。此时就需要EL表达式的支持,首先
确定引入EL jar包且版本正确。然后使用如:
使用如EL表达式:${validatedValue}得到输入的值,如zhangsan。当然我们还可以使用如${min > 1 ?
'大于1' : '小于等于1'},及在EL表达式中也能拿到如@Length的min等数据。
另外我们还可以拿到一个java.util.Formatter类型的formatter变量进行格式化:
public class User {
@Valid
@ConvertGroup(from=First.class, to=Second.class)
private Organization o;
}
@Length(min = 5, max = 20, message = "{user.name.length.illegal}", groups =
{First.class})
user.name.length.illegal=用户名长度必须在{min}到{max}之间
user.name.length.illegal=用户名[${validatedValue}]长度必须在5到20之间4、方法参数/返回值验证
这个可以参考《Spring3.1 对Bean Validation规范的新支持(方法级别验证)》,概念是类似的,具体可以
参考Bean Validation 文档。
5、自定义验证规则
有时候默认的规则可能还不够,有时候还需要自定义规则,比如屏蔽关键词验证是非常常见的一个功
能,比如在发帖时帖子中不允许出现admin等关键词。
1、定义验证注解
${formatter.format("%04d", min)}
package com.sishuok.spring4.validator;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.*;
/**
* <p>User: Zhang Kaitao
* <p>Date: 13-12-15
* <p>Version: 1.0
*/
@Target({ FIELD, METHOD, PARAMETER, ANNOTATION_TYPE })
@Retention(RUNTIME)
//指定验证器
@Constraint(validatedBy = ForbiddenValidator.class)
@Documented
public @interface Forbidden {
//默认错误消息
String message() default "{forbidden.word}";
//分组
Class<?>[] groups() default { };
//负载
Class<? extends Payload>[] payload() default { };
//指定多个时使用
@Target({ FIELD, METHOD, PARAMETER, ANNOTATION_TYPE })
@Retention(RUNTIME)
@Documented
@interface List {
Forbidden[] value();
}}
2、 定义验证器
package com.sishuok.spring4.validator;
import
org.hibernate.validator.internal.engine.constraintvalidation.ConstraintValidator
ContextImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.util.StringUtils;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.io.Serializable;
/**
* <p>User: Zhang Kaitao
* <p>Date: 13-12-15
* <p>Version: 1.0
*/
public class ForbiddenValidator implements ConstraintValidator<Forbidden, String>
{
private String[] forbiddenWords = {"admin"};
@Override
public void initialize(Forbidden constraintAnnotation) {
//初始化,得到注解数据
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if(StringUtils.isEmpty(value)) {
return true;
}
for(String word : forbiddenWords) {
if(value.contains(word)) {
return false;//验证失败
}
}
return true;
}
}
验证器中可以使用spring的依赖注入,如注入:@Autowired private ApplicationContext ctx;
3、使用public class User implements Serializable {
@Forbidden()
private String name;
}
4、当我们在提交name中含有admin的时候会输出错误消息:
forbidden.word=您输入的数据中有非法关键词
问题来了,哪个词是非法的呢?bean validation 和 hibernate validator都没有提供相应的api提供这个
数据,怎么办呢?通过跟踪代码,发现一种不是特别好的方法:我们可以覆盖
org.hibernate.validator.internal.metadata.descriptor.ConstraintDescriptorImpl实现(即复制一份代
码放到我们的src中),然后覆盖buildAnnotationParameterMap方法;
private Map<String, Object> buildAnnotationParameterMap(Annotation
annotation) {
……
//将Collections.unmodifiableMap( parameters );替换为如下语句
return parameters;
}
即允许这个数据可以修改;然后在ForbiddenValidator中:
for(String word : forbiddenWords) {
if(value.contains(word)) {
((ConstraintValidatorContextImpl)context).getConstraintDescriptor().getAttribute
s().put("word", word);
return false;//验证失败
}
}
通过
((ConstraintValidatorContextImpl)context).getConstraintDescriptor().getAttributes().put("word",
word);添加自己的属性;放到attributes中的数据可以通过${} 获取。然后消息就可以变成:
forbidden.word=您输入的数据中有非法关键词【{word}】
这种方式不是很友好,但是可以解决我们的问题。
典型的如密码、确认密码的场景,非常常用;如果没有这个功能我们需要自己写代码来完成;而且经常
重复自己。接下来看看bean validation 1.1如何实现的。6、类级别验证器
6.1、定义验证注解
6.2、 定义验证器
package com.sishuok.spring4.validator;
import javax.validation.Constraint;
import javax.validation.Payload;
import javax.validation.constraints.NotNull;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.*;
/**
* <p>User: Zhang Kaitao
* <p>Date: 13-12-15
* <p>Version: 1.0
*/
@Target({ TYPE, ANNOTATION_TYPE})
@Retention(RUNTIME)
//指定验证器
@Constraint(validatedBy = CheckPasswordValidator.class)
@Documented
public @interface CheckPassword {
//默认错误消息
String message() default "";
//分组
Class<?>[] groups() default { };
//负载
Class<? extends Payload>[] payload() default { };
//指定多个时使用
@Target({ FIELD, METHOD, PARAMETER, ANNOTATION_TYPE })
@Retention(RUNTIME)
@Documented
@interface List {
CheckPassword[] value();
}
}
package com.sishuok.spring4.validator;
import com.sishuok.spring4.entity.User;
import org.springframework.util.StringUtils;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
/**
* <p>User: Zhang Kaitao* <p>Date: 13-12-15
* <p>Version: 1.0
*/
public class CheckPasswordValidator implements ConstraintValidator<CheckPassword,
User> {
@Override
public void initialize(CheckPassword constraintAnnotation) {
}
@Override
public boolean isValid(User user, ConstraintValidatorContext context) {
if(user == null) {
return true;
}
//没有填密码
if(!StringUtils.hasText(user.getPassword())) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate("{password.null}")
.addPropertyNode("password")
.addConstraintViolation();
return false;
}
if(!StringUtils.hasText(user.getConfirmation())) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate("
{password.confirmation.null}")
.addPropertyNode("confirmation")
.addConstraintViolation();
return false;
}
//两次密码不一样
if (!user.getPassword().trim().equals(user.getConfirmation().trim())) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate("
{password.confirmation.error}")
.addPropertyNode("confirmation")
.addConstraintViolation();
return false;
}
return true;
}
}
其中我们通过disableDefaultConstraintViolation禁用默认的约束;然后通过
buildConstraintViolationWithTemplate(消息模板)/addPropertyNode(所属属
性)/addConstraintViolation定义我们自己的约束。
6.3、使用放到类头上即可。
7、通过脚本验证
通过脚本验证是非常简单而且强大的,lang指定脚本语言(请参考javax.script.ScriptEngineManager
JSR-223),alias是在脚本验证中User对象的名字,但是大家会发现一个问题:错误消息怎么显示呢?
在springmvc 中会添加到全局错误消息中,这肯定不是我们想要的,我们改造下吧。
7.1、定义验证注解
@CheckPassword()
public class User implements Serializable {
}
@ScriptAssert(script = "_this.password==_this.confirmation", lang = "javascript",
alias = "_this", message = "{password.confirmation.error}")
public class User implements Serializable {
}
package com.sishuok.spring4.validator;
import
org.hibernate.validator.internal.constraintvalidators.ScriptAssertValidator;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Target({ TYPE })
@Retention(RUNTIME)
@Constraint(validatedBy = {PropertyScriptAssertValidator.class})
@Documented
public @interface PropertyScriptAssert {
String message() default "
{org.hibernate.validator.constraints.ScriptAssert.message}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
String lang();
String script();String alias() default "_this";
String property();
@Target({ TYPE })
@Retention(RUNTIME)
@Documented
public @interface List {
PropertyScriptAssert[] value();
}
}
和ScriptAssert没什么区别,只是多了个property用来指定出错后给实体的哪个属性。
7.2、验证器
package com.sishuok.spring4.validator;
import javax.script.ScriptException;
import javax.validation.ConstraintDeclarationException;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import com.sishuok.spring4.validator.PropertyScriptAssert;
import org.hibernate.validator.constraints.ScriptAssert;
import org.hibernate.validator.internal.util.Contracts;
import org.hibernate.validator.internal.util.logging.Log;
import org.hibernate.validator.internal.util.logging.LoggerFactory;
import org.hibernate.validator.internal.util.scriptengine.ScriptEvaluator;
import
org.hibernate.validator.internal.util.scriptengine.ScriptEvaluatorFactory;
import static org.hibernate.validator.internal.util.logging.Messages.MESSAGES;
public class PropertyScriptAssertValidator implements
ConstraintValidator<PropertyScriptAssert, Object> {
private static final Log log = LoggerFactory.make();
private String script;
private String languageName;
private String alias;
private String property;
private String message;
public void initialize(PropertyScriptAssert constraintAnnotation) {
validateParameters( constraintAnnotation );
this.script = constraintAnnotation.script();
this.languageName = constraintAnnotation.lang();
this.alias = constraintAnnotation.alias();
this.property = constraintAnnotation.property();
this.message = constraintAnnotation.message();
}public boolean isValid(Object value, ConstraintValidatorContext
constraintValidatorContext) {
Object evaluationResult;
ScriptEvaluator scriptEvaluator;
try {
ScriptEvaluatorFactory evaluatorFactory =
ScriptEvaluatorFactory.getInstance();
scriptEvaluator = evaluatorFactory.getScriptEvaluatorByLanguageName(
languageName );
}
catch ( ScriptException e ) {
throw new ConstraintDeclarationException( e );
}
try {
evaluationResult = scriptEvaluator.evaluate( script, value, alias );
}
catch ( ScriptException e ) {
throw log.getErrorDuringScriptExecutionException( script, e );
}
if ( evaluationResult == null ) {
throw log.getScriptMustReturnTrueOrFalseException( script );
}
if ( !( evaluationResult instanceof Boolean ) ) {
throw log.getScriptMustReturnTrueOrFalseException(
script,
evaluationResult,
evaluationResult.getClass().getCanonicalName()
);
}
if(Boolean.FALSE.equals(evaluationResult)) {
constraintValidatorContext.disableDefaultConstraintViolation();
constraintValidatorContext
.buildConstraintViolationWithTemplate(message)
.addPropertyNode(property)
.addConstraintViolation();
}
return Boolean.TRUE.equals( evaluationResult );
}
private void validateParameters(PropertyScriptAssert constraintAnnotation) {
Contracts.assertNotEmpty( constraintAnnotation.script(),
MESSAGES.parameterMustNotBeEmpty( "script" ) );
Contracts.assertNotEmpty( constraintAnnotation.lang(),
MESSAGES.parameterMustNotBeEmpty( "lang" ) );
Contracts.assertNotEmpty( constraintAnnotation.alias(),
MESSAGES.parameterMustNotBeEmpty( "alias" ) );
Contracts.assertNotEmpty( constraintAnnotation.property(),
MESSAGES.parameterMustNotBeEmpty( "property" ) );
Contracts.assertNotEmpty( constraintAnnotation.message(),
MESSAGES.parameterMustNotBeEmpty( "message" ) );
}
}和之前的类级别验证器类似,就不多解释了,其他代码全部拷贝自
org.hibernate.validator.internal.constraintvalidators.ScriptAssertValidator。
7.3、使用
和之前的区别就是多了个property,用来指定出错时给哪个字段。 这个相对之前的类级别验证器更通用
一点。
8、cross-parameter,跨参数验证
直接看示例;
8.1、首先注册MethodValidationPostProcessor,起作用请参考《Spring3.1 对Bean Validation规范的
新支持(方法级别验证)》
8.2、Service
通过@Validated注解UserService表示该类中有需要进行方法参数/返回值验证; @CrossParameter注
解方法表示要进行跨参数验证;即验证password和confirmation是否相等。
8.3、验证注解
@PropertyScriptAssert(property = "confirmation", script =
"_this.password==_this.confirmation", lang = "javascript", alias = "_this",
message = "{password.confirmation.error}")
<bean
class="org.springframework.validation.beanvalidation.MethodValidationPostProcess
or">
<property name="validator" ref="validator"/>
</bean>
@Validated
@Service
public class UserService {
@CrossParameter
public void changePassword(String password, String confirmation) {
}
}
package com.sishuok.spring4.validator;
//省略import@Constraint(validatedBy = CrossParameterValidator.class)
@Target({ METHOD, CONSTRUCTOR, ANNOTATION_TYPE })
@Retention(RUNTIME)
@Documented
public @interface CrossParameter {
String message() default "{password.confirmation.error}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
8.4、验证器
package com.sishuok.spring4.validator;
//省略import
@SupportedValidationTarget(ValidationTarget.PARAMETERS)
public class CrossParameterValidator implements
ConstraintValidator<CrossParameter, Object[]> {
@Override
public void initialize(CrossParameter constraintAnnotation) {
}
@Override
public boolean isValid(Object[] value, ConstraintValidatorContext context) {
if(value == null || value.length != 2) {
throw new IllegalArgumentException("must have two args");
}
if(value[0] == null || value[1] == null) {
return true;
}
if(value[0].equals(value[1])) {
return true;
}
return false;
}
}
其中@SupportedValidationTarget(ValidationTarget.PARAMETERS)表示验证参数; value将是参数列
表。
8.5、使用调用userService.changePassword方法,如果验证失败将抛出ConstraintViolationException异常,然
后得到ConstraintViolation,调用getMessage即可得到错误消息;然后到前台显示即可。
从以上来看,不如之前的使用方便,需要自己对错误消息进行处理。 下一节我们也写个脚本方式的跨参
数验证器。
9、混合类级别验证器和跨参数验证器
9.1、验证注解
此处我们通过@Constraint指定了两个验证器,一个类级别的,一个跨参数的。validationAppliesTo指
定为ConstraintTarget.IMPLICIT,表示隐式自动判断。
9.2、验证器
@RequestMapping("/changePassword")
public String changePassword(
@RequestParam("password") String password,
@RequestParam("confirmation") String confirmation, Model model) {
try {
userService.changePassword(password, confirmation);
} catch (ConstraintViolationException e) {
for(ConstraintViolation violation : e.getConstraintViolations()) {
System.out.println(violation.getMessage());
}
}
return "success";
}
package com.sishuok.spring4.validator;
//省略import
@Constraint(validatedBy = {
CrossParameterScriptAssertClassValidator.class,
CrossParameterScriptAssertParameterValidator.class
})
@Target({ TYPE, FIELD, PARAMETER, METHOD, CONSTRUCTOR, ANNOTATION_TYPE })
@Retention(RUNTIME)
@Documented
public @interface CrossParameterScriptAssert {
String message() default "error";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
String script();
String lang();
String alias() default "_this";
String property() default "";
ConstraintTarget validationAppliesTo() default ConstraintTarget.IMPLICIT;
}请下载源码查看
9.3、使用
9.3.1、类级别使用
指定property即可,其他和之前的一样。
9.3.2、跨参数验证
通过args[0]==args[1] 来判断是否相等。
这样,我们的验证注解就自动适应两种验证规则了。
10、组合验证注解
有时候,可能有好几个注解需要一起使用,此时就可以使用组合验证注解
这样我们验证时只需要:
简洁多了。
@CrossParameterScriptAssert(property = "confirmation", script =
"_this.password==_this.confirmation", lang = "javascript", alias = "_this",
message = "{password.confirmation.error}")
@CrossParameterScriptAssert(script = "args[0] == args[1]", lang =
"javascript", alias = "args", message = "{password.confirmation.error}")
public void changePassword(String password, String confirmation) {
}
@Target({ FIELD})
@Retention(RUNTIME)
@Documented
@NotNull(message = "{user.name.null}")
@Length(min = 5, max = 20, message = "{user.name.length.illegal}")
@Pattern(regexp = "[a-zA-Z]{5,20}", message = "{user.name.length.illegal}")
@Constraint(validatedBy = { })
public @interface Composition {
String message() default "";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
@Composition()
private String name;11、本地化
即根据不同的语言选择不同的错误消息显示。
1、本地化解析器
此处使用cookie存储本地化信息,当然也可以选择其他的,如Session存储。
2、设置本地化信息的拦截器
即请求参数中通过language设置语言。
3、消息文件
4、 浏览器输入
http://localhost:9080/spring4/changePassword?password=1&confirmation=2&language=en_US
到此,我们已经完成大部分Bean Validation的功能实验了。对于如XML配置、编程式验证API的使用等
对于我们使用SpringMVC这种web环境用处不大,所以就不多介绍了,有兴趣可以自己下载官方文档学
习。
<bean id="localeResolver"
class="org.springframework.web.servlet.i18n.CookieLocaleResolver">
<property name="cookieName" value="locale"/>
<property name="cookieMaxAge" value="-1"/>
<property name="defaultLocale" value="zh_CN"/>
</bean>
<mvc:interceptors>
<bean
class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor">
<property name="paramName" value="language"/>
</bean>
</mvc:interceptors>
相关文章:
集成Bean Validation 1.1(JSR-349)到 SpringMVC
Spring4新特性——集成Bean Validation 1.1(JSR-349)到 SpringMVC Bean Validation 1.1当前实现是Hibernate validator 5,且spring4才支持。接下来我们从以下几个方法 讲解Bean Validation 1.1,当然不一定是新特性: 1. 集成Bean Valida…...

【软考中级】软件设计师选择题题集(一)
海明校验码是在n个数据位之外增设k个校验位,从而形成一个k+n位的新的码字, 使新的码字的码距比较均匀地拉大。n与k的关系是(1)。 (1)A.2k - 1≥n + k B.2n - 1≤ n + k C.n = k D.n-1≤k 【答案】A 【解析】 【答案】B A 【解析】 在采用结构化方法进行系统分析时,…...
10个炫酷特效的网页写法(附源码),拿去就能用,奈斯奈斯
这是我借鉴其他博主的,给网页加个背景,给鼠标加个特效,“使用简单”,“效果爆炸”的页面,分享给大家,如果觉得有帮助可以点赞收藏支持一下,如果能关注一下就再好不过了。 内容转载于https://bl…...
vscode开发stm32的编译环境配置教程
文章目录 1. 背景2. 配置步骤2.1. vscode插件安装2.2. 新建工程2.3. 配置编译环境2.3.1. 芯片支持2.3.2. 编译器选择2.3.3. CPU类型2.3.4. 硬件浮点选项2.3.5. 使用自定义的链接脚本2.3.6. RAM/FLASH布局2.3.7. 构建器选项2.4. 编译3. 问题汇总3.1. 浮点编译开关3.2. MicroLIB编…...

Live800在线客服系统:客户体验即是业绩来源
“客户就是上帝”,这一论断在商业行为中早已成为不争的事实。 所有企业都知道,满足客户的需求才能让企业稳步发展。在产品同质化严重的今天,客户服务成为核心竞争力,试想一下,在产品、价格相差无几的两个企业中&#…...

SAP工具箱 MR22自定义BAPI
点击蓝字 关注我们 一 前言 标准事务代码MR22 通过调整金额影响物料的成本价,前台界面中单个凭证中允许输入多行物料, 但是对应的BAPI函数仅支持输入单行物料 BAPI_MATVAL_DEBIT_CREDIT 正常库存BAPI_SALESORDSTCK_DEBIT_CREDIT 销售订单库存 这种情况 婶可忍叔不可忍 (感谢用户…...
ASP.NET 网上选课系统的设计与实现(源代码+论文)
随着教育改革的不断深化,建立一套能够适应这些改变的行政管理方案也就显得尤为重要。在高等院校的日常工作中,每个学期都要面临学生的选课工作。以往在选课工作的各个阶段都是手工操作,不但效率低下、工作繁琐,而且容易出错,于是开发一套适合校情的网上选课系统便成为了教…...

SpringMVC学习总结(路由映射、参数传递、转发和重定向...)
目录 1. MVC简介 2. SpringMVC简介 3. 路由映射注解 3.1 RequestMapping 3.2 GetMapping与PostMapping 4. 接收前端传递参数 4.1 接收单/多个参数 4.2 接收对象 4.3 接收JSON对象 4.4 后端参数重命名/映射 4.5 设置参数必传/非必传 4.6 获取URL中的参数 4.7 获取文…...

基于MAC地址的ACL配置
基于MAC地址的ACL配置 【实验目的】 掌握基于MAC地址的标准ACL的配置。验证配置。 【实验拓扑】 实验拓扑如图1所示。 图1 实验拓扑 设备参数如表所示。 表1 设备参数表 设备 接口 IP地址 子网掩码 默认网关 S1 e0/0 N/A N/A N/A e0/1 N/A N/A N/A PC1 N/…...

软件设计师 计算机网络
名字带IP AP的都是网络层 所有带T的除了TFTP其他都是TCP,所有不带T的除了POP3其他都是UDP **物理层的互联设备有中继器和集线器,集线器是一种特殊的多路多端口中继器 网络层:路由器 物理层:中继器 数据链路层:网桥…...

Gradle ——Gradle安装与配置
目录 一、简介 二、功能和特点 三、安装 参考: Gradle_百度百科 Gradle 学习 ----Gradle 入门_你若不离不弃,我必生死相依的博客-CSDN博客 还有硬石科技的开源wifiAPP程序,没找到他们的码仓 一、简介 Gradle是一个基于Apache Ant和Apache Maven概念…...

网络安全这条路到底该怎么走?
我之前就写过一篇文章专门解答了这个问题。但是还是有很多小伙伴并不清楚这条路该怎么走下去! 不同于Java、C/C等后端开发岗位有非常明晰的学习路线,网路安全更多是靠自己摸索,要学的东西又杂又多,难成体系。 网络安全虽然是计算…...

【C++】位图(海量数据处理)
文章目录 抛出问题:引入位图位图解决 位图的概念位图的实现结构构造函数设置位清空位判断这个数是否存在反转位size与count打印函数 位图的应用 抛出问题:引入位图 问题:给40亿个不重复的无符号整数,没排序,给一个无符号整数,如何…...

外包干了五年,废了...
先说一下自己的情况。大专生,17年通过校招进入湖南某软件公司,干了接近5年的测试点点点,今年年上旬,感觉自己不能够在这样下去了,长时间呆在一个舒适的环境会让一个人堕落!而我已经在一个企业干了五年的点工…...

请问你如何理解以下的歌词“unravel - TK from 凛冽时雨 (TK from 凛として時雨)为什么很多人说崖山海战以后无中国
目录 请问你如何理解以下的歌词“unravel - TK from 凛冽时雨 (TK from 凛として時雨) 为什么很多人说崖山海战以后无中国 请问你如何理解以下的歌词“unravel - TK from 凛冽时雨 (TK from 凛として時雨) 以下是我对《unravel - TK from 凛冽时雨》这首歌词的理解࿱…...

从8连挂到面面offer,我只用了一个月,面试25K测试岗血泪经验分享给你
直到如今,我才敢把这段经历分享出来,毕竟一个多月前,我是经历了面试八连挂的人。作为一只骄傲的软件测试工程师,恨不得找一块豆腐撞死。但是在闭关修炼了一个多月之后,重新出来面试,面试了五家公司…...

计算机操作系统(慕课版)第二章课后题答案
一、简答题 (1)什么是前趋图?试画出下面四条语句的前趋图. S1:axy; S2:bz1; S3:ca-b; S4:wc1; 答:前趋图(Precedence Graph)是一个有向无循环图,…...
【离散数学】置换群和伯恩赛德定理编程题
1:置换的轮换表示 给出一个置换,写出该置换的轮换表示。比如 (1 2 3 4 5 6 7 8 9) (3 1 6 2 9 7 8 4 5) 表示为(1 3 6 7 8 4 2)(5 9) 输入: 置换后的序列 输出: 不相杂的轮换乘积,每行表示一个轮换(轮换的起…...

【自然语言处理】 - 作业2: seq2seq模型机器翻译
课程链接: 清华大学驭风计划 代码仓库:Victor94-king/MachineLearning: MachineLearning basic introduction (github.com) 驭风计划是由清华大学老师教授的,其分为四门课,包括: 机器学习(张敏教授) , 深度学习(胡晓林教授), 计算…...

随身WIFI折腾日记(四)---拓展USB接口读取U盘内容
五、USB行为控制 随身WIFI对外交互的接口只有WIFI和USB接口。如果要想接入其他硬件设备,拓展USB接口至关重要,对于USB接口的控制,参考如下链接: openstick项目官方教程:控制usb行为 HandsomeMod/gc: A Simple Tool To Control Usb Gadget …...
SkyWalking 10.2.0 SWCK 配置过程
SkyWalking 10.2.0 & SWCK 配置过程 skywalking oap-server & ui 使用Docker安装在K8S集群以外,K8S集群中的微服务使用initContainer按命名空间将skywalking-java-agent注入到业务容器中。 SWCK有整套的解决方案,全安装在K8S群集中。 具体可参…...
DeepSeek 赋能智慧能源:微电网优化调度的智能革新路径
目录 一、智慧能源微电网优化调度概述1.1 智慧能源微电网概念1.2 优化调度的重要性1.3 目前面临的挑战 二、DeepSeek 技术探秘2.1 DeepSeek 技术原理2.2 DeepSeek 独特优势2.3 DeepSeek 在 AI 领域地位 三、DeepSeek 在微电网优化调度中的应用剖析3.1 数据处理与分析3.2 预测与…...

《Qt C++ 与 OpenCV:解锁视频播放程序设计的奥秘》
引言:探索视频播放程序设计之旅 在当今数字化时代,多媒体应用已渗透到我们生活的方方面面,从日常的视频娱乐到专业的视频监控、视频会议系统,视频播放程序作为多媒体应用的核心组成部分,扮演着至关重要的角色。无论是在个人电脑、移动设备还是智能电视等平台上,用户都期望…...
Python爬虫(二):爬虫完整流程
爬虫完整流程详解(7大核心步骤实战技巧) 一、爬虫完整工作流程 以下是爬虫开发的完整流程,我将结合具体技术点和实战经验展开说明: 1. 目标分析与前期准备 网站技术分析: 使用浏览器开发者工具(F12&…...

多种风格导航菜单 HTML 实现(附源码)
下面我将为您展示 6 种不同风格的导航菜单实现,每种都包含完整 HTML、CSS 和 JavaScript 代码。 1. 简约水平导航栏 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport&qu…...

(一)单例模式
一、前言 单例模式属于六大创建型模式,即在软件设计过程中,主要关注创建对象的结果,并不关心创建对象的过程及细节。创建型设计模式将类对象的实例化过程进行抽象化接口设计,从而隐藏了类对象的实例是如何被创建的,封装了软件系统使用的具体对象类型。 六大创建型模式包括…...

STM32---外部32.768K晶振(LSE)无法起振问题
晶振是否起振主要就检查两个1、晶振与MCU是否兼容;2、晶振的负载电容是否匹配 目录 一、判断晶振与MCU是否兼容 二、判断负载电容是否匹配 1. 晶振负载电容(CL)与匹配电容(CL1、CL2)的关系 2. 如何选择 CL1 和 CL…...
tomcat入门
1 tomcat 是什么 apache开发的web服务器可以为java web程序提供运行环境tomcat是一款高效,稳定,易于使用的web服务器tomcathttp服务器Servlet服务器 2 tomcat 目录介绍 -bin #存放tomcat的脚本 -conf #存放tomcat的配置文件 ---catalina.policy #to…...

Unity VR/MR开发-VR开发与传统3D开发的差异
视频讲解链接:【XR马斯维】VR/MR开发与传统3D开发的差异【UnityVR/MR开发教程--入门】_哔哩哔哩_bilibili...

GAN模式奔溃的探讨论文综述(一)
简介 简介:今天带来一篇关于GAN的,对于模式奔溃的一个探讨的一个问题,帮助大家更好的解决训练中遇到的一个难题。 论文题目:An in-depth review and analysis of mode collapse in GAN 期刊:Machine Learning 链接:...