Spring Boot提供了两种类型的错误处理机制,一种是依赖于内嵌容器的ErrorPage机制;另外一种是基于Spring Mvc的异常处理机制;
错误配置类
Spring Boot错误处理相关的自动配置主要是通过ErrorMvcAutoConfiguration实现的:
@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实现了HandlerExceptionResolver接口,且为最高优先级,意味着当Spring MVC发生错误时,首先会由它进行处理;
- resolveException方法将异常对象保存到request中;
- 关于HandlerExceptionResolver是如何生效的,可以查看DispatcherServlet的相关代码;
ErrorPage机制介绍
接下来看看ErrorMvcAutoConfiguration中定义的ErrorPageCustomizer,这是ErrorPage错误处理比较关键的地方:
首先看内嵌容器的自动配置类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);
}
}
当创建bean的时候,如果发现bean实现了ErrorPageRegistry接口,会从Spring容器中查找实现了ErrorPageRegistrar的类,并调用该类的registerErrorPages方法注册Error Page;
而在ErrorMvcAutoConfiguration里,提供了ErrorPageRegistrar的实现类ErrorPageCustomizer,默认将错误转发给/error进行处理;也就是说通过ErrorPageRegistrarBeanPostProcessor会将ErrorPageCustomizer注册到ErrorPageRegistry;
既然默认情况下将错误交给/error进行处理,那我们只要自定义controller类处理/error请求,就可以按需实现自己的错误处理;
在ErrorMvcAutoConfiguration类中,提供了BasicErrorController来处理/error的请求,但用户可以覆盖实现;
接下来看看有哪些类实现了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绕了一大圈,实现了ErrorPageRegistry接口,这意味着创建TomcatEmbeddedServletContainerFactory时,将会注册ErrorPage;
上面说介绍的,就是利用ErrorPage处理错误的相关实现;
Spring MVC异常处理
使用Spring Boot框架时,很多时候都是采用Spring MVC处理Http请求;Spring MVC也提供自己异常处理机制:
Spring MVC框架的入口是DispatcherServlet,其中定义了成员变量:
private List handlerExceptionResolvers;
private void initHandlerExceptionResolvers(ApplicationContext context) {
this.handlerExceptionResolvers = null;
//默认为true
if (this.detectAllHandlerExceptionResolvers) {
// Find all HandlerExceptionResolvers in the ApplicationContext, including ancestor contexts.
Map matchingBeans = BeanFactoryUtils
.beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerExceptionResolvers = new ArrayList(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的实现呢?具体的实现在WebMvcAutoConfiguration类,这里为了方便介绍,将EnableWebMvcConfiguration和它的父类WebMvcConfigurationSupport代码放到一起:
@Bean
public HandlerExceptionResolver handlerExceptionResolver() {
List exceptionResolvers = new ArrayList();
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 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 exceptionResolvers) {
ExceptionHandlerExceptionResolver exceptionHandlerResolver = createExceptionHandlerExceptionResolver();
exceptionHandlerResolver.setContentNegotiationManager(mvcContentNegotiationManager());
exceptionHandlerResolver.setMessageConverters(getMessageConverters());
exceptionHandlerResolver.setCustomArgumentResolvers(getArgumentResolvers());
exceptionHandlerResolver.setCustomReturnValueHandlers(getReturnValueHandlers());
if (jackson2Present) {
exceptionHandlerResolver.setResponseBodyAdvice(
Collections.>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的实现,包装了ExceptionHandlerExceptionResolver、ResponseStatusExceptionResolver和
DefaultHandlerExceptionResolver三个具体的实现类;
其中ExceptionHandlerExceptionResolver会读取方法的@ExceptionHandler注解,从而进行异常处理;
ExceptionHandlerExceptionResolver首先会搜索Controller类本身,接着从添加了ControllerAdvice注解的类里面寻找;