springboot之全局处理异常封装
springboot之全局处理异常封装
简介
在项目中经常出现系统异常的情况,比如NullPointerException
等等。如果默认未处理的情况下,springboot
会响应默认的错误提示,这样对用户体验不是友好,系统层面的错误,用户不能感知到,即使为500
的错误,可以给用户提示一个类似服务器开小差
的友好提示等。
在微服务里,每个服务中都会有异常情况,几乎所有服务的默认异常处理配置一致,导致很多重复编码,我们将这些重复默认异常处理可以抽出一个公共starter
包,各个服务依赖即可,定制化异常处理在各个模块里开发。
配置
unified-dispose-springboot-starter
这个模块里包含异常处理以及全局返回封装等功能,下面。
完整目录结构如下:
├── pom.xml
├── src
│ ├── main
│ │ ├── java
│ │ │ └── com
│ │ │ └── purgetiem
│ │ │ └── starter
│ │ │ └── dispose
│ │ │ ├── GlobalDefaultConfiguration.java
│ │ │ ├── GlobalDefaultProperties.java
│ │ │ ├── Interceptors.java
│ │ │ ├── Result.java
│ │ │ ├── advice
│ │ │ │ └── CommonResponseDataAdvice.java
│ │ │ ├── annotation
│ │ │ │ ├── EnableGlobalDispose.java
│ │ │ │ └── IgnorReponseAdvice.java
│ │ │ └── exception
│ │ │ ├── GlobalDefaultExceptionHandler.java
│ │ │ ├── category
│ │ │ │ └── BusinessException.java
│ │ │ └── error
│ │ │ ├── CommonErrorCode.java
│ │ │ └── details
│ │ │ └── BusinessErrorCode.java
│ │ └── resources
│ │ ├── META-INF
│ │ │ └── spring.factories
│ │ └── dispose.properties
│ └── test
│ └── java
异常处理
@RestControllerAdvice
或者 @ControllerAdvice
为spring
的异常处理注解。
我们先创建GlobalDefaultExceptionHandler
全局异常处理类:
@RestControllerAdvice
public class GlobalDefaultExceptionHandler {
private static final Logger log = LoggerFactory.getLogger(GlobalDefaultExceptionHandler.class);
/**
* NoHandlerFoundException 404 异常处理
*/
@ExceptionHandler(value = NoHandlerFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public Result handlerNoHandlerFoundException(NoHandlerFoundException exception) {
outPutErrorWarn(NoHandlerFoundException.class, CommonErrorCode.NOT_FOUND, exception);
return Result.ofFail(CommonErrorCode.NOT_FOUND);
}
/**
* HttpRequestMethodNotSupportedException 405 异常处理
*/
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public Result handlerHttpRequestMethodNotSupportedException(
HttpRequestMethodNotSupportedException exception) {
outPutErrorWarn(HttpRequestMethodNotSupportedException.class,
CommonErrorCode.METHOD_NOT_ALLOWED, exception);
return Result.ofFail(CommonErrorCode.METHOD_NOT_ALLOWED);
}
/**
* HttpMediaTypeNotSupportedException 415 异常处理
*/
@ExceptionHandler(HttpMediaTypeNotSupportedException.class)
public Result handlerHttpMediaTypeNotSupportedException(
HttpMediaTypeNotSupportedException exception) {
outPutErrorWarn(HttpMediaTypeNotSupportedException.class,
CommonErrorCode.UNSUPPORTED_MEDIA_TYPE, exception);
return Result.ofFail(CommonErrorCode.UNSUPPORTED_MEDIA_TYPE);
}
/**
* Exception 类捕获 500 异常处理
*/
@ExceptionHandler(value = Exception.class)
public Result handlerException(Exception e) {
return ifDepthExceptionType(e);
}
/**
* 二次深度检查错误类型
*/
private Result ifDepthExceptionType(Throwable throwable) {
Throwable cause = throwable.getCause();
if (cause instanceof ClientException) {
return handlerClientException((ClientException) cause);
}
if (cause instanceof FeignException) {
return handlerFeignException((FeignException) cause);
}
outPutError(Exception.class, CommonErrorCode.EXCEPTION, throwable);
return Result.ofFail(CommonErrorCode.EXCEPTION);
}
/**
* FeignException 类捕获
*/
@ExceptionHandler(value = FeignException.class)
public Result handlerFeignException(FeignException e) {
outPutError(FeignException.class, CommonErrorCode.RPC_ERROR, e);
return Result.ofFail(CommonErrorCode.RPC_ERROR);
}
/**
* ClientException 类捕获
*/
@ExceptionHandler(value = ClientException.class)
public Result handlerClientException(ClientException e) {
outPutError(ClientException.class, CommonErrorCode.RPC_ERROR, e);
return Result.ofFail(CommonErrorCode.RPC_ERROR);
}
/**
* BusinessException 类捕获
*/
@ExceptionHandler(value = BusinessException.class)
public Result handlerBusinessException(BusinessException e) {
outPutError(BusinessException.class, CommonErrorCode.BUSINESS_ERROR, e);
return Result.ofFail(e.getCode(), e.getMessage());
}
/**
* HttpMessageNotReadableException 参数错误异常
*/
@ExceptionHandler(HttpMessageNotReadableException.class)
public Result handleHttpMessageNotReadableException(HttpMessageNotReadableException e) {
outPutError(HttpMessageNotReadableException.class, CommonErrorCode.PARAM_ERROR, e);
String msg = String.format("%s : 错误详情( %s )", CommonErrorCode.PARAM_ERROR.getMessage(),
e.getRootCause().getMessage());
return Result.ofFail(CommonErrorCode.PARAM_ERROR.getCode(), msg);
}
/**
* BindException 参数错误异常
*/
@ExceptionHandler(BindException.class)
public Result handleMethodArgumentNotValidException(BindException e) {
outPutError(BindException.class, CommonErrorCode.PARAM_ERROR, e);
BindingResult bindingResult = e.getBindingResult();
return getBindResultDTO(bindingResult);
}
private Result getBindResultDTO(BindingResult bindingResult) {
List<FieldError> fieldErrors = bindingResult.getFieldErrors();
if (log.isDebugEnabled()) {
for (FieldError error : fieldErrors) {
log.error("{} -> {}", error.getDefaultMessage(), error.getDefaultMessage());
}
}
if (fieldErrors.isEmpty()) {
log.error("validExceptionHandler error fieldErrors is empty");
Result.ofFail(CommonErrorCode.BUSINESS_ERROR.getCode(), "");
}
return Result
.ofFail(CommonErrorCode.PARAM_ERROR.getCode(), fieldErrors.get(0).getDefaultMessage());
}
public void outPutError(Class errorType, Enum secondaryErrorType, Throwable throwable) {
log.error("[{}] {}: {}", errorType.getSimpleName(), secondaryErrorType, throwable.getMessage(),
throwable);
}
public void outPutErrorWarn(Class errorType, Enum secondaryErrorType, Throwable throwable) {
log.warn("[{}] {}: {}", errorType.getSimpleName(), secondaryErrorType, throwable.getMessage());
}
}
大致内容处理了一些项目常见的异常Exception
,BindException
参数异常等。
这里将默认的404
、405
、415
等默认http
状态码也重写了。
重写这个默认的状态码需要配置throw-exception-if-no-handler-found
以及add-mappings
。
# 出现错误时, 直接抛出异常(便于异常统一处理,否则捕获不到404)
spring.mvc.throw-exception-if-no-handler-found=true
# 是否开启默认的资源处理,默认为true
spring.resources.add-mappings=false
ps: 请注意这两个配置会将静态资源忽略。
请产考WebMvcAutoConfiguration#addResourceHandlers
Exception
为了防止未知的异常没有防护到,默认给用户返回服务器开小差,请稍后再试
等提示。
具体异常默认会以小到大去匹配。
如果抛出BindException
,自定义有BindException
就会去这个处理器里处理。没有就会走到它的父类去匹配,请参考java-异常体系
。
其他已知异常可以自己用@ExceptionHandler
注解进行捕获处理。
通用异常枚举
为了避免异常值不好维护,我们使用CommonErrorCode
枚举把常见的异常提示维护起来。
@Getter
public enum CommonErrorCode {
/**
* 404 Web 服务器找不到您所请求的文件或脚本。请检查URL 以确保路径正确。
*/
NOT_FOUND("CLOUD-404",
String.format("哎呀,无法找到这个资源啦(%s)", HttpStatus.NOT_FOUND.getReasonPhrase())),
/**
* 405 对于请求所标识的资源,不允许使用请求行中所指定的方法。请确保为所请求的资源设置了正确的 MIME 类型。
*/
METHOD_NOT_ALLOWED("CLOUD-405",
String.format("请换个姿势操作试试(%s)", HttpStatus.METHOD_NOT_ALLOWED.getReasonPhrase())),
/**
* 415 Unsupported Media Type
*/
UNSUPPORTED_MEDIA_TYPE("CLOUD-415",
String.format("呀,不支持该媒体类型(%s)", HttpStatus.UNSUPPORTED_MEDIA_TYPE.getReasonPhrase())),
/**
* 系统异常 500 服务器的内部错误
*/
EXCEPTION("CLOUD-500", "服务器开小差,请稍后再试"),
/**
* 系统限流
*/
TRAFFIC_LIMITING("CLOUD-429", "哎呀,网络拥挤请稍后再试试"),
/**
* 服务调用异常
*/
API_GATEWAY_ERROR("API-9999", "网络繁忙,请稍后再试"),
/**
* 参数错误
*/
PARAM_ERROR("CLOUD-100", "参数错误"),
/**
* 业务异常
*/
BUSINESS_ERROR("CLOUD-400", "业务异常"),
/**
* rpc调用异常
*/
RPC_ERROR("RPC-510", "呀,网络出问题啦!");
private String code;
private String message;
CommonErrorCode(String code, String message) {
this.code = code;
this.message = message;
}
}
其实starter
包中不建议使用@Getter
等lombok
注解,防止他人未使用lombok
依赖该项目出现问题。
通用业务异常
这两个类完成基本可以正常使用异常拦截了,不过为了业务方便,我们创建一个一般通用的业务异常。
BusinessException
继承RuntimeException
即可。
@Getter
public class BusinessException extends RuntimeException {
private String code;
private boolean isShowMsg = true;
/**
* 使用枚举传参
*
* @param errorCode 异常枚举
*/
public BusinessException(BusinessErrorCode errorCode) {
super(errorCode.getMessage());
this.code = errorCode.getCode();
}
/**
* 使用自定义消息
*
* @param code 值
* @param msg 详情
*/
public BusinessException(String code, String msg) {
super(msg);
this.code = code;
}
}
将BusinessException
加入GlobalDefaultExceptionHandler
全局异常拦截。
/**
* BusinessException 类捕获
*/
@ExceptionHandler(value = BusinessException.class)
public Result handlerBusinessException(BusinessException e) {
outPutError(BusinessException.class, CommonErrorCode.BUSINESS_ERROR, e);
return Result.ofFail(e.getCode(), e.getMessage());
}
程序主动抛出异常可以通过下面方式:
throw new BusinessException(BusinessErrorCode.BUSINESS_ERROR);
// 或者
throw new BusinessException("CLOUD800","没有多余的库存");
通常不建议直接抛出通用的BusinessException异常,应当在对应的模块里添加对应的领域的异常处理类以及对应的枚举错误类型。
如会员模块:
创建UserException
异常类、UserErrorCode
枚举、以及UserExceptionHandler
统一拦截类。
UserException:
@Data
public class UserException extends RuntimeException {
private String code;
private boolean isShowMsg = true;
/**
* 使用枚举传参
*
* @param errorCode 异常枚举
*/
public UserException(UserErrorCode errorCode) {
super(errorCode.getMessage());
this.setCode(errorCode.getCode());
}
}
UserErrorCode:
@Getter
public enum UserErrorCode {
/**
* 权限异常
*/
NOT_PERMISSIONS("CLOUD401","您没有操作权限"),
;
private String code;
private String message;
CommonErrorCode(String code, String message) {
this.code = code;
this.message = message;
}
}
UserExceptionHandler:
@Slf4j
@RestControllerAdvice
public class UserExceptionHandler {
/**
* UserException 类捕获
*/
@ExceptionHandler(value = UserException.class)
public Result handler(UserException e) {
log.error(e.getMessage(), e);
return Result.ofFail(e.getCode(), e.getMessage());
}
}
最后业务使用如下:
// 判断是否有权限抛出异常
throw new UserException(UserErrorCode.NOT_PERMISSIONS);
加入spring容器
最后将GlobalDefaultExceptionHandler
以bean
的方式注入spring
容器。
@Configuration
@EnableConfigurationProperties(GlobalDefaultProperties.class)
@PropertySource(value = "classpath:dispose.properties", encoding = "UTF-8")
public class GlobalDefaultConfiguration {
@Bean
public GlobalDefaultExceptionHandler globalDefaultExceptionHandler() {
return new GlobalDefaultExceptionHandler();
}
@Bean
public CommonResponseDataAdvice commonResponseDataAdvice(GlobalDefaultProperties globalDefaultProperties){
return new CommonResponseDataAdvice(globalDefaultProperties);
}
}
将GlobalDefaultConfiguration
在resources/META-INF/spring.factories
文件下加载。
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.purgetime.starter.dispose.GlobalDefaultConfiguration
不过我们这次使用注解方式开启。其他项目依赖包后,需要添加@EnableGlobalDispose
才可以将全局拦截的特性开启。
将刚刚创建的spring.factories
注释掉,创建EnableGlobalDispose
注解。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(GlobalDefaultConfiguration.class)
public @interface EnableGlobalDispose {
}
使用@Import
将GlobalDefaultConfiguration
导入即可。
使用
添加依赖
<dependency>
<groupId>com.purgeteam</groupId>
<artifactId>unified-dispose-deepblueai-starter</artifactId>
<version>0.1.1.RELEASE</version>
</dependency>
启动类开启@EnableGlobalDispose
注解即可。
总结
项目里很多重复的code,我们可以通过一定的方式去简化,以达到一定目的减少开发量。
示例代码地址:unified-dispose-springboot
作者GitHub:
Purgeyao 欢迎关注
springboot之全局处理异常封装的更多相关文章
- SpringBoot处理全局统一异常
在后端发生异常或者是请求出错时,前端通常显示如下 Whitelabel Error Page This application has no explicit mapping for /error, ...
- (转)springboot全局处理异常(@ControllerAdvice + @ExceptionHandler)
1.@ControllerAdvice 1.场景一 在构建RestFul的今天,我们一般会限定好返回数据的格式比如: { "code": 0, "data": ...
- 十四、springboot全局处理异常(@ControllerAdvice + @ExceptionHandler)
1.@ControllerAdvice 1.场景一 在构建RestFul的今天,我们一般会限定好返回数据的格式比如: { "code": 0, "data": ...
- springboot之全局处理统一返回
springboot之全局处理统一返回 简介 在REST风格的开发中,避免通常会告知前台返回是否成功以及状态码等信息.这里我们通常返回的时候做一次util的包装处理工作,如:Result类似的类,里面 ...
- ExceptionHandler配合RestControllerAdvice全局处理异常
Java全局处理异常 引言 对于controller中的代码,为了保证其稳定性,我们总会对每一个controller中的代码进行try-catch,但是由于接口太多,try-catch会显得太冗杂,s ...
- Java异常封装
转载: Java异常封装 Java里面的异常在真正工作中使用还是十分普遍的.什么时候该抛出什么异常,这个是必须知道的. 当然真正工作里面主动抛出的异常都是经过分装过的,自己可以定义错误码和异常描述. ...
- spring mvc 全局处理异常
spring框架支持很多种全局处理异常的方式 一.Spring MVC处理异常有4种方式: (1)使用Spring-MVC提供的SimpleMappingExceptionResolver: (2)实 ...
- Ubuntu18.06 Mate桌面环境下VirtuslBox打开虚拟机“全局菜单”异常退出解决办法
在安装完Ubuntu18.06 Mate桌面环境后在VirtuslBox里打开虚拟机会出现“全局菜单”异常退出问题. 产生上面问题的原因是你的虚拟机可能在 显示= >屏幕= >硬件加速里勾 ...
- Java异常封装(自定义错误信息和描述)
一.checked异常和unchecked异常 checked异常: unchecked异常: 二.异常封装示例 2.1.添加一个枚举LuoErrorCode.java如下: 2.2.创建一个异常类B ...
随机推荐
- Go-如何读取yaml,json,ini等配置文件
1. json使用 JSON 应该比较熟悉,它是一种轻量级的数据交换格式.层次结构简洁清晰 ,易于阅读和编写,同时也易于机器解析和生成. 创建 conf.json: { "enabled&q ...
- ES解决bootstrap checks failed, memory locking requested for elasticsearch process but memory is not locked问题
问题描述: ERROR: [1] bootstrap checks failed[1]: memory locking requested for elasticsearch process but ...
- react native https
1. ios解决方案 1.1 Xcode打开工程,Libraries -> RCTNetworking -> RCTHTTPRequestHandler.mm -> #pragma ...
- ExecutorService 的理解和使用
前言: 我们之前使用线程的时候都是使用new Thread来进行线程的创建,但是这样会有一些问题.如: a. 每次new Thread新建对象性能差.b. 线程缺乏统一管理,可能无限制新建线程,相互之 ...
- RabbitMQ简洁安装
在实际开发过程中,为了解决并发量大的问题,我们往往会引入消息中间件这个杀手锏,今天带大家先入门一个消息中间件RabbitMQ,我们会从RabbitMQ安装.使用来分享. 1. RabbitMQ安装 这 ...
- 微服务架构 - 网关 Spring Cloud Gateway
Spring Cloud Gateway 工作原理 客户端向 Spring Cloud Gateway 发出请求,如果请求与网关程序定义的路由匹配,则将其发送到网关 Web 处理程序,此处理程序运行特 ...
- Vue仿微信app页面跳转动画
10:14:11独立开发者在开发移动端产品时,为了更高效,通常会使用Web技术来开发移动端项目,可以同时适配Android.iOS.H5,稍加改动还可适配微信小程序. 在使用Vue.js开发移动端页面 ...
- CSS3 表单
<form action="http://baidu.com"> <input type="text" placeholder="请 ...
- CSS3 transform封装
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...
- Leetcode之深度优先搜索(DFS)专题-494. 目标和(Target Sum)
Leetcode之深度优先搜索(DFS)专题-494. 目标和(Target Sum) 深度优先搜索的解题详细介绍,点击 给定一个非负整数数组,a1, a2, ..., an, 和一个目标数,S.现在 ...