首页 > 其他分享 >技术派全局异常处理

技术派全局异常处理

时间:2024-07-01 22:27:47浏览次数:3  
标签:Status status return 处理 public 全局 异常 response

前言

全局的异常处理是Java后端不可或缺的一部分,可以提高代码的健壮性和可维护性。

在我们的开发中,总是难免会碰到一些未经处理的异常,假如没有做全局异常处理,那么我们返回给用户的信息应该是不友好的,很抽象的,用户会认为我们的程序是不安全的。

相反,如果有了全局异常处理,那么我们就可以给用户提供更友好的反馈。

我们甚至可以把全局异常处理写到简历上,比如说你可以这样描述:项目采用了 HandlerExceptionResolver(或者 ControllerAdvice 方案)的全局异常处理策略,提高了代码的健壮性和可维护性,优化了用户体验。

我会结合具体的业务场景给大家一种身临其境的感觉(@),讲一讲HandlerExceptionResolver 和 ControllerAdvice 具体怎么在项目中使用。

业务场景

技术派整合了Redis,比如用户登录的时候会从Redis中获取缓存,那假如我们没有启动Redis服务呢?

然后我们在本地启动技术派的服务端。

然后点击登录 ->一键登录。

然后就会收到这样一条提示信息。

由于我们项目是开源的,所以这里就直接把服务端的信息返回出来,好让大家第一时间辨别出是哪里出了问题,可以及时去调整。

当你看到这样一条错误提示,第一时间就能明白,哦,原来是 Redis 没有启动啊。

在服务器端的控制台面板中(错误堆栈信息中),可以找到对应的错误信恙。

其中 ForumExceptionHandler 就是用来进行全局异常处理的,它是HandlerExceptionResolver 接囗的实现类

HandlerExceptionResolver

HandlerExceptionResolver 是 Spring 提供的一种异常处理机制,它允许我们在应用程序中以统一的方式处理控制器方法引发的异常。

要使用 HandlerExceptionResolver,我们需要创建一个实现该接口的类,并在其中定义如何处理异常。例如:

@Slf4j
@Order(-100)
public class ForumExceptionHandler implements HandlerExceptionResolver {

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

  • @Slf4j 是 lombok 提供的一个日志注解。
  • @0rder 注解用于指定 Spring 中组件的加载顺序。它接受一个整数值,数值越小,组件的优先级越高,加载顺序越靠前。
  • 在 resolveException 方法中,我们可以自定义异常处理逻辑,根据异常类型返回不同的 ModelAndView。

我们来看一下 resolveException 方法中的具体写法:

@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
    Status errStatus = buildToastMsg(ex);

    if (restResponse(request, response)) {
        // 表示返回json数据格式的异常提示信息
        if (response.isCommitted()) {
            // 如果返回已经提交过,直接退出即可
            return new ModelAndView();
        }

        try {
            response.reset();
            // 若是rest接口请求异常时,返回json格式的异常数据;而不是专门的500页面
            response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
            response.setHeader("Cache-Control", "no-cache, must-revalidate");
            response.getWriter().println(JsonUtil.toStr(ResVo.fail(errStatus)));
            response.getWriter().flush();
            response.getWriter().close();
            return new ModelAndView();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    String view = getErrorPage(errStatus, response);
    ModelAndView mv = new ModelAndView(view);
    response.setContentType(MediaType.TEXT_HTML_VALUE);
    mv.getModel().put("global", SpringUtil.getBean(GlobalInitService.class).globalAttr());
    mv.getModel().put("res", ResVo.fail(errStatus));
    mv.getModel().put("toast", JsonUtil.toStr(ResVo.fail(errStatus)));
    return mv;
}

技术派做了两种处理,一种是REST接口请求的异常,一种是针对普通页请求的异常。

1、如果是 REST 接口请求异常,代码会返回一个 JSON 格式的异常提示信息:

  • 首先检查响应是否已经提交,如果已经提交,则直接返回一个空的 ModelAndView。
  • 如果响应未提交,将重置响应对象,设置响应的内容类型为JSON,并添加相关的响应头。
  • 使用 response.getwriter()将异常状态对象 errStatus 转换为 JSON 格式并写入响应。完成后,返回一个空的 ModelAndView。

②、如果是普通页面请求异常,代码会返回一个包含错误信息的 HTML 页面:

  • 根据异常状态对象 errStatus 和响应对象 response 获取错误页面的视图名称。
  • 创建-个 ModelAndView 对象,并设置视图名称。
  • 设置响应的内容类型为 HTML。
  • 向 ModelAndView 中添加全局属性、错误响应对象以及错误信息(以JSON 格式)
  • 最后返回这个 ModelAndView 对象,用于展示错误页面。

下图是当遇到 404 错误的时候,返回的 404 页面。

其中 buildToastMsg 方法用来对异常进行分类,使用 instanceof 关键字来判断不同类型的异常,添加不同的异常码和提示消息。

private Status buildToastMsg(Exception ex) {
if (ex instanceof ForumException) {
    return ((ForumException) ex).getStatus();
} else if (ex instanceof AsyncRequestTimeoutException) {
    return Status.newStatus(StatusEnum.UNEXPECT_ERROR, "超时未登录");
} else if (ex instanceof HttpMediaTypeNotAcceptableException) {
    return Status.newStatus(StatusEnum.RECORDS_NOT_EXISTS, ExceptionUtils.getStackTrace(ex));
} else if (ex instanceof HttpRequestMethodNotSupportedException || ex instanceof MethodArgumentTypeMismatchException || ex instanceof IOException) {
    // 请求方法不匹配
    return Status.newStatus(StatusEnum.ILLEGAL_ARGUMENTS, ExceptionUtils.getStackTrace(ex));
} else if (ex instanceof NestedRuntimeException) {
    log.error("unexpect NestedRuntimeException error! {}", ReqInfoContext.getReqInfo(), ex);
    return Status.newStatus(StatusEnum.UNEXPECT_ERROR, ex.getMessage());
} else {
    log.error("unexpect error! {}", ReqInfoContext.getReqInfo(), ex);
    return Status.newStatus(StatusEnum.UNEXPECT_ERROR, ExceptionUtils.getStackTrace(ex));
}
}

StatusEnum 中定义了异常码的规范,举几个例子。

/**
 * 异常码规范:
 * xxx - xxx - xxx
 * 业务 - 状态 - code
 * <p>
 * 业务取值
 * - 100 全局
 * - 200 文章相关
 * - 300 评论相关
 * - 400 用户相关
 * <p>
 * 状态:基于http status的含义
 * - 4xx 调用方使用姿势问题
 * - 5xx 服务内部问题
 * <p>
 * code: 具体的业务code
 */
@Getter
public enum StatusEnum {
    SUCCESS(0, "OK"),

    // -------------------------------- 通用

    // 全局传参异常
    ILLEGAL_ARGUMENTS(100_400_001, "参数异常"),
    ILLEGAL_ARGUMENTS_MIXED(100_400_002, "参数异常:%s"),

    // 全局权限相关
    FORBID_ERROR(100_403_001, "无权限"),
}

getErrorPage 方法用于返回不同的错误页面,比如常见的 404 Not Found(请求的资源不存在,服务器无法找到请求的资源)、403 Forbidden(服务器理解请求,但是拒绝处理它,一般是由于权限问题或者访问被拒绝)500 lnternal Server Error(服务器发生了错误,无法完成请求)等。

private String getErrorPage(Status status, HttpServletResponse response) {
    // 根据异常码解析需要返回的错误页面
    if (StatusEnum.is5xx(status.getCode())) {
        response.setStatus(500);
        return "error/500";
    } else if (StatusEnum.is403(status.getCode())) {
        response.setStatus(403);
        return "error/403";
    } else {
        response.setStatus(404);
        return "error/404";
    }
}

restResponse 方法用来判断是否是 REST 请求,比如说 admin 后台请求、api数据请求、上传图片等接口

Ajax 请求等,这些请求统一返回 JSON 格式的异常提示信息,否则返回普通的页面格式的异常提示信息。

**
* 后台请求、api数据请求、上传图片等接口,返回json格式的异常提示信息
* 其他异常,返回500的页面
*
* @param request
* @param response
* @return
*/
private boolean restResponse(HttpServletRequest request, HttpServletResponse response) {
    if (request.getRequestURI().startsWith("/api/admin/") || request.getRequestURI().startsWith("/admin/")) {
        return true;
    }

    if (request.getRequestURI().startsWith("/image/upload")) {
        return true;
    }

    if (response.getContentType() != null && response.getContentType().contains(MediaType.APPLICATION_JSON_VALUE)) {
        return true;
    }

    if (isAjaxRequest(request)) {
        return true;
    }

    // 数据接口请求
    AntPathMatcher pathMatcher = new AntPathMatcher();
    if (pathMatcher.match("/**/api/**", request.getRequestURI())) {
        return true;
    }
    return false;
}

再来看一下自定义的异常类 ForumException,非常简单,继承了 RuntimeException。

/**
 * 业务异常
 *
 */
public class ForumException extends RuntimeException {
    @Getter
    private Status status;

    public ForumException(Status status) {
        this.status = status;
    }

    public ForumException(int code, String msg) {
        this.status = Status.newStatus(code, msg);
    }

    public ForumException(StatusEnum statusEnum, Object... args) {
        this.status = Status.newStatus(statusEnum, args);
    }

}

HandlerExceptionResolver 的工作原理主要基于 Spring MVC 的异常处理流程。当一个请求进入 Spring MVC后,它会根据请求信息找到对应的处理器(handler,也就是Controller)。在Controller 执行过程中,如果抛出了异常,Spring MVC 就会启动异常处理流程。

1)异常发生:当 Controller 执行过程中抛出异常,Spring MVC 捕获到这个异常后,会进入异常处理流程

2)查找异常解析器:Spring MVC 会遍历所有已注册的 HandlerExceptionResolver 实现。比如说我们自定义的

ForumExceptionHandler,Spring MVC 本身也提供了一些默认的实现,比如DefaultHandlerExceptionResolver、ExceptionHandlerExceptionResolver.

3)执行异常解析器:对于每个 HandlerExceptionResolver 实现,Spring MVC会调用它的 resolveException方法,并传入请求、响应、处理器和异常对象。如果解析器能处理这个异常,它会返回一个非空的ModelAndView 对象。这个对象封装了异常处理后的视图和模型数据。

4)处理返回结果:当 resolveException 方法返回一个非空的 ModelAndView 对象时,Spring MVC 会将这个对象用于生成最终的响应。可能渲染一个错误视图、设置响应状态码等。如果所有的HandlerExceptionResolver 都无法处理这个异常(即都返回了空的 ModelAndView对象),那么 SpringMVC 会将异常重新抛出,以便其他异常处理器(如 Servet 容器)进行处理。

通过这个流程,HandlerExceptionResolver 能够在 Spring MVC 中统一管理和处理异常。记得在 Spring Boot的启动类中将自定义的 HandlerExceptionResolver 添加到 Spring 配置中。

@Slf4j
@EnableAsync
@EnableScheduling
@EnableCaching
@ServletComponentScan  //与@WebFilter(urlPatterns = "/*", filterName = "reqRecordFilter", asyncSupported = true)注解配套
@SpringBootApplication
public class QuickForumApplication implements WebMvcConfigurer, ApplicationRunner {

    @Override
    public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
        resolvers.add(0, new ForumExceptionHandler());
    }
}

@ControllerAdvice

除了 HandlerExceptionResolver,全局异常还可以采用 @ControllerAdvice 注解的方式。它可以将通用的操作和逻辑抽离出来,避免在每个控制器中重复相同的操作。

第一步,新建一个自定义的异常类 ForumAdviceException。

/**
 * 业务异常
 *
 */
public class ForumAdviceException extends RuntimeException {
    @Getter
    private Status status;

    public ForumAdviceException(Status status) {
        this.status = status;
    }

    public ForumAdviceException(int code, String msg) {
        this.status = Status.newStatus(code, msg);
    }

    public ForumAdviceException(StatusEnum statusEnum, Object... args) {
        this.status = Status.newStatus(statusEnum, args);
    }

}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Status {

    /**
     * 业务状态码
     */
    @ApiModelProperty(value = "状态码, 0表示成功返回,其他异常返回", required = true, example = "0")
    private int code;

    /**
     * 描述信息
     */
    @ApiModelProperty(value = "正确返回时为ok,异常时为描述文案", required = true, example = "ok")
    private String msg;

    public static Status newStatus(int code, String msg) {
        return new Status(code, msg);
    }

    public static Status newStatus(StatusEnum status, Object... msgs) {
        String msg;
        if (msgs.length > 0) {
            msg = String.format(status.getMsg(), msgs);
        } else {
            msg = status.getMsg();
        }
        return newStatus(status.getCode(), msg);
    }
}

第二步,新建一个全局异常控制器 GlobalExceptionHandler,内容如下所示。

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(value = ForumAdviceException.class)
    public ResVo<String> handleForumAdviceException(ForumAdviceException e) {
        return ResVo.fail(e.getStatus());
    }
}

@RestControllerAdvice 是一个特殊的 @ControllerAdvice 注解,适用于处理 RESTfu API 异常的情况。这意味着它将用于处理来自带有 @RestController 注解的控制器抛出的异常。

此类中定义的方法 handleForumAdviceException 使用 @ExceptionHandler 注解,表示它将处理ForumAdviceException 类型的异常。

第三步,加一个测试的控制器方法 testControllerAdvice。

@RequestMapping(path = "testControllerAdvice")
@ResponseBody
public String testControllerAdvice() {
    throw new ForumAdviceException(StatusEnum.ILLEGAL_ARGUMENTS_MIXED, "测试ControllerAdvice异常");
}

第四步,如果之前在启动类中注册了 ForumExceptionHandler,此时需要干掉。

第五步,重启启动服务,测试 testControllerAdvice 接口。

接口返回的内容如下所示

两种全局异常处理的优缺点

好,我们来对比一下两种全局异常处理 HandlerExceptionResolver 和 @ControllerAdvice(或@RestControllerAdvice)的优缺点。

1、HandlerExceptionResolver

HandlerExceptionResolver 是一个接口,用于处理由 Controller 抛出的异常,我们可以重写resolveException方法,在其中实现该接口来自定义全局异常处理逻辑。然后在Spring Boot 的启动类中通过

extendHandlerExceptionResolvers 将自定义的 HandlerExceptionResolver 添加到解析器中。

  • 优点:可以更加灵活地处理异常,因为你可以编写任何处理逻辑。
  • 缺点:与其他 Spring MVC 组件的集成不够紧密,需要手动添加和配置,。

2、@ControllerAdvice(或@RestControllerAdvice)

@ControllerAdvice(或 @RestControllerAdvice)是用于定义全局异常处理类的注解。在这个类中,我们可以使用 @ExceptionHandler 注解来处理不同类型的异常。@ControllerAdvice 需要与 @ExceptionHandler 注解一起使用。

  • 优点:更容易实现和集成,只需创建一个带有 @ControllerAdvice(或 @RestControllerAdvice)注解的类,并使用 @ExceptionHandler 注解定义异常处理方法。
  • 缺点:异常处理逻辑可能不如 HandlerxceptionResolver 那么灵活。

我们可以根据具体的需求和使用场景,选择其中之一来实现全局异常处理。另外二者可以共存,

ControllerAddvice优先级比HandlerExceptionResolver高,也可以用@order注解指定优先级。

标签:Status,status,return,处理,public,全局,异常,response
From: https://blog.csdn.net/qq_64948664/article/details/140111383

相关文章

  • 用栈来处理算式
    3302.表达式求值-AcWing题库#include<bits/stdc++.h>usingnamespacestd;#defineintlonglong#defineendl'\n'constintN=5e5+10;stack<int>st;//存数字stack<char>op;//存操作符voideval(){inta=st.top();//第二个数st.pop();......
  • 选择合适的 Fluent 前处理工具
    随着商业软件的不断发展,Fluent 仿真的前处理工具也越来越多。同时,大多数软件都在不断的更新功能,使我们的工作越来越方便、快捷、精准。当然,面对着种类繁多的前处理工具,如何选择适合自己、适合仿真的软件,反而成为了一件“幸福的烦心事”。本文就尝试从一个最为普适 Fluent 仿......
  • Open3D 点云快速全局配准FGR算法(粗配准)
    目录一、概述1.1原理和步骤1.2关键技术和优势1.3应用场景二、代码实现2.1关键代码2.1.1.函数:execute_fast_global_registration2.1.2调用registration_fgr_based_on_feature_matching函数2.2完整代码三、实现效果3.1原始点云3.2粗配准后点云一、概述    ......
  • alert 窗口的处理
    alert窗口的类型:警告框、确认框、对话框 操作alert窗口:切换至alert窗口:driver.switch_to.alert获取alert窗口的值:alert.text确定:alert.accept()取消:alert.dismiss()输入值:alert.send_keys("内容") fromseleniumimportwebdriverimportti......
  • Spring Boot 全局异常捕获机制详解
    在SpringBoot中,全局异常捕获机制是处理RESTHTTP请求时的一个重要功能,它可以确保所有未被捕获的异常都能被统一处理。本文将深入探讨SpringBoot中全局异常捕获的实现,从请求进入到异常处理的全过程。请求处理流程概述请求进入DispatcherServlet:所有HTTP请求首先到达......
  • 【AI绘画SD】ComfyUI-神级插件-一键高清放大,不改变原图,只做高清处理!
    哈喽大家好,我是设计师阿威以前介绍的一些AI绘画放大插件都是相当于等比例重绘,这就导致了有些细节部分可能会改变,今天给大家分享一个ComfyUI的插件—ComfyUI-SUPIR,可以不改变原图,只做高清处理。下图是作者的案例效果1、下载插件在Github下载插件:https://github.com/kijai......
  • 可计算离散整体几何结构的 MeshDGP使用——基于C#的geometry processing framework几
    目录引出MeshDGP项目下载和打开遇到的报错解决如何运行使用打开使用函数工具菜单等总结其他CAD/CAE/CAM几何引擎-软件概述郝建兵CAD/CAE/CAMCADCAECAM几何模型内核ACIS两个老大之一OpenCascadeParasolid两个老大之一Autodesk的内核各种CAD自定义信号和槽1.自定......
  • 探索开源世界:常用的条形码图像处理库及其应用
                    在数字时代,条形码作为信息传输的重要媒介,其识别与处理技术显得尤为重要。无论是物流管理、库存控制,还是产品追溯,条形码的准确读取都是实现高效运营的关键。本文将带领大家走进开源软件的世界,探索那些优秀的条形码图像处理库,了解它们的特点、......
  • 数据预处理与特征工程原理与代码实战案例讲解
    数据预处理与特征工程原理与代码实战案例讲解1.背景介绍1.1问题的由来在数据科学和机器学习领域,数据是基础,而有效的数据预处理与特征工程则是提升模型性能的关键。数据通常来源于多种渠道,包括传感器、社交媒体、在线平台等,这些数据可能包含噪声、缺失值、异常值以及不......
  • 微服务服务添加数据源、认证授权、日志记录,安全处理
    为了增强SpringBoot后端服务的功能,我们可以添加数据库支持、认证授权、日志记录和安全处理。以下是如何集成这些功能的基本步骤。数据库集成添加依赖:在pom.xml或build.gradle中添加数据库驱动和SpringDataJPA的依赖。配置数据库:在src/main/resources/applicat......