SpringMVC之参数检验篇

项目开发中我们经常需要对接口参数进行检验,有些参数必须有值,有些参数比如手机号,身份证号必须符合一定的格式规范。如果把参数检验都写在业务代码中,会造成业务代码又长又杂,且每个接口都得去校验,影响开发效率以及代码质量。有没有方法可以把参数检验统一在一个地方进行,不对我们的业务代码进行干扰呢?

SpringMVC给我们提供了一些支持数据校验的注解,如@NotNull,@NotBlank,@Length(min=,max=)等,将这些注解添在bean的属性上,结合在Controller里对请求参数添加@Valid注解即可完成参数的校验。

实例演练:

1、创建需要被校验的实体类:

package com.ouyangjia.miaosha.vo;

import lombok.Data;
import org.hibernate.validator.constraints.NotBlank;
@Data
public class LoginVo {
    @NotBlank(message="电话号码不能为空")
    private String phone;
    @NotBlank(message="密码不能为空")
    private String password;
}

2、controller 接口设计,在参数接受的地方添加 @Valid 关键字:

@RequestMapping("/do_login_1")
@ResponseBody
public Result<String> doLogin_1(@RequestBody@Valid LoginVo loginVo){
    log.info(loginVo.toString());
    String str=loginService.login(loginVo);
    return Result.success(str);
}

3、访问测试,数据如下:

{
    "phone":"",
    "password":""
}

4、响应为:

{
    "timestamp": 1537152065877,
    "status": 400,
    "error": "Bad Request",
    "exception": "org.springframework.web.bind.MethodArgumentNotValidException",
    "errors": [
        {
            "codes": [
                "NotBlank.loginVo.password",
                "NotBlank.password",
                "NotBlank.java.lang.String",
                "NotBlank"
            ],
            "arguments": [
                {
                    "codes": [
                        "loginVo.password",
                        "password"
                    ],
                    "arguments": null,
                    "defaultMessage": "password",
                    "code": "password"
                }
            ],
            "defaultMessage": "密码不能为空",
            "objectName": "loginVo",
            "field": "password",
            "rejectedValue": "",
            "bindingFailure": false,
            "code": "NotBlank"
        },
        {
            "codes": [
                "NotBlank.loginVo.phone",
                "NotBlank.phone",
                "NotBlank.java.lang.String",
                "NotBlank"
            ],
            "arguments": [
                {
                    "codes": [
                        "loginVo.phone",
                        "phone"
                    ],
                    "arguments": null,
                    "defaultMessage": "phone",
                    "code": "phone"
                }
            ],
            "defaultMessage": "电话号码不能为空",
            "objectName": "loginVo",
            "field": "phone",
            "rejectedValue": "",
            "bindingFailure": false,
            "code": "NotBlank"
        }
    ],
    "message": "Validation failed for object='loginVo'. Error count: 2",
    "path": "/login/do_login_1.json"
}

我们可以看到,抛出了MethodArgumentNotValidException异常,由于我们没有对异常进行处理,异常信息将会直接展现在页面上,对用户很不友好。

 

改进一:在接口中获取异常信息,并按照统一格式返回。

如果@Valid校验不通过,那么错误信息就会封装到BindingResult对象了,可以通过BindingResult的相关方法获取详细的错误信息并返回给用户。 代码如下:

@RequestMapping("/do_login_2")
@ResponseBody
public Result<String> doLogin_2(@RequestBody@Valid LoginVo loginVo,BindingResult bindingResult){
    StringBuffer stringBuffer=new StringBuffer();
    if(bindingResult.hasErrors()){
        List<ObjectError> errorList = bindingResult.getAllErrors();
        for(ObjectError error:errorList){
            stringBuffer.append(error.getDefaultMessage());
        }
        return Result.fail(CodeMsg.BIND_ERROR.fillArgs(stringBuffer.toString()));
    }
    log.info(loginVo.toString());
    String str=loginService.login(loginVo);
    return Result.success(str);
}

响应如下:

{
    "code": "500101",
    "msg": "参数校验异常:密码不能为空电话号码不能为空",
    "data": null
}

虽然这样能够对异常进行处理,但是在controller中除了正常的业务代码外,还有很大一部分代码用于处理BindingResult对象。造成了对业务代码的侵入,我们希望能够在一个地方统一处理这些异常。

 

改进二:异常统一处理

使用@ControllerAdvice+@ExceptionHandler进行全局的Controller层异常处理,其中@ControllerAdvice定义了全局异常处理类,@ExceptionHandler则声明异常处理的方法。这样我们不仅能够处理@Valid检验器的异常,还可以处理controller层的其他异常,少了很多try,catch的代码,是不是棒棒哒!

注:@ControllerAdvice注解,字面意思控制器增强。可用于定义@ExceptionHandler,@InitBinder,@ModelAttribute,并将应用到所有的@RequestMapping方法中

实例演练:

@ControllerAdvice
@ResponseBody
public class GlobalExceptionHandler {

    @ExceptionHandler(value = Exception.class)
    public Result<String> exceptionHandler(HttpServletRequest request, Exception e){
        e.printStackTrace();
        if(e instanceof MethodArgumentNotValidException){
            MethodArgumentNotValidException ex= (MethodArgumentNotValidException) e;
            List<ObjectError> errors = ex.getBindingResult().getAllErrors();
            ObjectError error = errors.get(0);
            String msg = error.getDefaultMessage();
            return Result.fail(CodeMsg.BIND_ERROR.fillArgs(msg));
        }else {
            return Result.fail(CodeMsg.SERVER_ERROR);
        }
    }
}

这样,我们的controller中的代码如下:

@RequestMapping("/do_login")
@ResponseBody
public Result<String> doLogin(HttpServletResponse response,@Valid LoginVo loginVo) {
    log.info(loginVo.toString());
    //登录
    String token=userService.login(response,loginVo);
    return Result.success(token);
}

少了绑定校验结果BindingResult,代码看起来也清爽了不少。

 

改进三:自定义注解校验

有的时候已有的注解满足不了我们的校验需求,我们需要自定义注解进行检验。

1、定义注解

@Target({ElementType.FIELD,ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy={IsMobile.MobileValidator.class})
public @interface IsMobile {
    String message() default "手机号码格式错误";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};

    class MobileValidator implements ConstraintValidator<IsMobile,String>{

        public void initialize(IsMobile isMobile) {

        }
        public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
            if(StringUtils.isBlank(s)){
                return false;
            }else {
                Pattern mobile_pattern=Pattern.compile("1\\d{10}");
                Matcher m=mobile_pattern.matcher(s);
                return m.matches();
            }
        }
    }
}

上述代码,通过@Constraint(validatedBy =IsMobile.MobileValidator.class)限定了注解的方法逻辑---该注解类的名为MobileValidator的内部类。而该内部类实现了ConstraintValidator<IsMobile,String>接口。

2、在要校验的属性上添加@IsMobile注解

@Data
public class LoginVo {
    @NotBlank(message="电话号码不能为空")
    @IsMobile
    private String phone;
    @NotBlank(message="密码不能为空")
    private String password;
}

3、测试参数:

{
    "phone":"1",
    "password":"1"
}

4、响应为:

{
    "code": "500101",
    "msg": "参数校验异常:手机号码格式错误",
    "data": null
}

总结:

运用注解校验(Spring提供的注解+自定义注解)和全局统一处理器,对参数进行统一的校验和异常处理,避免对业务代码侵入,保持代码简洁性,提升开发效率。你学会了吗?

  • 0
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值