本文简单介绍如何引入validation的步骤,如何通过自定义validation减少代码量,提高生产力。特别提及:非基本类型属性的valid,GET方法的处理,validation错误信息的统一resolve。

本文中validation的实际实现委托给Hibernate validation处理

基本配置

pom引入maven依赖

<!-- validation begin -->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>1.1.0.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.4.0.Final</version>
</dependency>
<!-- validation end -->

增加validation配置

在spring-mvc-servlet.xml中增加如下配置:

<mvc:annotation-driven validator="validator">

<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
<property name="providerClass" value="org.hibernate.validator.HibernateValidator" />
<property name="validationMessageSource" ref="messageSource"/>
</bean>
 
//messageSource 为i18n资源管理bean,见applicationContext.xml配置

自定义exceptionHandler

个性化处理validation错误信息,返回给调用方的信息更加友好,在applicationContext.xml中增加如下配置:

<!--  加载i18n消息资源文件 -->
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basenames">
<list>
<value>errormsg</value>
<value>validation_error</value>
</list>
</property>
</bean> <bean id="validationExceptionResolver" class="com.*.exception.ValidationExceptionResovler"/>

在项目类路径上增加:validation_error_zh_CN.properties资源文件:

#the error msg for input validation
#common
field.can.not.be.null={field}不能为空
field.can.not.be.empty={field}不能为空或者空字符串
field.must.be.greater.than.min={field}不能小于{value}
field.must.be.letter.than.max={field}不能大于{value}
ValidationExceptionResovler实现:
 @Slf4j
public class ValidationExceptionResovler extends AbstractHandlerExceptionResolver {
public ValidationExceptionResovler() {
// 设置order,在DefaultHandlerExceptionResolver之前执行
this.setOrder(0);
} /**
* Handle the case where an argument annotated with {@code @Valid} such as
* an {@link } or {@link } argument fails validation.
* <p>
* 自定义ValidationException 异常处理器
* 获取到具体的validation 错误信息,并组装CommonResponse,返回给调用方。
*
* @param request current HTTP request
* @param response current HTTP response
* @param handler the executed handler
* @return an empty ModelAndView indicating the exception was handled
* @throws IOException potentially thrown from response.sendError()
*/
@ResponseBody
protected ModelAndView handleMethodArgumentNotValidException(BindingResult bindingResult,
HttpServletRequest request,
HttpServletResponse response,
Object handler)
throws IOException { List<ObjectError> errors = bindingResult.getAllErrors();
StringBuffer errmsgBF = new StringBuffer();
for (ObjectError error : errors) {
String massage = error.getDefaultMessage();
errmsgBF.append(massage);
errmsgBF.append("||");
}
String errmsgString = errmsgBF.toString();
errmsgString = errmsgString.length() > 2 ? errmsgString.substring(0, errmsgString.length() - 2) : errmsgString;
log.error("Validation failed! {} ", errmsgString); Map<String, Object> map = new TreeMap<String, Object>();
map.put("success", false);
map.put("errorCode", "9999");
map.put("errorMsg", errmsgString); ModelAndView mav = new ModelAndView();
MappingJackson2JsonView view = new MappingJackson2JsonView();
view.setAttributesMap(map);
mav.setView(view); return mav;
} @Override
protected ModelAndView doResolveException(HttpServletRequest request,
HttpServletResponse response, Object handler,
Exception ex) {
BindingResult bindingResult = null;
if (ex instanceof MethodArgumentNotValidException) {
bindingResult = ((MethodArgumentNotValidException) ex).getBindingResult();
} else if(ex instanceof BindException) {
bindingResult = ((BindException) ex).getBindingResult();
} else {
//other exception , ignore
} if(bindingResult != null) {
try {
return handleMethodArgumentNotValidException(bindingResult, request, response, handler);
} catch (IOException e) {
log.error("doResolveException: ", e);
}
} return null;
}
}

ValidationExceptionResovler.java

在controller中增加@Valid

@RequestMapping("/buy")
@ResponseBody
public BaseResponse buy(@RequestBody @Valid BuyFlowerRequest request) throws Exception {
//......
}

在request bean上为需要validation的属性增加validation注解

@Setter
@Getter
public class BuyFlowerRequest { @NotEmpty(message = "{name.can.not.be.null}")
private String name;

二级对象的validation

上面的写法,只能对BuyFlowerRequest在基本类型属性上做校验,但是没有办法对对象属性的属性进行validation,如果需要对二级对象的属性进行validation,则需要在二级对象及二级对象属性上同时添加@Valid 和 具体的validation注解.

如下写法:

@Setter
@Getter
public class BuyFlowerRequest { @NotEmpty(field = "花名")
private String name; @Min(field = "价格", value = 1)
private int price; @NotNull
private List<PayType> payTypeList; } @Setter
@Getter
public class PayType { @Valid
@Min(value = 1)
private int payType; @Valid
@Min(value = 1)
private int payAmount; }

进一步减少编码量

为了减少编码工作量,通过自定义Validation注解,尝试将validation作用的filed名称传递到 错误信息的资源文件中,从而避免为每个域编写不同的message模版.

下面以重写的@NotNull为例讲解:

1、定义Validation注解,注意相比原生注解增加了field(),用于传递被validated的filed名字

@Target( { ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER })
@Constraint(validatedBy = { NotNullValidator.class })
@Retention(RetentionPolicy.RUNTIME)
public @interface NotNull { String field() default ""; String message() default "{field.can.not.be.null}"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {};
}

NotNull.java

2、定义Validator,所有的Validator均实现ConstraintValidator接口:

 public class NotNullValidator implements ConstraintValidator<NotNull, Object> {

     @Override
public void initialize(NotNull annotation) { } @Override
public boolean isValid(Object str, ConstraintValidatorContext constraintValidatorContext) {
return str != null;
} }

NotNullValidator.java

3、在filed上加入Validation注解,注意指定filed值,message如果没有个性化需求,可以不用指明,validation组件会自行填充default message。

@Setter
@Getter
public class BuyFlowerRequest { @NotEmpty(field = "花名")
private String name; @Min(field = "价格", value = 1)
private int price; }

BuyFlowerRequest.java

注:@NotNull注解已经支持对list的特殊校验,对于List类型节点,如果list==null || list.size() == 0都会返回false,validation失败。目前已按照此思路自定义实现了@NotNull、@NotEmpty、@Min、@Max注解,在goods工程中可以找到.

支持GET请求

上面的示例都是POST请求,@RequestBody可以 resolve POST请求,但是不支持GET请求,阅读spring的文档和源码,发现@ModelAttribute可以将GET请求resolve成Bean,且支持Validation。具体可以翻阅spring源码:ModelAttributeMethodProcessor.resolveArgument()方法。

使用示例:

@RequestMapping(value = "/buy", method = RequestMethod.GET)
@ResponseBody
public BaseResponse detail(@Valid @ModelAttribute DetailFlowerRequest request) throws Exception { DetailFlowerResponse response = new DetailFlowerResponse();
response.setName(request.getName()); return ResultFactory.success(response, BaseResponse.class);
}

TODO

1、根据业务场景扩展validation,如:日期格式、金额等

2、支持多个field关系校验的validation

附:spring validation实现关键代码

@RequestBody

实现类:RequestResponseBodyMethodProcessor.java

public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
Object arg = this.readWithMessageConverters(webRequest, parameter, parameter.getGenericParameterType());
String name = Conventions.getVariableNameForParameter(parameter);
WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
if (arg != null) {
this.validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors() && this.isBindExceptionRequired(binder, parameter)) {
throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
}
} mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
return arg;
}

@ModelAttibute

实现类:ModelAttributeMethodProcessor.java

public final Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
String name = ModelFactory.getNameForParameter(parameter);
Object attribute = mavContainer.containsAttribute(name) ? mavContainer.getModel().get(name) : this.createAttribute(name, parameter, binderFactory, webRequest);
if (!mavContainer.isBindingDisabled(name)) {
ModelAttribute ann = (ModelAttribute)parameter.getParameterAnnotation(ModelAttribute.class);
if (ann != null && !ann.binding()) {
mavContainer.setBindingDisabled(name);
}
} WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
if (binder.getTarget() != null) {
if (!mavContainer.isBindingDisabled(name)) {
this.bindRequestParameters(binder, webRequest);
} this.validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors() && this.isBindExceptionRequired(binder, parameter)) {
throw new BindException(binder.getBindingResult());
}
} Map<String, Object> bindingResultModel = binder.getBindingResult().getModel();
mavContainer.removeAttributes(bindingResultModel);
mavContainer.addAllAttributes(bindingResultModel);
return binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
}

springMVC引入Validation详解的更多相关文章

  1. <转>ASP.NET学习笔记之MVC 3 数据验证 Model Validation 详解

    MVC 3 数据验证 Model Validation 详解  再附加一些比较好的验证详解:(以下均为引用) 1.asp.net mvc3 的数据验证(一) - zhangkai2237 - 博客园 ...

  2. SpringMVC 常用注解 详解

    SpringMVC 常用注解 详解 SpringMVC 常用注解 1.@RequestMapping                                      路径映射 2.@Requ ...

  3. 《网页设计基础——CSS的四种引入方式详解》

    网页设计基础--CSS的四种引入方式详解     一.行内式:   规则: 1. 行内式是所有样式方法中最为直接的一种,它直接对HTML的标记使用style属性,然后将CSS代码直接写在其中.   格 ...

  4. springMVC的注解详解

    springmvc常用注解标签详解 1.@Controller 在SpringMVC 中,控制器Controller 负责处理由DispatcherServlet 分发的请求,它把用户请求的数据经过业 ...

  5. springmvc常用注解详解

    1.@Controller 在SpringMVC 中,控制器Controller 负责处理由DispatcherServlet 分发的请求,它把用户请求的数据经过业务处理层处理之后封装成一个Model ...

  6. SpringMVC异常处理机制详解[附带源码分析]

    目录 前言 重要接口和类介绍 HandlerExceptionResolver接口 AbstractHandlerExceptionResolver抽象类 AbstractHandlerMethodE ...

  7. springMVC(1)---@RequestMapping详解

    @RequestMapping详解 RequestMapping是一个用来处理请求地址映射的注解,可用于类或方法上.用于类上,表示类中的所有响应请求的方法都是以该地址作为父路径.这句话,太熟悉了.   ...

  8. SpringMvc测试框架详解----服务端测试

    随着RESTful Web Service的流行,测试对外的Service是否满足期望也变的必要的.从Spring 3.2开始Spring了Spring Web测试框架,如果版本低于3.2,请使用sp ...

  9. SpringMVC @RequestMapping 用法详解之地址映射

    @RequestMapping 用法详解之地址映射 http://blog.csdn.net/walkerjong/article/details/7994326

随机推荐

  1. 【Qt编程】QWT在QtCreator中的安装与使用

    由于导师项目的需要,需要画图,二维+三维.三维图我用的是Qt+opengl,二维图我决定使用qwt工具库来加快我的项目进展,毕竟还有期末考试.关于Qt+opengl的使用有时间的话以后再介绍.     ...

  2. eclipse或者AS链接手机真机之后,logcat里面日志信息乱跳

    乱跳的日志信息不会对应用产生影响,但是它会影响视觉,影响查看logcat.那主要原因在哪里呢 ? 这是由于手机里面,正在的运行的进程太多导致的.^_^ 因此课件添加过滤器的作用之大. 对了,在logc ...

  3. Gradle 1.12用户指南翻译——第三十三章. PMD 插件

    本文由CSDN博客万一博主翻译,其他章节的翻译请参见: http://blog.csdn.net/column/details/gradle-translation.html 翻译项目请关注Githu ...

  4. android Native堆

    Android 应用开发大家都知道可以通过DDMS来查看应用程序进程占用的内存大小:然而Native 内存并不能在虚拟堆上看到:Android系统基于Linux,这样的话其具备Linux的大多数特性: ...

  5. 第一个Polymer应用 - (4)收尾工作

    原文链接: Step 4: Finishing touches翻译日期: 2014年7月8日翻译人员: 铁锚在本节中,会在卡片上添加收藏按钮,并可以通过切换选项卡(tabs)连接到不同的 <po ...

  6. unity连接数据库工具

    这里用的是一个集成工具UPUPW(Nginx+mysql+php版本) 网址:http://php.upupw.net/ 数据库登录: 本地: http://127.0.0.1/pmd 外网: htt ...

  7. 基于ARM-contexA9-蜂鸣器pwm驱动开发

    上次,我们写过一个蜂鸣器叫的程序,但是那个程序仅仅只是驱动蜂鸣器,用电平1和0来驱动而已,跟驱动LED其实没什么两样.我们先来回顾一下蜂鸣器的硬件还有相关的寄存器吧: 还是和以前一样的步骤: 1.看电 ...

  8. Populating Next Right Pointers in Each Node(I and II)

    Given a binary tree struct TreeLinkNode { TreeLinkNode *left; TreeLinkNode *right; TreeLinkNode *nex ...

  9. 春天aopframework实现

    Java的代码   包 cn.itcast.day3.aopframework; 进口 java.io.IOException的; 进口 的java.io.InputStream; 进口 java.u ...

  10. JAVA物联网九大核心热点技术

    1. Unix/Linux平台技术: Unix系统原理.Unix常用命令.Shell编程.  2. Java EE核心技术: Java语言核心.Java高级API.JVM及性能优化.Java Secu ...