九、SpringBoot之错误处理机制原理

1、SpringBoot默认的错误处理机制

默认效果:

  • ​ 1.浏览器,返回一个默认的错误页面

浏览器发送请求的请求头:

  • ​ 2.如果是其他客户端,默认响应一个json数据

​  

原理:

可以参照ErrorMvcAutoConfiguration.java;错误处理的自动配置;

ErrorMvcAutoConfiguration给容器中添加了以下组件

  • ​ 1.DefaultErrorAttributes:帮我们在页面共享信息;
ErrorMvcAutoConfiguration.java
    @Bean
    @ConditionalOnMissingBean(
        value = {ErrorAttributes.class},
        search = SearchStrategy.CURRENT
    )
    public DefaultErrorAttributes errorAttributes() {
        return new DefaultErrorAttributes(this.serverProperties.getError().isIncludeException());
    }


@Order(-2147483648)
public class DefaultErrorAttributes implements ErrorAttributes, HandlerExceptionResolver, Ordered {

//页面能获取的信息:
    ​ //timestamp:时间戳
    ​ //status:状态码
    ​ //error:错误提示
    ​ //exception:异常对象
    ​ //message:异常消息
    ​ //errors:JSR303数据校验的错误都在这里
 public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
        Map<String, Object> errorAttributes = new LinkedHashMap();
        errorAttributes.put("timestamp", new Date());
        this.addStatus(errorAttributes, webRequest);
        this.addErrorDetails(errorAttributes, webRequest, includeStackTrace);
        this.addPath(errorAttributes, webRequest);
        return errorAttributes;
    }
  • ​ 2.BasicErrorController:处理默认/error请求
ErrorMvcAutoConfiguration.java    
    @Bean
    @ConditionalOnMissingBean(
        value = {ErrorController.class},
        search = SearchStrategy.CURRENT
    )
    public BasicErrorController basicErrorController(ErrorAttributes errorAttributes) {
        return new BasicErrorController(errorAttributes, this.serverProperties.getError(), this.errorViewResolvers);
    }


//来处理/error请求的
@Controller
@RequestMapping({"${server.error.path:${error.path:/error}}"})
public class BasicErrorController extends AbstractErrorController {
   

 @RequestMapping(
        produces = {"text/html"}//产生html类型的数据;浏览器发送的请求来到这个方法处理
    )
    public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
        //获取请求状态码
        HttpStatus status = this.getStatus(request);
        Map<String, Object> model = Collections.unmodifiableMap(this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.TEXT_HTML)));
        response.setStatus(status.value());
        //去哪个页面作为错误页面;包含页面地址和页面内容
        ModelAndView modelAndView = this.resolveErrorView(request, response, status, model);
        return modelAndView != null ? modelAndView : new ModelAndView("error", model);
    }

//响应页面;去哪个页面是由DefaultErrorViewResolver解析得到的;
 protected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, HttpStatus status, Map<String, Object> model) {
        Iterator var5 = this.errorViewResolvers.iterator();

        ModelAndView modelAndView;
        do {
            if (!var5.hasNext()) {
                return null;
            }

            ErrorViewResolver resolver = (ErrorViewResolver)var5.next();
            所有的ErrorViewResolver得到ModelAndView
            modelAndView = resolver.resolveErrorView(request, status, model);
        } while(modelAndView == null);

        return modelAndView;
    }

    @RequestMapping
    @ResponseBody //产生json数据,其他客户端来到这个方法处理;
    public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
        Map<String, Object> body = this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.ALL));
        HttpStatus status = this.getStatus(request);
        return new ResponseEntity(body, status);
    }
  • ​ 3.ErrorPageCustomizer:
ErrorMvcAutoConfiguration.java    
    @Bean
    public ErrorMvcAutoConfiguration.ErrorPageCustomizer errorPageCustomizer() {
        return new ErrorMvcAutoConfiguration.ErrorPageCustomizer(this.serverProperties, this.dispatcherServletPath);
    } 

    private static class ErrorPageCustomizer implements ErrorPageRegistrar, Ordered {
        private final ServerProperties properties;
        private final DispatcherServletPath dispatcherServletPath;

        protected ErrorPageCustomizer(ServerProperties properties, DispatcherServletPath dispatcherServletPath) {
            this.properties = properties;
            this.dispatcherServletPath = dispatcherServletPath;
        }
        //注册错误页面的响应规则
        public void registerErrorPages(ErrorPageRegistry errorPageRegistry) {
            //从配置文件中去的错误页面的路径
            ErrorPage errorPage = new ErrorPage(this.dispatcherServletPath.getRelativePath(this.properties.getError().getPath()));
            errorPageRegistry.addErrorPages(new ErrorPage[]{errorPage});
        }

        public int getOrder() {
            return 0;
        }
    }   

    @Value("${error.path:/error}")
    private String path = "/error";  系统出现错误以后来到error请求进行处理;(web.xml注册的错误页面规则)
  • ​ 4.DefaultErrorViewResolver:
ErrorMvcAutoConfiguration.java        
        @Bean
        @ConditionalOnBean({DispatcherServlet.class})
        @ConditionalOnMissingBean
        public DefaultErrorViewResolver conventionErrorViewResolver() {
            return new DefaultErrorViewResolver(this.applicationContext, this.resourceProperties);
        }    


public class DefaultErrorViewResolver implements ErrorViewResolver, Ordered {

   public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
        ModelAndView modelAndView = this.resolve(String.valueOf(status), model);
        if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
            modelAndView = this.resolve((String)SERIES_VIEWS.get(status.series()), model);
        }

        return modelAndView;
    }

    private ModelAndView resolve(String viewName, Map<String, Object> model) {
        //默认SpringBoot可以去找到一个页面,例如:error/404
        String errorViewName = "error/" + viewName;
        //模板引擎可以解析这个页面地址就用模板引擎解析
        TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName, this.applicationContext);
        //模板引擎可用的情况下返回到errorViewName指定的视图地址
        //模板引擎不可用,就在静态资源文件夹下找errorViewName对应的页面   error/404.html
        return provider != null ? new ModelAndView(errorViewName, model) : this.resolveResource(errorViewName, model);
    }

一但系统出现4xx或者5xx之类的错误;ErrorPageCustomizer就会生效(定制错误的响应规则);就会来到/error请求;就会被BasicErrorController处理;响应页面;去哪个页面是由DefaultErrorViewResolver解析得到的;

2、如何定制错误响应:

1.如何定制错误的页面;

  • 有模板引擎的情况下;error/状态码.html; (将错误页面命名为错误状态码.html 放在模板引擎文件夹里面的error文件夹下),发生此状态码的错误就会来到对应的页面;

​ 我们可以使用4xx和5xx作为错误页面的文件名来匹配这种类型的所有错误,精确优先(优先寻找精确的状态码.html);

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
	<head>
		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
		<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
		<meta name="description" content="">
		<meta name="author" content="">
		<title>Dashboard Template for Bootstrap</title>
		<link th:href="@{/webjars/bootstrap/4.1.3/css/bootstrap.css}" rel="stylesheet">
		<link th:href="@{/asserts/css/dashboard.css}" rel="stylesheet">
		<style type="text/css">
			@-webkit-keyframes chartjs-render-animation {
				from {
					opacity: 0.99
				}
				to {
					opacity: 1
				}
			}
			
			@keyframes chartjs-render-animation {
				from {
					opacity: 0.99
				}
				to {
					opacity: 1
				}
			}
			
			.chartjs-render-monitor {
				-webkit-animation: chartjs-render-animation 0.001s;
				animation: chartjs-render-animation 0.001s;
			}
		</style>
	</head>

	<body>
		<!--引入抽取的topbar-->
		<!--模板名:会使用thymeleaf的前后缀配置规则进行解析-->
		<div th:replace="commons/bar::topbar"></div>
		<div class="container-fluid">
			<div class="row">
				<!--引入侧边栏-->
				<div th:replace="commons/bar::#sidebar(activeUri='emps')"></div>
				<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
					<h1>status:[[${status}]]</h1>
					<h2>timestamp:[[${timestamp}]]</h2>
				</main>
			</div>
		</div>
		<script type="text/javascript" src="asserts/js/jquery-3.2.1.slim.min.js" th:src="@{/webjars/jquery/3.3.1/jquery.js}"></script>
		<script type="text/javascript" src="asserts/js/popper.min.js" th:src="@{/webjars/popper.js/1.11.1/dist/popper.js}"></script>
		<script type="text/javascript" src="asserts/js/bootstrap.min.js" th:src="@{/webjars/bootstrap/4.0.0/js/bootstrap.js}"></script>
		<script type="text/javascript" src="asserts/js/feather.min.js" th:src="@{/asserts/js/feather.min.js}"></script>
		<script>
			feather.replace()
		</script>
	</body>
</html>
  • 没有模板引擎(模板引擎找不到这个错误页面),静态资源文件夹下找;
  • 以上都没有错误页面,就是默认来到SpringBoot默认的错误提示页面;

2.如何定制错误的json数据;

  • 自定义异常处理&返回定制json数据;
//自定义异常
public class UserNotExistException extends RuntimeException{
    public UserNotExistException(){
        super("用户不存在");
    }
}
    @ResponseBody
    @RequestMapping("/hello")
    public String hello(@RequestParam("user") String user){
        if(user.equals("aaa")){
            throw new UserNotExistException();

        }
        return "Hello World";
    }
//异常处理器来处理异常
@ControllerAdvice
public class MyExceptionHandler {
    //缺点:没有自适应效果,浏览器和客户端返回的都是json数据
    @ResponseBody
    @ExceptionHandler(UserNotExistException.class)
    public Map<String,Object> handleException(Exception e){
        Map<String,Object> map = new HashMap<>();
        map.put("code","user.notexist");
        map.put("message",e.getMessage());
        return map;
    }
}
  • 转发到/error进行自适应响应效果处理
@ExceptionHandler(UserNotExistException.class)
    public String handleException(Exception e, HttpServletRequest request){
        Map<String,Object> map = new HashMap<>();
        //传入我们自己的错误状态码  4xx 5xx,否则就不会进入定制错误页面的解析流程
        //Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");
        request.setAttribute("javax.servlet.error.status_code",500);
        map.put("code","user.notexist");
        map.put("message","用户出错啦");
        request.setAttribute("ext",map);
        //转发到/error
        return "forward:/error";
    }

将我们的定制数据携带出去;

  1. ​编写一个ErrorController的实现类或者是编写AbstractErrorController的子类,放在容器中;(出现错误以后,会来到/error请求,会被BasicErrorController处理,响应出去可以获取的数据是由getErrorAttributes得到的(是AbstractErrorController(ErrorController)规定的默认方法));
  2. ​自定义ErrorAttributes;页面上能用的数据,或者是json返回能用的数据都是通过errorAttributes.getErrorAttributes得到; 容器中DefaultErrorAttributes.getErrorAttributes();默认进行数据处理的;
//给容器中加入我们自己定义的ErrorAttributes
@Component
public class MyErrorAttributes extends DefaultErrorAttributes {
    //获取错误属性返回值的map就是页面和json能获取的所有字段
    @Override
    public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
        Map<String, Object> map = super.getErrorAttributes(webRequest, includeStackTrace);
        map.put("company","atguigu");
        //我们异常处理器携带的数据
        //0是request  1是session
        Map<String,Object> ext = (Map<String, Object>) webRequest.getAttribute("ext",0);
        map.put("ext",ext);
        return map;
    }
}

5xx.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
	<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
	<meta name="description" content="">
	<meta name="author" content="">
	<title>Dashboard Template for Bootstrap</title>
	<link th:href="@{/webjars/bootstrap/4.1.3/css/bootstrap.css}" rel="stylesheet">
	<link th:href="@{/asserts/css/dashboard.css}" rel="stylesheet">
	<style type="text/css">
		@-webkit-keyframes chartjs-render-animation {
			from {
				opacity: 0.99
			}
			to {
				opacity: 1
			}
		}

		@keyframes chartjs-render-animation {
			from {
				opacity: 0.99
			}
			to {
				opacity: 1
			}
		}

		.chartjs-render-monitor {
			-webkit-animation: chartjs-render-animation 0.001s;
			animation: chartjs-render-animation 0.001s;
		}
	</style>
</head>

<body>
<!--引入抽取的topbar-->
<!--模板名:会使用thymeleaf的前后缀配置规则进行解析-->
<div th:replace="commons/bar::topbar"></div>
<div class="container-fluid">
	<div class="row">
		<!--引入侧边栏-->
		<div th:replace="commons/bar::#sidebar(activeUri='emps')"></div>
		<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
			<h1>status:[[${status}]]</h1>
			<h2>timestamp:[[${timestamp}]]</h2>
			<h2>exception:[[${exception}]]</h2>
			<h2>message:[[${message}]]</h2>
			<h2>ext:[[${ext.code}]]</h2>
			<h2>ext:[[${ext.message}]]</h2>
		</main>
	</div>
</div>
<script type="text/javascript" src="asserts/js/jquery-3.2.1.slim.min.js" th:src="@{/webjars/jquery/3.3.1/jquery.js}"></script>
<script type="text/javascript" src="asserts/js/popper.min.js" th:src="@{/webjars/popper.js/1.11.1/dist/popper.js}"></script>
<script type="text/javascript" src="asserts/js/bootstrap.min.js" th:src="@{/webjars/bootstrap/4.0.0/js/bootstrap.js}"></script>
<script type="text/javascript" src="asserts/js/feather.min.js" th:src="@{/asserts/js/feather.min.js}"></script>
<script>
    feather.replace()
</script>
</body>
</html>

最终的效果:响应是自适应的,可以通过定制ErrorAttributes改变需要返回的内容,

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值