SpringBoot统一异常拦截处理

2023-11-20

 

前言

大家你好! 这是我的第一篇博客 ,我会把我所学所悟的知识以最简单的语言描述清楚,让大家通俗易懂。

正文

下面我要对springboot(1.5.8.RELEASE)中异常拦截处理进行讲解。项目中我们是一定要碰到的情况就是无论在控制层,业务层还是Dao层都需要校验一些数据,无论是前端传过来的,还是经过业务处理判断的,如果不合法需要友好的提示给用户,否则用户收到一个NullPointerException这玩意,他可能会很懵逼,再说直接将错误的信息直接暴露给用户,这样的体验可想而知,且对黑客而言,详细异常信息往往会提供非常大的帮助…

下面我只讲解
一.校验Body中的参数校验友好提示给用户。
二.手动抛出异常信息友好提示给用户。

1.由于笔者用的是SpringCloud,首先要有一个Eureka服务,下面分别是eureka服务的启动类和配置,很简单,这里不做详细解释。

//eureka服务启动类
@SpringBootApplication
@EnableEurekaServer
public class EurekaApp {
	public static void main(String[] args) {
		SpringApplication.run(EurekaApp.class, args);
	}
}
#注册中心端口号
server.port=1111
#注册中的地址
eureka.instance.hostname=localhost
#是否开启基本的鉴权,默认为true
security.basic.enabled=true
#指定默认的用户名,默认为user.
security.user.name=admin
#默认的用户密码.
security.user.password=admin
#表示是否将自己注册到Eureka Server,默认为true
eureka.client.register-with-eureka=false
#表示是否从Eureka Server获取注册信息,默认为true
eureka.client.fetch-registry=false
#设置与Eureka Server交互的地址,查询服务和注册服务都需要依赖这个地址。默认是http://localhost:8761/eureka ;多个地址可使用 , 分隔。
eureka.client.service-url.defaultZone=http://${security.user.name}:${security.user.password}@${eureka.instance.hostname}:${server.port}/eureka/
#实例名称显示IP配置
eureka.instance.perfer-ip-address=true
# 设为false,关闭自我保护
eureka.server.enable-self-preservation=false
# 清理间隔(单位毫秒,默认是60*1000)
#启用主动失效,并且每次主动失效检测间隔为3s
eureka.server.eviction-interval-timer-in-ms=10000

2.创建一个module,名字:eureka-client ,作为一个服务(为了方便讲解,我把所有的异常配置都写在这个项目里)

eureka-client服务的application.properties文件

eureka.client.service-url.defaultZone=http://admin:admin@localhost:1111/eureka/
server.port=8762
spring.application.name=eureka-client
management.security.enabled=false

下面是 eureka-client服务的启动类。

/**
 * @author yanlin
 * @version v1.2
 * @date 2018-08-16 下午2:21
 * @since v8.0
 **/
@SpringBootApplication
@EnableDiscoveryClient
@RestController
public class EurekaClientApp {
    public static void main(String[] args) {
        SpringApplication.run(EurekaClientApp.class, args);
    }


    @PostMapping("/ClientService/body")
    public String testBodyE(@Valid @RequestBody Student student) {
        return "访问无异常:" + student.toString();

    }
    @GetMapping("/ClientService/{name}")
    public String test(@PathVariable("name") String name) {
        if (name.equals("1")) {
            throw new ParameterServiceException("这里填写错误代码,规范应是一个枚举", "描述当前错误原因,例如参数不能为空等");
        }
        return name;
    }

下面把注册中心启动,然后启动eureka-client,由于笔者很懒,所以get请求一律在浏览器进行

请求 http://localhost:8762/ClientService/2 是ok的

用postman请求 http://localhost:8762/ClientService/body

下面是重点内容

在当前的服务右键new一个class 名字叫 GlobalDefultExceptionHandler,利用@RestControllerAdvice对全局异常进行拦截,然后利用@ExceptionHandler根据自己特定需要,配置对什么样的异常进行拦截处理,我只写了两种。

/**
 * 统一拦截异常
 *
 * @author yanlin
 * @version v1.3
 * @date 2018-10-18 下午2:27
 * @since v8.0
 **/
@RestControllerAdvice
public class GlobalDefultExceptionHandler {
    /**
     * 处理参数异常,一般用于校验body参数
     *
     * @param e
     * @return
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ErrorMessage handleValidationBodyException(MethodArgumentNotValidException e) {
        for (ObjectError s : e.getBindingResult().getAllErrors()) {
            return new ErrorMessage("Invalid_Request_Parameter", s.getObjectName() + ": " + s.getDefaultMessage());
        }
        return new ErrorMessage("Invalid_Request_Parameter", "未知参数错误");
    }

    /**
     * 主动throw的异常
     *
     * @param e
     * @param response
     * @return
     */
    @ExceptionHandler(ServiceException.class)
    public ErrorMessage handleUnProccessableServiceException(ServiceException e, HttpServletResponse response) {
        response.setStatus(e.getStatusCode().value());
        return new ErrorMessage(e.getErrorCode(), e.getMessage());
    }
}

下面我回贴出来四个类,大家一看便知

/**
 * @author yanlin
 * @version v1.3
 * @date 2018-10-18 下午2:36
 * @since v8.0
 **/
public class ErrorMessage implements Serializable {

    private static final long serialVersionUID = 8065583911104112360L;
    private String errorCode;
    private String errorMessage;

    public String getErrorCode() {
        return errorCode;
    }

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

    public String getErrorMessage() {
        return errorMessage;
    }

    public void setErrorMessage(String errorMessage) {
        this.errorMessage = errorMessage;
    }

    public ErrorMessage(String errorCode, String errorMessage) {
        super();
        this.errorCode = errorCode;
        this.errorMessage = errorMessage;
    }

    public ErrorMessage() {
        super();
    }
}
/**
 * @author yanlin
 * @version v1.3
 * @date 2018-10-18 下午2:50
 * @since v8.0
 **/
public abstract class ServiceException extends RuntimeException {

    private static final long serialVersionUID = 8109469326798389194L;
    protected HttpStatus statusCode = HttpStatus.INTERNAL_SERVER_ERROR;


    private String errorCode;


    public HttpStatus getStatusCode() {
        return statusCode;
    }

    public void setStatusCode(HttpStatus statusCode) {
        this.statusCode = statusCode;
    }

    public ServiceException(String errorCode, String message) {
        super(message);
        this.errorCode = errorCode;
    }

    public String getErrorCode() {
        return errorCode;
    }

    public void setErrorCode(String errorCode) {
        this.errorCode = errorCode;
    }
}
/**
 * @author yanlin
 * @version v1.3
 * @date 2018-10-18 下午3:00
 * @since v8.0
 **/
public class ParameterServiceException extends ServiceException {
    private static final long serialVersionUID = 8362753245631601878L;

    public ParameterServiceException(String errorCode, String message) {
        super(errorCode, message);
        this.statusCode = HttpStatus.UNPROCESSABLE_ENTITY;
    }
}
/**
 * @author yanlin
 * @version v1.3
 * @date 2018-10-18 下午3:16
 * @since v8.0
 **/
public class Student implements Serializable {
    private static final long serialVersionUID = 7003907324788760110L;
    @NotBlank(message = "姓名不能为空")
    private String name;
    @Min(value = 2, message = "不能小于2")
    private String age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAge() {
        return age;
    }

    public void setAge(String age) {
        this.age = age;
    }

    public Student() {

    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                '}';
    }
}

这四个类我值得解释的是ParameterServiceException ,这是可以扩展的,根据你的需要。我这里描述的是参数异常的类,也就是你判断参数不合法时 throw new的那个类,在eureka-client服务的启动类我已经写了。

下面我要利用我定义好的这几个类演示一下我请求有异常是返回给用户的效果

首先是get请求,测试手动抛出异常,当我请求参数是 1 触发了我手动抛出的异常。

然后我利用postman测试post请求,我利用了javax.validation.constraints下面的注解校验的参数,上面方法的参数前一定要@Valid,否则你实体类里写的所有类似@NotNull(message = "姓名不能为空")这样的注解全部不生效。

 

这样前端在响应体里找到,就可以输出具体异常信息里了。

总结说明

以下是笔者关于这方面的经验和技巧分享给大家。

1.参数校验非法是一般使用手动抛出异常的方式告知前端,上面有代码贴出,如:throw new ParameterServiceException("这里填写错误代码,规范应是一个枚举", "描述当前错误原因,例如参数不能为空等");

2.业务代码块里尽量少出现try存在大量代码的情况,这样一旦某一行出错,类会跟随一张 异常表(exception table),每一个try catch都会在这个表里添加行记录,每一个记录都有4个信息(try catch的开始地址,结束地址,异常的处理起始位,异常类名称)。当代码在运行时抛出了异常时,首先拿着抛出位置到异常表中查找是否可以被catch(例如看位置是不是处于任何一栏中的开始和结束位置之间),如果可以则跑到异常处理的起始位置开始处理,如果没有找到则原地return,并且copy异常的引用给父调用方,接着看父调用的异常表。。。以此类推。这样的操作太多是对jvm处理造成资源浪费的,当然,经过多年的改进,异常对jvm性能的影响越来越小。

3.大家都知道所有的异常例如 NullPointerException都是继承 RuntimeException 而RuntimeException 又继承 Exception,所以,catch(Exception e){
                e.printStackTrace();
            }

这种写法是很不负责任的。

            try {

                //大量代码
            }catch(Exception e){
                e.printStackTrace();
            }finally {
            }

4.大家在对对象内参数校验对时候尽量利用javax.validation.constraints的注解,避免校验更多的参数时出现大量的代码,不宜阅读。

注:对本文有异议或不明白的地方微信探讨,wx:15524579896

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

SpringBoot统一异常拦截处理 的相关文章

随机推荐

  • (一)轻松工作必学:windows bat脚本语法

    记得在初中的时候 电脑刚刚进入了我们的生活 有一天同学发过来一个 xxx bat 的文件 我双击打开之后 电脑就很快就关机了 当时的我还是一头雾水 后来知道这只是一个只需一行代码的批处理文件 批处理文件是一种简化的脚本 可以帮助我们运行一些
  • Node服务器-express框架

    1 Express认识初体验 2 Express中间件使用 3 Express请求和响应 4 Express路由的使用 5 Express的错误处理 6 Express的源码解析 一 手动创建express的过程 1 在项目文件的根目录创建
  • 爬虫从入门到放弃——组件的使用和定制

    以前我们提到了WebMagic的组件 WebMagic的一大特色就是可以灵活的定制组件功能 实现你自己想要的功能 在Spider类里 PageProcessor Downloader Scheduler和Pipeline四个组件都是Spid
  • STM32 HAL——GPIO

    HAL的代码规范建议 以下内容是我自己参照HAL做的总结 如果公司有固定的编码规范 就跟公司保持一致 如果没有 那就推荐和所用库保持一致 注意 因人而异 并不具有普遍适用性 HAL库有如下代码规范 1 目录名除了专有词汇外 统一是单词首字母
  • IDEA去除掉虚线,波浪线,和下划线实线的方法

    推荐一下个人的公众号 终码一生 专注于Java技术学习 开源项目分享和常见问题解决等 喜欢的小伙伴可以关注下 感谢大家的支持 初次安装使用IDEA 总是能看到导入代码后 出现很多的波浪线 下划线和虚线 这是IDEA给我们的一些提示和警告 但
  • 【机器学习】核函数

    有任何的书写错误 排版错误 概念错误等 希望大家包含指正 核方法 核技巧 非线性分类问题是指通过利用非线性模型才能很好地进行分类的问题 如图 1 1 1 所示 表示正样本 表示负样本 显然无法用直线 线性模型 将正负样本正确分开 但是可以用
  • android service 返回值,Android平台调用Web Service:线程返回值(示例代码)

    接上文 前文中的遗留问题 对于Java多线程的理解 我曾经只局限于实现Runnable接口或者继承Thread类 然后重写run 方法 最后start 调用就算完事 可是一旦涉及死锁以及对共享资源的訪问和随时监控线程的状态和运行顺序和线程返
  • macOS M2使用conda配置pytorch环境

    一 conda 首先我们需要有conda miniconda的安装配置流程可以看这个http t csdn cn ShcNb 二 进入官网 https pytorch org 选择对应的版本 复制下面的命令 conda install py
  • 2023年1月手机热门品牌型号排行榜(手机行业趋势分析)

    近期苹果降价引起人们的关注 并且优惠较大 iPhone 14 Pro系列在苹果官方授权渠道均降价 线下授权门店普遍降价700元左右 iPhone Pro与Pro Max作为苹果的旗舰产品 通常被认为是更为保值的机型 一般来说 不会在短期内大
  • ueditor编辑器右键粘贴、复制不能用的解决办法

    ueditor编辑器功能非常强大 现在比较多的网站页面或者系统中都集成了百度的ueditor编辑器 使系统编辑文本变得非常容易 但是默认状态下 在使用google内核的浏览器时 会出现右键粘贴复制都不能用的情况 会提示使用ctrl v粘贴
  • WEB交互界面易用性设计和验收的指导性原则

    随着企业intranet和国际internet的迅速发展 越来越多的工作流程 商务交易 教育 培训 会议和讲座 以及个人消费娱乐都被转移到所谓的万维网 World Wide Web 以下简称WEB 上来了 与此相对应的是交互操作的复杂性越来
  • keil device没有芯片

    若试了各种办法没有找到解决方案 可以试试我这个方法 若工程后缀是 uvproj 则可能是keil4的工程 而电脑装的是keil5的版本 改为 uvprojx后即可正常找到芯片了 也可以Project gt Manage gt Migrate
  • Java多线程的一个简单示例

    Java多线程简单示例 代码如下 public class Canteen extends Thread 餐总数 static int rice 20 取餐凭证 static Object key KEY public Canteen St
  • Spring Profiles提供了一种隔离应用程序配置的方式

    24 Profiles Spring Profiles提供了一种隔离应用程序配置的方式 并让这些配置只能在特定的环境下生效 任何 Component或 Configuration都能被 Profile标记 从而限制加载它的时机 Config
  • MySQL 8.0字符集校正

    MySQL升级为8 0版本时 之前版本的字符集往往是不同的 需要校正 执行下面的三个SQL语句的查询结果 可以从库 表 列三个层面对字符集进行校正 库 select concat alter database schema name def
  • pytest的介绍与使用

    pytest介绍 pytest整合了unittest 包含了unittest中的方法 并且比其更轻便快捷 而且结合Allure报告能够清晰的生成测试报告 1 安装 pip install pytest 2 测试文件 test py test
  • C# 选择ListView 报错误:InvalidArgument=“0”的值对于“index”无效。

    加判断 if this ListView SelectedItems Count gt 0 问题解决
  • flutter 安卓打包出现:Transform‘s input file does not exist:

    我的版本 Android studio 4 0 targetSdkVersion 30 出现了这个问题 解决方案 在你的build gradle文件里面添加一句话就可以了 android studio 4 0 以上打包会出现这个问题 lin
  • 工业安全生产信息化平台的基本架构和关键功能分享

    工业安全生产信息化平台是指利用信息技术手段 将工业安全生产管理与数据采集 传输 处理相结合 实现对工业安全生产全过程的数字化 信息化 智能化管理的平台 它通过集成多种信息系统和设备 实现对重大危险源监控预警 安全风险分级管控 安全生产一张图
  • SpringBoot统一异常拦截处理

    前言 大家你好 这是我的第一篇博客 我会把我所学所悟的知识以最简单的语言描述清楚 让大家通俗易懂 正文 下面我要对springboot 1 5 8 RELEASE 中异常拦截处理进行讲解 项目中我们是一定要碰到的情况就是无论在控制层 业务层