SpringBoot全局异常处理(优缺点)以及统一返回对象、错误码按业务模块设计示例,序列化后与数据库数据时间差8小时
项目工程中的全局异常处理原理在开发过程中,不管是Dao、Servie、Controller,层都有可能发生异常,对于异常处理,通常是try-catch或者直接throw,这会让try-catch的代码在代码中任意出现,系统的代码耦合度高,代码不美观,统一异常处理可以美化代码。错误处理Spring Boot/error默认提供了一个映射,以合理的方式处理所有错误,并在 servlet 容器中注册为“
项目工程中的全局异常处理原理
为什么要统一处理异常?
在开发过程中,不管是Dao、Servie、Controller,层都有可能发生异常,对于异常处理,通常是try-catch或者直接throw,这会让try-catch的代码在代码中任意出现,系统的代码耦合度高,代码不美观,统一异常处理可以美化代码。
错误处理
Spring Boot
/error
默认提供了一个映射,以合理的方式处理所有错误,并在 servlet 容器中注册为“全局”错误页面。对于机器客户端(如postman等),它将生成一个 JSON 响应,其中包含错误的详细信息、HTTP 状态和异常消息。对于浏览器客户端,有一个“whitelabel”错误视图,它以 HTML 格式呈现相同的数据(要自定义它,只需添加一个View
解析为“错误”的数据)。要完全替换默认行为,您可以实现ErrorController
并注册该类型的 bean 定义,或者简单地添加一个类型的 beanErrorAttributes
以使用现有机制替换内容。
BasicErrorController
可以用作自定义基类ErrorController
。如果您想为新的内容类型添加处理程序(默认是text/html
专门处理并为其他所有内容提供后备),这将特别有用。要做到这一点,只需扩展BasicErrorController
并添加一个@RequestMapping
具有produces
属性的公共方法 ,然后创建一个新类型的 bean。
DispatchServlet中doService中会进行异常处理
@ExceptionHandler注解实现异常处理 参考下面的链接SpringBoot官方文档
定义
@ControllerAdvice
来自定义 JSON 文档以针对特定控制器和/或异常类型返回。或者定义@RestControllerAdvice
与controller层上的注解一一对应。
全局统一异常处理好处:
1、用户友好返回
2、统一处理,简化业务逻辑
3、异常标准化
在当前控制器中进行异常处理
在controller类中加上如下方法,进行controller层所有异常拦截,String改为你的全局返回对象,并返回必要的提示信息。
/**
* 基础异常
*/
@ExceptionHandler(Exception.class)
public Sting baseException(Exception e) {
return e.getMessage();
}
统一异常处理缺点
与没有统一异常处理的代码发生异常比较起来,统一异常处理会吃掉异常信息,使得问题排查变得复杂。
全局异常统一处理
全局异常处理步骚
1:写一个类,需要加上RestControllerAdvice注解
2:写一个异常处理方法,方法上面需要加上@ExceptionHandler(value=Exception.class)这个注解,在该方法里面处理异常,value的异常类可以更明细一些,具体化
/**
* 全局异常处理器
*
*/
@RestControllerAdvice
public class GlobalExceptionHandler {
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
/**
* 基础异常
*/
@ExceptionHandler(BaseException.class)
public AjaxResult baseException(BaseException e) {
return AjaxResult.error(e.getMessage());
}
/**
* 业务异常
*/
@ExceptionHandler(CustomException.class)
public AjaxResult businessException(CustomException e) {
if (StringUtils.isNull(e.getCode())) {
return AjaxResult.error(e.getMessage());
}
return AjaxResult.error(e.getCode(), e.getMessage());
}
@ExceptionHandler(NoHandlerFoundException.class)
public AjaxResult handlerNoFoundException(Exception e) {
log.error(e.getMessage(), e);
return AjaxResult.error(HttpStatus.NOT_FOUND, "路径不存在,请检查路径是否正确");
}
@ExceptionHandler(AccessDeniedException.class)
public AjaxResult handleAuthorizationException(AccessDeniedException e) {
log.error(e.getMessage());
return AjaxResult.error(HttpStatus.FORBIDDEN, "没有权限,请联系管理员授权");
}
@ExceptionHandler(AccountExpiredException.class)
public AjaxResult handleAccountExpiredException(AccountExpiredException e) {
log.error(e.getMessage(), e);
return AjaxResult.error(e.getMessage());
}
@ExceptionHandler(UsernameNotFoundException.class)
public AjaxResult handleUsernameNotFoundException(UsernameNotFoundException e) {
log.error(e.getMessage(), e);
return AjaxResult.error(e.getMessage());
}
@ExceptionHandler(Exception.class)
public AjaxResult handleException(Exception e) {
log.error(e.getMessage(), e);
return AjaxResult.error(e.getMessage());
}
/**
* 自定义验证异常
*/
@ExceptionHandler(BindException.class)
public AjaxResult validatedBindException(BindException e) {
log.error(e.getMessage(), e);
String message = e.getAllErrors().get(0).getDefaultMessage();
return AjaxResult.error(message);
}
/**
* 自定义验证异常
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public AjaxResult validExceptionHandler(MethodArgumentNotValidException e) {
log.error(e.getMessage(), e);
String message = e.getBindingResult().getFieldError().getDefaultMessage();
return AjaxResult.error(message);
}
}
如果统一异常处理中又出现了异常怎么办?BasicErrorController
- 是一个Controller
- 类上注解:@RequestMapping("${server.error.path:${error.path:/error}}")
- 处理默认 /error 路径的请求;
- 页面响应 new ModelAndView("error", model);
- 容器中有组件 View->id是error;(响应默认错误页)
- 容器中放组件 BeanNameViewResolver(视图解析器);按照返回的视图名作为组件的id去容器中找View对象。
@Controller
@RequestMapping({"${server.error.path:${error.path:/error}}"})
public class BasicErrorController extends AbstractErrorController {
private final ErrorProperties errorProperties;
public BasicErrorController(ErrorAttributes errorAttributes, ErrorProperties errorProperties) {
this(errorAttributes, errorProperties, Collections.emptyList());
}
public BasicErrorController(ErrorAttributes errorAttributes, ErrorProperties errorProperties, List<ErrorViewResolver> errorViewResolvers) {
super(errorAttributes, errorViewResolvers);
Assert.notNull(errorProperties, "ErrorProperties must not be null");
this.errorProperties = errorProperties;
}
public String getErrorPath() {
return this.errorProperties.getPath();
}
/**
* html形式的请求错误返回
**/
@RequestMapping(
produces = {"text/html"}
)
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
HttpStatus status = this.getStatus(request);
Map<String, Object> model = Collections.unmodifiableMap(this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.TEXT_HTML)));
response.setStatus(status.value());
// 如果自定义了视图,使用自定义的,否则使用默认的
ModelAndView modelAndView = this.resolveErrorView(request, response, status, model);
return modelAndView != null ? modelAndView : new ModelAndView("error", model);
}
@RequestMapping
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
HttpStatus status = this.getStatus(request);
if (status == HttpStatus.NO_CONTENT) {
return new ResponseEntity(status);
} else {
Map<String, Object> body = this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.ALL));
return new ResponseEntity(body, status);
}
}
/**
* 机器请求错误返回
**/
@ExceptionHandler({HttpMediaTypeNotAcceptableException.class})
public ResponseEntity<String> mediaTypeNotAcceptable(HttpServletRequest request) {
HttpStatus status = this.getStatus(request);
return ResponseEntity.status(status).build();
}
protected boolean isIncludeStackTrace(HttpServletRequest request, MediaType produces) {
IncludeStacktrace include = this.getErrorProperties().getIncludeStacktrace();
if (include == IncludeStacktrace.ALWAYS) {
return true;
} else {
return include == IncludeStacktrace.ON_TRACE_PARAM ? this.getTraceParameter(request) : false;
}
}
protected ErrorProperties getErrorProperties() {
return this.errorProperties;
}
}
BasicErrorController在何处注入到容器中的?
在ErrorMvcAutoConfiguration类中注入到容器中。
自定义错误处理(getErrorAttributes)
/**
* 自定义错误处理controller
*/
public class MyErrorController extends BasicErrorController{
public MyErrorController(ErrorAttributes errorAttributes, ErrorProperties errorProperties, List<ErrorViewResolver> errorViewResolvers) {
super(errorAttributes, errorProperties, errorViewResolvers);
}
/**
* {
x"timestamp": "2021-08-22 13:41:17",
x"status": 500,
x"error": "Internal Server Error",
x"exception": "java.lang.IllegalArgumentException",
"message": "编号不可为空",
x"path": "/manager/products"
+ code
+ canRetry
}
*/
@Override
protected Map<String, Object> getErrorAttributes(HttpServletRequest request, boolean includeStackTrace) {
Map<String, Object> attrs = super.getErrorAttributes(request, includeStackTrace);
attrs.remove("timestamp");
attrs.remove("status");
attrs.remove("error");
attrs.remove("exception");
attrs.remove("path");
String errorCode = (String) attrs.get("message");
ErrorEnum errorEnum = ErrorEnum.getByCode(errorCode);
attrs.put("message",errorEnum.getMessage());
attrs.put("code",errorEnum.getCode());
attrs.put("canRetry",errorEnum.isCanRetry());
return attrs;
}
}
相关配置
/**
* 错误处理相关配置
*/
@Configuration
public class ErrorConfiguration {
@Bean
public MyErrorController basicErrorController(ErrorAttributes errorAttributes, ServerProperties serverProperties,
ObjectProvider<List<ErrorViewResolver>> errorViewResolversProvider) {
return new MyErrorController(errorAttributes, serverProperties.getError(),
errorViewResolversProvider.getIfAvailable());
}
}
基础业务异常模块
/**
* 基础异常
*
*/
public class BaseException extends RuntimeException {
private static final long serialVersionUID = 1L;
/**
* 所属模块
*/
private String module;
/**
* 错误码
*/
private String code;
/**
* 错误码对应的参数
*/
private Object[] args;
/**
* 错误消息
*/
private String defaultMessage;
public BaseException(String module, String code, Object[] args, String defaultMessage) {
this.module = module;
this.code = code;
this.args = args;
this.defaultMessage = defaultMessage;
}
public BaseException(String module, String code, Object[] args) {
this(module, code, args, null);
}
public BaseException(String module, String defaultMessage) {
this(module, null, null, defaultMessage);
}
public BaseException(String code, Object[] args) {
this(null, code, args, null);
}
public BaseException(String defaultMessage) {
this(null, null, null, defaultMessage);
}
@Override
public String getMessage() {
String message = null;
if (!StringUtils.isEmpty(code)) {
message = MessageUtils.message(code, args);
}
if (message == null) {
message = defaultMessage;
}
return message;
}
public String getModule() {
return module;
}
public String getCode() {
return code;
}
public Object[] getArgs() {
return args;
}
public String getDefaultMessage() {
return defaultMessage;
}
}
非Map的统一返回结果对象
@Data
@ToString
@Accessors(chain = true)
@ApiModel
public class BaseRet<T> implements Serializable {
public static final int SUCCESS_CODE = 0;
private static final long serialVersionUID = 4167538907607374141L;
@ApiModelProperty(value = "响应码,0为成功,错误一般为-1,具体看接口约定")
Integer code;
@ApiModelProperty(value = "提示信息")
String msg;
@ApiModelProperty(value = "返回数据,具体看接口约定")
T data;
public BaseRet() {}
public BaseRet( int code, String msg) {
this.code = code;
this.msg = msg;
}
public BaseRet(int code, String msg, T data) {
this.code = code;
this.msg = msg;
this.data = data;
}
public static <T> BaseRet<T> createSuccessRet() {
BaseRet<T> ret = new BaseRet<>();
ret.setMsg("请求成功!");
ret.setCode(SUCCESS_CODE);
return ret;
}
public static <T> BaseRet<T> createSuccessRet(int code) {
BaseRet<T> ret = new BaseRet<>();
ret.setCode(code);
return ret;
}
public static <T> BaseRet<T> createSuccessRet(T data) {
BaseRet<T> ret = new BaseRet<>();
ret.setMsg("请求成功!");
ret.setCode(0);
ret.setData(data);
return ret;
}
public static <T> BaseRet<T> createSuccessRet(int code, String msg) {
BaseRet<T> ret = new BaseRet<>();
ret.setMsg(msg);
ret.setCode(code);
return ret;
}
public static <T> BaseRet<T> createFailureRet() {
BaseRet<T> ret = new BaseRet<>();
ret.setMsg("请求失败!");
ret.setCode(-1);
return ret;
}
public static <T> BaseRet<T> createFailureRet(int code, String msg) {
BaseRet<T> ret = new BaseRet<>();
ret.setMsg(msg);
ret.setCode(code);
return ret;
}
public static <T> BaseRet<T> createFailureRetPhoneBondWithData(T data) {
BaseRet<T> ret = new BaseRet<>();
ret.setMsg("该手机号已绑定其他账号");
ret.setCode(-3);
ret.setData(data);
return ret;
}
public static <T> BaseRet<T> createFailureRet(String msg) {
BaseRet<T> ret = new BaseRet<>();
ret.setMsg(msg);
ret.setCode(-1);
return ret;
}
public static <T> BaseRet<T> createFailureRet(T data) {
BaseRet<T> ret = new BaseRet<>();
ret.setMsg("请求失败!");
ret.setCode(-1);
ret.setData(data);
return ret;
}
public static <T> BaseRet<T> createMissParamFailureRet() {
BaseRet <T> ret = new BaseRet<>();
ret.setMsg("缺少必填参数!");
ret.setCode(-2);
return ret;
}
public static <T> BaseRet<T> createMissDataFailureRet() {
BaseRet <T> ret = new BaseRet<>();
ret.setMsg("数据异常丢失!");
ret.setCode(-3);
return ret;
}
public static <T> BaseRet<T> error(Integer code, String message) {
BaseRet<T> response = new BaseRet<>();
response.setCode(code);
response.setMsg(message);
return response;
}
/**
* 创建失败,但是要返回参数
*/
public static <T> BaseRet<T> createFailWithData(int code, String msg,T data) {
BaseRet<T> ret = new BaseRet<>();
ret.setMsg(msg);
ret.setCode(code);
ret.setData(data);
return ret;
}
public static <T> BaseRet<T> createRecordNotFundRet() {
BaseRet<T> ret = new BaseRet<>();
ret.setMsg("记录不存在!");
ret.setCode(-1000);
return ret;
}
@JSONField(serialize=false)
@JsonIgnore
public boolean isSuccess() {
return SUCCESS_CODE == this.getCode() ;
}
@JSONField(serialize=false)
@JsonIgnore
public boolean isFail() {
return !isSuccess();
}
public static BaseRet<Boolean> createRetByBoolean(Boolean succeed) {
if (succeed) {
BaseRet<Boolean> ret = createSuccessRet();
ret.setData(true);
return ret;
} else {
BaseRet<Boolean> ret = createFailureRet();
ret.setData(false);
return ret;
}
}
}
BaseController
public class BaseController {
/** 基于@ExceptionHandler异常处理 */
@ExceptionHandler
public String exp(HttpServletRequest request, Exception ex) {
request.setAttribute("ex", ex);
// 根据不同错误转向不同页面
if(ex instanceof BusinessException) {
return "error-business";
}else if(ex instanceof ParameterException) {
return "error-parameter";
} else {
return "error";
}
}
}
统一请求返回对象
/**
* 统一请求返回对象
*
*/
public class AjaxResult extends HashMap<String, Object> {
private static final long serialVersionUID = 1L;
/**
* 状态码
*/
public static final String CODE_TAG = "code";
/**
* 返回内容
*/
public static final String MSG_TAG = "msg";
/**
* 数据对象
*/
public static final String DATA_TAG = "data";
/**
* 初始化一个新创建的 AjaxResult 对象,使其表示一个空消息。
*/
public AjaxResult() {
}
/**
* 初始化一个新创建的 AjaxResult 对象
*
* @param code 状态码
* @param msg 返回内容
*/
public AjaxResult(int code, String msg) {
super.put(CODE_TAG, code);
super.put(MSG_TAG, msg);
}
/**
* 初始化一个新创建的 AjaxResult 对象
*
* @param code 状态码
* @param msg 返回内容
* @param data 数据对象
*/
public AjaxResult(int code, String msg, Object data) {
super.put(CODE_TAG, code);
super.put(MSG_TAG, msg);
if (StringUtils.isNotNull(data)) {
super.put(DATA_TAG, data);
}
}
/**
* 返回成功消息
*
* @return 成功消息
*/
public static AjaxResult success() {
return AjaxResult.success("操作成功");
}
/**
* 返回成功数据
*
* @return 成功消息
*/
public static AjaxResult success(Object data) {
return AjaxResult.success("操作成功", data);
}
/**
* 返回成功消息
*
* @param msg 返回内容
* @return 成功消息
*/
public static AjaxResult success(String msg) {
return AjaxResult.success(msg, null);
}
/**
* 返回成功消息
*
* @param msg 返回内容
* @param data 数据对象
* @return 成功消息
*/
public static AjaxResult success(String msg, Object data) {
return new AjaxResult(HttpStatus.SUCCESS, msg, data);
}
/**
* 返回错误消息
*
* @return
*/
public static AjaxResult error() {
return AjaxResult.error("操作失败");
}
/**
* 返回错误消息
*
* @param msg 返回内容
* @return 警告消息
*/
public static AjaxResult error(String msg) {
return AjaxResult.error(msg, null);
}
/**
* 返回错误消息
*
* @param msg 返回内容
* @param data 数据对象
* @return 警告消息
*/
public static AjaxResult error(String msg, Object data) {
return new AjaxResult(HttpStatus.ERROR, msg, data);
}
/**
* 返回错误消息
*
* @param code 状态码
* @param msg 返回内容
* @return 警告消息
*/
public static AjaxResult error(int code, String msg) {
return new AjaxResult(code, msg, null);
}
}
错误码枚举(按模块划分)
/**
* 按模块定义公共错误码
*
*
*/
public enum CommonError{
/**
* 获取错误码
* @return code
*/
int getCode();
/**
* 获取错误消息
* @return message
*/
String getMessage();
/**
* 转换为消息
* @param code
* @param message
* @return string
*/
String toString(int code, String message) {
return code + ":" + message;
}
/**
* 按模块定义错误码
*/
SUCCESS(0, "成功"),
UNKNOWN_ERROR(-1, "未定义异常"),
TOKEN_ERROR(1001, "获取token失败"),
IDENTIFY_INVALID(3001, "密码错误"),
CABINET_ERROR(4000, "商家账号与绑定取餐柜不匹配"),
GET_FOURS_INVALID(4001, "获取门店信息失败"),
GET_FOURS_EMPTY(4002, "商家查询门店信息为空"),
GET_FOURS_OFFLINE(4003, "门店已下线"),
// 商业云模块
ORDER_CANCEL(6001, "订单已取消"),
ORDER_INVALID(6002, "订单号或是取餐码错误"),
ORDER_UNPAY(6004, "订单未支付"),
ORDER_HAS_FINISHED(6003, "该订单已完成"),
ORDER_ERROR(6005, "查询商业云订单信息失败"),
STORE_ERROR(6006, "商业云订单号与此商家不匹配"),
ORDER_NOT_EXSIST(6007, "订单不存在"),
ORDER_IS_EXSIST(6008, "该订单已存餐"),
ORDER_EXTACTED(6010, "订单已取餐"),
ORDER_STATE_ERROR(6011, "订单状态错误"),
ORDER_IS_CANCEL(6012, "该订单已取消"),
// 取餐业务模块
SAVE_ORDER_FAILED (8000, "存餐失败"),
MEALCODE_ERROR(8001, "取餐码不正确,请确认后重新输入"),
MEALCODE_CACELED(8002, "该订单已作废"),
EXTRACTCODE_CACELED(8003, "该验证码已作废或是订单已作废"),
MEAL_ERROR(8004, "取餐错误"),
// 设备业务模块10
IOTSERVER_ERROR(10004, "查询设备属性失败"),
IOTSERVER_INVALID(10005, "查询设备失败"),
BIND_ERROR(10006, "该设备没有绑定商家"),
// **相关错误码 业务模块11
NON_DISH_ERROR(110001, "无绑定餐盘信息"),
MERCHANT_DISH_ERROR(110002, "餐盘校验错误"),
BIND_DISH_ERROR(110006, "餐盘已被其他用户绑定"),
/**
* 其他
*/
FEIGN_ERROR(500, "内部服务调用错误");
private Integer code;
private String message;
CommonError(Integer code, String message) {
this.code = code;
this.message = message;
}
@Override
public int getCode() {
return this.code;
}
@Override
public String getMessage() {
return this.message;
}
@Override
public String toString() {
return toString(getCode(), getMessage());
}
}
配置响应时间格式
spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GM+8
更多推荐
所有评论(0)