springMVC引入Validation的具体步骤详解

本文简单介绍如何引入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实现:

ValidationExceptionResovler.java

@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;
  }
}

在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名字

NotNull.java

@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 {};
}

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

NotNullValidator.java

public class NotNullValidator implements ConstraintValidator<NotNull, Object> {

  @Override
  public void initialize(NotNull annotation) {

  }

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

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

BuyFlowerRequest.java

@Setter
@Getter
public class BuyFlowerRequest {

  @NotEmpty(field = "花名")
  private String name;

  @Min(field = "价格", value = 1)
  private int price;
}

注:@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);
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • 详解SpringMVC验证框架Validation特殊用法

    基本用法不说了,网上例子很多,这里主要介绍下比较特殊情况下使用的方法. 1. 分组 有的时候,我们对一个实体类需要有多中验证方式,在不同的情况下使用不同验证方式,比如说对于一个实体类来的id来说,保存的时候是不需要的,对于更新时是必须的,可以如下配置: public class UserModel { @NotNull(message = "{id.empty}", groups = { First.class }) private int id; @NotNull(message =

  • springMVC引入Validation的具体步骤详解

    本文简单介绍如何引入validation的步骤,如何通过自定义validation减少代码量,提高生产力.特别提及:非基本类型属性的valid,GET方法的处理,validation错误信息的统一resolve. 本文中validation的实际实现委托给Hibernate validation处理 基本配置 pom引入maven依赖 <!-- validation begin --> <dependency> <groupId>javax.validation<

  • SpringMVC通过Ajax处理Json数据的步骤详解

    SpringMVC通过Ajax处理Json数据的实现 一.前言: Ajax: 在不重新加载整个页面的前提下,对网页的部分进行刷新,例如点赞,发送短信验证码功能等等,都可以通过ajax进行实现,Ajax实现了网页的异步刷新 想要实现的效果,点击按钮,前端显示后台发送过来的学生信息,本次我们使用jquery形式的Ajax来完成 二.使用步骤 1.引入jar jar: jackson-annotation.jar jackson-core.jar jackson-datebind.jar 如果不使用M

  • vue中引入mxGraph的步骤详解

    第一步:下载npm包 npm install mxgraph --save 第二步:新建一个index.js文件 文件内容如下 import mx from 'mxgraph'; const mxgraph = mx({ mxImageBasePath: './src/images', mxBasePath: './src' }); // decode bug https://github.com/jgraph/mxgraph/issues/49 window.mxGraph = mxgraph

  • Spring引入外部属性文件配置数据库连接的步骤详解

    直接配置数据库的信息 xml配置文件直接配置: <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.sprin

  • uniapp引入支付宝原生扫码插件步骤详解

    目录 1.打开插件市场 2.阿里云操作 开通阿里云 mPaaS 创建 mPaaS 应用 配置应用 3.配置项目 打开manifest.json 1.打开插件市场 链接:ext.dcloud.net.cn/plugin?id=2… 点击红色框按钮 绑定包名,这个包名与后续有关系,请记录下 2.阿里云操作 开通阿里云 mPaaS 打开mPaaS产品 创建 mPaaS 应用 创建一个应用效果如下 配置应用 点击你创建用的应用进去里面的配置页面 绑定刚才第一步我们绑定的包名,还有上次这个包名打包后的ap

  • SpringMVC加载控制与Postmand的使用和Rest风格的引入及RestFul开发全面详解

    目录 前言 一.bean的加载控制 二.容器加载 1.createServletApplicationContext()方法 2.createRootApplicationContext()方法 3.getServletMappings()方法 三.PostMan的引入 1.发送GET请求 2.发送POST请求 3.中文乱码问题解决 四.Rest风格 1.REST简介 2.RESTful传参 3.RESTful简便形式(快速开发) 4.放行静态资源 前言 从繁到简是贯彻SSM学习过程的原始真解

  • SpringBoot整合Swagger2的步骤详解

    简介 swagger是一个流行的API开发框架,这个框架以"开放API声明"(OpenAPI Specification,OAS)为基础, 对整个API的开发周期都提供了相应的解决方案,是一个非常庞大的项目(包括设计.编码和测试,几乎支持所有语言). springfox大致原理: springfox的大致原理就是,在项目启动的过种中,spring上下文在初始化的过程, 框架自动跟据配置加载一些swagger相关的bean到当前的上下文中,并自动扫描系统中可能需要生成api文档那些类,

  • springmvc与mybatis集成配置实例详解

    简单之美,springmvc,mybatis就是一个很好的简单集成方案,能够满足一般的项目需求.闲暇时间把项目配置文件共享出来,供大家参看: 1.首先我们来看下依赖的pom: <!-- spring --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${spring.ve

  • jstl标签基础开发步骤(详解)

    step1. 导包(导入要使用的标签的jar文件). step2. 使用taglib指令引入要使用的标签. taglib指令: uri:标签的命名空间.     prefix:命名空间的别名. 注: 命名空间:是为了区分同名的元素而添加的前缀. 自定义标签: step1. 写一个java类,继承SimpleTagSupport类. step2. 在doTag方法里面,编写处理逻辑. step3. 描述标签.(.tld文件里面) 注: <body-content>的值可以是 empty  没有标

  • vue实现新闻展示页的步骤详解

    需求 1.  做一个新闻展示页 2.  新闻分类可以自定义 3.  每类新闻的内容,样式不一样 4.  上拉加载新的数据 5.  点击进入详情页,再返回时,定位到原来的位置 图片展示 采用的技术 轮播图使用:swiper     zepto.js     vue.js     vue.resource.js     vue-router.js 滑动插件:iscroll.js 界面样式采用的   weui 设计的思路 因为使用了vue.js  每个分类的样式不一样,而且分类是可以配置的.所以呢,想

随机推荐