• SpringBoot自定义错误页


    一、概述

    在处理异常时,开发者可以根据实际情况返回不同的页面,这种异常处理方式一般用来处理应用级别的异常。但是,有一些容器级别的错误就处理不了,例如Filter中抛出异常,使用@ControllerAdvice定义的全局异常处理机制就无法处理。因此,Spring Boot中对于异常的处理还有另外的方式。

    Spring Boot中默认的处理行为,如果DispatcherServlet执行发生异常,内部如果没有处理掉(比如没有被自定义的全局异常处理器处理掉),而交给tomcat处理时,最终会转发到/error请求。Spring Boot在启动tomcat容器时,注册了一个状态码为0,路径为/error的错误页面,而状态码为0时可以匹配所有的状态码,因此最终的错误处理又回到了Spring Boot,更准确的说回到了DispatherServlet,而处理/error的控制器为BasicErrorController。该控制器根据请求的Accept请求头响应两种格式,即htmlajax数据。

    1、错误页面示例

    在Spring Boot 中,默认情况下,如果用户在发起请求时发生了404错误,Spring Boot 会有一个默认的页面展示给用户,如图所示。

    在这里插入图片描述

    如果发起请求时发生了500错误,Spring Boot也会有一个默认的页面展示给用户,如图所示。

    在这里插入图片描述

    事实上, Spring Boot在返回错误信息时不一定返回HTML页面,而是根据实际情况返回HTML页面或者一段JSON(若开发者发起 Ajax 请求,则错误信息是一段JSON)。对于开发者而言,这一段HTML或者JSON都能够自由定制。

    2、原理介绍

    (1)核心方法

    Spring Boot 中的错误默认是由BasicErrorController类来处理的,该类中的核心方法主要有两个:

    	@RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
    	public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
    		HttpStatus status = getStatus(request);
    		Map<String, Object> model = Collections
    				.unmodifiableMap(getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.TEXT_HTML)));
    		response.setStatus(status.value());
    		ModelAndView modelAndView = resolveErrorView(request, response, status, model);
    		return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
    	}
    
    	@RequestMapping
    	public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
    		HttpStatus status = getStatus(request);
    		if (status == HttpStatus.NO_CONTENT) {
    			return new ResponseEntity<>(status);
    		}
    		Map<String, Object> body = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL));
    		return new ResponseEntity<>(body, status);
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    其中, errorHtml方法用来返回错误HTML页面, error用来返回错误JSON,具体返回的是HTML还是JSON,则要看请求头的Accept参数。返回JSON的逻辑很简单,不必过多介绍,返回HTML的逻辑稍微有些复杂,在 errorHtml方法中,通过调用resolveErrorView方法来获取一个错误视图的ModelAndView。而resolveErrorView方法的调用最终会来到DefaultErrorViewResolver类中。

    (2)视图解析器

    DefaultErrorViewResolver类是Spring Boot中默认的错误信息视图解析器,部分源码如下:

    public class DefaultErrorViewResolver implements ErrorViewResolver, Ordered {
    
    	private static final Map<Series, String> SERIES_VIEWS;
    
    	static {
    		Map<Series, String> views = new EnumMap<>(Series.class);
    		views.put(Series.CLIENT_ERROR, "4xx");
    		views.put(Series.SERVER_ERROR, "5xx");
    		SERIES_VIEWS = Collections.unmodifiableMap(views);
    	}
    ...
    ...
    	private ModelAndView resolve(String viewName, Map<String, Object> model) {
    		String errorViewName = "error/" + viewName;
    		TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName,
    				this.applicationContext);
    		if (provider != null) {
    			return new ModelAndView(errorViewName, model);
    		}
    		return resolveResource(errorViewName, model);
    	}
    ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    从这一段源码中可以看到,Spring Boot默认是在error目录下查找4xx、5xx的文件作为错误视图,当找不到时会回到 errorHtml方法中,然后使用error 作为默认的错误页面视图名,如果名为error的视图也找不到,用户就会看到本节一开始展示的两个错误提示页面。整个错误处理流程大致就是这样的。

    二、案例(SpringBoot默认报错)

    通过这个案例先来看下SpringBoot的默认报错信息处理方式。

    1、错误代码编写

    编写一段会发生500错误的代码。

    @RestController
    public class TestErrorController {
        @RequestMapping("/test/error")
        public Object testError() {
            return 1/0;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    2、查看报错

    (1)浏览器访问

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Xi890Rfg-1667888625566)(SpringBoot%E8%87%AA%E5%AE%9A%E4%B9%89%E9%94%99%E8%AF%AF%E9%A1%B5.assets/image-20221104124403305.png)]

    (2)Postman访问

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TnXyodHs-1667888625567)(SpringBoot%E8%87%AA%E5%AE%9A%E4%B9%89%E9%94%99%E8%AF%AF%E9%A1%B5.assets/image-20221104124522167.png)]

    三、案例(报错信息)

    通过这个案例,可以了解SpringBoot报错信息中,可以自己定义显示哪些属性。

    1、修改默认配置

    修改下默认配置来看看结果如何

    server:
      port: 80
      error:
        # 展示异常调用栈
        include-stacktrace: always
        # 展示异常类型信息
        include-exception: true
        # 展示错误信息
        include-message: always
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    2、查看报错

    (1)浏览器访问

    Whitelabel Error Page
    This application has no explicit mapping for /error, so you are seeing this as a fallback.
    
    Fri Nov 04 12:53:50 CST 2022
    There was an unexpected error (type=Internal Server Error, status=500).
    / by zero
    java.lang.ArithmeticException: / by zero
    	at com.scy.payment.controller.TestErrorController.testError(TestErrorController.java:11)
    	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    	at java.lang.reflect.Method.invoke(Method.java:498)
    	at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190)
    	at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138)
    	at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:105)
    	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:878)
    	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:792)
    	at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
    	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040)
    	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943)
    	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
    	at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898)
    	at javax.servlet.http.HttpServlet.service(HttpServlet.java:626)
    	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
    	at javax.servlet.http.HttpServlet.service(HttpServlet.java:733)
    	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227)
    	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
    	at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
    	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
    	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
    	at com.scy.frame.filter.RequestDataFilter.doFilter(RequestDataFilter.java:34)
    	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
    	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
    	at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
    	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
    	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
    	at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
    	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
    	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
    	at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
    	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
    	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
    	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)
    	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)
    	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542)
    	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:143)
    	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
    	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)
    	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:357)
    	at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:374)
    	at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
    	at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:893)
    	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1707)
    	at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
    	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    	at java.lang.Thread.run(Thread.java:748)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61

    (2)Postman访问

    {
        "timestamp": "2022-11-04T04:55:37.998+00:00",
        "status": 500,
        "error": "Internal Server Error",
        "exception": "java.lang.ArithmeticException",
        "trace": "java.lang.ArithmeticException: / by zero\r\n\tat com.scy.payment.controller.TestErrorController.testError(TestErrorController.java:11)\r\n\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\r\n\tat sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)\r\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\r\n\tat java.lang.reflect.Method.invoke(Method.java:498)\r\n\tat org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190)\r\n\tat org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:105)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:878)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:792)\r\n\tat org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)\r\n\tat org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040)\r\n\tat org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943)\r\n\tat org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)\r\n\tat org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898)\r\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:626)\r\n\tat org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)\r\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:733)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)\r\n\tat org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)\r\n\tat com.scy.frame.filter.RequestDataFilter.doFilter(RequestDataFilter.java:34)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)\r\n\tat org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)\r\n\tat org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)\r\n\tat org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)\r\n\tat org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)\r\n\tat org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)\r\n\tat org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542)\r\n\tat org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:143)\r\n\tat org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)\r\n\tat org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)\r\n\tat org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:357)\r\n\tat org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:374)\r\n\tat org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)\r\n\tat org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:893)\r\n\tat org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1707)\r\n\tat org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)\r\n\tat java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)\r\n\tat java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)\r\n\tat org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)\r\n\tat java.lang.Thread.run(Thread.java:748)\r\n",
        "message": "/ by zero",
        "path": "/test/error"
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    四、案例(自定义错误页面)

    1、说明

    (1)寻找视图逻辑

    Ⅰ、响应状态码寻找

    Spring Boot在返回错误页面时,会先根据响应状态码去寻找视图。

    1. 精准匹配:根据响应状态码查找(404、500)
    2. 模糊匹配:如果查不到,则查询4xx或者5xx这个通用配置(根据响应状态码是4开头还是5开头来决定是4xx还是5xx)。

    注意:

    视图名称会拼一个error/,如果error/404error/500error/4xxerror/5xx

    若用户定义了多个错误页面,则响应码.html页面的优先级高于4xx.html5xx.html页面的优先级,即若当前是一个404错误,则优先展示404.html而不是4xx.html

    动态页面的优先级高于静态页面,即若resources/templatesresources/static 下同时定义了4xx.html,则优先展示resources/templates/4xx.html

    这个视图所在的位置取决于项目有没有依赖模板技术,如thymeleaf

    • 如存在thymeleaf模板依赖,则根目录为templates,最终路径为templates/error/500.html
    • 如不存在模板技术依赖,则根目录为静态资源目录publicstatic,最终路径为static/error/500.html
    Ⅱ、寻找默认的视图

    如果经过以上步骤还是没有找到对应视图,则寻找默认的beanName=error的视图。

    也就是我们看到的默认错误页面,该错误页面由ErrorProperties类中的whitelabel属性控制,默认是开启的。该逻辑可在BasicErrorControllererrorHtml方法中找到。

    2、thymeleaf示例

    (1)引入相关依赖

    thymeleaf为例,先引入相关依赖

    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-thymeleafartifactId>
    dependency>
    
    • 1
    • 2
    • 3
    • 4

    (2)自定义页面

    在项目resources/templates/error目录下,新建一个500.html

    500.html文件内容如下:

    DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="utf-8"/>
        <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
        <meta name="viewport" content="width=device-width, initial-scale=1"/>
        <title>自定义错误页面title>
    head>
    <body>
        <div>
            <span>状态码:span>
            <span th:text="${status}">span>
        div>
        <div>
            <span>错误原因:span>
            <span th:text="${message}">span>
        div>
        <div>
            <span>异常类:span>
            <span th:text="${exception}">span>
        div>
        <div>
            <span>请求路径:span>
            <span th:text="${path}">span>
        div>
    body>
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    resources/templates这是thymeleaf模板页面默认存放的位置。

    Spring Boot在模型中给我们放了一些有用的属性,如上面JSON返回数据所示,诸如timestampstatusmessageexceptiontracepath等,当然了有些属性是需要配置才会包含的。

    (3)查看报错

    Ⅰ、浏览器访问

    在浏览器中再次访问将会得到以下结果

    状态码: 500
    错误原因: / by zero
    异常类: java.lang.ArithmeticException
    请求路径: /test/error
    
    • 1
    • 2
    • 3
    • 4
    Ⅱ、Postman访问
    {
        "timestamp": "2022-11-04T05:24:33.106+00:00",
        "status": 500,
        "error": "Internal Server Error",
        "exception": "java.lang.ArithmeticException",
        "trace": "java.lang.ArithmeticException: / by zero\r\n\tat com.scy.payment.controller.TestErrorController.testError(TestErrorController.java:11)\r\n\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\r\n\tat sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)\r\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\r\n\tat java.lang.reflect.Method.invoke(Method.java:498)\r\n\tat org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190)\r\n\tat org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:105)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:878)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:792)\r\n\tat org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)\r\n\tat org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040)\r\n\tat org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943)\r\n\tat org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)\r\n\tat org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898)\r\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:626)\r\n\tat org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)\r\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:733)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)\r\n\tat org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)\r\n\tat com.scy.frame.filter.RequestDataFilter.doFilter(RequestDataFilter.java:34)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)\r\n\tat org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)\r\n\tat org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)\r\n\tat org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)\r\n\tat org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)\r\n\tat org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)\r\n\tat org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542)\r\n\tat org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:143)\r\n\tat org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)\r\n\tat org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)\r\n\tat org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:357)\r\n\tat org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:374)\r\n\tat org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)\r\n\tat org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:893)\r\n\tat org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1707)\r\n\tat org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)\r\n\tat java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)\r\n\tat java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)\r\n\tat org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)\r\n\tat java.lang.Thread.run(Thread.java:748)\r\n",
        "message": "/ by zero",
        "path": "/test/error"
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    五、高级案例(自定义Error数据)

    1、说明

    • 自定义 Error数据,就是对返回的数据进行自定义。
    • Spring Boot 返回的Error信息一共有5条,分别是timestamp、status、error、message、path
    • BasicErrorControllererrorHtml 方法和 error方法中,都是通过getErrorAttributes方法获取 Error信息的。该方法最终会调用到DefaultErrorAttributes类的 getErrorAttributes方法,而DefaultErrorAttributes类是在 ErrorMvcAutoConfiguration中默认提供的。

    ErrorMvcAutoConfiguration类的errorAttributes方法源码如下:

    @Bean
    @ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)
    public DefaultErrorAttributes errorAttributes() {
    	return new DefaultErrorAttributes();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    从这段源码中可以看出,当系统没有提供 ErrorAttributes时才会采用DefaultErrorAttributes

    因此自定义错误提示时,只需要自己提供一个 ErrorAttributes即可,而 DefaultErrorAttributesErrorAttributes的子类,因此你写的类只需要继承 DefaultErrorAttributes即可。

    2、代码实现

    (1)java代码

    @Component
    public class MyErrorAttributes extends DefaultErrorAttributes {
        @Override
        public Map<String, Object> getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) {
            Map<String, Object> errorAttributes=super.getErrorAttributes(webRequest,options);
            errorAttributes.put("custommsg","出错了");
            errorAttributes.remove("error");
            return errorAttributes;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    代码解释:

    • 自定义 MyErrorAttribute 继承自DefaultErrorAttributes,重写DefaultErrorAttributes 中的getErrorAttributes方法。MyErrorAttribute类添加@Component注解,该类将被注册到Spring容器中。
    • 第5、6行通过super.getErrorAttributes获取Spring Boot 默认提供的错误信息,然后在此基础上添加Error信息或者移除Error信息。

    (2)html代码

    500.html文件中加入下面的代码。

    error:
    custommsg:
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    3、浏览器访问

    状态码: 500
    错误原因: / by zero
    异常类: java.lang.ArithmeticException
    请求路径: /test/error
    error:
    custommsg: 出错了
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    六、高级案例(自定义Error视图)

    1、说明

    Error视图是展示给用户的页面,在 BasicErrorControllererrorHtml方法中调用resolveErrorView方法获取一个 ModelAndView实例。 resolveErrorView方法是由ErrorViewResolver提供的,通过 ErrorMvcAutoConfiguration类的源码可以看到 Spring Boot默认采用的ErrorViewResolverDefaultErrorViewResolverErrorMvcAutoConfiguration部分源码如下:

    @Bean
    @ConditionalOnBean(DispatcherServlet.class)
    @ConditionalOnMissingBean(ErrorViewResolver.class)
    DefaultErrorViewResolver conventionErrorViewResolver() {
    	return new DefaultErrorViewResolver(this.applicationContext, this.resources);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    从这一段源码可以看到,如果用户没有定义 ErrorViewResolver,那么默认使用的ErrorViewResolverDefaultErrorViewResolver,正是在DefaultErrorViewResolver中配置了默认去error目录下寻找4xx.html5xx.html。因此,开发者想要自定义 Error 视图,只需要提供自己的ErrorViewResolver即可

    2、代码实现

    (1)java代码

    @Component
    public class MyErrorViewResolver implements ErrorViewResolver {
        @Override
        public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
            ModelAndView modelAndView=new ModelAndView("errorpage");
            modelAndView.addObject("custommsg","出错了!");
            modelAndView.addAllObjects(model);
            return modelAndView;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    代码解释:

    • 自定义MyErrorViewResolver实现ErrorViewResolver接口并实现接口中的resolveErrorView方法,使用@Component注解将该类注册到Spring容器中。
    • resolveErrorView方法中,最后一个 Map参数就是Spring Boot提供的默认的5条Error信息(可以按照前面自定义Error数据的步骤对这5条消息进行修改)。在resolveErrorView方法中,返回一个ModelAndView,在 ModelAndView中设置Error视图和Error数据。
    • 理论上,开发者也可以通过实现 ErrorViewResolver接口来实现 Error 数据的自定义,但是如果只是单纯地想自定义Error 数据,还是建议继承DefaultErrorAttributes

    (2)html代码

    接下来在resources/templates目录下提供errorPage.html视图。(直接复制上面的500.html即可)

    DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="utf-8"/>
        <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
        <meta name="viewport" content="width=device-width, initial-scale=1"/>
        <title>自定义错误页面title>
    head>
    <body>
    <div>
        <span>状态码:span>
        <span th:text="${status}">span>
    div>
    <div>
        <span>错误原因:span>
        <span th:text="${message}">span>
    div>
    <div>
        <span>异常类:span>
        <span th:text="${exception}">span>
    div>
    <div>
        <span>请求路径:span>
        <span th:text="${path}">span>
    div>
    <div>
        <span>error:span>
        <span th:text="${error}">span>
    div>
    <div>
        <span>custommsg:span>
        <span th:text="${custommsg}">span>
    div>
    body>
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    在errorPage.html中,除了展示Spring Boot提供的5条Error信息外,也展示了开发者自定义的Error信息。此时,无论请求发生4xx 的错误还是发生5xx的错误,都会来到errorPage.html页面。

    3、浏览器访问

    状态码: 500
    错误原因: / by zero
    异常类: java.lang.ArithmeticException
    请求路径: /test/error
    error: Internal Server Error
    custommsg: 出错了!
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    七、高级案例(完全自定义)

    1、说明

    前面提到的两种自定义方式都是对BasicErrorController类中的某个环节进行修补。查看Error自动化配置类ErrorMvcAutoConfiguration,读者可以发现 BasicErrorController本身只是一个默认的配置,相关源码如下:

    public class ErrorMvcAutoConfiguration {
      ...
    
    	@Bean
    	@ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT)
    	public BasicErrorController basicErrorController(ErrorAttributes errorAttributes,
    			ObjectProvider<ErrorViewResolver> errorViewResolvers) {
    		return new BasicErrorController(errorAttributes, this.serverProperties.getError(),
    				errorViewResolvers.orderedStream().collect(Collectors.toList()));
    	}
        
    	...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    从这段源码中可以看到,若开发者没有提供自己的 ErrorController,则 Spring Boot 提供BasicErrorController作为默认的ErrorController。因此,如果开发者需要更加灵活地对Error视图和数据进行处理,那么只需要提供自己的ErrorController即可。提供自己的 ErrorController有两种方式:一种是实现 ErrorController接口,另一种是直接继承BasicErrorController。由于ErrorController接口只提供一个待实现的方法,而BasicErrorController已经实现了很多功能,因此这里选择第二种方式,即通过继承BasicErrorController来实现自己的ErrorController

    2、代码实现

    (1)java代码

    具体定义如下:

    @Controller
    public class MyBasicErrorController extends BasicErrorController {
    
        @Autowired
        public MyBasicErrorController(ErrorAttributes errorAttributes,
                                 ServerProperties serverProperties,
                                 List<ErrorViewResolver> errorViewResolvers) {
            super(errorAttributes, serverProperties.getError(), errorViewResolvers);
        }
    
        @Override
        public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
            HttpStatus status = getStatus(request);
            // 获取 Spring Boot 默认提供的错误信息,然后添加一个自定义的错误信息
            Map<String, Object> model = Collections
                    .unmodifiableMap(getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.TEXT_HTML)));
            ModelAndView modelAndView = new ModelAndView("myerrorPage", model, status);
            return modelAndView;
        }
    
        @Override
        public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
            HttpStatus status = getStatus(request);
            // 获取 Spring Boot 默认提供的错误信息,然后添加一个自定义的错误信息
            Map<String, Object> body = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL));
            return new ResponseEntity<>(body, status);
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    代码解释:

    • 自定义 MyErrorController 继承自BasicErrorController并添加@Controller注解,将MyErrorController注册到Spring MVC容器中。
    • 由于BasicErrorController没有无参构造方法,因此在创建BasicErrorController实例时需要传递参数,在MyErrorController的构造方法上添加@Autowired注解注入所需参数。
    • 参考BasicErrorController中的实现,重写errorHtmlerror方法,对 Error的视图和数据进行充分的自定义。

    (2)html代码

    最后,在resources/templates目录下提供myErrorPage.html页面作为视图页面。(直接复制上面的500.html即可)

    DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="utf-8"/>
        <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
        <meta name="viewport" content="width=device-width, initial-scale=1"/>
        <title>自定义错误页面title>
    head>
    <body>
    <div>
        <span>状态码:span>
        <span th:text="${status}">span>
    div>
    <div>
        <span>错误原因:span>
        <span th:text="${message}">span>
    div>
    <div>
        <span>异常类:span>
        <span th:text="${exception}">span>
    div>
    <div>
        <span>请求路径:span>
        <span th:text="${path}">span>
    div>
    <div>
        <span>error:span>
        <span th:text="${error}">span>
    div>
    <div>
        <span>custommsg:span>
        <span th:text="${custommsg}">span>
    div>
    body>
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    2、浏览器访问

    状态码: 500
    错误原因: / by zero
    异常类: java.lang.ArithmeticException
    请求路径: /test/error
    error: Internal Server Error
    custommsg:
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    八、参考

    SpringBoot中SpringMVC异常处理机制
    https://www.cnblogs.com/wt20/p/16717758.html
    Spring Boot 自定义错误页
    https://blog.csdn.net/qq_43581790/article/details/123900905

  • 相关阅读:
    GPT-4V的图片识别和分析能力
    【神经网络】Dropout原理
    总结:Prometheus之PromQL操作符
    如何保证Redis与数据库的数据一致性
    微信小程序合集更更更之实现自定义tabbar凹起效果
    redis 外部访问 --chatGPT
    【FOC控制】英飞凌TC264无刷驱动方案simplefoc移植(5)-磁编码器移植AS5600 软件IIC
    读书笔记--从一到无穷大的关键金句和阅读感悟
    Java设计模式之简单工厂模式(不属于23种设计模式)
    功能比较:Redisson vs Jedis
  • 原文地址:https://blog.csdn.net/qq_25775675/article/details/127750103