Java Controller实现参数验证与统一异常处理流程详细讲解

目录
  • 一,前期数据及类准备
    • 1.1 统一状态码
    • 1.2 统一返回格式
    • 1.3 自定义接口API异常类
    • 1.4 参数封装类
  • 二,参数验证
    • 2.1 pom.xml
    • 2.2 修改参数封装类
    • 2.3 controller
    • 2.4 测试
  • 三,统一异常处理
    • 3.1 方法参数验证异常处理
    • 3.2 其他异常处理

最近开发了比较多的接口,因为没有可参考的案例,所以一开始一直按照我的理解进行开发。开发多了发现自己每个结果都写了相同的代码:try() {} catch() {}, 和关于参数判空的:StringUtils.empty(xxx)。开发结束后自然想下次更加优雅的开发。因此,使用了springboot的参数验证和统一异常处理。

一,前期数据及类准备

1.1 统一状态码

对于不同的返回类型,我们应该要有不同对应的状态码。接口的返回类型在统一状态码中必须存在。

package com.lmc.common.enums;
/**
 * @Description: TODO 接口API返回状态码枚举
 * @version: 1.0
 */
public enum ResultCodeEnum {
    SUCCESS(1000, "请求成功"),
    FAILURE(1001, "请求失败"),
    VALIDATE_PARAMS_ERROR(1002, "参数校验失败");
    private int code;
    private String msg;
    ResultCodeEnum(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }
    /**
     * 获取code
     * @return
     */
    public int getCode() {
        return code;
    }
    /**
     * 获取信息
     * @return
     */
    public String getMsg() {
        return msg;
    }
}

1.2 统一返回格式

统一状态码完成后,还需要定义统一返回格式,为了前端的方便调用

package com.lmc.common.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.lmc.common.enums.ResultCodeEnum;
import lombok.Data;
import java.util.Date;
/**
 * @Description: TODO 接口返回结果类型
 * @version: 1.0
 */
@Data
public class ResultVo {
    /**
     * 状态码
     */
    private int code;
    /**
     * 状态码信息
     */
    private String msg;
    /**
     * 返回描述信息(预备为调用失败的情况下提供详细的失败原因)
     */
    private String desc;
    /**
     * 返回数据
     */
    private Object data;
    /**
     * 接口调用结束时间
     */
    @JsonFormat(locale="zh", timezone="GMT+8", pattern="yyyy-MM-dd HH:mm:ss")
    private Date searchTime;
    public ResultVo(int code, String msg, Object data) {
        this.code = code;
        this.msg = msg;
        this.data = data;
        this.searchTime = new Date();
    }
    /**
     * 调用成功时返回
     * @param data
     * @return
     */
    public static ResultVo success(Object data) {
        return new ResultVo(ResultCodeEnum.SUCCESS.getCode(), ResultCodeEnum.SUCCESS.getMsg(), data);
    }
    /**
     * 调用失败时返回
     * @param data
     * @return
     */
    public static ResultVo fail(Object data) {
        return new ResultVo(ResultCodeEnum.FAILURE.getCode(), ResultCodeEnum.FAILURE.getMsg(), data);
    }
    /**
     * 调用时指定状态码
     * @param enums
     * @param data
     * @return
     */
    public static ResultVo result(ResultCodeEnum enums, Object data) {
        return new ResultVo(enums.getCode(), enums.getMsg(), data);
    }
    public ResultVo withDesc(String desc) {
        this.desc = desc;
        return this;
    }
}

1.3 自定义接口API异常类

然后再自定义接口的异常类,当然也可以不用,看个人喜好

package pers.lmc.tools2.provider.exception;
import com.lmc.common.enums.ResultCodeEnum;
/**
 * @Description: TODO API异常类
 * @version: 1.0
 */
public class ApiException extends RuntimeException{
    private int code;
    private String msg;
    public ApiException(String msg) {
        super(msg);
        this.code = ResultCodeEnum.FAILURE.getCode();
        this.msg = ResultCodeEnum.FAILURE.getMsg();
    }
    public ApiException(ResultCodeEnum enums, String msg) {
        super(msg);
        this.code = enums.getCode();
        this.msg = enums.getMsg();
    }
}

1.4 参数封装类

为了调试参数验证,还需要自定义一个参数的封装类

package pers.lmc.tools2.provider.vo;
import lombok.Data;
/**
 * @Description: TODO
 * @version: 1.0
 */
@Data
public class Param01Vo {
    private String name;
    private Integer age;
    private Short sex;
}

二,参数验证

参数验证需要用到springboot的validation依赖

2.1 pom.xml

		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>
		<!--   关于校验     -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>${jackson.version}</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
            <version>${jackson.version}</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-annotations</artifactId>
            <version>${jackson.version}</version>
        </dependency>

2.2 修改参数封装类

package pers.lmc.tools2.provider.vo;
import lombok.Data;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
/**
 * @Description: TODO
 * @version: 1.0
 */
@Data
public class Param01Vo {
    @NotNull(message = "名称不能为空")
    @Size(min = 1, max = 50, message = "名称name长度必须是1-50个字符")
    private String name;
    @NotNull(message = "年龄age不能为空")
    @Min(value = 10, message = "年龄age不能低于10岁")
    @Max(value = 25, message = "年龄age不能超过25岁")
    private Integer age;
    @Min(value = 0, message = "性别sex只能是0和1,0=女1=男")
    @Max(value = 1, message = "性别sex只能是0和1,0=女1=男")
    private Short sex;
}

在这里对该封装类的三个参数都做了限制

2.3 controller

在controller中对参数做验证时,需要在类上使用注解@Validated,同时在接口的该参数也使用注解@Valid

package pers.lmc.tools2.provider.controller;
import com.lmc.common.vo.ResultVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import pers.lmc.tools2.provider.vo.Param01Vo;
import javax.validation.Valid;
/*
 * @Description: TODO
 * @version: 1.0
 */
@RestController
@Validated
@RequestMapping("/valicate")
@Slf4j
public class ValicateController {
    @PostMapping("/add")
    public ResultVo addParam01(@Valid @RequestBody Param01Vo param01Vo) {
        log.info("执行add()方法,参数:" + param01Vo.toString());
        return ResultVo.success(param01Vo);
    }
}

2.4 测试

开发完成,准备测试,到APIPost上 访问 http://localhost:9003/provider/valicate/add,带上参数:

{
    "name":"lmc",
    "age": 22,
    "sex": 1
}

访问成功,返回结果如下:

{
	"code": 1000,
	"msg": "请求成功",
	"desc": null,
	"data": {
		"name": "lmc",
		"age": 22,
		"sex": 1
	},
	"searchTime": "2022-06-26 19:59:55"
}

如果参数输入不正确,例如:

{
    "name":"",
    "age": 220,
    "sex": 2
}

得到结果如下:

{
	"timestamp": "2022-06-26T12:02:21.748+00:00",
	"status": 400,
	"error": "Bad Request",
	"message": "",
	"path": "/provider/valicate/add"
}

日志是这样的:

2022-06-26 20:02:21 [http-nio-9003-exec-1] WARN  o.s.w.s.m.support.DefaultHandlerExceptionResolver - Resolved [org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public com.lmc.common.vo.ResultVo pers.lmc.tools2.provider.controller.ValicateController.addParam01(pers.lmc.tools2.provider.vo.Param01Vo) with 3 errors: [Field error in object 'param01Vo' on field 'sex': rejected value [2]; codes [Max.param01Vo.sex,Max.sex,Max.java.lang.Short,Max]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [param01Vo.sex,sex]; arguments []; default message [sex],1]; default message [性别sex只能是0和1,0=女1=男]] [Field error in object 'param01Vo' on field 'age': rejected value [220]; codes [Max.param01Vo.age,Max.age,Max.java.lang.Integer,Max]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [param01Vo.age,age]; arguments []; default message [age],25]; default message [年龄age不能超过25岁]] [Field error in object 'param01Vo' on field 'name': rejected value []; codes [Size.param01Vo.name,Size.name,Size.java.lang.String,Size]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [param01Vo.name,name]; arguments []; default message [name],50,1]; default message [名称name长度必须是1-50个字符]] ]

抛出了MethodArgumentNotValidException异常。

虽然参数错误时确实被拦截了,但格式已经和我们想要返回的不一致了。这个时候,就需要用到统一异常处理了。

三,统一异常处理

3.1 方法参数验证异常处理

通过以上的问题,我们可以设置controller的统一异常处理,当出现参数验证错误时,就捕获MethodArgumentNotValidException异常,然后我们自己做处理。

package pers.lmc.tools2.provider.aop;
import com.lmc.common.enums.ResultCodeEnum;
import com.lmc.common.vo.ResultVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import pers.lmc.tools2.provider.exception.ApiException;
import java.util.List;
import java.util.stream.Collectors;
/**
 * @Description: TODO
 * @version: 1.0
 */
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
    /**
     * 处理所有校验失败的异常(MethodArgumentNotValidException异常)
     * @param e
     * @return
     */
    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public ResultVo handleBindGetException(MethodArgumentNotValidException e) {
        // 获取所有异常参数
        List<String> errors = e.getBindingResult()
                .getFieldErrors()
                .stream()
                .map(x -> x.getDefaultMessage())
                .collect(Collectors.toList());
        return ResultVo.result(ResultCodeEnum.VALIDATE_PARAMS_ERROR, null).withDesc("参数校验失败:" + errors);
    }
    /**
     * 处理自定义APIException异常
     * @param e
     * @return
     */
    @ExceptionHandler(value = ApiException.class)
    public ResultVo handleApiException(ApiException e) {
        return ResultVo.fail(null).withDesc(e.getMessage());
    }
    /**
     * 处理其他异常
     * @param e
     * @return
     */
    @ExceptionHandler(value = Exception.class)
    public ResultVo handleException(Exception e) {
        log.info("执行到统一处理方法...");
        return ResultVo.fail(null).withDesc(e.getMessage());
    }
}

通过以上配置,再次以非法参数传输时,会报出以下错误:

{
	"code": 1002,
	"msg": "参数校验失败",
	"desc": "参数校验失败:[性别sex只能是0和1,0=女1=男, 名称name长度必须是1-50个字符, 年龄age不能超过25岁]",
	"data": null,
	"searchTime": "2022-06-26 20:08:22"
}

这个时候格式已经我们想要的返回格式了。

3.2 其他异常处理

刚刚我们尝试的是方法的参数验证异常的处理,对于程序还可能出现的错误,配置统一异常处理后也不需要使用try{} catch() {},因为我们已经在全局异常处理类中配置了:

	/**
     * 处理其他异常
     * @param e
     * @return
     */
    @ExceptionHandler(value = Exception.class)
    public ResultVo handleException(Exception e) {
        log.info("执行到统一处理方法...");
        return ResultVo.fail(null).withDesc(e.getMessage());
    }

这个时候在程序中抛出其他异常,就会执行到这里的代码,同样返回我们想要的格式。举例如下

修改controller接口:

    @PostMapping("/add")
    public ResultVo addParam01(@Valid @RequestBody Param01Vo param01Vo) {
        log.info("执行add()方法,参数:" + param01Vo.toString());
        int k = 1/0; // 调用该接口时执行到这里会抛出异常
        return ResultVo.success(param01Vo);
    }

调用接口返回结果:

{
	"code": 1001,
	"msg": "请求失败",
	"desc": "/ by zero",
	"data": null,
	"searchTime": "2022-06-26 20:13:51"
}

到此这篇关于Java Controller实现参数验证与统一异常处理流程详细讲解的文章就介绍到这了,更多相关Java Controller参数验证与异常处理内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java Spring Controller 获取请求参数的几种方法详解

    Java Spring Controller 获取请求参数的几种方法  1.直接把表单的参数写在Controller相应的方法的形参中,适用于get方式提交,不适用于post方式提交.若"Content-Type"="application/x-www-form-urlencoded",可用post提交 url形式:http://localhost:8080/SSMDemo/demo/addUser1?username=lixiaoxi&password=1

  • Java Spring MVC 上传下载文件配置及controller方法详解

    下载: 1.在spring-mvc中配置(用于100M以下的文件下载) <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"> <property name="messageConverters"> <list> <!--配置下载返回类型--> <bean class="or

  • Java后台Controller实现文件下载操作

    代码 参数: 1.filePath:文件的绝对路径(d:\download\a.xlsx) 2.fileName(a.xlsx) 3.编码格式(GBK) 4.response.request不介绍了,从控制器传入的http对象 代码片. //控制器 @RequestMapping(UrlConstants.BLACKLIST_TESTDOWNLOAD) public void downLoad(String filePath, HttpServletResponse response, Http

  • Java中Controller引起的Ambiguous mapping问题及解决

    目录 Controller引起的Ambiguous mapping问题 问题描述 解决办法 Ambiguous mapping(模糊映射) 小白的报错日常 解决办法 Controller引起的Ambiguous mapping问题 问题描述 出现java.lang.IllegalStateException: Ambiguous mapping. Cannot map 'xxx' method异常. 通过上面代码我们可以看出来当spring添加Controller的接口Mapping的时候会先进

  • Java Controller实现参数验证与统一异常处理流程详细讲解

    目录 一,前期数据及类准备 1.1 统一状态码 1.2 统一返回格式 1.3 自定义接口API异常类 1.4 参数封装类 二,参数验证 2.1 pom.xml 2.2 修改参数封装类 2.3 controller 2.4 测试 三,统一异常处理 3.1 方法参数验证异常处理 3.2 其他异常处理 最近开发了比较多的接口,因为没有可参考的案例,所以一开始一直按照我的理解进行开发.开发多了发现自己每个结果都写了相同的代码:try() {} catch() {}, 和关于参数判空的:StringUti

  • java、spring、springboot中整合Redis的详细讲解

    java整合Redis 1.引入依赖或者导入jar包 <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.9.0</version> </dependency> 2.代码实现 public class JedisTest { public static void main(String[]

  • java validation 后台参数验证的使用详解

    一.前言 在后台开发过程中,对参数的校验成为开发环境不可缺少的一个环节.比如参数不能为null,email那么必须符合email的格式,如果手动进行if判断或者写正则表达式判断无意开发效率太慢,在时间.成本.质量的博弈中必然会落后.所以把校验层抽象出来是必然的结果,下面说下几种解决方案. 二.几种解决方案 1.struts2的valid可以通过配置xml,xml中描述规则和返回的信息,这种方式比较麻烦.开发效率低,不推荐 2.validation bean 是基于JSR-303标准开发出来的,使

  • Spring中统一异常处理示例详解

    前言 系统很多地方都会抛出异常, 而Java的异常体系目标就是与逻辑解耦,Spring提供了统一的异常处理注解,用户只需要在错误的时候提示信息即可 在具体的SSM项目开发中,由于Controller层为处于请求处理的最顶层,再往上就是框架代码的. 因此,肯定需要在Controller捕获所有异常,并且做适当处理,返回给前端一个友好的错误码. 不过,Controller一多,我们发现每个Controller里都有大量重复的.冗余的异常处理代码,很是啰嗦. 能否将这些重复的部分抽取出来,这样保证Co

  • java开发建造者模式验证实例详解

    目录 引言 经典再现 建造者模式优点及应用场景 工厂方法模式和建造者模式区别 拓展与总结 引言 创建一个类的实例,我们通常使用类中构造函数来完成对象的初始化,如果一个对象构造过程很复杂,如果将构造过程和对象使用的过程放在一起,就显得这个类很笨重,职责也不单一,最好的解决办法就是将构造过程拿出来单独进行封装,类的使用单独封装一个类就会好很多.如:mybaits中的SqlSessionFactoryBulider和SqlSessionFactory两个类,下图为SqlSessionFactoryBu

  • 超详细讲解SpringBoot参数校验实例

    目录 使用传统方式的弊端 引入依赖 注解说明 一.对实体类进行校验 1.entity 2.controller 3.编写全局统一异常处理 二.针对单个参数进行校验 三.分组校验 1.entity 2.controller 四.自定义分组校验 1.entity 2.CustomSequenceProvider 3.controller 五.自定义校验 1.定义校验注解 2.实现注解 六.嵌套校验 七.快速失败 注意事项 总结 使用传统方式的弊端 public String addUser(User

  • 超详细讲解Java异常

    目录 一.Java异常架构与异常关键字 Java异常简介 Java异常架构 1.Throwable 2.Error(错误) 3.Exception(异常) 4.受检异常与非受检异常 Java异常关键字 二.Java异常处理 声明异常 抛出异常 捕获异常 如何选择异常类型 常见异常处理方式 1.直接抛出异常 2.封装异常再抛出 3.捕获异常 4.自定义异常 5.try-catch-finally 6.try-with-resource 三.Java异常常见面试题 1.Error 和 Excepti

  • Java使用@Validated注解进行参数验证的方法

    目前项目中大部分代码进行参数验证都是写代码进行验证,为了提升方便性和代码的简洁性,所以整理了下使用注解进行参数验证.使用效果如下: // 要验证的实体类 @Data public class User implements Serializable { @NotBlank(message = "id不能为空!",groups = Update.class) protected String id = ""; @NotBlank(message = "商户i

  • java实现统一异常处理的示例

    对于Dao层 和Service产生的异常要一直网上抛,直至Controller层,但是对于controller层不能处理的异常也不能直接抛给前端. 为什么不能在service处理异常? 答:Service 层往往涉及数据库事务,出现异常同样不适合捕获,否则事务无法自动回滚.此外 Service 层涉及业务逻辑,有些业务逻辑执行中遇到业务异常,可能需要在异常后转入分支业务流程.如果业务异常都被框架捕获了,业务功能就会不正常.[引用:极客时间的Java业务开发常见错误100例] 实现统一异常处理:

  • Java和C#下的参数验证方法

    参数的输入和验证问题是开发时经常遇到的,一般的验证方法如下: public bool Register(string name, int age) { if (string.IsNullOrEmpty(name)) { throw new ArgumentException("name should not be empty", "name"); } if (age < 10 || age > 70) { throw new ArgumentExcept

随机推荐