首页 > 其他分享 >SpringBoot全局异常处理(三十)

SpringBoot全局异常处理(三十)

时间:2022-11-10 15:34:16浏览次数:65  
标签:return SpringBoot OutputResult 404 三十 全局 异常 public 页面


生活打了我们一巴掌,我们,一定要想办法再打回来

上一章简单介绍了SpringBoot上传文件到远程服务器(二十九),如果没有看过,​​请观看上一章​​

一. 为什么要实现异常信息自定义展示

在 Springboot 项目开发中,包括以前的 SSM 框架开发中,我们常常会碰到 404 500 相应的错误信息.

如访问一个除以 0 的功能 (500) 错误

@RequestMapping("/div")
@ResponseBody
public String div(){
int result=10/0;
return "相除后的结果是:"+result;
}

SpringBoot全局异常处理(三十)_全局异常处理

或者访问一个不存在的页面 404 错误

SpringBoot全局异常处理(三十)_错误页面_02

这两个页面都是 SpringBoot 默认提供的.

用户在使用的过程中,如果没有相应的开发或者网络经验,对这些提示信息是很讨厌的。

所以常常进行一些有趣的设计,来减轻项目运行错误导致的不良感受。

如: (以下图片来源于网络)

SpringBoot全局异常处理(三十)_自定义_03

SpringBoot全局异常处理(三十)_自定义_04

我们可以自定义错误页面.

二. 自定义错误页面

二.一 Tomcat 服务器配置错误页面

在以前的 Tomcat 和 JSP 时代 ,我们可以这样配置错误页面.

二.一.一 自定义 404.jsp 和 505.jsp 页面

404.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" isErrorPage="true"%>
<% response.setStatus(HttpServletResponse.SC_OK);%>

404,地址错误

500.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" isErrorPage="true"%>
<% response.setStatus(HttpServletResponse.SC_OK);%>

500, 服务器错误,联系管理员

二.一.二 web.xml 配置信息

在 web.xml 配置中,进行配置

<!--错误状态码指定-->  
<error-page>
<error-code>404</error-code>
<location>/WEB-INF/jsp/404.jsp</location>
</error-page>
<!--异常类型指定,也可以细化一下-->
<error-page>
<exception-type>java.lang.Throwable</exception-type>
<location>/WEB-INF/jsp/500.jsp</location>
</error-page>
<!--错误状态码指定-->
<error-page>
<error-code>500</error-code>
<location>/WEB-INF/jsp/500.jsp</location>
</error-page>

二. 二SpringBoot 配置错误页面

根据上面的提示,我们知道,需要在 /error 目录下进行配置相关的页面信息.

配置错误页面,包括两种,一种是静态的异常页面,一种是动态的异常页面.

二.二.一 静态异常页面

在 static 目录下 创建 error 目录, 里面放置 404.html 和 500.html 页面

SpringBoot全局异常处理(三十)_SpringBoot异常处理_05

404.html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>404异常</title>
</head>
<body>
404异常
</body>
</html>

500.html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>500 异常信息</title>
</head>
<body>
500异常
</body>
</html>

重新运行服务器,并且访问

在访问 div (有异常的方法)

SpringBoot全局异常处理(三十)_错误页面_06

在访问 404 错误页面时

SpringBoot全局异常处理(三十)_SpringBoot异常处理_07

就变成了我们自定义的异常页面了.

除了指定 404 ,500 这样确切的错误状态码外,也可以使用 4xx, 5xx 这样的来统一进行接收.

二.二.二 动态错误页面

有时候常常需要将错误的信息展示出来,便于开发人员进行处理.

可以使用动态错误页面,通常放置在 templates 目录下

在 templates 目录下,创建动态错误页面

SpringBoot全局异常处理(三十)_错误页面_08

404.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>404状态码展示错误</title>
</head>
<body>
<h1>404</h1>
<table border="1">
<tr>
<td>path</td>
<td th:text="${path}"></td>
</tr>
<tr>
<td>error</td>
<td th:text="${error}"></td>
</tr>
<tr>
<td>message</td>
<td th:text="${message}"></td>
</tr>
<tr>
<td>timestamp</td>
<td th:text="${timestamp}"></td>
</tr>
<tr>
<td>status</td>
<td th:text="${status}"></td>
</tr>
</table>
</body>
</html>

500.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>5xx状态码展示错误</title>
</head>
<body>
<h1>5xx</h1>
<table border="1">
<tr>
<td>path</td>
<td th:text="${path}"></td>
</tr>
<tr>
<td>error</td>
<td th:text="${error}"></td>
</tr>
<tr>
<td>message</td>
<td th:text="${message}"></td>
</tr>
<tr>
<td>timestamp</td>
<td th:text="${timestamp}"></td>
</tr>
<tr>
<td>status</td>
<td th:text="${status}"></td>
</tr>
</table>
</body>
</html>

这五个属性信息, path, error,message,timestamp,status

是 由: org.springframework.boot.web.reactive.error.DefaultErrorAttributes 进行定义的

SpringBoot全局异常处理(三十)_自定义_09

这个时候,再进行访问

SpringBoot全局异常处理(三十)_自定义_10

SpringBoot全局异常处理(三十)_全局异常处理_11

会展示出具体的信息.

发现,动态的错误页面是生效的。

如果动态页面和静态页面同时定义了异常处理页面,

例如 ​​classpath:/static/error/404.html​​​ 和 ​​classpath:/templates/error/404.html​​ 同时存在时,

默认使用动态页面。

完整的错误页面查找方式应该是这样:

发生了 404错误–>查找动态 404.html 页面–>查找静态 404.html –> 查找动态 4xx.html–>查找静态 4xx.html。

500 也一样.

发生了 500 错误–>查找动态 500.html 页面–>查找静态 500.html –> 查找动态 5xx.html–>查找静态 5xx.html。

三. 后端服务器自定义异常

三.一 提示信息和内容自定义

SpringBoot 提供的页面和属性信息,有时候不符合业务场景,需要后端开发人员进行自定义(自带的 message 是英文的,不符合国人习惯)

我们可以自定义修改

package top.yueshushu.learn.error;

import org.springframework.boot.web.servlet.error.DefaultErrorAttributes;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.WebRequest;

import java.util.Map;

/**
* @ClassName:MyErrorAttributes
* @Description 后端自定义的提示信息
* @Author zk_yjl
* @Date 2021/11/24 17:34
* @Version 1.0
* @Since 1.0
**/
@Component
public class MyErrorAttributes extends DefaultErrorAttributes {
@Override
public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
Map<String, Object> map = super.getErrorAttributes(webRequest, includeStackTrace);
//也可以放置其他的属性信息,或者替换属性,如 message,或者 timestamp
if ((Integer)map.get("status") == 500) {
map.put("message", "服务器内部错误!");
}
if ((Integer)map.get("status") == 404) {
map.put("message", "页面找不到!");
}
if ((Integer)map.get("status") == 403) {
map.put("message", "未授权!");
}
return map;
}
}

SpringBoot全局异常处理(三十)_全局异常处理_12

SpringBoot全局异常处理(三十)_html_13

提示信息发生了改变,变成了开发人员自定义的提示信息.

三.二 自定义视图解析和返回内容

将 MyErrorAttributes 去掉 @Component 组件注解

继承DefaultErrorViewResolver 解析视图类

@Component
public class MyErrorViewResolver extends DefaultErrorViewResolver {
/**
构造方法
*/
public MyErrorViewResolver(ApplicationContext applicationContext, ResourceProperties resourceProperties) {
super(applicationContext, resourceProperties);
}

@Override
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
// 可以根据状态 status, 自定义视图页面和 model
//不能直接修改 model
Map<String, Object> newMap = new HashMap<>();
for(Map.Entry<String,Object> entry:model.entrySet()){
newMap.put(entry.getKey(),entry.getValue());
}
newMap.put("findUser","两个蝴蝶飞");
// NOT_FOUND(404, "Not Found"),
if(HttpStatus.NOT_FOUND.equals(status)){
return new ModelAndView("/self/404.html",newMap);
}
// 500错误
// INTERNAL_SERVER_ERROR(500, "Internal Server Error"),
if(HttpStatus.INTERNAL_SERVER_ERROR.equals(status)){
return new ModelAndView("/self/500.html",newMap);
}
//走一个默认的页面
return new ModelAndView("/static/error/500.html",newMap);
}
}

在 template 目录下, 创建 self , 后续创建 500.html, 404.html

SpringBoot全局异常处理(三十)_SpringBoot异常处理_14

500.html , 404.html 添加一个自定义的属性, 就是 findUser

SpringBoot全局异常处理(三十)_SpringBoot异常处理_15

SpringBoot全局异常处理(三十)_错误页面_16

SpringBoot全局异常处理(三十)_错误页面_17

四. 前后端处理自定义异常信息

现在的项目,都是前后端分离的项目,希望在出现异常时,返回的是一个 json格式的数据,并不是跳转到页面。

前端开发人员,拿到错误的信息之后,个性化进行处理.

去掉 MyErrorViewResolver 上面的 @Component 注解

同时,去掉 自定义的 静态异常和动态异常信息, 将 error 文件夹重命名为 error2

SpringBoot全局异常处理(三十)_html_18

四.一 定义异常信息

四.一.一 统一返回结果 OutputResult

@Data
public class OutputResult implements Serializable {
/**
* @param code 响应代码
* @param message 响应信息
* @param data 响应的数据
*/
private Integer code;
private String message;
private Map<String,Object> data=new HashMap<String,Object>();

/**
* 构造方法 私有。 避免外部构造
*/
private OutputResult(){

}
/**
* 成功
* @return
*/
public static OutputResult fail(){
OutputResult outputResult=new OutputResult();
outputResult.code=500;
outputResult.message="失败";
return outputResult;
}
/**
* 成功
* @return
*/
public static OutputResult fail(String message){
OutputResult outputResult=new OutputResult();
outputResult.code=500;
outputResult.message=message;
return outputResult;
}
/**
* 成功
* @return
*/
public static OutputResult success(){
OutputResult outputResult=new OutputResult();
outputResult.code=200;
outputResult.message="成功";
return outputResult;
}

/**
* 成功
* @param data 要响应的数据
* @return
*/
public static OutputResult success(Object data){
OutputResult outputResult=new OutputResult();
outputResult.code=200;
outputResult.message="成功";
outputResult.data.put("result",data);
return outputResult;
}
}

四.一.二 自定义的异常类 BusinessException

public class BusinessException extends Exception {
String message="";
public BusinessException(String message){
super(message);
this.message=message;
}

public void setMessage(String message) {
this.message = message;
}

@Override
public String getMessage() {
return message;
}
}

四.一.三 Controller 层异常方法 ExceptionController

@Controller
public class ExceptionController {
@RequestMapping("/")
public String index(){
return "index";
}
/**
* 会出现除 0异常
* @date 2021/11/9 20:54
* @author zk_yjl
* @param
* @return java.lang.String
*/
@RequestMapping("/div")
@ResponseBody
public OutputResult div(){
int result=10/0;
return OutputResult.success(result);
}
/**
* 会出现空指针异常
* @date 2021/11/9 20:54
* @author zk_yjl
* @param
* @return java.lang.String
*/
@RequestMapping("/npe")
@ResponseBody
public OutputResult npe(){
String str=null;
return OutputResult.success(str.length());
}
/**
* 会出现下标越界异常
* @date 2021/11/9 20:54
* @author zk_yjl
* @param
* @return java.lang.String
*/
@RequestMapping("/array")
@ResponseBody
public OutputResult array(){
String[] arr=new String[]{"岳泽霖","两个蝴蝶飞"};
return OutputResult.success(arr[arr.length]);
}


/**
* 会出现下业务型异常
* @date 2021/11/9 20:54
* @author zk_yjl
* @param
* @return java.lang.String
*/
@RequestMapping("/bus")
@ResponseBody
public OutputResult bus() throws BusinessException {
try{
int aa=10/0;
}catch (Exception e){
//去查询数据库
throw new BusinessException("查询数据库失败了");
}
return OutputResult.success("查询数据库成功");

}

/**
* 会出现下业务型异常
* @date 2021/11/9 20:54
* @author zk_yjl
* @param
* @return java.lang.String
*/
@RequestMapping("/other")
@ResponseBody
public OutputResult other() throws Exception {
//去查询数据库
try{
int aa=10/0;
}catch (Exception e){
//去查询数据库
throw new Exception("其他的异常信息");
}
return OutputResult.success("查询数据库成功");
}
}

进行调用时,

SpringBoot全局异常处理(三十)_错误页面_19

SpringBoot全局异常处理(三十)_错误页面_20

SpringBoot全局异常处理(三十)_html_21

SpringBoot全局异常处理(三十)_自定义_22

这样很不好看,也不方便管理.

四.二 自定义全局异常处理器 MyExceptionHandler

通过 @ExceptionHandler 注解,接收相应的异常信息

@RestControllerAdvice
public class MyExceptionHandler {
/**
* 处理空指向的异常信息
* @date 2021/11/10 11:52
* @author zk_yjl
* @param
* @return top.yueshushu.learn.response.OutputResult
*/
@ExceptionHandler(NullPointerException.class)
public OutputResult npeException(HttpServletRequest req, NullPointerException e){
return OutputResult.fail(e.getMessage());
}

/**
* 处理算术的异常信息
* @date 2021/11/10 11:52
* @author zk_yjl
* @param
* @return top.yueshushu.learn.response.OutputResult
*/
@ExceptionHandler(ArithmeticException.class)
public OutputResult ariException(HttpServletRequest req, ArithmeticException e){
return OutputResult.fail(e.getMessage());
}

/**
* 处理数组下标越界异常的异常信息
* @date 2021/11/10 11:52
* @author zk_yjl
* @param
* @return top.yueshushu.learn.response.OutputResult
*/
@ExceptionHandler(ArrayIndexOutOfBoundsException.class)
public OutputResult arrException(HttpServletRequest req, ArrayIndexOutOfBoundsException e){
return OutputResult.fail(e.getMessage());
}

/**
* 处理自定义的业务异常的异常信息
* @date 2021/11/10 11:52
* @author zk_yjl
* @param
* @return top.yueshushu.learn.response.OutputResult
*/
@ExceptionHandler(BusinessException.class)
public OutputResult busException(HttpServletRequest req, BusinessException e){
return OutputResult.fail(e.getMessage());
}


/**
* 处理其他的另外异常的异常信息
* @date 2021/11/10 11:52
* @author zk_yjl
* @param
* @return top.yueshushu.learn.response.OutputResult
*/
@ExceptionHandler(Exception.class)
public OutputResult otherException(HttpServletRequest req, Exception e){
return OutputResult.fail(e.getMessage());
}
}

这时候,再进行访问:

SpringBoot全局异常处理(三十)_自定义_23

SpringBoot全局异常处理(三十)_SpringBoot异常处理_24

SpringBoot全局异常处理(三十)_全局异常处理_25

SpringBoot全局异常处理(三十)_错误页面_26

目前老蝴蝶的 状态码都是 500, 实际用的时候,会定义成不同的状态码, npe表示 300, 除 o表示 301,其它异常都有各自的状态码。

前端可以根据状态码,进行不同的展示信息

从而实现前后端分离下的全局异常处理机制.

五. 非抛出异常处理

在实际的业务中,会有 dao数据库层,servie层, controller 层,每一层都会有相应的异常抛出,也可以各自在自己的层捕获异常。

当异常被补获时,没有被抛出时,是什么情况呢? 一般不会在 dao层捕获异常.

五.一 Service 层

public interface ExceptionService {
/**
普通的两个异常信息,不补获
*/
public void div();
/**
普通的两个异常信息,内部进行补获
*/
public void tryDiv();

/**
往外抛出异常
*/

public void throwDivExcepiton() throws Exception;
public void throwBusExcepiton() throws Exception;




}

五.二 SerivceImpl 实现

@Service
public class ExceptionServiceImpl implements ExceptionService {

@Override
public void div() {
int a=10/0;
}
@Override
public void tryDiv() {
try{
int a=10/0;
}catch (Exception e){
e.printStackTrace();
}
}

@Override
public void throwDivExcepiton() throws Exception {
try{
int a=10/0;
}catch (Exception e){
throw e;
}
}

@Override
public void throwBusExcepiton() throws Exception {
throw new BusinessException("数据库查询出现异常");
}
}

五.三 Controller 层 ServiceExceptionController

@RestController
@RequestMapping("/exce")
public class ServiceExceptionController {
@Autowired
private ExceptionService exceptionService;

@GetMapping("/div")
public OutputResult div(){
exceptionService.div();
return OutputResult.success();
}

@GetMapping("/tryDiv")
public OutputResult tryDiv(){
exceptionService.tryDiv();
return OutputResult.success();
}
@GetMapping("/throwDivExcepiton")
public OutputResult throwDivExcepiton() throws Exception {
exceptionService.throwDivExcepiton();
return OutputResult.success();
}

@GetMapping("/throwBusExcepiton")
public OutputResult throwBusExcepiton() throws Exception {
exceptionService.throwBusExcepiton();
return OutputResult.success();
}


@GetMapping("/throwDivExcepiton2")
public OutputResult throwDivExcepiton2() {
try{
exceptionService.throwDivExcepiton();
return OutputResult.success();
}catch (Exception e){
e.printStackTrace();
return OutputResult.success("内部补获异常");
}
}

@GetMapping("/throwBusExcepiton2")
public OutputResult throwBusExcepiton2() {
try{
exceptionService.throwBusExcepiton();
return OutputResult.success();
}catch (Exception e){
e.printStackTrace();
return OutputResult.success("内部补获异常");
}
}

/*
* 异常处理,
* 出现的异常,没有显式的捕获,都会接收到。如果被try catch 到,即不往外抛出, 抛出到 controller 层的话, 是不会接收到这个异常的。
* 从而不触发异常机制信息.
* */
}

运行处理展示:

五.三.一 div 方法

service 层和 controller 层均没有 捕获异常

SpringBoot全局异常处理(三十)_自定义_27

五.三.二 tryDiv

service 层捕获异常, controller 层没有捕获

SpringBoot全局异常处理(三十)_错误页面_28

没有接收到异常信息

五.三.三 throwDivExcepiton

service 层捕获异常,但往外抛出了, controller 层继续抛出

SpringBoot全局异常处理(三十)_html_29

五.三.四 throwBusExcepiton

service 层抛出异常, controller 层继续抛出

SpringBoot全局异常处理(三十)_html_30

五.三.五 throwDivExcepiton2

service 层捕获异常,但继续抛出异常, controller 捕获异常

SpringBoot全局异常处理(三十)_全局异常处理_31

五.三.六 throwBusExcepiton2

service 层抛出异常, controller 捕获异常

SpringBoot全局异常处理(三十)_错误页面_32

总结: 异常捕获,是在 Controller 层再往上一层进行处理的, 如果 Controller 层抛出异常,才能获取到,转换成 json的形式,如果 controller 层不抛出异常,是无法获取到的,也无法转换成 json的形式.

本章节的代码放置在 github 上:

​https://github.com/yuejianli/springboot/tree/develop/SpringBoot_Exception​

谢谢您的观看,如果喜欢,请关注我,再次感谢 !!!


标签:return,SpringBoot,OutputResult,404,三十,全局,异常,public,页面
From: https://blog.51cto.com/u_13420484/5841784

相关文章

  • SpringBoot自定义日志Starter(二十五)
    即使有一天,我放弃了自己的身体,也请你,不要放弃我,我亲爱的灵魂.上一章简单介绍了SpringBoot自定义Starter(二十四),如果没有看过,​​请观看上一章​​一.AOP实现日志功能......
  • SpringBoot整合Ehcache缓存(二十二)
    二八佳人体似酥,腰间仗剑斩愚夫。虽然不见人头落,暗里教君骨髓枯。上一章简单介绍了SpringBoot整合Cache缓存技术(二十一),如果没有看过,​​请观看上一章​​一.Ehcache关于......
  • SpringBoot整合Redis_Jedis版(二十)
    二八佳人体似酥,腰间仗剑斩愚夫。虽然不见人头落,暗里教君骨髓枯。上一章简单介绍了SpringBoot整合Redis(十九),如果没有看过,​​请观看上一章​​SpringBoot2.0版本之后,采......
  • SpringBoot整合MyBatisPlus(十四)
    二八佳人体似酥,腰间仗剑斩愚夫。虽然不见人头落,暗里教君骨髓枯。上一章简单介绍了SpringBoot整合Thymeleaf(十三),如果没有看过,​​请观看上一章​​一.MyBatisPlus的简......
  • SpringBoot系统启动任务(三十二)的方式
    当我们离开世界的时候,那些人,知道我们来过就好.上一章简单介绍了SpringBoot通过Cors解决跨域问题(三十一),如果没有看过,​​请观看上一章​​本章节参考江南一点雨大神的文......
  • Springboot项目部署到docker
    Manve项目部署到docker第一步:将springboot项目打包Maven打包SpringBoot项目报错(repackagefailed:Unabletofindmainclass),排除寻找Main方法,一般用于被依赖的公用......
  • SpringBoot启动报错The APR based Apache Tomcat Native..
    SpringBoot项目启动报错TheAPRbasedApacheTomcatNativel...一、报错信息2022-11-1009:50:53org.apache.catalina.core.AprLifecycleListenerinit信息:TheAP......
  • 记录一次springboot 集成 openfeign 实现模块间调用异常
    记录一次springboot集成openfeign实现模块间调用异常 问题背景product 服务作为服务端,提供了一个对外通信Fegin接口ProductClient,放在了com.imooc.product.clie......
  • uniapp 全局去除半圆阴影
     随便一个页面添加bounce:none"app-plus":{"bounce":"none"}     注意只有任意的一个页面和全局页面同时添加才能去除!  ......
  • springboot整合项目-商城项目展示购物车勾选到支付页面并展示功能
    显示勾选的购物车数据1.持久层1.规划sql语句用户在购物车列表页面通过随机勾选相关的商品,在点击结算俺就后,跳转到结算页面,在这个页面需要展示用户在上个页面说勾选的购......