Springboot联结万物学习笔记--Springboot微服务基础搭建篇(六)--SpringBoot全局异常处理
博客说明:撰写博客目的是在记录自己所学知识、在工作中使用技术遇到的技术问题、一些技术感悟,因此避免不了涉及到和其他文章有相似之处。本文从作者自己的实践中指出相关踩坑问题,着重指出学习过程中遇到的相关问题。如果存在相关侵权问题请联系博主删除,同时有技术上的见解可以在评论去里发出,会不定期回复,谢谢。
gitee地址:https://gitee.com/woniurunfast/springbootwitheverything
01背景
在企业SpringBoot的日常开发中,代码常常会遇到bug,为了方便对接调试等,要做统一异常处理
02目标
1、实现全局异常处理机制
03全局异常设置步骤
(1)统一封装异常处理枚举类
package com.hkx.demo.exception;
import lombok.Getter;
/**
*
*/
@Getter
public enum ResultCodeEnum {
UNKNOWN_REASON(false, 20001, "未知错误"),
SERVER_ERROR(false, 500, "服务器忙,请稍后在试"),
ORDER_CREATE_FAIL(false, 601, "订单下单失败");
private Boolean success;
private Integer code;
private String message;
private ResultCodeEnum(Boolean success, Integer code, String message) {
this.success = success;
this.code = code;
this.message = message;
}
}
package com.hkx.demo.exception;
import lombok.*;
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Data
@ToString
public class ErrorHandler {
// 异常的状态码,从枚举中获得
private Integer status;
// 异常的消息,写用户看得懂的异常,从枚举中得到
private String message;
// 异常的名字
private String exception;
/**
* 对异常处理进行统一封装
*
* @param resultCodeEnum 异常枚举
* @param throwable 出现异常
* @param message 异常的消息 null /by zero
* @return
*/
public static ErrorHandler fail(ResultCodeEnum resultCodeEnum, Throwable throwable, String message) {
ErrorHandler errorHandler = ErrorHandler.fail(resultCodeEnum, throwable);
errorHandler.setMessage(message);
return errorHandler;
}
/**
* 对异常枚举进行封装
*
* @param resultCodeEnum
* @param throwable
* @return
*/
public static ErrorHandler fail(ResultCodeEnum resultCodeEnum, Throwable throwable) {
ErrorHandler errorHandler = new ErrorHandler();
errorHandler.setMessage(resultCodeEnum.getMessage());
errorHandler.setStatus(resultCodeEnum.getCode());
errorHandler.setException(throwable.getClass().getName());
return errorHandler;
}
}
(2)封装异常结果处理:
package com.hkx.demo.exception;
import lombok.*;
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Data
@ToString
public class ErrorHandler {
// 异常的状态码,从枚举中获得
private Integer status;
// 异常的消息,写用户看得懂的异常,从枚举中得到
private String message;
// 异常的名字
private String exception;
/**
* 对异常处理进行统一封装
*
* @param resultCodeEnum 异常枚举
* @param throwable 出现异常
* @param message 异常的消息 null /by zero
* @return
*/
public static ErrorHandler fail(ResultCodeEnum resultCodeEnum, Throwable throwable, String message) {
ErrorHandler errorHandler = ErrorHandler.fail(resultCodeEnum, throwable);
errorHandler.setMessage(message);
return errorHandler;
}
/**
* 对异常枚举进行封装
*
* @param resultCodeEnum
* @param throwable
* @return
*/
public static ErrorHandler fail(ResultCodeEnum resultCodeEnum, Throwable throwable) {
ErrorHandler errorHandler = new ErrorHandler();
errorHandler.setMessage(resultCodeEnum.getMessage());
errorHandler.setStatus(resultCodeEnum.getCode());
errorHandler.setException(throwable.getClass().getName());
return errorHandler;
}
}
(3)在配置类定义全局异常处理器:
package com.hkx.demo.config;
import com.hkx.demo.exception.BusinessException;
import com.hkx.demo.exception.ErrorHandler;
import com.hkx.demo.exception.ResultCodeEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.servlet.http.HttpServletRequest;
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
@ExceptionHandler(Throwable.class)
public ErrorHandler makeExcepton(Throwable e, HttpServletRequest request){
log.error("请求的地址是:{},出现的异常是:{}", request.getRequestURL(), e);
return ErrorHandler.fail(ResultCodeEnum.SERVER_ERROR,e);
}
/**
* 对服务器端出现500异常进行统一处理
* 缺点:明确异常信息
*/
@ExceptionHandler(BusinessException.class)
public ErrorHandler makeOrderException(BusinessException businessException, HttpServletRequest request) {
ErrorHandler errorHandler = ErrorHandler.builder()
.message(businessException.getMessage())
.status(businessException.getCode())
.build();
log.error("请求的地址是:{},出现的异常是:{}", request.getRequestURL(), businessException);
return errorHandler;
}
}
其中第二个方法扫描的师BusinessException类是自定义异常类,集成RuntimeException
package com.hkx.demo.exception;
import lombok.Data;
@Data
public class BusinessException extends RuntimeException {
private Integer code;
private String message;
public BusinessException(ResultCodeEnum resultCodeEnum) {
this.code = resultCodeEnum.getCode();
this.message = resultCodeEnum.getMessage();
}
public BusinessException(Integer code, String message) {
this.code = code;
this.message = message;
}
}
测试异常处理
创建异常处理接口访问控制层:
package com.hkx.demo.controller;
import com.hkx.demo.entity.WoNiu;
import com.hkx.demo.exception.BusinessException;
import io.swagger.annotations.Api;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/error")
@Slf4j
@Api("异常返回结果")
public class ErrorController {
@GetMapping("/error1")
public WoNiu error1(Integer id) {
if (id.equals(1)) {
throw new RuntimeException("run exception!");
}
WoNiu user = new WoNiu();
user.setId(1);
user.setDesc("yyds");
user.setHobby("451212");
user.setType("kkk");
return user;
}
@GetMapping("/error2")
public WoNiu error2(Integer id) {
if (id.equals(1)) {
throw new BusinessException(501,"参数不对");
}
WoNiu user = new WoNiu();
user.setId(1);
user.setDesc("yyds");
user.setHobby("451212");
user.setType("kkk");
return user;
}
@GetMapping("/error3")
public WoNiu error3(Integer id) {
int i=1/0;
WoNiu user = new WoNiu();
user.setId(1);
user.setDesc("yyds");
user.setHobby("451212");
user.setType("kkk");
return user;
}
}
上述三个接口是不同的触发异常访问的方法
error1:
error2:
error3:
统一异常和统一结果返回结合处理:
在config包下创建ResultResponseHandler类:
package com.hkx.demo.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.hkx.demo.common.Result;
import com.hkx.demo.exception.ErrorHandler;
import lombok.SneakyThrows;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import java.lang.reflect.Type;
@ControllerAdvice(basePackages = "com.hkx.demo")
public class ResultResponseHandler implements ResponseBodyAdvice<Object> {
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}
// 参数o 代表其实就是springmvc的请求的方法的结果
@SneakyThrows
@Override
public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
// 对请求的结果在这里统一返回和处理
if (o instanceof ErrorHandler) {
// 1、如果返回的结果是一个异常的结果,就把异常返回的结构数据倒腾到R.fail里面即可
ErrorHandler errorHandler = (ErrorHandler) o;
return Result.fail(errorHandler.getStatus(), errorHandler.getMessage());
} else if (o instanceof String) {
// 2、因为springmvc数据转换器对String是有特殊处理 StringHttpMessageConverter
ObjectMapper objectMapper = new ObjectMapper();
Result r = Result.success(o);
return objectMapper.writeValueAsString(r);
}
return Result.success(o);
}
}
这样无论成功或失败都会自动封装返回。