集成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 …...
idea大量爆红问题解决
问题描述 在学习和工作中,idea是程序员不可缺少的一个工具,但是突然在有些时候就会出现大量爆红的问题,发现无法跳转,无论是关机重启或者是替换root都无法解决 就是如上所展示的问题,但是程序依然可以启动。 问题解决…...
【HarmonyOS 5.0】DevEco Testing:鸿蒙应用质量保障的终极武器
——全方位测试解决方案与代码实战 一、工具定位与核心能力 DevEco Testing是HarmonyOS官方推出的一体化测试平台,覆盖应用全生命周期测试需求,主要提供五大核心能力: 测试类型检测目标关键指标功能体验基…...
Golang dig框架与GraphQL的完美结合
将 Go 的 Dig 依赖注入框架与 GraphQL 结合使用,可以显著提升应用程序的可维护性、可测试性以及灵活性。 Dig 是一个强大的依赖注入容器,能够帮助开发者更好地管理复杂的依赖关系,而 GraphQL 则是一种用于 API 的查询语言,能够提…...
数据链路层的主要功能是什么
数据链路层(OSI模型第2层)的核心功能是在相邻网络节点(如交换机、主机)间提供可靠的数据帧传输服务,主要职责包括: 🔑 核心功能详解: 帧封装与解封装 封装: 将网络层下发…...
智能分布式爬虫的数据处理流水线优化:基于深度强化学习的数据质量控制
在数字化浪潮席卷全球的今天,数据已成为企业和研究机构的核心资产。智能分布式爬虫作为高效的数据采集工具,在大规模数据获取中发挥着关键作用。然而,传统的数据处理流水线在面对复杂多变的网络环境和海量异构数据时,常出现数据质…...
安宝特方案丨船舶智造的“AR+AI+作业标准化管理解决方案”(装配)
船舶制造装配管理现状:装配工作依赖人工经验,装配工人凭借长期实践积累的操作技巧完成零部件组装。企业通常制定了装配作业指导书,但在实际执行中,工人对指导书的理解和遵循程度参差不齐。 船舶装配过程中的挑战与需求 挑战 (1…...
招商蛇口 | 执笔CID,启幕低密生活新境
作为中国城市生长的力量,招商蛇口以“美好生活承载者”为使命,深耕全球111座城市,以央企担当匠造时代理想人居。从深圳湾的开拓基因到西安高新CID的战略落子,招商蛇口始终与城市发展同频共振,以建筑诠释对土地与生活的…...
无人机侦测与反制技术的进展与应用
国家电网无人机侦测与反制技术的进展与应用 引言 随着无人机(无人驾驶飞行器,UAV)技术的快速发展,其在商业、娱乐和军事领域的广泛应用带来了新的安全挑战。特别是对于关键基础设施如电力系统,无人机的“黑飞”&…...
Mysql8 忘记密码重置,以及问题解决
1.使用免密登录 找到配置MySQL文件,我的文件路径是/etc/mysql/my.cnf,有的人的是/etc/mysql/mysql.cnf 在里最后加入 skip-grant-tables重启MySQL服务 service mysql restartShutting down MySQL… SUCCESS! Starting MySQL… SUCCESS! 重启成功 2.登…...
【Linux手册】探秘系统世界:从用户交互到硬件底层的全链路工作之旅
目录 前言 操作系统与驱动程序 是什么,为什么 怎么做 system call 用户操作接口 总结 前言 日常生活中,我们在使用电子设备时,我们所输入执行的每一条指令最终大多都会作用到硬件上,比如下载一款软件最终会下载到硬盘上&am…...
