项目中的异常处理
规范异常类型
在Service类的业务方法中有很多的参数合法性校验,当请求参数不合法的时候会抛出异常,但此时异常信息只会在控制台输出
,前端界面并不会提示用户
实际开发中前端和后端需要做一些约定: 一般将错误提示信息统一以json格式返回给前端
,以HTTP状态码决定当前请求是否出错(非200为操作异常)
{
"timestamp":"2023-02-02T14:42:36.820+00:00",
// 添加课程时设置一个负数的课程价格会报500异常
"status":500,
"error":"Internal Server Error",
"message":"",
"path":"/content/course"
}
为了统一处理异常信息
,我们需要在业务方法中自定义并规范项目中抛出的异常类型,这样可以便于统一去捕获这一类或几类的异常
- 对于业务方法中抛出的
非项目自定义的异常类型即未知异常
,则统一向用户提示指定的错误信息如执行过程异常请重试的
规范了异常类型, 我们还需要去捕获异常信息
,使用try/catch
方式去捕获代码比较臃肿,可以统一由SpringMVC提供的控制器增强类
去完成异常的捕获
异常处理(base工程)
第一步: 添加依赖,在base基础工程实现统一异常处理
,由于各模块依赖了base基础工程所以都可以使用异常处理
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
第二步: 定义一个枚举类CommonError
,枚举一些通用的异常信息对象
package com.xuecheng.base.execption;
/**
* @description 通用错误信息
*/
public enum CommonError {
UNKOWN_ERROR("执行过程异常,请重试"),
PARAS_ERROR("非法参数"),
OBJECT_NULL("对象为空"),
QUERY_NULL("查询结果为空"),
REQUEST_NULL("请求参数为空");
private String errMessage;
public String getErrMessage() {
return errMessage;
}
CommonError(String errMessage) {
this.errMessage = errMessage;
}
}
第三步: 自定义项目的异常类型XueChengPlusException
package com.xuecheng.base.execption;
/**
* @description 学成在线项目异常类
*/
public class XueChengPlusException extends RuntimeException {
private String errMessage;
public String getErrMessage() {
return 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);
}
}
第四步: 自定义响应异常信息的模型类
package com.xuecheng.base.execption;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class RestErrorResponse implements Serializable {
private String errMessage;
}
第五步: 定义全局异常处理器去捕获异常信息同时记录异常日志
, 将异常信息封装到异常信息的模型类并响应给用户,实现微服务端全局异常处理
@ControllerAdvice或@RestControllerAdvice)(类上)
: 将当前类标识为异常处理的组件@ExceptionHandler(方法或类上)
: 用于表明方法处理的异常类型,可以指定一个或多个@ResponseStatus(方法或类上)
: 标记捕获异常的方法或类指定发生异常时异常处理器向前端响应的状态码和原因
package com.xuecheng.base.execption;
/**
* @description 全局异常处理器
*/
@Slf4j
@ControllerAdvice
public class GlobalExceptionHandler {
@ResponseBody
@ExceptionHandler(XueChengPlusException.class)// 处理项目自定义异常类型
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) // 该异常枚举对象的错误码为500
public RestErrorResponse customException(XueChengPlusException exception) {
log.error("系统异常:{}", exception.getErrMessage());
return new RestErrorResponse(exception.getErrMessage());
}
@ResponseBody
@ExceptionHandler(Exception.class)// 未知类型的异常
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)// 该异常枚举对象的错误码为500
public RestErrorResponse exception(Exception exception) {
log.error("系统异常:{}", exception.getMessage());
return new RestErrorResponse(exception.getMessage());
}
}
异常处理测试(api工程)
第一步: 在内容管理服务的api工程中添加base工程的依赖
<dependency>
<groupId>com.xuecheng</groupId>
<artifactId>xuecheng-plus-base</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
第二步: 当业务方法中出现异常时抛出项目自定义的异常类型,这里以新增课程的业务方法
为例进行代码修改
@Override
public CourseBaseInfoDto createCourseBase(Long companyId,AddCourseDto dto) {
//合法性校验
if (StringUtils.isBlank(dto.getName())) {
throw new XueChengPlusException("课程名称为空");
}
if (StringUtils.isBlank(dto.getMt())) {
throw new XueChengPlusException("课程分类为空");
}
if (StringUtils.isBlank(dto.getSt())) {
throw new XueChengPlusException("课程分类为空");
}
if (StringUtils.isBlank(dto.getGrade())) {
throw new XueChengPlusException("课程等级为空");
}
if (StringUtils.isBlank(dto.getTeachmode())) {
throw new XueChengPlusException("教育模式为空");
}
if (StringUtils.isBlank(dto.getUsers())) {
throw new XueChengPlusException("适应人群");
}
if (StringUtils.isBlank(dto.getCharge())) {
throw new XueChengPlusException("收费规则为空");
}
if(charge.equals("201001")){
if(courseMarketNew.getPrice() ==null || courseMarketNew.getPrice().floatValue()<=0){
throw new XueChengPlusException("课程的价格不能为空并且必须大于0");
}
}
}
第三步: 使用HTTP Client进行测试,故意将收费课程价格设置为负数, 查看捕获到的响应信息
POST http://localhost:53040/content/course
HTTP/1.1 500
Content-Type: application/json
Transfer-Encoding: chunked
Date: Fri, 03 Feb 2023 02:32:20 GMT
Connection: close
{
"errMessage": "课程设置了收费,价格不能为空,且必须大于0"
}