springboot判空工具_SpringBoot 统一异常处理(附核心工具类-ErrorInfoBuilder)

本文介绍了SpringBoot如何进行统一异常处理,包括默认的错误处理和自定义全局异常处理器。通过ErrorInfoBuilder工具类,可以简化异常信息的获取和处理,减少控制层代码,提高开发效率。示例代码展示了如何创建GlobalErrorHandler、ErrorInfo和ErrorInfoBuilder,以及如何在控制层使用。
摘要由CSDN通过智能技术生成

序言:此前,我们主要通过在控制层(Controller)中手动捕捉异常(TryCatch)和处理错误,在SpringBoot 统一异常处理的做法主要有两种:一是基于注解ExceptionHandler,二是基于接口

ErrorController,两者都可以让控制器层代码快速“瘦身”,让业务逻辑看起来更加清晰明朗!

一. 默认错误处理

SpringBoot 默认为我们提供了BasicErrorController 来处理全局错误/异常,并在Servlet容器中注册error为全局错误页。所以在浏览器端访问,发生错误时,我们能及时看到错误/异常信息和HTTP状态等反馈。工作原理如下:

@Controller

@RequestMapping("${server.error.path:${error.path:/error}}")

public class BasicErrorController extends AbstractErrorController{

// 统一异常处理(View)

@RequestMapping(produces = "text/html")

public ModelAndView errorHtml(HttpServletRequest request,

HttpServletResponse response){

HttpStatus status = getStatus(request);

Map model = Collections.unmodifiableMap(getErrorAttributes(

request, isIncludeStackTrace(request, MediaType.TEXT_HTML)));

response.setStatus(status.value());

ModelAndView modelAndView = resolveErrorView(request, response, status, model);

return (modelAndView == null ? new ModelAndView("error", model) : modelAndView);

}

复制代码

例如下面这两个错误,对于日常开发而言,再熟悉不过了。

404 Not Found

500 服务器错误

二. 统一异常处理(具体步骤)

默认的英文空白页,显然不能够满足我们复杂多变的需求,因此我们可以通过专门的类来收集和管理这些异常信息,这样做不仅可以减少控制层的代码量,还有利于线上故障排查和紧急短信通知等。

具体步骤

为了让小伙伴少走一些弯路,楼主根据官方源码和具体实践,提炼这些核心工具类:

ErrorInfo 错误信息

ErrorInfoBuilder 错误信息的构建工具

注:在CSDN和大牛博客中,不乏关于Web应用的统一异常处理的教程,但更多的是基础学习使用,并不能投入实际项目使用,为了让大家少走一些弯路和快速投入生产,楼主根据官方源码和项目实践,提炼出了核心工具类(ErrorInfoBuilder ),将构建异常信息的逻辑从异常处理器/控制器中抽离出来,让大家通过短短几行代码就能获取丰富的异常信息,更专注于业务开发!!

1. 统一异常处理器(GlobalErrorHandler)

@ControllerAdvice 限定范围 例如扫描某个控制层的包

@ExceptionHandler 指定异常 例如指定处理运行异常。

具体如下:

package com.hehe.error;

@ControllerAdvice

public class GlobalErrorHandler{

private final static String DEFAULT_ERROR_VIEW = "error";//错误信息页

@Autowired

private ErrorInfoBuilder errorInfoBuilder;//错误信息的构建工具

/**

* 根据业务规则,统一处理异常。

*/

@ExceptionHandler(Exception.class)

@ResponseBody

public Object exceptionHandler(HttpServletRequest request, Throwable error){

//1.若为AJAX请求,则返回异常信息(JSON)

if (isAjaxRequest(request)) {

return errorInfoBuilder.getErrorInfo(request,error);

}

//2.其余请求,则返回指定的异常信息页(View).

return new ModelAndView(DEFAULT_ERROR_VIEW, "errorInfo", errorInfoBuilder.getErrorInfo(request, error));

}

private boolean isAjaxRequest(HttpServletRequest request){

return "XMLHttpRequest".equals(request.getHeader("X-Requested-With"));

}

}复制代码

2. 统一异常信息(ErrorInfo)

虽然官方提供了ErrorAttributes来存储错误信息,但其返回的是存储结构是Map,为了更好的服务统一异常,这里我们统一采用标准的ErrroInfo来记载错误信息。

package com.hehe.error;

public class ErrorInfo{

private String time;//发生时间

private String url;//访问Url

private String error;//错误类型

String stackTrace;//错误的堆栈轨迹

private int statusCode;//状态码

private String reasonPhrase;//状态码

//Getters And Setters

...

}复制代码

3. 统一异常信息的构建工具(ErrorInfoBuilder)

ErrorInfoBuilder 作为核心工具类,其意义不言而喻,重点API:

获取错误/异常

通信状态

堆栈轨迹

注:正确使用ErrorInfoBuilder,可以让处理器减少80%的代码。总而言之,ErrorInfoBuilder是个好东西,值得大家细细琢磨。

package com.hehe.error;

@Order(Ordered.HIGHEST_PRECEDENCE)

@Component

public class ErrorInfoBuilder implements HandlerExceptionResolver, Ordered{

/**

* 错误KEY

*/

private final static String ERROR_NAME = "hehe.error";

/**

* 错误配置(ErrorConfiguration)

*/

private ErrorProperties errorProperties;

public ErrorProperties getErrorProperties(){

return errorProperties;

}

public void setErrorProperties(ErrorProperties errorProperties){

this.errorProperties = errorProperties;

}

/**

* 错误构造器 (Constructor) 传递配置属性:server.xx -> server.error.xx

*/

public ErrorInfoBuilder(ServerProperties serverProperties){

this.errorProperties = serverProperties.getError();

}

/**

* 构建错误信息.(ErrorInfo)

*/

public ErrorInfo getErrorInfo(HttpServletRequest request){

return getErrorInfo(request, getError(request));

}

/**

* 构建错误信息.(ErrorInfo)

*/

public ErrorInfo getErrorInfo(HttpServletRequest request, Throwable error){

ErrorInfo errorInfo = new ErrorInfo();

errorInfo.setTime(LocalDateTime.now().toString());

errorInfo.setUrl(request.getRequestURL().toString());

errorInfo.setError(error.toString());

errorInfo.setStatusCode(getHttpStatus(request).value());

errorInfo.setReasonPhrase(getHttpStatus(request).getReasonPhrase());

errorInfo.setStackTrace(getStackTraceInfo(error, isIncludeStackTrace(request)));

return errorInfo;

}

/**

* 获取错误.(Error/Exception)

*

* @see DefaultErrorAttributes #addErrorDetails

*/

public Throwable getError(HttpServletRequest request){

//根据HandlerExceptionResolver接口方法来获取错误.

Throwable error = (Throwable) request.getAttribute(ERROR_NAME);

//根据Request对象获取错误.

if (error == null) {

error = (Throwable) request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE);

}

//当获取错误非空,取出RootCause.

if (error != null) {

while (error instanceof ServletException && error.getCause() != null) {

error = error.getCause();

}

}//当获取错误为null,此时我们设置错误信息即可.

else {

String message = (String) request.getAttribute(WebUtils.ERROR_MESSAGE_ATTRIBUTE);

if (StringUtils.isEmpty(message)) {

HttpStatus status = getHttpStatus(request);

message = "Unknown Exception But " + status.value() + " " + status.getReasonPhrase();

}

error = new Exception(message);

}

return error;

}

/**

* 获取通信状态(HttpStatus)

*

* @see AbstractErrorController #getStatus

*/

public HttpStatus getHttpStatus(HttpServletRequest request){

Integer statusCode = (Integer) request.getAttribute(WebUtils.ERROR_STATUS_CODE_ATTRIBUTE);

try {

return statusCode != null ? HttpStatus.valueOf(statusCode) : HttpStatus.INTERNAL_SERVER_ERROR;

} catch (Exception ex) {

return HttpStatus.INTERNAL_SERVER_ERROR;

}

}

/**

* 获取堆栈轨迹(StackTrace)

*

* @see DefaultErrorAttributes #addStackTrace

*/

public String getStackTraceInfo(Throwable error, boolean flag){

if (!flag) {

return "omitted";

}

StringWriter stackTrace = new StringWriter();

error.printStackTrace(new PrintWriter(stackTrace));

stackTrace.flush();

return stackTrace.toString();

}

/**

* 判断是否包含堆栈轨迹.(isIncludeStackTrace)

*

* @see BasicErrorController #isIncludeStackTrace

*/

public boolean isIncludeStackTrace(HttpServletRequest request){

//读取错误配置(server.error.include-stacktrace=NEVER)

IncludeStacktrace includeStacktrace = errorProperties.getIncludeStacktrace();

//情况1:若IncludeStacktrace为ALWAYS

if (includeStacktrace == IncludeStacktrace.ALWAYS) {

return true;

}

//情况2:若请求参数含有trace

if (includeStacktrace == IncludeStacktrace.ON_TRACE_PARAM) {

String parameter = request.getParameter("trace");

return parameter != null && !"false".equals(parameter.toLowerCase());

}

//情况3:其它情况

return false;

}

/**

* 保存错误/异常.

*

* @see DispatcherServlet #processHandlerException 进行选举HandlerExceptionResolver

*/

@Override

public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex){

request.setAttribute(ERROR_NAME, ex);

return null;

}

/**

* 提供优先级 或用于排序

*/

@Override

public int getOrder(){

return Ordered.HIGHEST_PRECEDENCE;

}

}

复制代码

注:ErrorBuilder之所以使用@Order注解和实现HandlerExceptionResolver接口是为了获取错误/异常,通常情况下@ExceptionHandler并不需要这么做,因为在映射方法注入Throwable就可以获得错误/异常,这是主要是为了ErrorController根据Request对象快速获取错误/异常。

4. 控制层代码(Controller)

上述,错误/异常处理器、错误信息、错误信息构建工具全部完成,我们编写控制层代码来测试相关效果。

package com.hehe;

@SpringBootApplication

@RestController

public class ErrorHandlerApplication{

/**

* 随机抛出异常

*/

private void randomException() throws Exception{

Exception[] exceptions = { //异常集合

new NullPointerException(),

new ArrayIndexOutOfBoundsException(),

new NumberFormatException(),

new SQLException()};

//发生概率

double probability = 0.75;

if (Math.random() < probability) {

//情况1:要么抛出异常

throw exceptions[(int) (Math.random() * exceptions.length)];

} else {

//情况2:要么继续运行

}

}

/**

* 模拟用户数据访问

*/

@GetMapping("/")

public List index() throws Exception{

randomException();

return Arrays.asList("正常用户数据1!", "正常用户数据2! 请按F5刷新!!");

}

public static void main(String[] args){

SpringApplication.run(ErrorHandlerApplication.class, args);

}

}复制代码

5. 页面代码(Thymeleaf)

代码完成之后,我们需要编写一个异常信息页面。为了方便演示,我们在resources目录下创建templates目录,并新建文件exception.html。页面代码如下:

GlobalError
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值