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实现:

 1 @Slf4j
 2 public class ValidationExceptionResovler extends AbstractHandlerExceptionResolver {
 3     public ValidationExceptionResovler() {
 4         // 设置order,在DefaultHandlerExceptionResolver之前执行
 5         this.setOrder(0);
 6     }
 7
 8     /**
 9      * Handle the case where an argument annotated with {@code @Valid} such as
10      * an {@link } or {@link } argument fails validation.
11      * <p>
12      * 自定义ValidationException 异常处理器
13      * 获取到具体的validation 错误信息,并组装CommonResponse,返回给调用方。
14      *
15      * @param request  current HTTP request
16      * @param response current HTTP response
17      * @param handler  the executed handler
18      * @return an empty ModelAndView indicating the exception was handled
19      * @throws IOException potentially thrown from response.sendError()
20      */
21     @ResponseBody
22     protected ModelAndView handleMethodArgumentNotValidException(BindingResult bindingResult,
23                                                                  HttpServletRequest request,
24                                                                  HttpServletResponse response,
25                                                                  Object handler)
26             throws IOException {
27
28         List<ObjectError> errors = bindingResult.getAllErrors();
29         StringBuffer errmsgBF = new StringBuffer();
30         for (ObjectError error : errors) {
31             String massage = error.getDefaultMessage();
32             errmsgBF.append(massage);
33             errmsgBF.append("||");
34         }
35         String errmsgString = errmsgBF.toString();
36         errmsgString = errmsgString.length() > 2 ? errmsgString.substring(0, errmsgString.length() - 2) : errmsgString;
37         log.error("Validation failed! {} ", errmsgString);
38
39         Map<String, Object> map = new TreeMap<String, Object>();
40         map.put("success", false);
41         map.put("errorCode", "9999");
42         map.put("errorMsg", errmsgString);
43
44         ModelAndView mav = new ModelAndView();
45         MappingJackson2JsonView view = new MappingJackson2JsonView();
46         view.setAttributesMap(map);
47         mav.setView(view);
48
49         return mav;
50     }
51
52     @Override
53     protected ModelAndView doResolveException(HttpServletRequest request,
54                                               HttpServletResponse response, Object handler,
55                                               Exception ex) {
56         BindingResult bindingResult = null;
57         if (ex instanceof MethodArgumentNotValidException) {
58             bindingResult = ((MethodArgumentNotValidException) ex).getBindingResult();
59         } else if(ex instanceof BindException) {
60             bindingResult = ((BindException) ex).getBindingResult();
61         } else {
62             //other exception , ignore
63         }
64
65         if(bindingResult != null) {
66             try {
67                 return handleMethodArgumentNotValidException(bindingResult, request, response, handler);
68             } catch (IOException e) {
69                 log.error("doResolveException: ", e);
70             }
71         }
72
73         return null;
74     }
75 }

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接口:

 1 public class NotNullValidator implements ConstraintValidator<NotNull, Object> {
 2
 3     @Override
 4     public void initialize(NotNull annotation) {
 5
 6     }
 7
 8     @Override
 9     public boolean isValid(Object str, ConstraintValidatorContext constraintValidatorContext) {
10         return str != null;
11     }
12
13 }

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

原文地址:https://www.cnblogs.com/daoqidelv/p/9061862.html

时间: 2024-10-11 19:27:43

springMVC引入Validation详解的相关文章

(转)MVC 3 数据验证 Model Validation 详解

继续我们前面所说的知识点进行下一个知识点的分析,这一次我们来说明一下数据验证.其实这是个很容易理解并掌握的地方,但是这会浪费大家狠多的时间,所以我来总结整理一下,节约一下大家宝贵的时间. 在MVC 3中 数据验证,已经应用的非常普遍,我们在web form时代需要在View端通过js来验证每个需要验证的控件值,并且这种验证的可用性很低.但是来到了MVC 新时代,我们可以通过MVC提供的数据验证Attribute来进行我们的数据验证.并且MVC 提供了客户端和服务器端 双层的验证,只有我们禁用了客

SpringMVC视图机制详解[附带源码分析]

目录 前言 重要接口和类介绍 源码分析 编码自定义的ViewResolver 总结 参考资料 前言 SpringMVC是目前主流的Web MVC框架之一. 如果有同学对它不熟悉,那么请参考它的入门blog:http://www.cnblogs.com/fangjian0423/p/springMVC-introduction.html 本文将分析SpringMVC的视图这部分内容,让读者了解SpringMVC视图的设计原理. 重要接口和类介绍 1. View接口 视图基础接口,它的各种实现类是无

SpringMVC拦截器详解[附带源码分析]

目录 前言 重要接口及类介绍 源码分析 拦截器的配置 编写自定义的拦截器 总结 前言 SpringMVC是目前主流的Web MVC框架之一. 如果有同学对它不熟悉,那么请参考它的入门blog:http://www.cnblogs.com/fangjian0423/p/springMVC-introduction.html 拦截器是每个Web框架必备的功能,也是个老生常谈的主题了. 本文将分析SpringMVC的拦截器功能是如何设计的,让读者了解该功能设计的原理. 重要接口及类介绍 1. Hand

Dubbo+SpringMVC工程创建详解(附工程文件)

Dubbo+SpringMVC工程创建详解(附工程文件) Dubbo出现的目的是为了应对现在高并发,高数据量请求的问题.目前的垂直应用架构已经无法满足现在大数据的冲击,SOA就应运而生,而Dubbo在国内使用的还是比较多,稳定性也比较不错. 架构 节点角色说明: Provider: 暴露服务的服务提供方. Consumer: 调用远程服务的服务消费方. Registry: 服务注册与发现的注册中心. Monitor: 统计服务的调用次调和调用时间的监控中心. Container: 服务运行容器.

&lt;转&gt;ASP.NET学习笔记之MVC 3 数据验证 Model Validation 详解

MVC 3 数据验证 Model Validation 详解 在MVC 3中 数据验证,已经应用的非常普遍,我们在web form时代需要在View端通过js来验证每个需要验证的控件值,并且这种验证的可用性很低.但是来到了MVC 新时代,我们可以通过MVC提供的数据验证Attribute来进行我们的数据验证.并且MVC 提供了客户端和服务器端 双层的验证,只有我们禁用了客户端js以后,也会执行服务端验证,所以大大提高了我们的开发进度.今天我们就一起以一个初学者的身份来进入数据验证的殿堂. 首先,

springMVC的注解详解

springmvc常用注解标签详解 1.@Controller 在SpringMVC 中,控制器Controller 负责处理由DispatcherServlet 分发的请求,它把用户请求的数据经过业务处理层处理之后封装成一个Model ,然后再把该Model 返回给对应的View 进行展示.在SpringMVC 中提供了一个非常简便的定义Controller 的方法,你无需继承特定的类或实现特定的接口,只需使用@Controller 标记一个类是Controller ,然后使用@Request

springmvc RequestMappingHandlerMapping初始化详解

springmvc中配置这个标签默认注册三个bean:RequestMappingHandlerMapping,RequestMappingHandlerAdapter,DefaultHandlerExceptionResolver RequestMappingHandlerMapping 我们看它实现了InitializingBean 接口,所以在getBean()实例化它时会执行afterPropertiesSet()方法,来看该方法干了什么? 方法中实例化了一个BuilderConfigu

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

随着RESTful Web Service的流行,测试对外的Service是否满足期望也变的必要的.从Spring 3.2开始Spring了Spring Web测试框架,如果版本低于3.2,请使用spring-test-mvc项目(合并到spring3.2中了). Spring MVC测试框架提供了对服务器端和客户端(基于RestTemplate的客户端)提供了支持. 对于服务器端:在Spring 3.2之前,我们测试时一般都是直接new控制器,注入依赖,然后判断返回值.但是我们无法连同Spri

springmvc RequestMappingHandlerAdapter初始化详解

我们来看一下RequestMappingHandlerAdapter初始化时做了什么? initControllerAdviceCache()方法是处理注解@ControllerAdvice的,此时我们暂且不关注 这个就比较关键了,注解了很多参数解析器,后文详解#1 返回用于@initbinder方法的参数解析器列表,包括内置的和自定义的解析器.注册步骤和上一步如出一辙不再详述 注册了很多返回值处理器,注册步骤和上一步如出一辙不再详述 书接前文#1 注册了基于注解的参数解析器包括注解@PathV