Spring Boot统一处理全局异常的实战教程

目录
  • 注解的介绍
    • @ControllerAdvice
    • @ExceptionHandler拦截异常并统一处理
  • 代码实现
    • 自定义异常
    • 统一异常处理
    • 前端返回值类
  • 测试用例
  • 附:Spring Boot默认的异常处理机制
  • 总结

注解的介绍

@ControllerAdvice

@ControllerAdvice注解是Spring3.2中新增的注解,学名是Controller增强器,作用是给Controller控制器添加统一的操作或处理。

这里ControllerAdvice也可以这么理解,其抽象级别应该是用于对Controller进行切面环绕的,而具体的业务织入方式则是通过结合其他的注解来实现的。@ControllerAdvice是在类上声明的注解,其用法主要有三点:

1.结合方法型注解@ExceptionHandler,用于捕获Controller中抛出的指定类型的异常,从而达到不同类型的异常区别处理的目的。

2.结合方法型注解@InitBinder,用于request中自定义参数解析方式进行注册,从而达到自定义指定格式参数的目的。

3.结合方法型注解@ModelAttribute,表示其注解的方法将会在目标Controller方法执行之前执行。

从上面的讲解可以看出,@ControllerAdvice的用法基本是将其声明在某个bean上,然后在该bean的方法上使用其他的注解来指定不同的织入逻辑。不过这里@ControllerAdvice并不是使用AOP的方式来织入业务逻辑的,而是Spring内置对其各个逻辑的织入方式进行了内置支持。

针对声明@ExceptionHandler 、 @InitBinder或@ModelAttribute方法的类的@Component @ExceptionHandler , @InitBinder在多个@Controller类之间共享。

使用@ControllerAdvice注解的类可以明确声明为 Spring bean 或通过类路径扫描自动检测。 所有此类 bean 都根据Ordered语义或@Order / @Priority声明进行Ordered , Ordered语义优先于@Order / @Priority声明。 然后在运行时按该顺序应用@ControllerAdvice bean。 但是请注意,实现PriorityOrdered @ControllerAdvice bean 的PriorityOrdered不高于实现Ordered @ControllerAdvice bean。 此外, Ordered不适用于范围内的@ControllerAdvice例如,如果这样的 bean 已被配置为请求范围或会话范围的 bean。 对于处理异常, @ExceptionHandler将在第一个具有匹配异常处理程序方法的通知中被选择。 对于模型的属性和数据绑定初始化, @ModelAttribute和@InitBinder方法将遵循@ControllerAdvice秩序。

注意:对于@ExceptionHandler方法,在特定建议 bean 的处理程序方法中,根异常匹配将优先于仅匹配当前异常的原因。 但是,与较低优先级建议 bean 上的任何匹配(无论是根还是原因级别)相比,更高优先级建议上的原因匹配仍然是首选。 因此,请在具有相应顺序的优先建议 bean 上声明您的主要根异常映射。

默认情况下, @ControllerAdvice ControllerAdvice 中的方法全局应用于所有控制器。 使用诸如annotations 、 basePackageClasses和basePackages (或其别名value )之类的选择器来定义目标控制器的更窄子集。 如果声明了多个选择器,则应用布尔OR逻辑,这意味着所选控制器应至少匹配一个选择器。 请注意,选择器检查是在运行时执行的,因此添加许多选择器可能会对性能产生负面影响并增加复杂性。

@ExceptionHandler拦截异常并统一处理

配合 @ExceptionHandler注解结合使用,当异常抛到controller层时,可以对异常进行统一的处理,规定返回的json格式或者跳转到指定的错误页面等.

@ExceptionHandler的作用主要在于声明一个或多个类型的异常,当符合条件的Controller抛出这些异常之后将会对这些异常进行捕获,然后按照其标注的方法的逻辑进行处理,从而改变返回的视图信息。

用于处理特定处理程序类和/或处理程序方法中的异常的注解。

使用此注解注释的处理程序方法允许具有非常灵活的签名。 它们可能具有以下类型的参数,按任意顺序排列:

异常参数:声明为一般异常或更具体的异常。 如果注解本身没有通过其value()缩小异常类型,这也可用作映射提示

代码实现

自定义异常

/**
 * 自定义一个异常类,用于处理我们发生的业务异常
 *
 * @author Promsing(张有博)
 * @version 1.0.0
 * @since 2021/11/27 - 20:14
 */
public class BizException extends RuntimeException {

    private static final long serialVersionUID = 1L;

    /**
     * 错误码
     */
    protected String errorCode;
    /**
     * 错误信息
     */
    protected String errorMsg;

    public BizException() {
        super();
    }

    public BizException(FrontResult errorInfoInterface) {
        super(errorInfoInterface.getCode());
        this.errorCode = errorInfoInterface.getMessage();
        this.errorMsg = errorInfoInterface.getMessage();
    }

    public BizException(FrontResult errorInfoInterface, Throwable cause) {
        super(errorInfoInterface.getCode(), cause);
        this.errorCode = errorInfoInterface.getCode();
        this.errorMsg = errorInfoInterface.getMessage();
    }

    public BizException(String errorMsg) {
        super(errorMsg);
        this.errorMsg = errorMsg;
    }

    public BizException(String errorCode, String errorMsg) {
        super(errorCode);
        this.errorCode = errorCode;
        this.errorMsg = errorMsg;
    }

    public BizException(String errorCode, String errorMsg, Throwable cause) {
        super(errorCode, cause);
        this.errorCode = errorCode;
        this.errorMsg = errorMsg;
    }

    public String getErrorCode() {
        return errorCode;
    }

    public void setErrorCode(String errorCode) {
        this.errorCode = errorCode;
    }

    public String getErrorMsg() {
        return errorMsg;
    }

    public void setErrorMsg(String errorMsg) {
        this.errorMsg = errorMsg;
    }

    public String getMessage() {
        return errorMsg;
    }

    @Override
    public Throwable fillInStackTrace() {
        return this;
    }

}

统一异常处理


import com.tfjy.arbackend.enumtool.ResultCodeEnum;
import com.tfjy.arbackend.enumtool.ResutlMsgEnum;
import com.tfjy.arbackend.util.FrontResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.sql.SQLException;

/**
 * 统一异常处理
 *
 * @author Promsing(张有博)
 * @version 1.0.0
 * @since 2021/11/27 - 20:14
 */
@ControllerAdvice//使用该注解表示开启了全局异常的捕获
public class GlobalExceptionHandler {
    private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    /**
     * 处理自定义的业务异常
     * @param req
     * @param e
     * @return
     */
    @ExceptionHandler(value = BizException.class)
    @ResponseBody
    public  FrontResult bizExceptionHandler(HttpServletRequest req, BizException e){
        logger.error("URL : " + req.getRequestURL().toString());
        logger.error("HTTP_METHOD : " + req.getMethod());
        logger.error("发生业务异常!原因是:{}",e.getErrorMsg());
        return FrontResult.getExceptionResult(e.getErrorCode(),e.getErrorMsg());
    }

    /**
     * 处理空指针的异常
     * @param req
     * @param e
     * @return
     */
    @ExceptionHandler(value =NullPointerException.class)
    @ResponseBody
    public FrontResult exceptionHandler(HttpServletRequest req, NullPointerException e)  {
        logger.error("URL : " + req.getRequestURL().toString());
        logger.error("HTTP_METHOD : " + req.getMethod());
        logger.error("发生空指针异常!原因是:",e);
        return FrontResult.getExceptionResult(ResultCodeEnum.FAIL.getCode(), ResutlMsgEnum.EXECUTE_FAIL.getMsg());
    }

    /**
     * 处理索引越界异常
     * @param req
     * @param e
     * @return
     */
    @ExceptionHandler(value =IndexOutOfBoundsException.class)
    @ResponseBody
    public FrontResult exceptionHandler(HttpServletRequest req, IndexOutOfBoundsException e){
        logger.error("URL : " + req.getRequestURL().toString());
        logger.error("HTTP_METHOD : " + req.getMethod());
        logger.error("索引越界异常!原因是:",e);
        return FrontResult.getExceptionResult(ResultCodeEnum.FAIL.getCode(), ResutlMsgEnum.EXECUTE_FAIL.getMsg());
    }

    /**
     * 处理类未找到异常
     * @param req
     * @param e
     * @return
     */
    @ExceptionHandler(value =ClassNotFoundException.class)
    @ResponseBody
    public FrontResult exceptionHandler(HttpServletRequest req, ClassNotFoundException e)  {
        logger.error("URL : " + req.getRequestURL().toString());
        logger.error("HTTP_METHOD : " + req.getMethod());
        logger.error("发生类未找到异常!原因是:",e);
        return FrontResult.getExceptionResult(ResultCodeEnum.FAIL.getCode(), ResutlMsgEnum.EXECUTE_FAIL.getMsg());
    }

    /**
     * 处理SQL异常
     * @param req
     * @param e
     * @return
     */
    @ExceptionHandler(value = SQLException.class)
    @ResponseBody
    public FrontResult exceptionHandler(HttpServletRequest req, SQLException e)  {
        logger.error("URL : " + req.getRequestURL().toString());
        logger.error("HTTP_METHOD : " + req.getMethod());
        logger.error("发生SQL异常!原因是:",e);
        return FrontResult.getExceptionResult(ResultCodeEnum.FAIL.getCode(), ResutlMsgEnum.EXECUTE_FAIL.getMsg());
    }

    /**
     * 处理IO异常
     * @param req
     * @param e
     * @return
     */
    @ExceptionHandler(value = IOException.class)
    @ResponseBody
    public FrontResult exceptionHandler(HttpServletRequest req, IOException e)  {
        logger.error("URL : " + req.getRequestURL().toString());
        logger.error("HTTP_METHOD : " + req.getMethod());
        logger.error("发生IO异常!原因是:",e);
        return FrontResult.getExceptionResult(ResultCodeEnum.FAIL.getCode(), ResutlMsgEnum.EXECUTE_FAIL.getMsg());
    }

    /**
     * 处理其他异常
     * @param req
     * @param e
     * @return
     */
    @ExceptionHandler(value =Exception.class)
    @ResponseBody
    public FrontResult exceptionHandler(HttpServletRequest req, Exception e){
        logger.error("URL : " + req.getRequestURL().toString());
        logger.error("HTTP_METHOD : " + req.getMethod());
        logger.error("未知异常!原因是:",e);
        return FrontResult.getExceptionResult(ResultCodeEnum.FAIL.getCode(), ResutlMsgEnum.EXECUTE_FAIL.getMsg());
    }
}

前端返回值类


import com.tfjy.arbackend.enumtool.ResultCodeEnum;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class FrontResult {
    /**
     * 结果状态码
     */
    private String code;
    /**
     * 响应结果描述
     */
    private String message;
    /**
     * 返回数据
     */
    private Object data;

    /**
     * 静态方法,返回前端实体结果
     *
     * @param code    状态码
     * @param message 消息
     * @param data    数据
     * @return 前端实体结果
     */
    public static FrontResult build(String code, String message, Object data) {
        return new FrontResult(code, message, data);
    }

    /**
     * 返回成功的结果实体
     *
     * @param message 消息
     * @param data    数据
     * @return 实体
     */
    public static FrontResult getSuccessResult(String message, Object data) {
        FrontResult result = new FrontResult();
        result.code = ResultCodeEnum.SUCCESS.getCode();
        result.message = message;
        result.data = data;
        return result;
    }

    /**
     * 返回无需data的成功结果实体
     *
     * @param message 消息内容
     * @return 返回结果
     */
    public static FrontResult getSuccessResultOnlyMessage(String message) {
        FrontResult result = new FrontResult();
        result.code = ResultCodeEnum.SUCCESS.getCode();
        result.message = message;
        result.data = null;
        return result;
    }

    /**
     * 获取一个异常结果
     *
     * @param code 错误码
     * @param message 自定义异常信息
     * @return FrontResult
     */
    public static FrontResult getExceptionResult(String code, String message) {
        FrontResult result = new FrontResult();
        result.code = code.isEmpty() ? ResultCodeEnum.CODE_EXCEPTION.getCode() : code;
        result.message = message.isEmpty() ? ResultCodeEnum.CODE_EXCEPTION.getMsg() : message;
        return result;
    }
}
import lombok.AllArgsConstructor;

@AllArgsConstructor
public enum ResultCodeEnum {
    // 请求成功
    SUCCESS("0000"),
    // 请求失败
    FAIL("1111"),
    // EXCEL 导入失败
    EXCEL_FAIL("1000"),
    // userID 为空
    ID_NULL("1001"),
    // 前端传的实体为空
    MODEL_NULL("1002"),
    // 更新失败
    UPDATE_FAIL("1011"),
    // 参数为空
    PARAM_ERROR("400"),
    // 代码内部异常
    CODE_EXCEPTION("500", "代码内部异常");

    /**
     * 状态码
     */
    private String code;

    public String getCode() {
        return code;
    }

    ResultCodeEnum(String code) {
        this.code = code;
    }

    private String msg;

    public String getMsg() {
        return msg;
    }

}

public enum ResutlMsgEnum {

    //查询成功
    FIND_SUCCESS("查询成功!"),
    //查询失败
    FIND_FAIL("查询失败!"),

    //更新成功
    UPDATE_SUCCESS("更新成功"),
    //更新失败
    UPDATE_FAIL("更新成功"),

    SEND_SUCCESS("发送成功"),

    SEND_FAIL("发送失败");

    private String msg;

    ResutlMsgEnum(String msg) {
        this.msg = msg;
    }

    public String getMsg() {
        return msg;
    }
}

测试用例

/**
 * 测试用例
 *
 * @author Promsing(张有博)
 * @version 1.0.0
 * @since 2021/11/29 - 9:05
 */
@Api(tags = {"测试controller"})
@RequestMapping(value = "/testController")
@RestController
public class TestController {

    @ApiOperation(value = "测试null")
    @GetMapping(value = "getNull")
    public FrontResult getNull() {
        int length = 0;

            String name=null;
            length = name.length();

        return FrontResult.build(ResultCodeEnum.SUCCESS.getCode(),
                ResutlMsgEnum.EXECUTE_SUCCESS.getMsg(), length);
    }
}

附:Spring Boot默认的异常处理机制

默认情况下,Spring Boot为两种情况提供了不同的响应方式。

一种是浏览器客户端请求一个不存在的页面或服务端处理发生异常时,一般情况下浏览器默认发送的请求头中Accept: text/html,所以Spring Boot默认会响应一个html文档内容,称作“Whitelabel Error Page”。

另一种是使用Postman等调试工具发送请求一个不存在的url或服务端处理发生异常时,Spring Boot会返回类似如下的Json格式字符串信息

{
    "timestamp": "2018-05-12T06:11:45.209+0000",
    "status": 404,
    "error": "Not Found",
    "message": "No message available",
    "path": "/index.html"
} 

原理也很简单,Spring Boot 默认提供了程序出错的结果映射路径/error。这个/error请求会在BasicErrorController中处理,其内部是通过判断请求头中的Accept的内容是否为text/html来区分请求是来自客户端浏览器(浏览器通常默认自动发送请求头内容Accept:text/html)还是客户端接口的调用,以此来决定返回页面视图还是 JSON 消息内容。

相关BasicErrorController中代码如下:

总结

其他异常同理,也可以捕获。完美,没问题。全局统一异常处理设置成功。

到此这篇关于Spring Boot统一处理全局异常的文章就介绍到这了,更多相关SpringBoot统一处理全局异常内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • SpringBoot如何优雅的处理全局异常

    前言 本篇文章主要介绍的是SpringBoot项目进行全局异常的处理. SpringBoot全局异常准备 说明:如果想直接获取工程那么可以直接跳到底部,通过链接下载工程代码. 开发准备 环境要求 JDK:1.8 SpringBoot:1.5.17.RELEASE 首先还是Maven的相关依赖: <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <java.

  • springboot全局异常处理详解

    一.单个controller范围的异常处理 package com.xxx.secondboot.web; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import

  • springboot全局异常处理代码实例

    这篇文章主要介绍了springboot全局异常处理代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 前言: 开发中异常的处理必不可少,常用的就是 throw 和 try catch,这样一个项目到最后会出现很多冗余的代码,使用全局异常处理避免过多冗余代码. 全局异常处理: 1.pom 依赖(延续上一章代码): <dependencies> <!-- Spring Boot Web 依赖 --> <!-- Web 依赖

  • SpringBoot处理全局统一异常的实现

    在后端发生异常或者是请求出错时,前端通常显示如下 Whitelabel Error Page This application has no explicit mapping for /error, so you are seeing this as a fallback. Fri Jun 07 15:38:07 CST 2019 There was an unexpected error (type=Not Found, status=404). No message available 对于

  • Springboot之自定义全局异常处理的实现

    前言: 在实际的应用开发中,很多时候往往因为一些不可控的因素导致程序出现一些错误,这个时候就要及时把异常信息反馈给客户端,便于客户端能够及时地进行处理,而针对代码导致的异常,我们一般有两种处理方式,一种是throws直接抛出,一种是使用try..catch捕获,一般的话,如果逻辑的异常,需要知道异常信息,我们往往选择将异常抛出,如果只是要保证程序在出错的情况下 依然可以继续运行,则使用try..catch来捕获. 但是try..catch会导致代码量的增加,让后期我们的代码变得臃肿且难以维护.当

  • Spring Boot统一处理全局异常的实战教程

    目录 注解的介绍 @ControllerAdvice @ExceptionHandler拦截异常并统一处理 代码实现 自定义异常 统一异常处理 前端返回值类 测试用例 附:Spring Boot默认的异常处理机制 总结 注解的介绍 @ControllerAdvice @ControllerAdvice注解是Spring3.2中新增的注解,学名是Controller增强器,作用是给Controller控制器添加统一的操作或处理. 这里ControllerAdvice也可以这么理解,其抽象级别应该是

  • Spring Boot统一接口返回及全局异常处理

    目录 1.解决方案 2.具体实现 2.1 定义状态码统一接口 2.2 公共模块状态码枚举类 2.3 定义全局自定义异常 2.4 定义统一接口格式输出类 2.5 定义统一接口格式输出类 2.6 接口统一输出优化 2.7 子系统如何实现 3.子系统定义状态码,实现BaseResultCode接口 前言: 前段时间接手了一个老项目,现在需要在此项目中添加一些新的需求,同事在开发过程中遇到了一些问题? 1.成功的状态到底是200还是0啊,订单系统200代表成功,而会员系统却是0代表成功. 2.接口返回的

  • 浅谈spring boot 1.5.4 异常控制

    spring boot 已经做了统一的异常处理,下面看看如何自定义处理异常 1.错误码页面映射 1.1静态页面 必须配置在 resources/static/error文件夹下,以错误码命名 下面是404错误页面内容,当访问一个不存在的链接的时候,定位到此页 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Not F

  • 详解Spring Boot工程集成全局唯一ID生成器 UidGenerator的操作步骤

    Spring Boot中全局唯一流水号ID生成器集成实验 概述 流水号生成器(全局唯一 ID生成器)是服务化系统的基础设施,其在保障系统的正确运行和高可用方面发挥着重要作用.而关于流水号生成算法首屈一指的当属 Snowflake 雪花算法,然而 Snowflake本身很难在现实项目中直接使用,因此实际应用时需要一种可落地的方案. UidGenerator 由百度开发,是Java实现的, 基于 Snowflake算法的唯一ID生成器.UidGenerator以组件形式工作在应用项目中, 支持自定义

  • Spring Boot项目维护全局json数据代码实例

    这篇文章主要介绍了Spring Boot项目维护全局json数据代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 概述 过去 我们在每一个方法中处理前端发过来的请求,需要自己构造请求数据,然后通过spring 提供的@ResponseBody 强制转为JSON数据吗,实际上出现了很多重复的代码,我么亦可以通过构造一个 工具类,实现只关注需要改变的数据. 下面给出这个工具类. public class JsonMsg { private i

  • 在spring中手写全局异常拦截器

    为什么要重复造轮子 你可能会问,Spring已经自带了全局异常拦截,为什么还要重复造轮子呢? 这是个好问题,我觉得有以下几个原因 装逼 Spring的全局异常拦截只是针对于Spring MVC的接口,对于你的RPC接口就无能为力了 无法定制化 除了写业务代码,我们其实还能干点别的事 我觉得上述理由已经比较充分的解答了为什么要重复造轮子,接下来就来看一下怎么造轮子 造个什么样的轮子? 我觉得全局异常拦截应该有如下特性 使用方便,最好和spring原生的使用方式一致,降低学习成本 能够支持所有接口

  • Spring Boot统一返回体的踩坑记录

    前言 在Spring Boot项目中我们可以通过RestControllerAdvice配合实现ResponseBodyAdvice<T>接口来保证Spring MVC接口具有统一的返回格式,以保证前端同学能够封装统一的数据接收工具.但是很多网上的文章并没有对实际开发中的细节作出更多的讲解.今天胖哥就来分享一下我的采坑经历,也算作一个总结. 控制作用范围 我记得在前面关于Swagger3的文章中提过,如果我们不指定范围将导致Swagger无法识别接口的元信息.因此如果你使用了Swagger必须

  • Spring Boot集成Druid出现异常报错的原因及解决

    Spring Boot集成Druid异常 在Spring Boot集成Druid项目中,发现错误日志中频繁的出现如下错误信息: discard long time none received connection. , jdbcUrl : jdbc:mysql://******?useSSL=false&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8, version : 1.2.3, las

  • Spring Boot两种全局配置和两种注解的操作方法

    目录 零.学习目标 一.全局配置文件概述 二.Application.properties配置文件 1.配置tomcat端口号和web虚拟路径 2.对象类型的配置与使用 3.两种属性注解方式的对比 三.Application.yaml配置文件 四.两种配置文件的比较 五.课后作业 零.学习目标 1.掌握application.properties配置文件 2.掌握application.yaml配置文件 3.掌握使用@ConfigurationProperties注入属性 4.掌握使用@Valu

  • Spring boot 数据源未配置异常的解决

    Spring boot 数据源未配置异常 问题 在使Springboot自动生成的项目框架时如果选择了数据源,比如选择了mysql,生成项目之后,启动会报一下异常: Description: Cannot determine embedded database driver class for database type NONE Action: If you want an embedded database please put a supported one on the classpat

随机推荐