美文网首页
Spring Boot错误处理

Spring Boot错误处理

作者: allanYan | 来源:发表于2017-04-27 13:20 被阅读0次

    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<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的实现呢?具体的实现在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的实现,包装了ExceptionHandlerExceptionResolver、ResponseStatusExceptionResolver和
    DefaultHandlerExceptionResolver三个具体的实现类;
    其中ExceptionHandlerExceptionResolver会读取方法的@ExceptionHandler注解,从而进行异常处理;

    ExceptionHandlerExceptionResolver首先会搜索Controller类本身,接着从添加了ControllerAdvice注解的类里面寻找;

    相关文章

      网友评论

          本文标题:Spring Boot错误处理

          本文链接:https://www.haomeiwen.com/subject/rxywzttx.html