专栏首页晏霖SpringBoot统一异常拦截处理

SpringBoot统一异常拦截处理

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

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

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

一、

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,所以,

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

            try {

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

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

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

本文分享自微信公众号 - 晏霖(yanlin199507),作者:晏霖

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-01-15

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Springboot读取配置文件、pom文件及自定义配置文件

    前言 很多人都知道读取配置文件,这是初级做法,上升一点难度是使用java bean的方式读取自定义配置文件,但是大家很少有知道读取pom文件信息,接下来我都会讲...

    胖虎
  • Spring Cloud Turbine聚合Hystrix

    前面文章提到了使用了Feign集成的Hystrix做了一个简单的实战练习,成功的进行了服务降级和失败快速返回。下面我要对熔断器监控进行页面化,并将多个服务的的...

    胖虎
  • 工厂模式

    在23中设计模式中,工厂方法属于创建型的设计模式,只有工厂方法和抽象工厂两种,但是实际我们常与简单工厂混淆,因为简单工厂模式违背了开闭原则。

    胖虎
  • Java基础巩固——反射

    什么是反射 ----    反射机制就是指程序运行时能够获取自身的信息。在Java中,只要给出类的名字,就可以通过反射机制来获取类的信息 哪里用的到反射机制 -...

    Janti
  • 快速学习-JPA的入门案例

    由于JPA是sun公司制定的API规范,所以我们不需要导入额外的JPA相关的jar包,只需要导入JPA的提供商的jar包。我们选择Hibernate作为JPA的...

    cwl_java
  • Spring系列 SpringMVC的请求与数据响应

    y以下案例均部署在Tomcat上,使用浏览器来访问一个简单的success.jsp页面来实现

    一只胡说八道的猴子
  • SpringBoot系列之YAML配置用法学习笔记

    配置文件的作用:修改SpringBoot自动配置的默认值,主要是默认值,因为SpringBoot启动时会自动加载很多默认配置,详细的可以参考我之前博客源码学习系...

    SmileNicky
  • 你知道@RequestMapping的name属性有什么用吗?带你了解URI Builder模式(UriComponents/UriComponentsBuilder)【享学Spring MVC】

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

    YourBatman
  • Java爬虫可以非常溜

    xxl-crawler是 许雪里 大佬开源的一个java爬虫,熟悉java语言的用起来可以非常顺手。

    java乐园
  • Android项目开发全程(三)-- 项目的前期搭建、网络请求封装是怎样实现的

      在前两篇博文中已经做了铺垫,下面咱们就可以用前面介绍过的内容开始做一个小项目了(项目中会用到Afinal框架,不会用Afinal的童鞋可以先看一下上一篇博文...

    codingblock

扫码关注云+社区

领取腾讯云代金券