文章目录
- 内容模块管理
- 一、自定义异常
- 1.1 全局异常处理器
- 1.2 自定义异常
- 1.3 异常统一响应类
- 1.4 封装通用异常信息
- 二、JSR303校验
- 2.1 Maven坐标
- 2.2 校验规则
- 2.3 代码示例
- 2.4 捕捉校验异常
- 2.5 分组校验
- 2.6 备注
- 三、全局异常处理2
- 3.1 全局异常处理器
- 3.2 结果集
- 3.3 常用注解
- 3.3.1 @ControllerAdvice
- 3.3.2 @ExceptionHandle
内容模块管理
一、自定义异常
之前也学过对异常的统一管理,既然现在又看到了,就再学学springboot——全局异常处理器及封装结果集
如果一致对异常进行try…catch…,代码也会很冗余,我们直接throw就行,然后可以对异常进行统一处理
不论是那一层,都会有异常的出现,我们遇到异常都是向上抛出,最后让框架对异常统一处理
假如dao出异常就会抛给service,service有异常就会抛给controller,controller会抛给spring框架
1.1 全局异常处理器
-
@ExceptionHandler
提供的标识在方法上或类上的注解,用来表明方法的处理异常类型
-
@ControllerAdvice
控制器增强,在项目中来增强SpringMVC中的Controller。通常和@ExceptionHandler结合使用,来处理SpringMVC的异常信息。
-
@ResponseStatus:
提供的标识在方法上或类上的注解,用状态代码和应返回的原因标记方法或异常类。
调用处理程序方法时,状态代码将应用于HTTP响应
/*** 统一异常处理类*/
@Slf4j
@ControllerAdvice
@ResponseBody//返回JSON数据
//@RestControllerAdvice = @ControllerAdvice + @ResponseBody
public class GlobalExceptionHandler {/*** 针对自定义异常进行处理的*/@ResponseBody@ExceptionHandler(XueChengPlusException.class)//捕捉这个异常@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)//状态码500public RestErrorResponse customException(XueChengPlusException e) {
// 捕捉到异常的信息后解析异常的信息log.error("【系统异常】{}", e.getErrMessage(), e);return new RestErrorResponse(e.getErrMessage());}/*** 捕捉除了自定义异常以外的其他异常*/@ResponseBody@ExceptionHandler(Exception.class)@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)//状态码500public RestErrorResponse exception(Exception e) {log.error("【系统异常】{}", e.getMessage(), e);return new RestErrorResponse(CommonError.UNKOWN_ERROR.getErrMessage());//执行过程异常,请重试}}
1.2 自定义异常
@EqualsAndHashCode(callSuper = true)
@Data
public class XueChengPlusException extends RuntimeException {private static final long serialVersionUID = -378480393877627738L;private String errMessage;public XueChengPlusException() {super();}public XueChengPlusException(String errMessage) {super(errMessage);this.errMessage = errMessage;}public static void cast(CommonError commonError) {throw new XueChengPlusException(commonError.getErrMessage());}public static void cast(String errMessage) {throw new XueChengPlusException(errMessage);}}
1.3 异常统一响应类
/*** 和前端约定返回的异常信息模型* 错误响应参数包装*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class RestErrorResponse implements Serializable {private static final long serialVersionUID = 9026504397012666687L;private String errMessage;}
1.4 封装通用异常信息
/*** 通用异常信息*/
public enum CommonError {UNKOWN_ERROR("执行过程异常,请重试。"),PARAMS_ERROR("非法参数"),OBJECT_NULL("对象为空"),QUERY_NULL("查询结果为空"),REQUEST_NULL("请求参数为空");private String errMessage;public String getErrMessage() {return errMessage;}private CommonError( String errMessage) {this.errMessage = errMessage;}}
二、JSR303校验
SpringBoot提供了JSR-303的支持,它就是spring-boot-starter-validation,它的底层使用Hibernate Validator,Hibernate Validator是Bean Validation 的参考实现。
所以,我们准备在Controller层使用spring-boot-starter-validation完成对请求参数的基本合法性进行校验。
-
前端请求后端接口传输参数,是在controller中校验还是在Service中校验?
答案是都需要校验,只是分工不同。因为某些Service不仅仅被一个Controller调用,如果Service中不写校验再被其他Controller或其他Service调用时也会发生问题
-
Controller里一般校验什么?
Contoller中校验请求参数的合法性,包括:必填项校验,数据格式校验,比如:是否是符合一定的日期格式,等。
-
Service里一般校验什么?
Service中要校验的是业务规则相关的内容,比如:课程已经审核通过所以提交失败。
Service中根据业务规则去校验不方便写成通用代码,Controller中则可以将校验的代码写成通用代码
2.1 Maven坐标
首先在Base工程添加spring-boot-starter-validation的依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId>
</dependency>
在javax.validation.constraints包下有很多这样的校验注解,直接使用注解定义校验规则即可
2.2 校验规则
2.3 代码示例
现在准备对内容管理模块添加课程接口进行参数校验,如下接口
定义好校验规则还需要开启校验,在controller方法中添加@Validated注解,否则校验不会生效
@ApiOperation("新增课程")@PostMapping("/course")public CourseBaseInfoDto createCourseBase(@Validated @RequestBody AddCourseDto addCourseDto) {
// 将来会集成SpringSecurity框架,用户登录之后就可以获取到用户所属机构的ID
// 先把机构ID写死return courseBaseInfoService.createCourseBase(10086L,addCourseDto);}
进入AddCourseDto类,在属性上添加校验规则
@NotEmpty表示属性不能为空
@Size表示限制属性内容的长短
/*** @description 添加课程dto*/
@Data
@ApiModel(value = "AddCourseDto", description = "新增课程基本信息")
public class AddCourseDto {@NotEmpty(message = "课程名称不能为空")@ApiModelProperty(value = "课程名称", required = true)private String name;@NotEmpty(message = "适用人群不能为空")@Size(message = "适用人群内容过少", min = 10)@ApiModelProperty(value = "适用人群", required = true)private String users;@ApiModelProperty(value = "课程标签")private String tags;@NotEmpty(message = "课程分类不能为空")@ApiModelProperty(value = "大分类", required = true)private String mt;@NotEmpty(message = "课程分类不能为空")@ApiModelProperty(value = "小分类", required = true)private String st;@NotEmpty(message = "课程等级不能为空")@ApiModelProperty(value = "课程等级", required = true)private String grade;@ApiModelProperty(value = "教学模式(普通,录播,直播等)", required = true)private String teachmode;@ApiModelProperty(value = "课程介绍")private String description;@ApiModelProperty(value = "课程图片", required = true)private String pic;@NotEmpty(message = "收费规则不能为空")@ApiModelProperty(value = "收费规则,对应数据字典", required = true)private String charge;@ApiModelProperty(value = "价格")private Float price;@ApiModelProperty(value = "原价")private Float originalPrice;@ApiModelProperty(value = "qq")private String qq;@ApiModelProperty(value = "微信")private String wechat;@ApiModelProperty(value = "电话")private String phone;@ApiModelProperty(value = "有效期")private Integer validDays;
}
2.4 捕捉校验异常
该异常表示在方法参数验证失败时抛出,其中验证失败的详细信息可以通过 BindingResult
对象获取
getBindingResult()
方法是 MethodArgumentNotValidException
类的一个方法,用于获取包含验证错误详细信息的 BindingResult
对象。BindingResult
包含了验证结果,包括错误对象、错误代码、默认错误消息等。
@ExceptionHandler(MethodArgumentNotValidException.class)@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)public RestErrorResponse methodArgumentNotValidException(MethodArgumentNotValidException e) {
// 这个异常对象有一个方法getBindingResultBindingResult bindingResult = e.getBindingResult();List<String> msgList = new ArrayList<>();//将错误信息放在msgList//因为校验的时候可能多个字段都会出现问题,所以使用了一个list集合存放有问题的消息,我们后面会将他们一块拼接起来的bindingResult.getFieldErrors().stream().forEach(item -> msgList.add(item.getDefaultMessage()));//拼接错误信息String msg = StringUtils.join(msgList, ",");log.error("【系统异常】{}", msg);return new RestErrorResponse(msg);}
比如下面的这一次请求中就是两个字段不合格,都会放到List集合中,最后由StringUtils.join将其拼接起来
2.5 分组校验
假如修改课程的Dto和增加课程的Dto是一个样子的,但是校验规则不一样,怎么办呢?
方法1:定义两个Dto来分别校验
方法2:分组校验
分组校验:将Dto中的校验规则分组,新增课程使用一个检验规则组,修改课程使用一个校验规则组,但是都还是同一个Dto
定义分组
/*** 用于分组校验,定义一些常用的组*/
public class ValidationGroups {public interface Insert{};public interface Update{};public interface Delete{};}
划分组别
// @NotEmpty(message = "课程名称不能为空")
// 这个地方就说明了下面@NotEmpty校验属于ValidationGroups.Insert.class组别@NotEmpty(message = "新增课程名称不能为空",groups = {ValidationGroups.Insert.class})
// 这个地方就说明了下面@NotEmpty校验属于ValidationGroups.Update.class组别@NotEmpty(message = "修改课程名称不能为空",groups = {ValidationGroups.Update.class})@ApiModelProperty(value = "课程名称", required = true)private String name;
此时Controller中也需要修改
@ApiOperation("新增课程")@PostMapping("/course")public CourseBaseInfoDto createCourseBase(@Validated(ValidationGroups.Insert.class) @RequestBody AddCourseDto addCourseDto) {
// 将来会集成SpringSecurity框架,用户登录之后就可以获取到用户所属机构的ID
// 先把机构ID写死return courseBaseInfoService.createCourseBase(10086L,addCourseDto);}
假如说是修改的话,如下所示
public CourseBaseInfoDto updateCourseBase(@Validated(ValidationGroups.Update.class) @RequestBody AddCourseDto addCourseDto) {...
}
2.6 备注
如果javax.validation.constraints包下的校验规则满足不了需求怎么办?
1、手写校验代码 。
2、自定义校验规则注解。
三、全局异常处理2
上面的异常处理是在看学成在线的时候做的笔记,但是怎么看怎么别扭,特别是封装的响应结果集,很别扭,然后就再整理一份
系统如何处理异常?
-
我们自定义一个统一的异常处理器去捕获并处理异常
-
使用控制器增加注解@ControllerAdvice和异常处理注解@ExceptionHandler来实现
-
处理自定义异常
程序在编写代码时根据校验结果主动抛出自定义异常类对象,抛出异常时指定详细的异常信息,异常处理器捕获异常信息记录异常日志并响应给用户
-
处理自定义异常
接口执行过程中的一些运行时异常也会由异常处理器统一捕获,记录异常日志,统一响应给用户500错误(状态码由前后端协定)
-
在异常处理中还可以针对某个异常类型进行单独处理
3.1 全局异常处理器
/*** 全局异常处理* 底层是通过代理,代理controller,通过AOP把我们的一些方法拦截到,如果有异常,就在这个类统一进行处理* 下面就是只要带有RestController.class, Controller.class,Service.class的注解的类或方法出现异常,我们都会进行统一处理*/
@ControllerAdvice(annotations = {RestController.class, Controller.class, Service.class}) // 通知
@ResponseBody //我们需要返回JSON数据
@Slf4j
public class GlobalExceptionHandler {// 表示处理SQL异常@ExceptionHandler(SQLIntegrityConstraintViolationException.class)public R<String> exceptionHandler(SQLIntegrityConstraintViolationException ex) {
// 打印日志信息log.error(ex.getMessage());//Duplicate entry 'zhangjingqi' for key 'employee.idx_username'// 在这里也可以判断异常的具体信息,比如:if(ex.getMessage().contains("Duplicate entry")){String[] s = ex.getMessage().split(" ");String username = s[2];String msg =s[2]+"已经存在";return R.error(msg);}
// 其他情况下可以直接输出return R.error("未知错误:"+ex.getMessage());}
}
3.2 结果集
不论是成功响应还是异常响应,都使用下面这个结果集
/*** 通用返回结果类,服务端响应的数据都会封装成此对象* @param <T> 这个类会接受多种类型,可能是普通对象,可能是数组、集合等等等等,所以我们要将这个加个泛型<T>,表示可以接收任何参数*/
// 为什么不用Object,而用<T>? 如果用object需要强转类型 而T不用
@Data
public class R<T> {private Integer code; //编码:1成功,0和其它数字为失败private String msg; //错误信息private T data; //数据private Map map = new HashMap(); //动态数据// 方法的返回值及参数中的T属于泛型public static <T> R<T> success(T object) {R<T> r = new R<T>();r.data = object;r.code = 1; //成功return r;}public static <T> R<T> error(String msg) {R r = new R();r.msg = msg;r.code = 0; //失败return r;}public R<T> add(String key, Object value) {this.map.put(key, value);return this;}}
3.3 常用注解
3.3.1 @ControllerAdvice
SpringMVC 中 @ControllerAdvice 注解的三种使用场景! - 江南一点雨 - 博客园 (cnblogs.com)
-
basePackages/basePackageClasses属性
指定扫描哪个包
@ControllerAdvice(basePackages = "com.zhangjingqi.controller")
public class GlobalExceptionHandler {// ...
}
-
annotations属性
指定注解类型,限定只有被特定注解标记的控制器才会受到
@ControllerAdvice
类的影响
@ControllerAdvice(annotations = {RestController.class, Controller.class, Service.class}) // 通知
@ResponseBody //我们需要返回JSON数据
@Slf4j
public class GlobalExceptionHandler {.......
}
-
assignableTypes属性
通过指定类类型,限定只有继承自特定类的控制器才会受到
@ControllerAdvice
类的影响
@ControllerAdvice(assignableTypes = MyController.class)
public class GlobalExceptionHandler {// ...
}
-
value
与
basePackages
属性类似,用于指定扫描的包。在很多情况下,value
属性可以替代basePackages
@ControllerAdvice(value = "com.zhangjingqi.controllers")
public class GlobalExceptionHandler {// ...
}
-
useDefaultResponseAdvice
默认为
true
。当设置为false
时,禁用默认的ResponseEntityExceptionHandler
,这样你就可以完全掌控异常处理的行为
@ControllerAdvice(useDefaultResponseAdvice = false)
public class CustomExceptionHandler {// ...
}
3.3.2 @ExceptionHandle
可以捕获到controller中抛出的一些自定义异常,统一进行处理,一般用于进行一些特定的异常处理
可以根据需要定义多个 @ExceptionHandler
方法,每个方法处理一种特定类型的异常
// 表示处理SQL异常@ExceptionHandler(SQLIntegrityConstraintViolationException.class)public R<String> exceptionHandler(SQLIntegrityConstraintViolationException ex) {......}
-
value
指定要处理的异常类型。可以是单个异常类或一组异常类
@ExceptionHandler(value = {NullPointerException.class, IllegalArgumentException.class})
public String handleSpecificExceptions(Exception e) {// 处理特定类型的异常return "处理特定类型的异常";
}
-
exceptions
与
value
属性类似,用于指定要处理的异常类型
@ExceptionHandler(exceptions = NullPointerException.class)
public String handleCustomException(Exception e) {// 处理自定义异常return "处理自定义异常";
}
-
basePackages/basePackageClasses
限定
@ExceptionHandler
方法的扫描范围,类似于@ControllerAdvice
的属性
@ExceptionHandler(basePackages = "com.zhangjingqi.controllers")
public String handleExceptionsInControllerPackage(Exception e) {// 处理指定包中的异常return "packageError";
}