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

}

以上是 springMVC引入Validation的具体步骤详解 的全部内容, 来源链接: utcz.com/z/344588.html

回到顶部