余晖落尽暮晚霞,黄昏迟暮远山寻
本站
当前位置:网站首页 > 编程知识 > 正文

Spring Mvc全局异常处理及统一结果返回(springboot全局异常处理)

xiyangw 2022-11-24 16:51 14 浏览 0 评论

java的异常体系


ThrowableErrorExceptionimplimpl

Throwable作为最顶层的类,下面分为Exception(异常)和Error(错误)

  • Error程序中无法处理的错误,表示运行应用程序中出现了严重的错误,一般由jvm引起,常见的有NoClassDefFoundErrorOutOfMemoryError
  • Exception程序运行过程中产生的异常,又分为可查异常(checked exception)不可查异常(unchecked exception)可查异常(checked exception)编译器要求必须处理的异常,需要try-catch捕获或者throws语句抛出否则编译不通过,常见的有ClassNotFoundExceptionNoSuchMethodException等。不可查异常(unchecked exception)编译器不会进行检查并且不要求必须处理的异常,包括RuntimeException以及其子类,常见的有NullPointerExceptionIllegalArgumentException等。

全局异常处理

Spring Mvc使用@ExceptionHandler注解来处理由控制层抛出的异常

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ExceptionHandler {
    Class<? extends Throwable>[] value() default {};
}
复制代码

@ExceptionHandler只有一个方法value()可以填写的值为继承自ThrowableClass数组,也就是说一个ExceptionHandler可以处理多个异常或错误

首先我们新建一个自定义异常方便后面的演示,接收一个message参数

public class CustomException extends RuntimeException{

    public CustomException(String message) {
        super(message);
    }
}
复制代码
  • @RestController@Controller在控制层使用如下@RestController @Slf4j public class DomainController { @ExceptionHandler(CustomException.class) @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public ResponseEntity<Object> domainExceptionHandler(CustomException e){ log.error("exception from DomainController", e); return ResponseEntity.status(500).body(e.getMessage()); } @GetMapping("/domain") public void test(int code){ if(code == 1){ throw new CustomException("自定义异常"); } } } 复制代码 请求接口传入参数code = 1,可以看到控制台打印了exception from DomainController 复制代码 说明异常被ExceptionHandler处理了,该方式只能处理单个控制器的异常
  • @ControllerAdvice@RestControllerAdvice@ControllerAdvice@RestControllerAdvice可以同时处理多个的控制器,通过下列的方式可以调整处理范围,因为在某些情况下我们并不想让异常控制器处理有些第三方框架的异常// 处理所有@RestController注解 @ControllerAdvice(annotations = RestController.class) public class ExampleAdvice1 {} // 处理包路径下的所有控制器 @ControllerAdvice(basePackages = "org.example.controllers") public class ExampleAdvice2 {} // 指定特定的类处理 @ControllerAdvice(assignableTypes = {ControllerInterface.class, AbstractController.class}) public class ExampleAdvice3 {} 复制代码 注意:annotations和basePackages生效的前提是被处理的控制器已经被扫描成Spring Bean

在大部分情况下我们只需要配置全局的异常处理,也就是通过@RestControllerAdvice处理所有的控制器,在单个控制器中配置的@ExceptionHandler优先级会高于全局的,我们可以利用这点来处理有些特殊的异常或者某些定制化需求(当然最好少一些定制化需求,会导致项目后期维护困难)。

关于Error

我们现在定义一个@ExceptionHandler处理所有的异常,如下

    @ExceptionHandler({Exception.class})
    public ResponseEntity<Object> handler(Exception e){
        log.error("error happened ", e);
        return ResponseEntity.status(500).body(e.getMessage());
    }
复制代码

修改测试代码code = 2时抛出Error

    @GetMapping("/domain")
    public void test(int code){
        if(code == 1){
            throw new CustomException("自定义异常");
        }
        if(code == 2){
            throw new AssertionError("code is 2");
        }
    }
复制代码

前面我们已经介绍过了ErrorException的区别,在这抛出Error,我们的异常处理应该不会处理,因为我们只处理了所有的Exception,并没有处理Error,启动项目调用接口,控制台得到如下信息

error happened Caused by: java.lang.AssertionError: code is 2
复制代码

可以看到AssertionError被异常处理器处理了,这是因为在Spring 4.3以后,DispatcherServletdoDispatch方法会处理从处理程序抛出的错误,使它们可用于@ExceptionHandler方法和其他场景。

...
catch (Exception ex) {
    dispatchException = ex;
}
catch (Throwable err) {
    // As of 4.3, we're processing Errors thrown from handler methods as well,
    // making them available for @ExceptionHandler methods and other scenarios.
    dispatchException = new NestedServletException("Handler dispatch failed", err);
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
...
复制代码

统一结果返回

在实际项目中我们通常会定义统一的返回结构,常见如下

@Getter
@Builder
public class ResponseData<T> {

    private long timestamp;
    private int status;
    private String message;
    private T data;
}
复制代码

我们不想在每个Controller上写重复的包装代码,可以定义一个统一的返回处理,实现ResponseBodyAdvice接口

@RestControllerAdvice
@Slf4j
public class ResponseHandler  implements ResponseBodyAdvice {
    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        return false;
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        return null;
    }
}
复制代码

其中提供的两个方法

  • supports此方法有两个参数returnType:控制器返回值类型converterType:http消息转换器类型,只有该方法返回true时,beforeBodyWrite才会执行,我们可以利用这个方法做一些配置,比如通过自定义注解@IgnoreBodyAdvice让某些接口不使用统一返回结构。
    @Override
    public boolean supports(final @NotNull MethodParameter methodParameter,
                            final Class<? extends HttpMessageConverter<?>> aClass) {
        Class<?> clz = methodParameter.getDeclaringClass();
        if (method == null) {
            return false;
        } 
        // 检查注解是否存在
        if (clz.isAnnotationPresent(IgnoreBodyAdvice.class)) {
            return false;
        } else
            return !method.isAnnotationPresent(IgnoreBodyAdvice.class);
    }
复制代码
  • beforeBodyWrite此方法有6个参数,会在HttpMessageConverterwrite方法之前调用body:返回的消息returnType: controller的返回值类型selectedContentType:选定的消息类型,比如application/jsonselectedConverterType:http消息转换器类型,比如StringHttpMessageConverterrequest:当前请求response:当前响应常见的用法如下
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
    if(body == null){
        return ResponseData.builder().message("ok").build();
    } else if(body instanceof ResponseData){
        return body;
    } else {
        return ResponseData.builder().data(body).build();
    }
}
复制代码

这只是一个最简单的例子,在实际项目可能还会有很多判断条件,可以根据项目情况自行添加。

相关推荐

Vue的框架(了解)

前端MVC设计模式MVC设计模式,其实就是将前端实现某个业务的所有代码划分为三部分Model:模型,指数据模型,这个数据一般来自于服务器View:视图,指页面标签内容Controller:控制...

Vue.js实战 第五章练习一

练习要求:在原有表格基础上,新增一项是否选中该商品的功能,总价变为只计算选中商品的总价,同时提供一个全选的按钮。实现思路:按照vue数据和dom元素双向绑定的特性,定义allCheckStatus变量...

Vue基础到进阶教程之class和style绑定

关于class和style我们并不陌生,这个在学习css的时候就是家常便饭了,操作元素的class列表和内联样式是数据绑定的一个常见需求。因为它们都是属性,所以我们可以用v-bind处理它们,...

深入Vue 必学高阶组件 HOC「进阶篇」

作者:ssh转发连接:https://mp.weixin.qq.com/s/seKoLSIMtTd1sU4uDrgZCA前言高阶组件这个概念在React中一度非常流行,但是在Vue的社区里讨论...

周末大礼包,23道高质量中级前端面试题。金九银十,建议收藏

这套面试题考察的内容比较常见,涉及到JavaScript、ES6、CSS、Vue、简单算法,浏览器相关知识等。题目列表1.JavaScript的数据类型有哪些2.什么是同源策略3.跨域的方法...

vue3.0-摒弃Object.defineProperty,基于 Proxy 的观察者机制

写在前面:11月16日早上,Vue.js的作者尤大大在VueToronto的主题演讲中预演了Vue.js3.0的一些新特性,其中一个很重要的改变就是Vue3将使用ES6的Proxy作...

程序员都必掌握的前端教程之VUE基础教程(七)

阅读本文约需要10分钟,您可以先关注我们,避免下次无法找到。本篇文章成哥继续带大家来学习前端VUE教程,今天主要讲解VUE的表单处理等知识点。下面我们就一起来学习该块内容吧!01简介在日常开发中,我...

web前端开之网站搭建框架之vue详解

网站搭建框架之vueVue是web前端快速搭建网站的框架之一。它与jQuery有所不同,是以数据驱动web界面(以操作数据改变页面,而jQuery是以操作节点来改变页面),同时,vue还实现了数据的双...

vue3.0尝鲜-基于 Proxy 的观察者机制探索

Vue.js的作者尤大大在VueToronto的主题演讲中预演了Vue.js3.0的一些新特性,其中一个很重要的改变就是Vue3将使用ES6的Proxy作为其观察者机制,取代之前使用...

TypeScript 设计模式之观察者模式

一、模式介绍1.背景介绍在软件系统中经常碰到这类需求:当一个对象的状态发生改变,某些与它相关的对象也要随之做出相应的变化。这是建立一种「对象与对象之间的依赖关系」,一个对象发生改变时将「自动通知其他...

vue面试3

1.单页面应用与多页面应用的去别2.简述一下Sass、Less,且说明区别?他们是动态的样式语言,是CSS预处理器,CSS上的一种抽象层。他们是一种特殊的语法/语言而编译成CSS。变量符不一样,les...

VUE v-bind 数据绑定

动态的绑定一个或多个attribute,也可以是组件的prop。缩写::或者.(当使用.prop修饰符)期望:any(带参数)|Object(不带参数)参数:attrOrP...

vue初学习之自定义选择框实现

v-model简单介绍在使用vue的过程中会经常用到input和textarea这类表单元素,vue对于这些元素的数据绑定和我们以前经常用的jQuery有些区别。vue使用v-model实现这些标签...

Vue实现拖拽穿梭框功能四种方式

一、使用原生js实现拖拽打开视频讲解更加详细Vue实现拖拽穿梭框功能的四种方式_哔哩哔哩_bilibili<html><head><meta...

Vue3.x setup 语法糖实现props双向绑定

1.背景为了封装一下Element-Plus的分页插件,需要实现父子组件之间的传值。2.父组件<scriptsetuplang="ts">letqueryPa...

取消回复欢迎 发表评论: