Spring4新特性——集成Bean Validation 1.1(JSR-349)到SpringMVC ?
Spring4新特性——注解、腳本、任務、MVC等其他特性改進 ?
?
在之前的《 跟我學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,當然不一定是新特性:
- ?集成Bean Validation 1.1到SpringMVC
- ?分組驗證、分組順序及級聯驗證
- ?消息中使用EL表達式
- ?方法參數/返回值驗證
- ?自定義驗證規則
- ?類級別驗證器
- ?腳本驗證器
- ?cross-parameter,跨參數驗證
- 混合類級別驗證器和跨參數驗證器
- 組合多個驗證注解
- 本地化
因為大多數時候驗證都配合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依賴:
<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}">
全局錯誤:<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>
?
大家以后可以根據這個做通用的錯誤消息顯示規則。比如我前端頁面使用validationEngine顯示錯誤消息,那么我可以定義一個tag來通用化錯誤消息的顯示: showFieldError.tag 。??
?
1.7、測試
輸入如: http://localhost:9080/spring4/save?name=123 ?, 我們得到如下錯誤:
name------用戶名必須是字母
name------用戶名長度必須在5到20之間
password------密碼不能為空
id------用戶編號不能為空
?
基本的集成就完成了。
?
如上測試有幾個小問題:
1、錯誤消息順序,大家可以看到name的錯誤消息順序不是按照書寫順序的,即不確定;
2、我想顯示如:用戶名【zhangsan】必須在5到20之間;其中我們想動態顯示:用戶名、min,max;而不是寫死了;
3、我想在修改的時候只驗證用戶名,其他的不驗證怎么辦。
接下來我們挨著試試吧。
?
2、分組驗證及分組順序
如果我們想在新增的情況驗證id和name,而修改的情況驗證name和password,怎么辦? 那么就需要分組了。
首先定義分組接口:
public interface First {
}
public interface Second {
}
分組接口就是兩個普通的接口,用于標識,類似于java.io.Serializable。
?
接著我們使用分組接口標識實體:
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表示那些沒有分組的在最后。這樣我們就可以實現按順序驗證分組了。
?
另一個比較常見的就是級聯驗證:
如:
public class User {
@Valid
@ConvertGroup(from=First.class, to=Second.class)
private Organization o;
}
?1、級聯驗證只要在相應的字段上加@Valid即可,會進行級聯驗證;@ ConvertGroup的作用是當驗證o的分組是First時,那么驗證o的分組是Second,即分組驗證的轉換。
?
3、消息中使用EL表達式
假設我們需要顯示如:用戶名[NAME]長度必須在[MIN]到[MAX]之間,此處大家可以看到,我們不想把一些數據寫死,如NAME、MIN、MAX;此時我們可以使用EL表達式。
?
如:
@Length(min = 5, max = 20, message = "{user.name.length.illegal}", groups = {First.class})
錯誤消息:
user.name.length.illegal=用戶名長度必須在{min}到{max}之間
?
其中我們可以使用{驗證注解的屬性}得到這些值;如{min}得到@Length中的min值;其他的也是類似的。
?
到此,我們還是無法得到出錯的那個輸入值,如name=zhangsan。此時就需要EL表達式的支持,首先確定引入EL jar包且版本正確。然后使用如:
user.name.length.illegal=用戶名[${validatedValue}]長度必須在5到20之間
使用如EL表達式:${validatedValue}得到輸入的值,如zhangsan。當然我們還可以使用如${min > 1 ? '大于1' : '小于等于1'},及在EL表達式中也能拿到如@Length的min等數據。
?
另外我們還可以拿到一個java.util.Formatter類型的formatter變量進行格式化:
${formatter.format("%04d", min)}
?
4、方法參數/返回值驗證
這個可以參考《 Spring3.1 對Bean Validation規范的新支持(方法級別驗證) ?》,概念是類似的,具體可以參考Bean Validation 文檔。
?
5、自定義驗證規則
有時候默認的規則可能還不夠,有時候還需要自定義規則,比如屏蔽關鍵詞驗證是非常常見的一個功能,比如在發帖時帖子中不允許出現admin等關鍵詞。
?
1、定義驗證注解
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.ConstraintValidatorContextImpl;
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().getAttributes().put("word", word);
return false;//驗證失敗
}
}
通過((ConstraintValidatorContextImpl)context).getConstraintDescriptor().getAttributes().put("word", word);添加自己的屬性;放到attributes中的數據可以通過${}?獲取。然后消息就可以變成:
forbidden.word=您輸入的數據中有非法關鍵詞【{word}】
這種方式不是很友好,但是可以解決我們的問題。
?
典型的如密碼、確認密碼的場景,非常常用;如果沒有這個功能我們需要自己寫代碼來完成;而且經常重復自己。接下來看看bean validation 1.1如何實現的。
?
6、類級別驗證器
6.1、定義驗證注解
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();
}
}
6.2、 定義驗證器
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、使用
@CheckPassword()
public class User implements Serializable {
}
?放到類頭上即可。
?
7、通過腳本驗證
@ScriptAssert(script = "_this.password==_this.confirmation", lang = "javascript", alias = "_this", message = "{password.confirmation.error}")
public class User implements Serializable {
}
通過腳本驗證是非常簡單而且強大的,lang指定腳本語言(請參考javax.script.ScriptEngineManager JSR-223),alias是在腳本驗證中User對象的名字,但是大家會發現一個問題:錯誤消息怎么顯示呢? 在springmvc 中會添加到全局錯誤消息中,這肯定不是我們想要的,我們改造下吧。
?
7.1、定義驗證注解
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、使用
@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規范的新支持(方法級別驗證) ?》?
<bean class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor">
<property name="validator" ref="validator"/>
</bean>
?
8.2、Service?
@Validated
@Service
public class UserService {
@CrossParameter
public void changePassword(String password, String confirmation) {
}
}
通過@Validated注解UserService表示該類中有需要進行方法參數/返回值驗證;???@CrossParameter注解方法表示要進行跨參數驗證;即驗證password和confirmation是否相等。
?
8.3、驗證注解?
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、使用
@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";
}
調用userService.changePassword方法,如果驗證失敗將拋出ConstraintViolationException異常,然后得到ConstraintViolation,調用getMessage即可得到錯誤消息;然后到前臺顯示即可。
?
從以上來看,不如之前的使用方便,需要自己對錯誤消息進行處理。 下一節我們也寫個腳本方式的跨參數驗證器。
?
9、混合類級別驗證器和跨參數驗證器
9.1、驗證注解
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;
}?
?
此處我們通過@Constraint指定了兩個驗證器,一個類級別的,一個跨參數的。validationAppliesTo指定為ConstraintTarget.IMPLICIT,表示隱式自動判斷。
?
9.2、驗證器
請下載源碼查看
?
9.3、使用
9.3.1、類級別使用
@CrossParameterScriptAssert(property = "confirmation", script = "_this.password==_this.confirmation", lang = "javascript", alias = "_this", message = "{password.confirmation.error}")
指定property即可,其他和之前的一樣。
9.3.2、跨參數驗證
@CrossParameterScriptAssert(script = "args[0] == args[1]", lang = "javascript", alias = "args", message = "{password.confirmation.error}")
public void changePassword(String password, String confirmation) {
}
通過args[0]==args[1] 來判斷是否相等。
?
這樣,我們的驗證注解就自動適應兩種驗證規則了。??
?
10、組合驗證注解?
有時候,可能有好幾個注解需要一起使用,此時就可以使用組合驗證注解
@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、本地化解析器
<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>
此處使用cookie存儲本地化信息,當然也可以選擇其他的,如Session存儲。
?
2、設置本地化信息的攔截器
<mvc:interceptors>
<bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor">
<property name="paramName" value="language"/>
</bean>
</mvc:interceptors>
即請求參數中通過language設置語言。
?
3、消息文件
?
4、 瀏覽器輸入
http://localhost:9080/spring4/changePassword?password=1&confirmation=2& language=en_US
?
?
?
到此,我們已經完成大部分Bean Validation的功能實驗了。對于如XML配置、編程式驗證API的使用等對于我們使用SpringMVC這種web環境用處不大,所以就不多介紹了,有興趣可以自己下載官方文檔學習。
?
?
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061
微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元

