Spring Boot提供了兩種類型的錯誤處理機制,一種是依賴于內(nèi)嵌容器的ErrorPage機制;另外一種是基于Spring Mvc的異常處理機制;
錯誤配置類
Spring Boot錯誤處理相關的自動配置主要是通過ErrorMvcAutoConfiguration實現(xiàn)的:
@Bean
@ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)
public DefaultErrorAttributes errorAttributes() {
return new DefaultErrorAttributes();
}
@Bean
@ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT)
public BasicErrorController basicErrorController(ErrorAttributes errorAttributes) {
return new BasicErrorController(errorAttributes, this.serverProperties.getError(),
this.errorViewResolvers);
}
@Bean
public ErrorPageCustomizer errorPageCustomizer() {
return new ErrorPageCustomizer(this.serverProperties);
}
@Bean
@ConditionalOnBean(DispatcherServlet.class)
@ConditionalOnMissingBean
public DefaultErrorViewResolver conventionErrorViewResolver() {
return new DefaultErrorViewResolver(this.applicationContext,
this.resourceProperties);
}
首先來看看DefaultErrorAttributes:
@Order(Ordered.HIGHEST_PRECEDENCE)
public class DefaultErrorAttributes
implements ErrorAttributes, HandlerExceptionResolver, Ordered{
@Override
public ModelAndView resolveException(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex) {
storeErrorAttributes(request, ex);
return null;
}
}
- DefaultErrorAttributes實現(xiàn)了HandlerExceptionResolver接口,且為最高優(yōu)先級,意味著當Spring MVC發(fā)生錯誤時,首先會由它進行處理;
- resolveException方法將異常對象保存到request中;
- 關于HandlerExceptionResolver是如何生效的,可以查看DispatcherServlet的相關代碼;
ErrorPage機制介紹
接下來看看ErrorMvcAutoConfiguration中定義的ErrorPageCustomizer,這是ErrorPage錯誤處理比較關鍵的地方:
首先看內(nèi)嵌容器的自動配置類EmbeddedServletContainerAutoConfiguration:
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
if (this.beanFactory == null) {
return;
}
registerSyntheticBeanIfMissing(registry,
"embeddedServletContainerCustomizerBeanPostProcessor",
EmbeddedServletContainerCustomizerBeanPostProcessor.class);
registerSyntheticBeanIfMissing(registry,
"errorPageRegistrarBeanPostProcessor",
ErrorPageRegistrarBeanPostProcessor.class);
}
Spring Boot注冊了ErrorPageRegistrarBeanPostProcessor:
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
if (bean instanceof ErrorPageRegistry) {
postProcessBeforeInitialization((ErrorPageRegistry) bean);
}
return bean;
}
private void postProcessBeforeInitialization(ErrorPageRegistry registry) {
for (ErrorPageRegistrar registrar : getRegistrars()) {
registrar.registerErrorPages(registry);
}
}
當創(chuàng)建bean的時候,如果發(fā)現(xiàn)bean實現(xiàn)了ErrorPageRegistry接口,會從Spring容器中查找實現(xiàn)了ErrorPageRegistrar的類,并調(diào)用該類的registerErrorPages方法注冊Error Page;
而在ErrorMvcAutoConfiguration里,提供了ErrorPageRegistrar的實現(xiàn)類ErrorPageCustomizer,默認將錯誤轉發(fā)給/error進行處理;也就是說通過ErrorPageRegistrarBeanPostProcessor會將ErrorPageCustomizer注冊到ErrorPageRegistry;
既然默認情況下將錯誤交給/error進行處理,那我們只要自定義controller類處理/error請求,就可以按需實現(xiàn)自己的錯誤處理;
在ErrorMvcAutoConfiguration類中,提供了BasicErrorController來處理/error的請求,但用戶可以覆蓋實現(xiàn);
接下來看看有哪些類實現(xiàn)了ErrorPageRegistry接口:
public class TomcatEmbeddedServletContainerFactory
extends AbstractEmbeddedServletContainerFactory{}
public abstract class AbstractEmbeddedServletContainerFactory
extends AbstractConfigurableEmbeddedServletContainer
implements EmbeddedServletContainerFactory{}
public abstract class AbstractConfigurableEmbeddedServletContainer
implements ConfigurableEmbeddedServletContainer{}
public interface ConfigurableEmbeddedServletContainer extends ErrorPageRegistry{}
從上面的繼承關系可以看到TomcatEmbeddedServletContainerFactory繞了一大圈,實現(xiàn)了ErrorPageRegistry接口,這意味著創(chuàng)建TomcatEmbeddedServletContainerFactory時,將會注冊ErrorPage;
上面說介紹的,就是利用ErrorPage處理錯誤的相關實現(xiàn);
Spring MVC異常處理
使用Spring Boot框架時,很多時候都是采用Spring MVC處理Http請求;Spring MVC也提供自己異常處理機制:
Spring MVC框架的入口是DispatcherServlet,其中定義了成員變量:
private List<HandlerExceptionResolver> handlerExceptionResolvers;
private void initHandlerExceptionResolvers(ApplicationContext context) {
this.handlerExceptionResolvers = null;
//默認為true
if (this.detectAllHandlerExceptionResolvers) {
// Find all HandlerExceptionResolvers in the ApplicationContext, including ancestor contexts.
Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils
.beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerExceptionResolvers = new ArrayList<HandlerExceptionResolver>(matchingBeans.values());
// We keep HandlerExceptionResolvers in sorted order.
AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers);
}
}
else {
try {
HandlerExceptionResolver her =
context.getBean(HANDLER_EXCEPTION_RESOLVER_BEAN_NAME, HandlerExceptionResolver.class);
this.handlerExceptionResolvers = Collections.singletonList(her);
}
catch (NoSuchBeanDefinitionException ex) {
// Ignore, no HandlerExceptionResolver is fine too.
}
}
// Ensure we have at least some HandlerExceptionResolvers, by registering
// default HandlerExceptionResolvers if no other resolvers are found.
if (this.handlerExceptionResolvers == null) {
this.handlerExceptionResolvers = getDefaultStrategies(context, HandlerExceptionResolver.class);
if (logger.isDebugEnabled()) {
logger.debug("No HandlerExceptionResolvers found in servlet '" + getServletName() + "': using default");
}
}
}
那么Spring Boot在什么地方提供了HandlerExceptionResolver的實現(xiàn)呢?具體的實現(xiàn)在WebMvcAutoConfiguration類,這里為了方便介紹,將EnableWebMvcConfiguration和它的父類WebMvcConfigurationSupport代碼放到一起:
@Bean
public HandlerExceptionResolver handlerExceptionResolver() {
List<HandlerExceptionResolver> exceptionResolvers = new ArrayList<HandlerExceptionResolver>();
configureHandlerExceptionResolvers(exceptionResolvers);
if (exceptionResolvers.isEmpty()) {
addDefaultHandlerExceptionResolvers(exceptionResolvers);
}
extendHandlerExceptionResolvers(exceptionResolvers);
HandlerExceptionResolverComposite composite = new HandlerExceptionResolverComposite();
composite.setOrder(0);
composite.setExceptionResolvers(exceptionResolvers);
return composite;
}
@Override
protected void configureHandlerExceptionResolvers(
List<HandlerExceptionResolver> exceptionResolvers) {
super.configureHandlerExceptionResolvers(exceptionResolvers);
if (exceptionResolvers.isEmpty()) {
addDefaultHandlerExceptionResolvers(exceptionResolvers);
}
if (this.mvcProperties.isLogResolvedException()) {
for (HandlerExceptionResolver resolver : exceptionResolvers) {
if (resolver instanceof AbstractHandlerExceptionResolver) {
((AbstractHandlerExceptionResolver) resolver)
.setWarnLogCategory(resolver.getClass().getName());
}
}
}
}
protected final void addDefaultHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
ExceptionHandlerExceptionResolver exceptionHandlerResolver = createExceptionHandlerExceptionResolver();
exceptionHandlerResolver.setContentNegotiationManager(mvcContentNegotiationManager());
exceptionHandlerResolver.setMessageConverters(getMessageConverters());
exceptionHandlerResolver.setCustomArgumentResolvers(getArgumentResolvers());
exceptionHandlerResolver.setCustomReturnValueHandlers(getReturnValueHandlers());
if (jackson2Present) {
exceptionHandlerResolver.setResponseBodyAdvice(
Collections.<ResponseBodyAdvice<?>>singletonList(new JsonViewResponseBodyAdvice()));
}
exceptionHandlerResolver.setApplicationContext(this.applicationContext);
exceptionHandlerResolver.afterPropertiesSet();
exceptionResolvers.add(exceptionHandlerResolver);
ResponseStatusExceptionResolver responseStatusResolver = new ResponseStatusExceptionResolver();
responseStatusResolver.setMessageSource(this.applicationContext);
exceptionResolvers.add(responseStatusResolver);
exceptionResolvers.add(new DefaultHandlerExceptionResolver());
}
從上面的代碼可以看到,最終提供了HandlerExceptionResolverComposite包裝類作為HandlerExceptionResolver的實現(xiàn),包裝了ExceptionHandlerExceptionResolver、ResponseStatusExceptionResolver和
DefaultHandlerExceptionResolver三個具體的實現(xiàn)類;
其中ExceptionHandlerExceptionResolver會讀取方法的@ExceptionHandler注解,從而進行異常處理;
ExceptionHandlerExceptionResolver首先會搜索Controller類本身,接著從添加了ControllerAdvice注解的類里面尋找;