错误处理-底层组件源码分析
# 420.错误处理-底层组件源码分析
本文讲讲原理,掌握原理后,就能更方便地定制化。
# 异常处理-自动配置原理
之前我们讲了SpringBoot的默认处理,那么这些处理肯定也是配置在源码的;
我们可以在IDEA中打开spring-boot-autoconfigure-2.3.4.RELEASE.jar,
然后打开该目录:org\springframework\boot\autoconfigure\web\servlet\error:
其中ErrorMvcAutoConfiguration
,就是自动配置异常处理规则的;
# BasicErrorController
在ErrorMvcAutoConfiguration
类中,还配置了一个ErrorController
:
@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()));
}
2
3
4
5
6
7
该类的源码如下:
@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {
2
3
可以看到,首先是一个Controller,然后定义了访问路径:先取出我们定义的配置文件中的server.error.path
,如果没有,则取出error.path
;还是没有,则会取值/error
,也就是我们之前说的默认值。
既然是Controller,那么就会响应内容,例如下面第一个方法是响应HTML,第二个方法是响应JSON:
@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);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
如果是响应HTML,则是响应error(第8行):
return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
如果是响应JSON,则直接封装JSON:
Map<String, Object> body = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL));
return new ResponseEntity<>(body, status);
2
# Error-View
ErrorMvcAutoConfiguration
就定义了一个叫做error的组件,其是一个View类型:
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(prefix = "server.error.whitelabel", name = "enabled", matchIfMissing = true)
@Conditional(ErrorTemplateMissingCondition.class)
protected static class WhitelabelErrorViewConfiguration {
private final StaticView defaultErrorView = new StaticView();
@Bean(name = "error")
@ConditionalOnMissingBean(name = "error")
public View defaultErrorView() {
return this.defaultErrorView;
}
// If the user adds @EnableWebMvc then the bean name view resolver from
// WebMvcAutoConfiguration disappears, so add it back in to avoid disappointment.
@Bean
@ConditionalOnMissingBean
public BeanNameViewResolver beanNameViewResolver() {
BeanNameViewResolver resolver = new BeanNameViewResolver();
resolver.setOrder(Ordered.LOWEST_PRECEDENCE - 10);
return resolver;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
可以看到还定义了BeanNameViewResolver
组件,这个就是通过Bean的名字,来寻找视图的一种视图解析器。
而StaticView
,里面的render方法是这样的:
@Override
public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
//......
builder.append("<html><body><h1>Whitelabel Error Page</h1>")
.append("<p>This application has no explicit mapping for /error, so you are seeing this as a fallback.</p>") .append("<div id='created'>")
.append(timestamp).append("</div>")
.append("<div>There was an unexpected error (type=").append(htmlEscape(model.get("error")))
.append(", status=").append(htmlEscape(model.get("status"))).append(").</div>");
//.....
2
3
4
5
6
7
8
9
所以这就是我们之前看到的默认错误页面。
# DefaultErrorViewResolver
还配置了一个id叫做conventionErrorViewResolver
的组件,其类型是DefaultErrorViewResolver
:
@Configuration(proxyBeanMethods = false)
static class DefaultErrorViewResolverConfiguration {
private final ApplicationContext applicationContext;
private final ResourceProperties resourceProperties;
DefaultErrorViewResolverConfiguration(ApplicationContext applicationContext,
ResourceProperties resourceProperties) {
this.applicationContext = applicationContext;
this.resourceProperties = resourceProperties;
}
@Bean
@ConditionalOnBean(DispatcherServlet.class)
@ConditionalOnMissingBean(ErrorViewResolver.class)
DefaultErrorViewResolver conventionErrorViewResolver() {
return new DefaultErrorViewResolver(this.applicationContext, this.resourceProperties);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
该类部分源码如下:
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);
}
2
3
4
5
6
7
8
9
10
可以看到,定义了客户端错误就是4xx,服务端错误是5xx
既然是视图解析器,那么就会解析请求返回内容,例如resolveErrorView方法,就回调用resolve方法,得到modelAndView对象并返回:
@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;
}
2
3
4
5
6
7
8
会传参status,也就是HTTP状态码;
然后resolve方法,就会拼接错误路径 + HTTP状态码,来拼接视图的名字:
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);
}
2
3
4
5
6
7
8
9
所以,如果发生了404错误,那么resolve方法收到的参数viewName就是404,然后会拼接成 /error/404.html页面;
# DefaultErrorAttributes
SpringBoot还自动配置了DefaultErrorAttributes
这个组件;
@Bean
@ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)
public DefaultErrorAttributes errorAttributes() {
return new DefaultErrorAttributes();
}
2
3
4
5
DefaultErrorAttributes
类,主要就是定义了错误信息应该包含什么,例如message,trace等属性,例如这是其getErrorAttributes
方法:
@Override
public Map<String, Object> getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) {
Map<String, Object> errorAttributes = getErrorAttributes(webRequest, options.isIncluded(Include.STACK_TRACE));
if (Boolean.TRUE.equals(this.includeException)) {
options = options.including(Include.EXCEPTION);
}
if (!options.isIncluded(Include.EXCEPTION)) {
errorAttributes.remove("exception");
}
if (!options.isIncluded(Include.STACK_TRACE)) {
errorAttributes.remove("trace");
}
if (!options.isIncluded(Include.MESSAGE) && errorAttributes.get("message") != null) {
errorAttributes.put("message", "");
}
if (!options.isIncluded(Include.BINDING_ERRORS)) {
errorAttributes.remove("errors");
}
return errorAttributes;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
默认会包含很多属性,if
判断里主要是看是否要包含,来决定是否移除。
该类实现了ErrorAttributes
和HandlerExceptionResolver
接口:
public class DefaultErrorAttributes implements ErrorAttributes, HandlerExceptionResolver, Ordered {
这两个接口是什么,我们后续再说
# 最后
我们说了很多自动配置的东西
- 想要修改返回的错误信息,就修改
DefaultErrorAttributes
- 想要修改默认的错误路径(/error),就修改
BasicErrorController
- 想要修改默认的错误页面路径,就修改
DefaultErrorViewResolver
至于他们是怎么串起来的,下节课再说