SpringBoot封装全局异常处理器
1、原因
全局异常处理就是指把整个系统的异常统一自动处理,可以做到不用些try/catch就能进行处理项目中出现的异常。
- 不用强制写try/catch,异常交由统一异常的处理机制进行捕获。
@GetMapping("/error1")
public String error1() {
int i = 1 / 0;
return "success";
}
在开发中,如果不用try/catch进行捕获的话。客户端就会跳转到springboot默认的异常页面。报出500的错误信息。
在开发中遇见了异常一般的程序开发者都会使用try/catch来进行捕获处理,如下:
@GetMapping("/error2")
public String error2() {
try {
int i = 1 / 0;
} catch (Exception ex) {
log.info("出现了异常:{}", ex);
return "no";
}
return "success";
}
- 自定义异常,只能用全局异常来捕获。
@GetMapping("/error4")
public void error4() {
throw new RuntimeException("用户名或密码有误!!!");
}
上面的方式没办法通过try/catch进行处理。
- JSR303的参数验证器,参数校验不通过会抛出异常,也是无法通过try/catch进行直接捕获处理的。
2、实现
2.1 统一封装异常处理枚举类
package com.example.exception;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* @Auther: 长颈鹿
* @Date: 2021/07/22/11:19
* @Description:
*/
@Getter
@AllArgsConstructor
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;
}
2.2 封装controller的异常结果处理
package com.example.exception;
import lombok.*;
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Data
@ToString
public class ErrorHandler {
// ErrorHandler === R 答案:不破坏R类。
// 异常状态码,从枚举中获得
private Integer status;
// 异常信息,从枚举中获得
private String message;
// 异常名字
private String exception;
/**
* 对异常枚举进行封装
* @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;
}
/**
* 对异常处理进行统一封装
* @param resultCodeEnum 异常枚举
* @param throwable 异常
* @param message 异常信息
* @return
*/
public static ErrorHandler fail(ResultCodeEnum resultCodeEnum, Throwable throwable, String message) {
ErrorHandler errorHandler = ErrorHandler.fail(resultCodeEnum, throwable);
errorHandler.setMessage(message);
return errorHandler;
}
}
2.3 定义一个全局异常处理器
package com.example.config;
import com.example.exception.ErrorHandler;
import com.example.exception.ResultCodeEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.servlet.http.HttpServletRequest;
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
/**
* 对服务器端出现500异常进行统一处理
* @param e
* @param request
* @return
*/
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler(Throwable.class) // 所有异常
public ErrorHandler makeExcepton(Throwable e, HttpServletRequest request) {
ErrorHandler errorHandler = ErrorHandler.fail(ResultCodeEnum.SERVER_ERROR, e);
log.error("请求地址:{},出现异常:{}", request.getRequestURL(), e);
return errorHandler;
}
}
- makeExcepton方法作用:把运行时异常封装为ErrorHandler对象进行统一捕获处理。
- @RestControllerAdvice和@ControllerAdvice是对controller的增强扩展处理,全局异常就是扩展能力之一。
- @ExceptionHandler(Throwable.class) :统一处理某一类型异常,从而减少代码的出现异常的复杂度和重复率,
- @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR):指定客户端收到的http状态码,这里配置的是500,就显示成500错误。不指定也是没问题的。因为返回会根据枚举进行处理。
2.4 定义测试类
package com.example.controller;
import com.example.common.R;
import com.example.entity.User;
import io.swagger.annotations.Api;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @Auther: 长颈鹿
* @Date: 2021/07/22/9:28
* @Description:
*/
@RestController
@Api(description = "用户中心")
public class IndexController {
@GetMapping("/get")
public User getUser(Integer id){
int i = 1 / 0;
User user = new User();
user.setId(1);
user.setNickname("中山靖王之后");
user.setPassword("123456");
user.setAdddress("河北省涿州市");
return user;
}
}
2.5 运行查看结果
{
"status":500,
"message":"服务器忙,请稍后再试",
"exception":"java.lang.ArithmeticException"
}
所有异常都被拦截成到全局异常处理。
访问error4
并没有显示对应的自定义异常处理信息,如何进行处理呢?
答案:自定义异常统一处理
3、自定义异常,并集成自定义异常处理器
自定义异常好处:可以根据自定义异常的信息快速的定位错误出现的模块以及方便记录日志文件,快速进行错误的分析和判定。
3.1 添加自定义异常
package com.example.exception;
import lombok.AllArgsConstructor;
import lombok.Data;
/**
* @Auther: 长颈鹿
* @Date: 2021/07/22/13:10
* @Description:
*/
@Data
@AllArgsConstructor
public class OrderException extends RuntimeException {
private Integer code;
private String message;
public OrderException(ResultCodeEnum resultCodeEnum) {
this.code = resultCodeEnum.getCode();
this.message = resultCodeEnum.getMessage();
}
}
3.2 添加自定义异常处理方法
@ExceptionHandler(OrderException.class)
public ErrorHandler makeOrderException(OrderException orderException, HttpServletRequest request) {
ErrorHandler errorHandler = ErrorHandler.builder()
.status(orderException.getCode())
.message(orderException.getMessage())
.exception(orderException.getClass().getName())
.build();
log.error("请求地址:{},出现异常:{}", request.getRequestURL(), orderException);
return errorHandler;
}
3.3 定义方法进行测试
@GetMapping("/createOrder")
public String createOrder(Integer id){
if(id.equals(1)){
throw new OrderException(ResultCodeEnum.ORDER_CREATE_FAIL);
}
return "success";
}
{
"code":601,
"data":null,
"message":"订单下单失败"
}
4、统一返回&异常返回进行结合处理
package com.example.config;
import com.example.common.R;
import com.example.exception.ErrorHandler;
import com.fasterxml.jackson.databind.ObjectMapper;
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.ResponseBodyAdvice;
/**
* @Auther: 长颈鹿
* @Date: 2021/07/22/9:41
* @Description:
*/
@ControllerAdvice(basePackages = "com.example")
public class ResultResponseHandler implements ResponseBodyAdvice<Object> {
/**
* 是否支持advice功能,true是支持,false是不支持
*
* @param methodParameter
* @param aClass
* @return
*/
@Override
public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
return true;
}
/**
* @param o 代表springMvc的请求方法的结果
* @param methodParameter
* @param mediaType
* @param aClass
* @param serverHttpRequest
* @param serverHttpResponse
* @return
*/
@SneakyThrows
@Override
public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
// 对返回的结果进行统一返回和处理
if (o instanceof ErrorHandler) {
ErrorHandler errorHandler = (ErrorHandler) o;
return R.fail(errorHandler.getStatus(), errorHandler.getMessage());
} else if (o instanceof String) {
// 因为springmvc数据转换器对String是有特殊处理 StringHttpMessageConverter
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.writeValueAsString(R.success(o));
}
return R.success(o);
}
}