05.Spring Boot 之错误处理机制

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

代码已经上传至 https://github.com/masteryourself-tutorial/tutorial-spring ,详见 tutorial-spring-boot-core/tutorial-spring-boot-errorhandler 工程

1.1 浏览器请求错误

浏览器错误页

浏览器发起的请求中,请求头是 text/html

浏览器请求头

1.2 postman 请求错误

Postman 错误页

postman 发起的请求中,请求头是 */*

Postman 请求头

1.3 错误处理原理

由于开启了自动装配,所以容器中导入了 ErrorMvcAutoConfiguration 组件,这个组件又向容器中添加了如下几个组件

1.3.1 DefaultErrorAttributes

处理参数信息

@Bean
@ConditionalOnMissingBean(value = ErrorAttributes.class,
		search = SearchStrategy.CURRENT)
public DefaultErrorAttributes errorAttributes() {
	return new DefaultErrorAttributes(
			this.serverProperties.getError().isIncludeException());
}
1.3.2 BasicErrorController

处理默认的 /error 请求,本质上就是一个 controller

@Bean
@ConditionalOnMissingBean(value = ErrorController.class,
		search = SearchStrategy.CURRENT)
public BasicErrorController basicErrorController(ErrorAttributes errorAttributes) {
	return new BasicErrorController(errorAttributes, this.serverProperties.getError(),
			this.errorViewResolvers);
}

处理 ${server.error.path:${error.path:/error} 路径的请求,默认是 /error

@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {

    ...

    // 处理 text/html 请求
	@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, isIncludeStackTrace(request, MediaType.TEXT_HTML)));
		response.setStatus(status.value());
		
		// 查找错误页面,包含页面地址和页面内容
		ModelAndView modelAndView = resolveErrorView(request, response, status, model);
		return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
	}

    // 产生 json 数据,其他客户端来到这个方法处理
	@RequestMapping
	public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
		Map<String, Object> body = getErrorAttributes(request,
				isIncludeStackTrace(request, MediaType.ALL));
		HttpStatus status = getStatus(request);
		return new ResponseEntity<>(body, status);
	}

    ...

}
1.3.3 ErrorPageCustomizer

系统出现错误以后来到 /error 请求进行处理

@Bean
public ErrorPageCustomizer errorPageCustomizer() {
	return new ErrorPageCustomizer(this.serverProperties, this.dispatcherServletPath);
}
1.3.4 DefaultErrorViewResolverConfiguration

主要是向容器中注入 DefaultErrorViewResolver 组件

@Configuration
static class DefaultErrorViewResolverConfiguration {

	...

	@Bean
	@ConditionalOnBean(DispatcherServlet.class)
	@ConditionalOnMissingBean
	public DefaultErrorViewResolver conventionErrorViewResolver() {
		return new DefaultErrorViewResolver(this.applicationContext,
				this.resourceProperties);
	}

}

DefaultErrorViewResolverConfiguration 这个类有如下两个方法,用来返回 ModelAndView 对象,供 BasicErrorController 调用

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

private ModelAndView resolve(String viewName, Map<String, Object> model) {

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

2. 定制错误页面

2.1 有模板引擎的情况

Spring Boot 错误页面命名为 错误状态码.html,放在模板引擎文件夹里面的 error 文件夹下

也可以使用 4xx5xx 作为错误页面的文件名来匹配这种类型的所有错误,精确优先(优先寻找精确的 状态码.html

页面能够获取的信息有(见 org.springframework.boot.web.servlet.error.DefaultErrorAttributes):

  • timestamp:时间戳

  • status:状态码

  • error:错误提示

  • exception:异常对象

  • message:异常消息

  • errors:JSR303 数据校验的错误都在这里

2.2 没有模板引擎的情况

将会在静态资源文件夹下找,查找原理同上

2.3 以上都没有

使用 Spring Boot 提供默认的错误页面,见 org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration.StaticView#render

3. 定制错误数据

3.1 @ControllerAdvice

没有自适应效果,浏览器请求也会响应 json 数据,不够优雅

@ControllerAdvice
public class MyExceptionHandler {

    @ResponseBody
    @ExceptionHandler(Exception.class)
    public Map<String, Object> handleException(Exception e) {
        Map<String, Object> map = new HashMap<>();
        map.put("code", "999999");
        map.put("message", e.getMessage());
        return map;
    }

}
3.2 转发到 /error

转发到 /error 进行自适应响应效果处理,因为 Spring Boot 提供的 /error 请求是自适应的

@ExceptionHandler(Exception.class)
public String handleException(Exception e, HttpServletRequest request) {
    request.setAttribute("javax.servlet.error.status_code", 500);
    request.setAttribute("author", "masteryourself");
    // 转发到 /error
    return "forward:/error";
}
3.3 定制错误数据

出现错误以后,会来到 /error 请求,会被 BasicErrorController 处理,响应出去可以获取的数据是由 getErrorAttributes() 得到的(是 AbstractErrorController规定的方法),所以我们有两种方法来解决这个问题

  • 完全来编写一个 ErrorController 的实现类(或者是编写 AbstractErrorController 的子类),放在容器中

  • 页面上能用的数据,或者是 json 返回能用的数据都是通过 errorAttributes.getErrorAttributes() 得到,那么就向容器中注入一个 DefaultErrorAttributes 组件

@Component
public class MyErrorAttributes extends DefaultErrorAttributes {

    @Override
    public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
        Map<String, Object> map = super.getErrorAttributes(webRequest, includeStackTrace);
        map.put("author", webRequest.getAttribute("author", RequestAttributes.SCOPE_REQUEST));
        return map;
    }

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值