Spring Boot错误处理

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注解的类里面寻找;

你可能感兴趣的:(Spring Boot错误处理)