SpringBoot 多种方式配置错误页面

参考资料

  1. SpringBoot异常处理机制-BasicErrorController与@ControllerAdvice
  2. Java开发从工作到原理–BasicErrorController统一异常处理
  3. 【spring boot】spring boot 处理异常
  4. SpringBoot一个请求的处理全过程
  5. @ControllerAdvice和ErrorPageRegistrar接口配置错误页面的问题
  6. SpringBoot全局异常处理
  7. Springboot异常处理只会@ControllerAdvice+@ExceptionHandler?还远远不够!


当系统出现异常时候,如404或500异常的时候,默认返回的错误页面通常非常简陋。用户也看不懂,这时候我们想通过一些手段,提示用户访问的资源不存在,或者告知系统异常。


前期准备

1.1 后台

  • 通过运行时异常来触发错误页面跳转
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller
@RequestMapping("/test22")
public class Test22Controller {

    @GetMapping("/init")
    public ModelAndView init() {
        
        // 🤪运行时异常
        int a = 1 / 0;

        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("test22");
        return modelAndView;
    }

    @PostMapping("/test")
    public ResponseEntity<Void> test() {

        // 🤪运行时异常
        int a = 1 / 0;

        return ResponseEntity.noContent().build();
    }
}

1.2 错误页面跳转Controller

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller
@RequestMapping("/error")
public class ExceptionPageController {
	
	// 404异常
    @GetMapping("/404error")
    public ModelAndView init404Page() {

        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("error/500");
        return modelAndView;
    }
	
	// 500异常
    @GetMapping("/500error")
    public ModelAndView init500Page() {

        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("error/500");
        return modelAndView;
    }
}

一. 静态资源文件的方式

当SpringBoot工程启动的时候,默认会去如下所示路径初始化存放静态异常页面

  • /META-INF/resources/error/ 👉优先顺最高
  • /resources/error/
  • /static/error/
  • /public/error/ 👉优先顺最低

❗注意,默认配置下,静态页面的名称要和状态码相同。
即404异常的静态页面需命名为404.html

1.1 在/static/error/下配置错误页面

可以看到只需创建error文件夹和对应的状态码.html页面,错误页面就配置成功

在这里插入图片描述

⏹ 效果

在这里插入图片描述

1.2 在/templates/error/下配置错误页面

❗注意,此时/static/error/下面还存在error页面

在这里插入图片描述

⏹ 效果

👉可以看到/templates/error/下配置的错误页面覆盖了/static/error/下的错误页面

在这里插入图片描述


二. 实现ErrorController接口

  • ErrorController接口的优先级 > 静态资源文件的方式
  • ❗❗❗ErrorController接口的实现类和BasicErrorController的继承类不能同时存在,否则会报Bean命名冲突。
import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.util.ObjectUtils;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.RequestDispatcher;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;

@Controller
public class CustomErrorController implements ErrorController {

    private final static String ERROR_PATH = "/error";

    // 状态码和对应页面的map映射
    private final static Map<Integer, String> codePageMap = new HashMap<>(){
        {
            put(HttpStatus.NOT_FOUND.value(), "/common/404_page");
            put(HttpStatus.INTERNAL_SERVER_ERROR.value(), "/common/500_page");
        }
    };

    @RequestMapping(value = ERROR_PATH, produces = MediaType.TEXT_HTML_VALUE)
    public String errorView(HttpServletRequest request) {

        // 从request对象中获取状态码
        Object status = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
        request.setAttribute("statusCode", "错误状态码:" + status);

        if (ObjectUtils.isEmpty(status)) {
            return this.getDefaultView();
        }

        // 若状态码包含在map映射中,就返回到指定的错误页面
        Integer statusCode = Integer.valueOf(status.toString());
        if (codePageMap.containsKey(statusCode)) {
            return codePageMap.get(statusCode);
        }

        return this.getDefaultView();
    }

    // 默认的页面
    private String getDefaultView() {
        return codePageMap.get(HttpStatus.INTERNAL_SERVER_ERROR.value());
    }
}

在这里插入图片描述

⏹ 效果

❗可以看到,断点经过我们自定义的CustomErrorController类之后,跳转到了
templates/common/中,我们自定义的404_page.html页面中。

由此可以证明: ErrorController接口的实现类的优先顺 > /templates/error/

在这里插入图片描述


三. 实现ErrorPageRegistrar接口

  • ErrorPageRegistrar接口的优先级 > ErrorController接口
import org.springframework.boot.web.server.ErrorPage;
import org.springframework.boot.web.server.ErrorPageRegistrar;
import org.springframework.boot.web.server.ErrorPageRegistry;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;

@Component
public class ErrorPageConfig implements ErrorPageRegistrar {

    @Override
    public void registerErrorPages(ErrorPageRegistry registry) {

        ErrorPage[] errorPages = {
                // 若指定了静态的html页面,则该页面需要存储在 static/ 路径下
                new ErrorPage(HttpStatus.NOT_FOUND, "/error/404.html"),
                // 如果需要指定 templates/ 路径下的页面,则需要配合get请求的方式,通过ModelAndView进行跳转
                new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/common/500error"),
        };

        registry.addErrorPages(errorPages);
    }
}
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller
@RequestMapping("/common")
public class ExceptionPageController1 {

    @GetMapping("/500error")
    public ModelAndView init500Page() {

        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("common/500_page");
        return modelAndView;
    }
}

⏹ 效果

在这里插入图片描述


四. @ControllerAdvice + @ExceptionHandler注解

  • 工作中大多使用这种方式
  • Exception异常可能由普通的get请求触发,大多数情况下会由Ajax请求的get和post方法触发

4.1 前台

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div>
    <a th:href="@{/test22/init}">点击跳转页面</a>
    <hr>
    <button id="sendRequest">发送jQuery-Ajax请求</button>
</div>
<script th:src="@{/js/public/jquery-3.6.0.min.js}"></script>
<script th:inline="javascript">
    $("#sendRequest").click(function () {

        $.ajax({
            url: `/test22/test`,
            type: 'POST',
            data: JSON.stringify(null),
            // 向服务器发送的数据类型
            contentType: 'application/json;charset=utf-8',
            success: function (data, status, xhr) {
                console.log(data);
            },
            error(xhr, textStatus, errorMessage) {
                // 若状态码为500,则跳转到系统异常页面
                if (xhr.status == 500) {
                    location.href = `${location.origin}/error/500Page`;
                }
            }
        });
    });
</script>
</body>
</html>

4.2 全局异常捕获

import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.RedirectView;
import org.springframework.web.servlet.view.json.MappingJackson2JsonView;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@ControllerAdvice
public final class GlobalExceptionHandler {

    @Resource
    private HttpServletRequest request;

    @Resource
    private HttpServletResponse response;

    @ExceptionHandler(Exception.class)
    public ModelAndView handleException(Exception ex) {

        /*
        * 如果是Ajax引发的Exception异常的话,就将错误的信息返回给前台
        * 然后在前台的error回调方法中访问后台的error接口,从而跳转到systemError页面
        * */
        String requestedWithHeader = request.getHeader("X-Requested-With");
        if ("XMLHttpRequest".equals(requestedWithHeader)) {

            // HttpServletResponse对象是java中的对象
            // response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);

            // HttpStatus对象是Spring中的对象,指定错误的状态码,保证jquery的ajax走error方法
            response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());

            MappingJackson2JsonView jsonView = new MappingJackson2JsonView();
            jsonView.setExtractValueFromSingleKeyModel(true);
            jsonView.setModelKey("jsonKey");

            ModelAndView mv = new ModelAndView(jsonView);
            mv.addObject("jsonKey", Map.of("exception", ex.getMessage()));
            return mv;
        }

        /*
        * 重定向到指定的Controller,然后在Controller中,跳转到systemError页面
        * */
        String redirectPath = request.getContextPath() + "/error/500error";
        RedirectView redirectView = new RedirectView(redirectPath);
        return new ModelAndView(redirectView, Map.of("exception", ex.getMessage()));
    }
}

4.3 Ajax请求异常效果

在这里插入图片描述

4.4 普通请求异常效果

在这里插入图片描述

五. @ControllerAdvice + @ExceptionHandler注解与其他方式共同使用

  • ModelAndView一定不能设置视图的名称
@ControllerAdvice
public final class GlobalExceptionHandler {
	
	// 省略注入......

    @ExceptionHandler(Exception.class)
    public ModelAndView handleException(Exception ex) {

        String requestedWithHeader = request.getHeader("X-Requested-With");
        if ("XMLHttpRequest".equals(requestedWithHeader)) {
			// 省略Ajax处理的情况......
        }
		
		// 仅返回ModelAndView对象,不在其中指定View视图的名称
        ModelAndView modelView = new ModelAndView();
        // 👉👉👉此处一定要注释掉,或者设置ViewName为空字符串
        // modelView.setViewName("");
        modelView.addObject("message", ex.getMessage());
        return modelView;
    }
}

在这里插入图片描述

5.1 与ErrorPageRegistrar接口的实现类配合使用

在这里插入图片描述

5.2 与ErrorController接口的实现类配合使用

  • 由于ErrorPageRegistrar接口的优先级 > ErrorController接口的优先级,因此ErrorController接口使用的时候,ErrorPageRegistrar接口是不能被实现的
  • 由于ErrorController接口和BasicErrorController类的Bean冲突,因此二者不能同时存在
    在这里插入图片描述

5.3 与BasicErrorController类来配合使用

  • 继承BasicErrorController类的时候,ErrorPageRegistrar接口和ErrorController接口都不能被实现,否则BasicErrorController的继承类失效,因为接口的优先级高。
import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController;
import org.springframework.boot.web.servlet.error.ErrorAttributes;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;

@Controller
public class AppErrorController extends BasicErrorController {

    public AppErrorController(ErrorAttributes errorAttributes) {
        super(errorAttributes, new ErrorProperties());
    }

    @RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
    @Override
    public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {

        // 获取并设置状态码
        HttpStatus status = getStatus(request);
        response.setStatus(status.value());

        Map<String, Object> model = getErrorAttributes(request, this.getErrorAttributeOptions(request, MediaType.TEXT_HTML));
        ModelAndView modelAndView = resolveErrorView(request, response, status, model);

        // 设置自定义错误页面的的路径
        return (modelAndView == null ? new ModelAndView("error/500", model) : modelAndView);
    }

}

在这里插入图片描述

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值