Spring MVC 十:异常处理

异常是每一个应用必须要处理的问题。

Spring MVC项目,如果不做任何的异常处理的话,发生异常后,异常堆栈信息会直接抛出到页面。

比如,我们在Controller写一个异常:

    @GetMapping(value="/hello",produces={"text/html; charset=UTF-8"})@ResponseBodypublic String hello(ModelAndView model){int c = 100 / 0;return "<h1>User info</h1>";}

浏览器访问:
在这里插入图片描述

用户体验相当不好,所以一般情况下,应用必须要想办法处理异常。

异常处理方式

常见的异常处理方式,无非:

  1. 应用中对所有可能发生异常的地方,都try catch,捕获异常后做相应的处理。
  2. 集中处理异常。

第一种方式显然不好,一方面是代码中需要到处都写try catch,万一某一段代码由于程序员的疏忽没有写,异常就会抛出到前台,很不好。

另外,某些情况下异常是不能捕获的,比如需要事务处理的代码,捕获异常后会影响到事务回滚。

所以,我们还是需要想办法用第2中方式来处理异常。n多年前曾经做过一个摩托罗拉的项目,其中一项需求就是对一个线上系统的异常做处理、不允许异常信息抛出到前台页面。当初那个项目并没有采用类似SpringMVC的框架,所以最终提出了用filter、在filter中拦截异常的处理方案并且被客户采纳。

其实SpringMVC的统一异常处理方案和我上面项目中采用的方案非常类似。

SpringMVC的异常处理

Spring MVC提供了如下异常处理选项:

  1. @ExceptionHandle
  2. @ExceptionHandle + @ControllerAdvice
  3. 自定义异常处理器HandlerExceptionResolver
@ExceptionHandle

@ExceptionHandle可以作用在Controller中,比如,在上面发生异常的Controller中增加一个@ExceptionHandle注解的方法:

    @GetMapping(value="/hello",produces={"text/html; charset=UTF-8"})@ResponseBodypublic String hello(ModelAndView model){int c = 100 / 0;return "<h1>User info</h1>";}@ExceptionHandler(Exception.class)@ResponseBodypublic String handle(Exception ex){return "错了在helloworld Controller error msg is ===";}

运行:
在这里插入图片描述

说明Hello方法中发生的异常,已经被handle方法处理,前台页面不再会出现异常信息。

问题解决了!

但是@ExceptionHandle只在当前Controller文件中生效,也就是说,当前Controller中的方法、或者方法调用的service层、dao层等发生的异常,才会被捕获到。其他Controller中发生的异常依然不会被捕获。

这样的话,就需要对每一个Controller增加@ExceptionHandle进行处理,处理起来还是有点麻烦。

统一异常处理

@ExceptionHandle + @ControllerAdvice可以实现统一异常处理。

@ControllerAdvice注解可以实现:

On startup, RequestMappingHandlerMapping and ExceptionHandlerExceptionResolver detect controller advice beans and apply them at runtime. Global @ExceptionHandler methods, from an @ControllerAdvice, are applied after local ones, from the @Controller. By contrast, global @ModelAttribute and @InitBinder methods are applied before local ones.

SpringMVC启动的过程中,RequestMappingHandlerMapping和ExceptionHandlerExceptionResolver会检测到advice bean并且在运行时会使用他们。在@ControllerAdvice中的@ExceptionHandler会变成一个全局的异常处理器、在本地异常处理器之后生效。并且,@ModelAttribute和 @InitBinder会在本地的之后生效。

这段话的意思就是,@ExceptionHandle + @ControllerAdvice之后,@ExceptionHandle就会变成全局异常处理器。所谓的本地异常处理器,就是写在Controller中的@ExceptionHandle异常处理器。

这个工作是ExceptionHandlerExceptionResolver干的,源码中可以看到:

@Nullableprotected ServletInvocableHandlerMethod getExceptionHandlerMethod(@Nullable HandlerMethod handlerMethod, Exception exception) {Class<?> handlerType = null;//先找"local"异常处理器if (handlerMethod != null) {// Local exception handler methods on the controller class itself.// To be invoked through the proxy, even in case of an interface-based proxy.handlerType = handlerMethod.getBeanType();ExceptionHandlerMethodResolver resolver = this.exceptionHandlerCache.get(handlerType);if (resolver == null) {resolver = new ExceptionHandlerMethodResolver(handlerType);this.exceptionHandlerCache.put(handlerType, resolver);}Method method = resolver.resolveMethod(exception);if (method != null) {return new ServletInvocableHandlerMethod(handlerMethod.getBean(), method);}// For advice applicability check below (involving base packages, assignable types// and annotation presence), use target class instead of interface-based proxy.if (Proxy.isProxyClass(handlerType)) {handlerType = AopUtils.getTargetClass(handlerMethod.getBean());}}//再找advice的异常处理器for (Map.Entry<ControllerAdviceBean, ExceptionHandlerMethodResolver> entry : this.exceptionHandlerAdviceCache.entrySet()) {ControllerAdviceBean advice = entry.getKey();if (advice.isApplicableToBeanType(handlerType)) {ExceptionHandlerMethodResolver resolver = entry.getValue();Method method = resolver.resolveMethod(exception);if (method != null) {return new ServletInvocableHandlerMethod(advice.resolveBean(), method);}}}return null;}

验证一下。

加一个MyGlobalExceptionController:

@ControllerAdvice
public class MyGlobalExceptionController {@ExceptionHandler(Exception.class)@ResponseBodypublic String handle(Exception ex){return return "错了MyGlobalExceptionController error msg is ===";}
}

先不去掉HelloWorldController中的异常处理器,运行hello:
在这里插入图片描述

生效的是HelloWorldController中的异常处理器。

然后去掉HelloWorldController中的异常处理器:
在这里插入图片描述

写在MyGlobalExceptionController中的全局异常处理器生效!

自定义异常处理器HandlerExceptionResolver

实现接口HandlerExceptionResolver,或者扩展虚拟类AbstractHandlerMethodExceptionResolver(勉强可以考虑),定义自己的异常处理器。

其实,非必要(没想到有什么必要性)不建议!

上一篇 Spring MVC 九:Context层级(基于配置)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/145381.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

搭建前端框架

在终端进入web目录&#xff0c;然后创建vuecrud工程 创建工程并引入ElementUI和axios手把手教学>传送门:VueCLI脚手架搭建

2023.09.30使用golang1.18编译Hel10-Web/Databasetools的windows版

#Go 1.21新增的 log/slog 完美解决了以上问题&#xff0c;并且带来了很多其他很实用的特性。 本次编译不使用log/slog 包 su - echo $GOPATH ;echo $GOROOT; cd /tmp; busybox wget --no-check-certificate https://go.dev/dl/go1.18.linux-amd64.tar.gz;\ which tar&&am…

驱动插入中断门示例代码

驱动插入中断描述符示例代码 最近做实验&#xff0c;每次在应用层代码写测试代码的时候都要手动挂一个中断描述符&#xff0c;很不方便所以就想着写个驱动挂一个中断门比较省事 驱动测试效果如下&#xff1a; 下面的代码是个架子&#xff0c;用的时候找个驱动历程传递你要插…

搭建智能桥梁,Amazon CodeWhisperer助您轻松编程

零&#xff1a;前言 随着时间的推移&#xff0c;人工智能技术以惊人的速度向前发展&#xff0c;正掀起着全新的编程范式革命。不仅仅局限于代码生成&#xff0c;智能编程助手等创新应用也进一步提升了开发效率和代码质量&#xff0c;极大地推动着软件开发领域的快速繁荣。 当前…

小白继续深入学习C++

第1节 指针的基本概念 1、变量的地址&#xff1a; 变量是内存地址的简称&#xff0c;在C中&#xff0c;每定义一个变量&#xff0c;系统就会给变量分配一块内存&#xff0c;内存是有地址的。 C用运算符&获取变量在内存中的起始地址。 语法&#xff1a; &变…

如果在 Mac 上的 Safari 浏览器中无法打开网站

使用网络管理员提供的信息更改代理设置。个人建议DNS解析&#xff0c;设置多个例如114.114.114.114 8.8.8.8 8.8.4.4 如果打不开网站&#xff0c;请尝试这些建议。 在 Mac 上的 Safari 浏览器 App 中&#xff0c;检查页面无法打开时出现的信息。 这可能会建议解决问题的…

Chrome(谷歌浏览器)如何关闭搜索栏历史记录

目录 问题描述解决方法插件解决&#xff08;亲测有效&#xff09;自带设置解决步骤首先打开 地址 输入&#xff1a;chrome://flags关闭浏览器&#xff0c;重新打开Chrome 发现 已经正常 问题描述 Chrome是大家熟知的浏览器&#xff0c;但是搜索栏的历史记录如何自己一条条的删…

第80步 时间序列建模实战:GRNN回归建模

基于WIN10的64位系统演示 一、写在前面 这一期&#xff0c;我们使用Matlab进行GRNN模型的构建。 使用的数据如下&#xff1a; 采用《PLoS One》2015年一篇题目为《Comparison of Two Hybrid Models for Forecasting the Incidence of Hemorrhagic Fever with Renal Syndrom…

(高阶) Redis 7 第16讲 预热/雪崩/击穿/穿透 缓存篇

面试题 什么是缓存预热/雪崩/击穿/穿透如何做缓存预热如何避免或减少缓存雪崩穿透和击穿的区别?穿透和击穿的解决方案出现缓存不一致时,有哪些修补方案缓存预热 理论 将需要的数据提前加载到缓存中,不需要用户使用的过程中进行数据回写。(比如秒杀活动数据等) 方案 1.…

软件设计师_操作系统基本原理_学习笔记

文章目录 2.1 操作系统概述2.2 进程2.2.1 进程状态转换图2.2.2 前趋图2.2.3 进程的同步与互斥2.2.4 PV操作2.2.5 死锁 2.3 存储管理2.3.1 分区存储管理 2.1 操作系统概述 2.2 进程 2.2.1 进程状态转换图 2.2.2 前趋图 哪些任务可以并行&#xff0c;哪些任务有先后关系&#xf…

设计一个简单的通讯录

目录 导读&#xff1a; 一、主函数 1. 打印功能菜单 2. 用枚举常量列举功能给功能赋值&#xff08;0-5&#xff09; 3. main主函数 二、头文件 三、通讯录各功能的实现 1. 初始化通讯录 2. 增加联系人 3. 展示所有联系人信息 4. 删除指定联系人 5. 查询指定联系人…

elasticsearch+logstash+kibana整合(ELK的使用)第一课

一、安装elasticsearch 0、创建目录&#xff0c;统一放到/data/service/elk 1、下载安装包 wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.1.0-linux-x86_64.tar.gz2、解压 tar -xzvf elasticsearch-7.1.0-linux-x86_64.tar.gz3、新建用户和组…

FFmpeg 命令:从入门到精通 | FFmpeg 基本介绍

FFmpeg 命令&#xff1a;从入门到精通 | FFmpeg 基本介绍 FFmpeg 命令&#xff1a;从入门到精通 | FFmpeg 基本介绍FFmpeg 简介FFmpeg 基础知识复用与解复用编解码器码率和帧率 资料 FFmpeg 命令&#xff1a;从入门到精通 | FFmpeg 基本介绍 本系列文章要解决的问题&#xff1…

【SQL】mysql创建定时任务执行存储过程--20230928

1.先设定时区 https://blog.csdn.net/m0_46629123/article/details/133382375 输入命令show variables like “%time_zone%”;&#xff08;注意分号结尾&#xff09;设置时区&#xff0c;输入 set global time_zone “8:00”; 回车,然后退出重启&#xff08;一定记得重启&am…

Web 中间件怎么玩?

本次主要是聊聊关于 web 中间件&#xff0c; 分为如下四个方面 什么是 web 框架中间件 为什么要使用 web 中间件 如何使用及其原理 哪些场景需要使用中间件 开门见山 web 中间件是啥 Web 框架中的中间件主要指的是在 web 请求到具体路由之前或者之后&#xff0c;会经过一个或…

Java进阶必会JVM-深入浅出Java虚拟机

系列文章目录 送书第一期 《用户画像&#xff1a;平台构建与业务实践》 送书活动之抽奖工具的打造 《获取博客评论用户抽取幸运中奖者》 送书第二期 《Spring Cloud Alibaba核心技术与实战案例》 送书第三期 《深入浅出Java虚拟机》 文章目录 系列文章目录前言一、推荐书籍二…

【信创】麒麟v10(arm)-mysql8-mongo-redis-oceanbase

Win10/Win11 借助qume模拟器安装arm64麒麟v10 前言 近两年的国产化进程一直在推进&#xff0c;基于arm架构的国产系统也在积极发展&#xff0c;这里记录一下基于麒麟v10arm版安装常见数据库的方案。 麒麟软件介绍: 银河麒麟高级服务器操作系统V10 - 国产操作系统、银河麒麟、中…

【JavaEE】HTML

JavaWeb HTML 超文本标记语言 超文本&#xff1a;文本、声音、图片、视频、表格、连接标记&#xff1a;有许许多多的标签组成 vscode开发工具搭建 因为我使用的IDEA是社区版&#xff0c;代码高亮补全缩进都有些问题&#xff0c;使用vscode是最好的选择~ 安装 Visual Stu…

机器学习之广义增量规则(Generalized Delta Rule)

文章目录 广义增量规则的公式s型函数的增量规则 广义增量规则的公式 对于单层神经网络的增量规则&#xff0c;已经过时啦&#xff0c;现在存在一种更广义的增量规则形式。对于任意激活函数&#xff0c;增量规则表示如下式它与前一节的delta规则相同&#xff0c;只是ei被替换为…

ElementUI之CUD+表单验证

目录 前言&#xff1a; 增删改查 表单验证 前言&#xff1a; 继上篇博客来写我们的增删改以及表单验证 增删改查 首先先定义接口 数据样式&#xff0c;我们可以去elementUI官网去copy我们喜欢的样式 <!-- 编辑窗体 --><el-dialog :title"title" :visib…