springboot的异常处理机制源码解析

本文目录

  1. springboot默认的异常处理机制
    • 默认异常处理规则
    • 定制异常处理的逻辑
    • 异常处理的自动配置原理
  2. 异常处理的步骤流程总结及源码解析

1.springboot默认的异常处理机制

默认异常处理规则

● 默认情况下,Spring Boot提供/error处理所有错误的映射。
● 对于机器客户端,它将生成JSON响应,其中包含错误,HTTP状态和异常消息的详细信息。对于浏览器客户端,响应一个“ whitelabel”错误视图,以HTML格式呈现相同的数据。

定制异常处理的逻辑(白点为黑点的子项)

● 自定义错误页
○ error/404.html error/5xx.html;有精确的错误状态码页面就匹配精确,没有就找 4xx.html;如果都没有就触发白页
● @ControllerAdvice+@ExceptionHandler处理全局异常;底层是 ExceptionHandlerExceptionResolver 支持的
● @ResponseStatus+自定义异常 ;底层是 ResponseStatusExceptionResolver ,把responsestatus注解的信息底层调用 response.sendError(statusCode, resolvedReason);tomcat发送的/error
● Spring底层的异常,如 参数类型转换异常;DefaultHandlerExceptionResolver 处理框架底层的异常。
○ response.sendError(HttpServletResponse.SC_BAD_REQUEST, ex.getMessage());

异常处理的自动配置原理
1.问题描述

为什么要剖析自动配置原理:因为上面的一些默认的异常处理行为得益于springboot已经帮助我们配置好的一些东西。

● ErrorMvcAutoConfiguration 自动配置异常处理规则
○ 容器中的组件:类型:DefaultErrorAttributes -> id:errorAttributes
■ public class DefaultErrorAttributes implements ErrorAttributes, HandlerExceptionResolver
■ DefaultErrorAttributes:定义错误页面中可以包含哪些数据。
○ 容器中的组件:类型:BasicErrorController --> id:basicErrorController(json+白页 适配响应)
■ 处理默认 /error 路径的请求;页面响应 new ModelAndView(“error”, model);
■ 容器中有组件 View->id是error;(响应默认错误页)注解:页面响应的ModelAndView(“error”, model);视图名称刚好为error,此处又注入了一个名称为error的view,相当于给页面响应这个视图。(注意只有给浏览器响应时才会找error这个视图)
■ 容器中放组件 BeanNameViewResolver(视图解析器);按照返回的视图名作为组件的id去容器中找View对象。(视图必须由视图解析器得到)这里得到一个StaticView(详细可以看ErrorMvcAutoConfiguration中对于StaticView的注释部分),最终页面显示什么都有这个view决定。
总结: 如果想要返回页面;就会找error视图【StaticView】。(默认是一个白页)
○ 容器中的组件:类型:DefaultErrorViewResolver -> id:conventionErrorViewResolver
■ 如果发生错误,会以HTTP的状态码 作为视图页地址(viewName),找到真正的页面,拼串就是error/viewName.html
■ error/404、5xx.html

org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration下定义了异常处理的配置信息

@Configuration(proxyBeanMethods = false)  //是否为该Bean创建代理对象
@ConditionalOnWebApplication(type = Type.SERVLET) //是否是一个servlet技术栈的web服务
@ConditionalOnClass({Servlet.class, DispatcherServlet.class}) //条件注解标注的类型的bean在容器中存在,该配置类才生效
@AutoConfigureBefore({WebMvcAutoConfiguration.class}) //定义了配置类的顺序
@EnableConfigurationProperties({ServerProperties.class, ResourceProperties.class, WebMvcProperties.class}) //定义配置项和javaBean实体的一个绑定关系
public class ErrorMvcAutoConfiguration {
    // 引入配置项信息,方便获取
    private final ServerProperties serverProperties;

    public ErrorMvcAutoConfiguration(ServerProperties serverProperties) {
        this.serverProperties = serverProperties;
    }
    
    @Bean
    @ConditionalOnMissingBean(  //如果没有ErrorAttributes类型的Bean,将new DefaultErrorAttributes() 类型的Bean放入到容器中,按照Springboot的莫认规则,这个组件的id为errorAttributes 即就是方法名称 
        value = {ErrorAttributes.class},
        search = SearchStrategy.CURRENT
    )
    public DefaultErrorAttributes errorAttributes() {
        return new DefaultErrorAttributes();
    }

    @Bean
    // 如果用户没有向容器中驻入一个ErrorController类型的Bean,则springboot会默认配置一个BasicErrorController
    @ConditionalOnMissingBean(
        value = {ErrorController.class},
        search = SearchStrategy.CURRENT
    )
    public BasicErrorController basicErrorController(ErrorAttributes errorAttributes, ObjectProvider<ErrorViewResolver> errorViewResolvers) {
        return new BasicErrorController(errorAttributes, this.serverProperties.getError(), (List)errorViewResolvers.orderedStream().collect(Collectors.toList()));
    }

    @Bean
    public ErrorMvcAutoConfiguration.ErrorPageCustomizer errorPageCustomizer(DispatcherServletPath dispatcherServletPath) {
        return new ErrorMvcAutoConfiguration.ErrorPageCustomizer(this.serverProperties, dispatcherServletPath);
    }

    @Bean
    public static ErrorMvcAutoConfiguration.PreserveErrorControllerTargetClassPostProcessor preserveErrorControllerTargetClassPostProcessor() {
        return new ErrorMvcAutoConfiguration.PreserveErrorControllerTargetClassPostProcessor();
    }

    static class PreserveErrorControllerTargetClassPostProcessor implements BeanFactoryPostProcessor {
        PreserveErrorControllerTargetClassPostProcessor() {
        }

        public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
            String[] errorControllerBeans = beanFactory.getBeanNamesForType(ErrorController.class, false, false);
            String[] var3 = errorControllerBeans;
            int var4 = errorControllerBeans.length;

            for(int var5 = 0; var5 < var4; ++var5) {
                String errorControllerBean = var3[var5];

                try {
                    beanFactory.getBeanDefinition(errorControllerBean).setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
                } catch (Throwable var8) {
                }
            }

        }
    }

    static class ErrorPageCustomizer implements ErrorPageRegistrar, Ordered {
        private final ServerProperties properties;
        private final DispatcherServletPath dispatcherServletPath;

        protected ErrorPageCustomizer(ServerProperties properties, DispatcherServletPath dispatcherServletPath) {
            this.properties = properties;
            this.dispatcherServletPath = dispatcherServletPath;
        }

        public void registerErrorPages(ErrorPageRegistry errorPageRegistry) {
            ErrorPage errorPage = new ErrorPage(this.dispatcherServletPath.getRelativePath(this.properties.getError().getPath()));
            errorPageRegistry.addErrorPages(new ErrorPage[]{errorPage});
        }

        public int getOrder() {
            return 0;
        }
    }
    //StaticView是springboot默认注入的一个view对象,通过render方法就可以看出,用于向浏览器响应白页的
    private static class StaticView implements View {
        private static final MediaType TEXT_HTML_UTF8;
        private static final Log logger;

        private StaticView() {
        }

        public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
            if (response.isCommitted()) {
                String message = this.getMessage(model);
                logger.error(message);
            } else {
                response.setContentType(TEXT_HTML_UTF8.toString());
                StringBuilder builder = new StringBuilder();
                Object timestamp = model.get("timestamp");
                Object message = model.get("message");
                Object trace = model.get("trace");
                if (response.getContentType() == null) {
                    response.setContentType(this.getContentType());
                }

                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(this.htmlEscape(model.get("error"))).append(", status=").append(this.htmlEscape(model.get("status"))).append(").</div>");
                if (message != null) {
                    builder.append("<div>").append(this.htmlEscape(message)).append("</div>");
                }

                if (trace != null) {
                    builder.append("<div style='white-space:pre-wrap;'>").append(this.htmlEscape(trace)).append("</div>");
                }

                builder.append("</body></html>");
                response.getWriter().append(builder.toString());
            }
        }

        private String htmlEscape(Object input) {
            return input != null ? HtmlUtils.htmlEscape(input.toString()) : null;
        }

        private String getMessage(Map<String, ?> model) {
            Object path = model.get("path");
            String message = "Cannot render error page for request [" + path + "]";
            if (model.get("message") != null) {
                message = message + " and exception [" + model.get("message") + "]";
            }

            message = message + " as the response has already been committed.";
            message = message + " As a result, the response may have the wrong status code.";
            return message;
        }

        public String getContentType() {
            return "text/html";
        }

        static {
            TEXT_HTML_UTF8 = new MediaType("text", "html", StandardCharsets.UTF_8);
            logger = LogFactory.getLog(ErrorMvcAutoConfiguration.StaticView.class);
        }
    }

    private static class ErrorTemplateMissingCondition extends SpringBootCondition {
        private ErrorTemplateMissingCondition() {
        }

        public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
            Builder message = ConditionMessage.forCondition("ErrorTemplate Missing", new Object[0]);
            TemplateAvailabilityProviders providers = new TemplateAvailabilityProviders(context.getClassLoader());
            TemplateAvailabilityProvider provider = providers.getProvider("error", context.getEnvironment(), context.getClassLoader(), context.getResourceLoader());
            return provider != null ? ConditionOutcome.noMatch(message.foundExactly("template from " + provider)) : ConditionOutcome.match(message.didNotFind("error template view").atAll());
        }
    }

    @Configuration(
        proxyBeanMethods = false
    )
    @ConditionalOnProperty(
        prefix = "server.error.whitelabel",
        name = {"enabled"},
        matchIfMissing = true
    )
    @Conditional({ErrorMvcAutoConfiguration.ErrorTemplateMissingCondition.class})
    protected static class WhitelabelErrorViewConfiguration {
        private final ErrorMvcAutoConfiguration.StaticView defaultErrorView = new ErrorMvcAutoConfiguration.StaticView();

        protected WhitelabelErrorViewConfiguration() {
        }
        // 向容器中放一个view,这个view默认类型是ErrorMvcAutoConfiguration.StaticView
        @Bean(
            name = {"error"}
        )
        @ConditionalOnMissingBean(
            name = {"error"}
        )
        public View defaultErrorView() {
            return this.defaultErrorView;
        }

        @Bean
        @ConditionalOnMissingBean
        public BeanNameViewResolver beanNameViewResolver() {
            BeanNameViewResolver resolver = new BeanNameViewResolver();
            resolver.setOrder(2147483637);
            return resolver;
        }
    }

    @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;
        }
        //配置一个错误的视图解析器 如果容器中没有时增加一个DefaultErrorViewResolver
        @Bean
        @ConditionalOnBean({DispatcherServlet.class})
        @ConditionalOnMissingBean({ErrorViewResolver.class})
        DefaultErrorViewResolver conventionErrorViewResolver() {
            return new DefaultErrorViewResolver(this.applicationContext, this.resourceProperties);
        }
    }
}
上面提到的BasicErrorController带注释详解
@Controller
@RequestMapping({"${server.error.path:${error.path:/error}}"}) //如果说没有配置server.error.path项取${error.path:/error}而error.path也没有配置,则取默认的/error,总体来说是处理/error的请求
public class BasicErrorController extends AbstractErrorController {
    private final ErrorProperties errorProperties;

    public BasicErrorController(ErrorAttributes errorAttributes, ErrorProperties errorProperties) {
        this(errorAttributes, errorProperties, Collections.emptyList());
    }

    public BasicErrorController(ErrorAttributes errorAttributes, ErrorProperties errorProperties, List<ErrorViewResolver> errorViewResolvers) {
        super(errorAttributes, errorViewResolvers);
        Assert.notNull(errorProperties, "ErrorProperties must not be null");
        this.errorProperties = errorProperties;
    }

    public String getErrorPath() {
        return null;
    }
    //如果是浏览器 当发生异常时会响应一个白页 响应一个HTML页面
    @RequestMapping(
        produces = {"text/html"}
    )
    public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
        //如果所有的异常解析器都不能处理异常,就会被springMVC底层默认转发到一个/error请求,由于在第一个异常解析器DefaultErrorAttribute解析时将异常信息放入到request请求域中,这里可以获取到。
        HttpStatus status = this.getStatus(request);
        Map<String, Object> model = Collections.unmodifiableMap(this.getErrorAttributes(request, this.getErrorAttributeOptions(request, MediaType.TEXT_HTML)));
        response.setStatus(status.value());
        ModelAndView modelAndView = this.resolveErrorView(request, response, status, model);
        return modelAndView != null ? modelAndView : new ModelAndView("error", model);
    }
    // 如果是postman等,将响应一个ResponseEntity 即就是JSON格式数据
    @RequestMapping
    public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
        HttpStatus status = this.getStatus(request);
        if (status == HttpStatus.NO_CONTENT) {
            return new ResponseEntity(status);
        } else {
            Map<String, Object> body = this.getErrorAttributes(request, this.getErrorAttributeOptions(request, MediaType.ALL));
            return new ResponseEntity(body, status);
        }
    }

    @ExceptionHandler({HttpMediaTypeNotAcceptableException.class})
    public ResponseEntity<String> mediaTypeNotAcceptable(HttpServletRequest request) {
        HttpStatus status = this.getStatus(request);
        return ResponseEntity.status(status).build();
    }

    protected ErrorAttributeOptions getErrorAttributeOptions(HttpServletRequest request, MediaType mediaType) {
        ErrorAttributeOptions options = ErrorAttributeOptions.defaults();
        if (this.errorProperties.isIncludeException()) {
            options = options.including(new Include[]{Include.EXCEPTION});
        }

        if (this.isIncludeStackTrace(request, mediaType)) {
            options = options.including(new Include[]{Include.STACK_TRACE});
        }

        if (this.isIncludeMessage(request, mediaType)) {
            options = options.including(new Include[]{Include.MESSAGE});
        }

        if (this.isIncludeBindingErrors(request, mediaType)) {
            options = options.including(new Include[]{Include.BINDING_ERRORS});
        }

        return options;
    }

    protected boolean isIncludeStackTrace(HttpServletRequest request, MediaType produces) {
        switch(this.getErrorProperties().getIncludeStacktrace()) {
        case ALWAYS:
            return true;
        case ON_PARAM:
        case ON_TRACE_PARAM:
            return this.getTraceParameter(request);
        default:
            return false;
        }
    }

    protected boolean isIncludeMessage(HttpServletRequest request, MediaType produces) {
        switch(this.getErrorProperties().getIncludeMessage()) {
        case ALWAYS:
            return true;
        case ON_PARAM:
            return this.getMessageParameter(request);
        default:
            return false;
        }
    }

    protected boolean isIncludeBindingErrors(HttpServletRequest request, MediaType produces) {
        switch(this.getErrorProperties().getIncludeBindingErrors()) {
        case ALWAYS:
            return true;
        case ON_PARAM:
            return this.getErrorsParameter(request);
        default:
            return false;
        }
    }

    protected ErrorProperties getErrorProperties() {
        return this.errorProperties;
    }
}
查看一下自动配置类中帮助我们配置的DefaultErrorViewResolver(核心看注释部分)
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);
	}

	private ApplicationContext applicationContext;

	private final ResourceProperties resourceProperties;

	private final TemplateAvailabilityProviders templateAvailabilityProviders;

	private int order = Ordered.LOWEST_PRECEDENCE;

	/**
	 * Create a new {@link DefaultErrorViewResolver} instance.
	 * @param applicationContext the source application context
	 * @param resourceProperties resource properties
	 */
	public DefaultErrorViewResolver(ApplicationContext applicationContext, ResourceProperties resourceProperties) {
		Assert.notNull(applicationContext, "ApplicationContext must not be null");
		Assert.notNull(resourceProperties, "ResourceProperties must not be null");
		this.applicationContext = applicationContext;
		this.resourceProperties = resourceProperties;
		this.templateAvailabilityProviders = new TemplateAvailabilityProviders(applicationContext);
	}

	DefaultErrorViewResolver(ApplicationContext applicationContext, ResourceProperties resourceProperties,
			TemplateAvailabilityProviders templateAvailabilityProviders) {
		Assert.notNull(applicationContext, "ApplicationContext must not be null");
		Assert.notNull(resourceProperties, "ResourceProperties must not be null");
		this.applicationContext = applicationContext;
		this.resourceProperties = resourceProperties;
		this.templateAvailabilityProviders = templateAvailabilityProviders;
	}
    //视图解析器解析得到视图
	@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;
	}

	private ModelAndView resolve(String viewName, Map<String, Object> model) {       
	    //默认的错误的视图名称就为 error/请求的状态码  viewName就是http请求的状态码,因此就是我们为什么在error文件夹下定义 4xx.html 和5xx.html够生效的原因。
		String errorViewName = "error/" + viewName;
		TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName,
				this.applicationContext);
		if (provider != null) {
			return new ModelAndView(errorViewName, model);
		}
		return resolveResource(errorViewName, model);
	}

	private ModelAndView resolveResource(String viewName, Map<String, Object> model) {
		for (String location : this.resourceProperties.getStaticLocations()) {
			try {
				Resource resource = this.applicationContext.getResource(location);
				resource = resource.createRelative(viewName + ".html");
				if (resource.exists()) {
					return new ModelAndView(new HtmlResourceView(resource), model);
				}
			}
			catch (Exception ex) {
			}
		}
		return null;
	}

	@Override
	public int getOrder() {
		return this.order;
	}

	public void setOrder(int order) {
		this.order = order;
	}

	/**
	 * {@link View} backed by an HTML resource.
	 */
	private static class HtmlResourceView implements View {

		private Resource resource;

		HtmlResourceView(Resource resource) {
			this.resource = resource;
		}

		@Override
		public String getContentType() {
			return MediaType.TEXT_HTML_VALUE;
		}

		@Override
		public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)
				throws Exception {
			response.setContentType(getContentType());
			FileCopyUtils.copy(this.resource.getInputStream(), response.getOutputStream());
		}

	}

}
接下来看看ErrorMvcAutoConfiguration帮我们自动配置的DefaultErrorAttributes有什么作用
@Order(-2147483648)
public class DefaultErrorAttributes implements ErrorAttributes, HandlerExceptionResolver, Ordered {
    private static final String ERROR_ATTRIBUTE = DefaultErrorAttributes.class.getName() + ".ERROR";
    private final Boolean includeException;

    public DefaultErrorAttributes() {
        this.includeException = null;
    }

    /** @deprecated */
    @Deprecated
    public DefaultErrorAttributes(boolean includeException) {
        this.includeException = includeException;
    }

    public int getOrder() {
        return -2147483648;
    }

    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        this.storeErrorAttributes(request, ex);
        return null;
    }
    //保存错误的信息 包含的内容
    private void storeErrorAttributes(HttpServletRequest request, Exception ex) {
        //保存异常信息到request域中 至于为什么继续看后边
        request.setAttribute(ERROR_ATTRIBUTE, ex);
    }

    public Map<String, Object> getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) {
        Map<String, Object> errorAttributes = this.getErrorAttributes(webRequest, options.isIncluded(Include.STACK_TRACE));
        if (Boolean.TRUE.equals(this.includeException)) {
            options = options.including(new Include[]{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;
    }

    /** @deprecated */
    @Deprecated
    public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
        Map<String, Object> errorAttributes = new LinkedHashMap();
        //时间戳
        errorAttributes.put("timestamp", new Date());
        //状态码
        this.addStatus(errorAttributes, webRequest);
        this.addErrorDetails(errorAttributes, webRequest, includeStackTrace);
        //添加路径
        this.addPath(errorAttributes, webRequest);
        return errorAttributes;
    }

    private void addStatus(Map<String, Object> errorAttributes, RequestAttributes requestAttributes) {
        Integer status = (Integer)this.getAttribute(requestAttributes, "javax.servlet.error.status_code");
        if (status == null) {
            errorAttributes.put("status", 999);
            errorAttributes.put("error", "None");
        } else {
            errorAttributes.put("status", status);

            try {
                errorAttributes.put("error", HttpStatus.valueOf(status).getReasonPhrase());
            } catch (Exception var5) {
                errorAttributes.put("error", "Http Status " + status);
            }

        }
    }
    //保存一些异常的详细信息
    private void addErrorDetails(Map<String, Object> errorAttributes, WebRequest webRequest, boolean includeStackTrace) {
        Throwable error = this.getError(webRequest);
        if (error != null) {
            while(true) {
                if (!(error instanceof ServletException) || error.getCause() == null) {
                    errorAttributes.put("exception", error.getClass().getName());
                    if (includeStackTrace) {
                        this.addStackTrace(errorAttributes, error);
                    }
                    break;
                }

                error = error.getCause();
            }
        }

        this.addErrorMessage(errorAttributes, webRequest, error);
    }

    private void addErrorMessage(Map<String, Object> errorAttributes, WebRequest webRequest, Throwable error) {
        BindingResult result = this.extractBindingResult(error);
        if (result == null) {
            this.addExceptionErrorMessage(errorAttributes, webRequest, error);
        } else {
            this.addBindingResultErrorMessage(errorAttributes, result);
        }

    }
    //异常响应的message信息
    private void addExceptionErrorMessage(Map<String, Object> errorAttributes, WebRequest webRequest, Throwable error) {
        Object message = this.getAttribute(webRequest, "javax.servlet.error.message");
        if (StringUtils.isEmpty(message) && error != null) {
            message = error.getMessage();
        }

        if (StringUtils.isEmpty(message)) {
            message = "No message available";
        }

        errorAttributes.put("message", message);
    }
    
    private void addBindingResultErrorMessage(Map<String, Object> errorAttributes, BindingResult result) {
        errorAttributes.put("message", "Validation failed for object='" + result.getObjectName() + "'. Error count: " + result.getErrorCount());
        errorAttributes.put("errors", result.getAllErrors());
    }

    private BindingResult extractBindingResult(Throwable error) {
        if (error instanceof BindingResult) {
            return (BindingResult)error;
        } else {
            return error instanceof MethodArgumentNotValidException ? ((MethodArgumentNotValidException)error).getBindingResult() : null;
        }
    }
    //异常响应的堆栈信息
    private void addStackTrace(Map<String, Object> errorAttributes, Throwable error) {
        StringWriter stackTrace = new StringWriter();
        error.printStackTrace(new PrintWriter(stackTrace));
        stackTrace.flush();
        errorAttributes.put("trace", stackTrace.toString());
    }
    //发生异常的访问路径
    private void addPath(Map<String, Object> errorAttributes, RequestAttributes requestAttributes) {
        String path = (String)this.getAttribute(requestAttributes, "javax.servlet.error.request_uri");
        if (path != null) {
            errorAttributes.put("path", path);
        }

    }

    public Throwable getError(WebRequest webRequest) {
        Throwable exception = (Throwable)this.getAttribute(webRequest, ERROR_ATTRIBUTE);
        return exception != null ? exception : (Throwable)this.getAttribute(webRequest, "javax.servlet.error.exception");
    }

    private <T> T getAttribute(RequestAttributes requestAttributes, String name) {
        return requestAttributes.getAttribute(name, 0);
    }
}

2.异常处理的步骤流程总结及源码解析

1、执行目标方法,目标方法运行期间有任何异常都会被catch、而且标志当前请求结束;并且用 dispatchException 封装。
2、进入视图解析流程(页面渲染?)
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
3、mv = processHandlerException;处理handler发生的异常,处理完成返回ModelAndView;
● 1、遍历所有的 handlerExceptionResolvers,看谁能处理当前异常【HandlerExceptionResolver处理器异常解析器】
● 2、具体的异常解析器有那些呢?默认两个,第二个又包含三个异常处理器在这里插入图片描述
○ 1、DefaultErrorAttributes先来处理异常。把异常信息保存到rrequest域,并且返回null;
○ 2、默认没有任何异常解析器能处理异常,所以异常会被抛出
■ 1、如果没有任何人能处理最终底层就会发送 /error 请求。会被底层的BasicErrorController处理。BasicErrorController源码在上面自动配置过。
■ 2、解析错误视图;遍历所有的 ErrorViewResolver 看谁能解析。

调试异常处理的流程,首先来到 org.springframework.web.servlet.DispatcherServlet的doDispatch方法。(这里需要对SpringMVC的底层有一定的了解)

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;
        boolean multipartRequestParsed = false;
        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

        try {
            try {
                ModelAndView mv = null;
                Object dispatchException = null;

                try {
                    //1.判断是否是一个文件上传的请求 如果是一个文件上传的请求就包装成为一个multipartRequestParsed
                    processedRequest = this.checkMultipart(request);
                    multipartRequestParsed = processedRequest != request;
                    //2.得到请求与相应的能处理该请求的控制器的映射关系(哪个类能处理那个请求的一一对应关系)
                    mappedHandler = this.getHandler(processedRequest);
                    if (mappedHandler == null) {
                        this.noHandlerFound(processedRequest, response);
                        return;
                    }
                    //3.根据请求的控制器得到能处理该请求的适配器
                    HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
                    String method = request.getMethod();
                    boolean isGet = "GET".equals(method);
                    if (isGet || "HEAD".equals(method)) {
                        long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                        if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {
                            return;
                        }
                    }

                    if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                        return;
                    }
                    //通过适配器执行目标方法(重点)适配器就相当于一个反射工具 通过反射执行方法
                    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                    if (asyncManager.isConcurrentHandlingStarted()) {
                        return;
                    }
                    //保存一个默认的视图名称
                    this.applyDefaultViewName(processedRequest, mv);
                    //过滤器相关
                    mappedHandler.applyPostHandle(processedRequest, response, mv);
                } catch (Exception var20) {
                    dispatchException = var20;
                } catch (Throwable var21) {
                    //发生异常就创建一个 dispatchException
                    dispatchException = new NestedServletException("Handler dispatch failed", var21);
                }
                //无论执行目标方法有无异常都会进入视图解析流程,如果视图解析流程发生异常进入后续catch 
                this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
            } catch (Exception var22) {
                this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
            } catch (Throwable var23) {
                this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));
            }

        } finally {
            if (asyncManager.isConcurrentHandlingStarted()) {
                if (mappedHandler != null) {
                    mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
                }
            } else if (multipartRequestParsed) {
                this.cleanupMultipart(processedRequest);
            }

        }
    }
进入视图解析方法
//参数说明 mappedHandler:那个控制器的那个方法发生异常  mv视图名称和数据(如果发生异常mv为NULL) exception异常信息
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv, @Nullable Exception exception) throws Exception {
        boolean errorView = false;
        //发生异常进入
        if (exception != null) {
            if (exception instanceof ModelAndViewDefiningException) {
                this.logger.debug("ModelAndViewDefiningException encountered", exception);
                mv = ((ModelAndViewDefiningException)exception).getModelAndView();
            } else {
                Object handler = mappedHandler != null ? mappedHandler.getHandler() : null;
                //处理handler的异常,无论为空还是不为空最终都返回一个ModelAndView
                mv = this.processHandlerException(request, response, handler, exception);
                errorView = mv != null;
            }
        }

//如何处理Handler发生的异常呢?
@Nullable
    protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) throws Exception {
        request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
        ModelAndView exMv = null;
        //handler的异常解析器(多个 遍历看那个能处理)
        if (this.handlerExceptionResolvers != null) {
            Iterator var6 = this.handlerExceptionResolvers.iterator();

            while(var6.hasNext()) {
                HandlerExceptionResolver resolver = (HandlerExceptionResolver)var6.next();
                //直到有一个异常解析器能够解析才能跳出
                exMv = resolver.resolveException(request, response, handler, ex);
                if (exMv != null) {
                    break;
                }
            }
        }
进入resolveException(request, response, handler, ex);方法来到org.springframework.boot.web.servlet.error.DefaultErrorAttributes中
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        this.storeErrorAttributes(request, ex);
        return null;
    }
    //将异常信息设置到request域中 直接返回NULL了 上面提到过 
private void storeErrorAttributes(HttpServletRequest request, Exception ex) {
        request.setAttribute(ERROR_ATTRIBUTE, ex);
    }
剖析一下HandlerExceptionResolver的作用
//  var3代表那个方法发生了异常 var4 发生的异常信息是什么 最终返回ModelAndView
public interface HandlerExceptionResolver {
    @Nullable
    ModelAndView resolveException(HttpServletRequest var1, HttpServletResponse var2, @Nullable Object var3, Exception var4);
}
由于DefaultErrorAttributes这个异常解析器只是保存异常信息到请求域就返回了,所以来到第二个异常解析器HandlerExceptionResolverComposite 是一个组合异常解析器,包含三个异常解析器,这三个异常解析器大致可以解析以下三种情况。

1.ExceptionHandlerExceptionResolver用于处理标注了@ExceptionHandler注解的情况
2.ResponseStatusExceptionResolver用来处理标注了@ResponseStatus注解的情况
3.DefaultHandlerExceptionResolver
最终结果是:这三个异常处理器都不能处理,异常被抛出。

 @Nullable
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
        if (this.resolvers != null) {
            Iterator var5 = this.resolvers.iterator();

            while(var5.hasNext()) {
                HandlerExceptionResolver handlerExceptionResolver = (HandlerExceptionResolver)var5.next();
                ModelAndView mav = handlerExceptionResolver.resolveException(request, response, handler, ex);
                if (mav != null) {
                    return mav;
                }
            }
        }
        return null;
    }

#### 默认被BasicErrorController处理器处理/error请求的细节进入org.springframework.boot.autoconfigure.web.servlet.error.asicErrorController 的errorHtml()方法的 resolveErrorView(request, response, status, model);中
```java
protected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, HttpStatus status,
        //遍历所有的错误视图解析器 看那个能解析 这里默认就是DefaultErrorViewResolver 上面在自动配置中注入过DefaultErrorViewResolver 
			Map<String, Object> model) {
		for (ErrorViewResolver resolver : this.errorViewResolvers) {
			ModelAndView modelAndView = resolver.resolveErrorView(request, status, model);
			if (modelAndView != null) {
				return modelAndView;
			}
		}
		return null;
	}
进入resolveErrorView(request, status, model);方法中来到DefaultErrorViewResolver
@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;
	}
private ModelAndView resolve(String viewName, Map<String, Object> model) {
        //自动拼串为 error/4xx.html (viewName就是请求的状态码)
		String errorViewName = "error/" + viewName;
		TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName,
				this.applicationContext);
		if (provider != null) {
			return new ModelAndView(errorViewName, model);
		}
		return resolveResource(errorViewName, model);
	}
展现在代码上就是如下的格式,作用是用于灵活定制自己的页面。在这里插入图片描述
接下来对目录提到的 定制异常处理的逻辑进行详细的说明

@ControllerAdvice+@ExceptionHandler处理全局异常;底层是 ExceptionHandlerExceptionResolver 支持的

@Slf4j
@ControllerAdvice
public class GlobalExceptionHandler {
//自定义的全局异常处理器 
@ExceptionHandler({ArithmeticException.class,NullPointerException.class})  //处理异常 该方法可以返回一个ModelAndView 这里为了简便,只做页面跳转,不携带数据
    public String handleArithException(Exception e){

        log.error("异常是:{}",e);
        return "login"; //视图地址
    }
}

@ResponseStatus+自定义异常 ;底层是 ResponseStatusExceptionResolver ,把responsestatus注解的信息底层调用 response.sendError(statusCode, resolvedReason);就是tomcat发送的/error。被BasicErrorController处理,上面已经讲过。

//value指明一个错误的状态码 reason错误的原因
@ResponseStatus(value= HttpStatus.FORBIDDEN,reason = "用户数量太多")
public class UserTooManyException extends RuntimeException {
    public  UserTooManyException(){
    }
    public  UserTooManyException(String message){
        super(message);
    }
}


>Spring底层的异常,如 参数类型转换异常;DefaultHandlerExceptionResolver 处理框架底层的异常。
  ○ response.sendError(HttpServletResponse.SC_BAD_REQUEST, ex.getMessage());交由tomcat发送/error请求去处理。
如果没有解析器能处理,tomcat那个非常丑的异常页就会出来。
```java
//自定义的异常解析器 如果被框架异常解析器处理了 就是优先级问题(因为遍历异常解析器会按顺序解析)
@Order(value= Ordered.HIGHEST_PRECEDENCE)  //优先级,数字越小优先级越高
@Component
public class CustomerHandlerExceptionResolver implements HandlerExceptionResolver {
    @Override
    public ModelAndView resolveException(HttpServletRequest request,
                                         HttpServletResponse response,
                                         Object handler, Exception ex) {

        try {
            response.sendError(511,"我喜欢的错误");
        } catch (IOException e) {
            e.printStackTrace();
        }
        return new ModelAndView();
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值