SpringBoot全局异常处理(三十)

本文详细介绍了如何在SpringBoot项目中自定义错误页面,包括静态和动态错误页面的设置,以及如何通过后端自定义异常信息和全局异常处理器实现前后端分离下的错误处理。此外,还探讨了非抛出异常的情况及其处理方式。
摘要由CSDN通过智能技术生成

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

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

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

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

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

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

image-20211124170231617

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

image-20211124170252218

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

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

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

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

image-20211124170615999

image-20211124170630817

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

二. 自定义错误页面

二.一 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 页面

image-20211124171906544

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 (有异常的方法)

image-20211124172050936

在访问 404 错误页面时

image-20211124172123178

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

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

二.二.二 动态错误页面

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

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

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

image-20211124172443166

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 进行定义的

image-20211124173158523

这个时候,再进行访问

image-20211124172755101

image-20211124172810190

会展示出具体的信息.

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

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

例如 classpath:/static/error/404.htmlclasspath:/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;
    }
}

image-20211124173753216

image-20211124173821269

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

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

将 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

image-20211124180611863

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

image-20211124180706819

image-20211124180829879

image-20211124181007986

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

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

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

去掉 MyErrorViewResolver 上面的 @Component 注解

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

image-20211124182020377

四.一 定义异常信息

四.一.一 统一返回结果 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("查询数据库成功");
    }
}

进行调用时,

image-20211124182205410

image-20211124182217870

image-20211124182239279

image-20211124182251973

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

四.二 自定义全局异常处理器 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());
    }
}

这时候,再进行访问:

image-20211124182753546

image-20211124182808596

image-20211124182821628

image-20211124182833313

目前老蝴蝶的 状态码都是 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 层均没有 捕获异常

image-20211124184900345

五.三.二 tryDiv

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

image-20211124185008278

没有接收到异常信息

五.三.三 throwDivExcepiton

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

image-20211124185117780

五.三.四 throwBusExcepiton

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

image-20211124185221101

五.三.五 throwDivExcepiton2

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

image-20211124185334138

五.三.六 throwBusExcepiton2

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

image-20211124185450018

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

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

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

两个蝴蝶飞

你的鼓励,是老蝴蝶更努力写作的

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值