需求是写一个 新增用户 的controller
接口
首先,提供一下 UserVo
类:
import lombok.Data;
/**
* todo 用户vo
*
* @author coderzpw.zhang
* @version 1.0
* @date 2022/7/23 12:36
*/
@Data
public class UserVo {
private String name;
private Integer age;
private String addr;
}
然后在controller
中写对应的addUser
接口,参数要求:name 和 age不能为空
/**
* todo 用户web接口
*
* @author coderzpw.zhang
* @version 1.0
* @date 2022/7/23 12:54
*/
@RequestMapping("/user")
@RestController
public class UserController {
@PostMapping("/save")
public Object paramTest(@RequestBody UserVo userVo) {
// 返回结果, 当然在实际开发中肯定是会有自己的返回结果类的, 这里我就用map来替代
HashMap<String, Object> result = new HashMap<>();
if (StringUtils.isEmpty(userVo.getName())) {
result.put("code","400");
result.put("msg","name参数为空!");
return result;
}
if (Objects.isNull(userVo.getAge())) {
result.put("code","400");
result.put("msg","age参数为空!");
return result;
}
result.put("code","200");
result.put("msg","success");
return result;
}
}
使用 postman 自测:
缺少name
缺少age
可以看到,返回结果是没有问题的,可以实现对 name
和 age
的校验。
但我们在编写代码是就会感觉有点繁琐,主要是两个问题:
@Valid
、@NotBlank
、@NotNull
这些注解是 Validation Starter 依赖下的注解,在 Spring Boot 2.3 之前内部包括这个依赖包,但是2.3更新之后就不再包括了,如果想要使用它们就需要额外引入spring-boot-starter-validation
这个依赖。
springboot2.3更新说明,有图有真相:
看一下我的pom依赖:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--参数校验(SpringBoot2.3后需要自行引入)-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
其实spring-boot-starter-validation
为我们提供了很多校验注解:
注解 | 作用 |
---|---|
@AssertFalse | 被注解的元素必须为false |
@AssertTrue | 被注解的元素必须为True |
@DecimalMax(value) | 被注解的元素必须为一个数字,其值必须小于等于指定的最小值 |
@DecimalMin(Value) | 被注解的元素必须为一个数字,其值必须大于等于指定的最小值 |
@Digits(integer=, fraction=) | 被注解的元素必须为一个数字,其值必须在可接受的范围内 |
被注释的元素必须是电子邮箱地址 | |
@Future | 被注解的元素必须是日期,检查给定的日期是否比现在晚 |
@Max(value) | 被注解的元素必须为一个数字,其值必须小于等于指定的最小值,检查该值是否小于或等于约束条件中指定的最大值. 会给对应的数据库表字段添加一个 check的约束条件 |
@Min | BigDecimal,BigInteger, byte,short, int, long,等任何Number或CharSequence(存储的是数字)子类型 验证注解的元素值大于等于@Min指定的value值 |
@NotEmpty | 被注释的对象必须为空(数据:String,Collection,Map,arrays) |
@NotBlank | 不同于@NotEmpty,@NotBlank只应用于字符串且在比较时会去除字符串的首位空格 |
@NotNull | 被注解的元素必须不为null |
@Null | 被注解的元素必须为null |
@Past(java.util.Date/Calendar) | 被注解的元素必须过去的日期,检查标注对象中的值表示的日期比当前早 |
@Pattern(regex=, flag=) | 被注解的元素必须符合正则表达式,检查该字符串是否能够在match指定的情况下被regex定义的正则表达式匹配 |
@Size(min=, max=) | 被注解的元素必须在制定的范围(数据类型:String, Collection, Map and arrays) |
@Valid | 递归的对关联对象进行校验, 如果关联对象是个集合或者数组, 那么对其中的元素进行递归校验,如果是一个map,则对其中的值部分进行校验 |
… | … |
没有列举完,还有很多,有兴趣自己去研究哈
接下来我们在UserVo
类上打上如下注解:
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
/**
* todo 用户vo
*
* @author coderzpw.zhang
* @version 1.0
* @date 2022/7/22 23:40
*/
@Data
public class UserVo {
@NotBlank(message = "name参数不能为空!")
private String name;
@NotNull(message = "age参数不能为空!")
private Integer age;
private String addr;
}
然后再 Controller 对应方法上,对这个userVo标上 @Valid
注解,表示我们对这个对象属性需要进行验证:
有验证就会有结果,想要拿到验证结果还需要在参数中添加BindingResult
参数,然后利用该参数来判断是否抛出了异常
import com.coderzpw.exception.vo.UserVo;
import org.springframework.validation.BindingResult;
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 javax.validation.Valid;
import java.util.HashMap;
/**
* todo 用户web接口
*
* @author coderzpw.zhang
* @version 1.0
*/
@RequestMapping("/user")
@RestController
public class UserController {
@PostMapping("/save")
public Object paramTest(@RequestBody @Valid UserVo userVo, BindingResult bindingResult) {
// 返回结果, 当然在实际开发中肯定是会有自己的返回结果类的, 这里我就用map来替代
HashMap<String, Object> result = new HashMap<>();
// 若校验UserVo中的参数不通过,就会有错误信息
if (bindingResult.hasErrors()) {
// 获取第一个错误信息
String errorMessage = bindingResult.getAllErrors().get(0).getDefaultMessage();
result.put("code","400");
result.put("msg",errorMessage);
return result;
}
result.put("code","200");
result.put("msg","success");
return result;
}
}
请求测试:
没有问题吧
现在我们来说说 @Validated
这个注解,该注解 是在 @Valid
基础上做的一个升级版。
我们在使用@Valid
时,还需要自己用一个BindingResult
对象来接收校验的结果,而且判断逻辑还需要自己写,好像有点鸡肋
现在我们使用@Validated
这个注解,就不需要在引入BindingResult
参数了,也不需要自己写逻辑处理
import com.coderzpw.exception.vo.UserVo;
import org.springframework.validation.BindingResult;
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 javax.validation.Valid;
import java.util.HashMap;
/**
* todo 用户web接口
*
* @author coderzpw.zhang
* @version 1.0
*/
@RequestMapping("/user")
@RestController
public class UserController {
@PostMapping("/save")
public Object paramTest(@RequestBody @Validated UserVo userVo) {
// 返回结果, 当然在实际开发中肯定是会有自己的返回结果类的, 这里我就用map来替代
HashMap<String, Object> result = new HashMap<>();
result.put("code","200");
result.put("msg","success");
return result;
}
}
请求测试:
后端控制台:
注意点:
@Validated
在校验Json
参数不通过时,会抛出一个MethodArgumentNotValidException
的异常。控制台为什么没有抛出异常信息呢?因为被springMvc的DefaultHandlerExceptionResolver
捕获并处理了DefaultHandlerExceptionResolver
捕获并处理了,并设置了返回的数据不信的话,我们可以dubug一下:ctrl+shift+alt+n
定位 DefaultHandlerExceptionResolver
这个类
打断点:
再次请求测试,查看dubug信息:
我就不再深入探究了,感兴趣的话自己可以研究哈!
这里有篇DefaultHandlerExceptionResolver的博客,感兴趣可以看一下:【Spring MVC : 工具 DefaultHandlerExceptionResolver】
实际开发中我们一般不会返回如下格式的数据:
我们常常会自己去定制化返回结果,那如何定制呢?
我们可以自定义一个 异常捕获的类 MyExceptionHandler,最关键的是加上@ControllerAdvice
这个注解,然后使用@ExceptionHandler(xxx.class)
来捕获指定的异常
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.HashMap;
import java.util.Map;
/**
* todo 全局异常捕获
*
* @author coderzpw.zhang
* @version 1.0
*/
@ControllerAdvice // 用来捕获异常的关键注解
public class MyExceptionHandler {
/**
* json参数 - 后端用对象接收
* @param exception
* @return
*/
@ResponseBody // 表明最终返回json格式数据
@ExceptionHandler(MethodArgumentNotValidException.class) // 指定要捕获的异常类型
public Map<String, Object> handleCustomException(MethodArgumentNotValidException exception) {
// 定制化返回结果
HashMap<String, Object> hashMap = new HashMap<>();
hashMap.put("code","400");
hashMap.put("msg", exception.getBindingResult().getAllErrors().get(0).getDefaultMessage());
return hashMap;
}
}
请求测试:
简单介绍一下@ControllerAdvice
、 @ExceptionHandler
两个注解
@ControllerAdvice
注解作用是给Controller控制器添加统一的操作或处理。
对于@ControllerAdvice
,我们比较熟知的用法是结合@ExceptionHandler
用于全局异常的处理,但其作用不止于此。ControllerAdvice拆开来就是Controller Advice,关于Advice,在Spring的AOP
中,是用来封装一个切面所有属性的,包括切入点和需要织入的切面逻辑。这里ControllerAdvice也可以这么理解,其抽象级别应该是用于对Controller
进行切面环绕的,而具体的业务织入方式则是通过结合其他的注解来实现的。@ControllerAdvice
是在类上声明的注解,其用法主要有三点:
@ExceptionHandler
:用于捕获Controller中抛出的指定类型的异常,从而达到不同类型的异常区别处理的目的
@InitBinder
:用于request中自定义参数解析方式进行注册,从而达到自定义指定格式参数的目的
@ModelAttribute
:表示其注解的方法将会在目标Controller方法执行之前执行
常见的参数异常有三种
接下来我们再写另外两种类型的接口:
@RequestMapping("/user")
@RestController
@Validated
public class UserController {
// @RequestBody用json接收参数。校验失败会抛MethodArgumentNotValidException异常
@PostMapping("/save")
public Object paramTest(@RequestBody @Validated UserVo userVo) {
HashMap<String, Object> result = new HashMap<>();
result.put("code","200");
result.put("msg","success");
return result;
}
// 去掉@RequestBody,不再用json接收。校验失败会抛BindException异常
@PostMapping("/save2")
public Object paramTest2(@Validated UserVo userVo) {
HashMap<String, Object> result = new HashMap<>();
result.put("code","200");
result.put("msg","success");
return result;
}
// 不使用对象而用多个参数接收,在每个参数前加@NotBlank等注解,这个需要在UserController类上加@Validated。校验失败会抛ConstraintViolationException异常
@PostMapping("/save3")
public Object paramTest2(@NotBlank(message = "name参数为空!") String name,
@NotNull Integer age,
String addr) {
HashMap<String, Object> result = new HashMap<>();
result.put("code","200");
result.put("msg","success");
return result;
}
}
再添加上对应的异常捕获方法:
/**
* todo 全局异常捕获
*
* @author coderzpw.zhang
* @version 1.0
*/
@ControllerAdvice // 用来捕获异常的关键注解
public class MyExceptionHandler {
/**
* json参数 - 后端用对象接收
* @param exception
* @return
*/
@ResponseBody
@ExceptionHandler(MethodArgumentNotValidException.class)
public Map<String, Object> handleCustomException(MethodArgumentNotValidException exception) {
System.out.println("MethodArgumentNotValidException异常拦截!");
HashMap<String, Object> hashMap = new HashMap<>();
hashMap.put("code","400");
hashMap.put("msg", exception.getBindingResult().getAllErrors().get(0).getDefaultMessage());
return hashMap;
}
/**
* form表单 或者 url参数 - 后端用对象接收
* @param exception
* @return
*/
@ResponseBody
@ExceptionHandler(BindException.class)
public Map<String, Object> handleCustomException(BindException exception) {
System.out.println("BindException异常拦截!");
HashMap<String, Object> hashMap = new HashMap<>();
hashMap.put("code","400");
hashMap.put("msg", exception.getBindingResult().getAllErrors().get(0).getDefaultMessage());
return hashMap;
}
/**
* form表单 或者 url参数 - 后端用多个参数接收
* @param exception
* @return
*/
@ResponseBody
@ExceptionHandler(ConstraintViolationException.class)
public Map<String, Object> handleCustomException2(ConstraintViolationException exception) {
System.out.println("ConstraintViolationException异常拦截!");
HashMap<String, Object> hashMap = new HashMap<>();
hashMap.put("code","400");
hashMap.put("msg", exception.getMessage());
return hashMap;
}
}
启动项目,并请求测试:
先测一下 /save2 接口
控制台输出:
再来测一下 /save3 接口
控制台输出:
自定义异常枚举类:
import lombok.Getter;
/**
* todo 异常枚举类
*
* @author coderzpw.zhang
* @version 1.0
*/
@Getter
public enum ResultEnum {
SUCCESS(200, "成功!"),
UN_EXPECTED(500, "系统发生错误,请联系管理员!"),
UN_AUTHORIZED(401, "未认证!"),
NO_PERMISSION(403, "无权限!");
private Integer code;
private String message;
ResultEnum(Integer code, String message) {
this.code = code;
this.message = message;
}
}
自定义异常类:
import com.coderzpw.exception.enums.ResultEnum;
import lombok.Data;
/**
* todo 自定义异常类
*
* @author coderzpw.zhang
* @version 1.0
*/
@Data
public class MyException extends RuntimeException {
private Integer code;
public MyException(Integer code, String message) {
super(message);
this.code = code;
}
public MyException(ResultEnum resultEnum) {
super(resultEnum.getMessage());
this.code = resultEnum.getCode();
}
}
为了返回统一的数据,我们还可以自定义一个返回类ResultVO
import lombok.Data;
import java.io.Serializable;
/**
*todo 统一返回类型
*
* @author coderzpw.zhang
* @version 1.0
*/
@Data
public class ResultVO<T> implements Serializable {
// 状态码
private Integer code;
// 提示信息
private String msg;
// 具体内容
private T data;
}
然后再写一个生成ResultVo对象的工具类
import com.coderzpw.exception.vo.ResultVO;
import com.coderzpw.exception.enums.ResultEnum;
/**
* todo 生成ResultVo对象
*
* @author coderzpw.zhang
* @version 1.0
*/
public class ResultVOUtil {
/**
* 返回成功信息(带返回数据)
* @param object
* @return
*/
public static ResultVO success(Object object) {
ResultVO resultVO = new ResultVO();
resultVO.setData(object);
resultVO.setCode(ResultEnum.SUCCESS.getCode());
resultVO.setMsg(ResultEnum.SUCCESS.getMessage());
return resultVO;
}
/**
* 返回成功信息(不带数据)
* @return
*/
public static ResultVO success() {
return success(null);
}
/**
* 返回错误数据
* @param code
* @param msg
* @return
*/
public static ResultVO error(Integer code, String msg) {
ResultVO resultVO = new ResultVO();
resultVO.setCode(code);
resultVO.setMsg(msg);
return resultVO;
}
/**
* 返回错误数据(枚举类型入参)
* @param resultEnum
* @return
*/
public static ResultVO error(ResultEnum resultEnum) {
ResultVO resultVO = new ResultVO();
resultVO.setCode(resultEnum.getCode());
resultVO.setMsg(resultEnum.getMessage());
return resultVO;
}
}
接下来我们在异常拦截类MyExceptionHandler
中对MyException
类型进行拦截
@ResponseBody
@ExceptionHandler(MyException.class)
public ResultVO handleCustomException2(MyException exception) {
System.out.println("customException拦截!");
return ResultVOUtil.error(exception.getCode(), exception.getMessage());
}
最后在Controller层写个接口测试一下:
/**
* 抛出自定义异常
* @return
*/
@GetMapping("/custom-exception")
public ResultVO customException() {
System.out.println("例如用户没权限访问");
throw new MyException(ResultEnum.NO_PERMISSION);
}
请求测试:
完结,撒花!!!
版权说明 : 本文为转载文章, 版权归原作者所有 版权申明
原文链接 : https://blog.csdn.net/qq_45464560/article/details/125946217
内容来源于网络,如有侵权,请联系作者删除!