1、springboot默认错误处理机制

spring boot默认异常处理机制用浏览器请求时返回错误页面,其他应用请求时返回json数据。如下

编写控制器

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

/**
* 测试
*
* @Author: Mr_li
* @CreateDate: 2019-05-02 12:05
* @Version: 1.0
*/
@Controller
public class TestController {

@RequestMapping("/say")
public String sayHi() {
return "hi";
}
}

浏览器请求

Spring boot异常处理机制_spring

postman请求

Spring boot异常处理机制_java_02

页面能获取的信息:

timestamp:时间戳
status:状态码
error:错误提示
exception:异常对象
message:异常消息
errors:JSR303数据校验的错误都在这里

2、springboot默认错误处理机制的实现

关键点在BasicErrorController这个类中

Spring boot异常处理机制_ide_03

查看其实现

/*
* Copyright 2012-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.boot.autoconfigure.web.servlet.error;

import java.util.Collections;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.autoconfigure.web.ErrorProperties.IncludeStacktrace;
import org.springframework.boot.web.servlet.error.ErrorAttributes;
import org.springframework.boot.web.servlet.server.AbstractServletWebServerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

/**
* Basic global error {@link Controller}, rendering {@link ErrorAttributes}. More specific
* errors can be handled either using Spring MVC abstractions (e.g.
* {@code @ExceptionHandler}) or by adding servlet
* {@link AbstractServletWebServerFactory#setErrorPages server error pages}.
*
* @author Dave Syer
* @author Phillip Webb
* @author Michael Stummvoll
* @author Stephane Nicoll
* @see ErrorAttributes
* @see ErrorProperties
*/
@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {

private final ErrorProperties errorProperties;

/**
* Create a new {@link BasicErrorController} instance.
* @param errorAttributes the error attributes
* @param errorProperties configuration properties
*/
public BasicErrorController(ErrorAttributes errorAttributes,
ErrorProperties errorProperties) {
this(errorAttributes, errorProperties, Collections.emptyList());
}

/**
* Create a new {@link BasicErrorController} instance.
* @param errorAttributes the error attributes
* @param errorProperties configuration properties
* @param errorViewResolvers error view resolvers
*/
public BasicErrorController(ErrorAttributes errorAttributes,
ErrorProperties errorProperties, List<ErrorViewResolver> errorViewResolvers) {
super(errorAttributes, errorViewResolvers);
Assert.notNull(errorProperties, "ErrorProperties must not be null");
this.errorProperties = errorProperties;
}

@Override
public String getErrorPath() {
return this.errorProperties.getPath();
}

/**请求头为MediaType.TEXT_HTML_VALUE的执行这个返回页面*/
@RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
public ModelAndView errorHtml(HttpServletRequest request,
HttpServletResponse response) {
HttpStatus status = getStatus(request);
Map<String, Object> model = Collections.unmodifiableMap(getErrorAttributes(
request, isIncludeStackTrace(request, MediaType.TEXT_HTML)));
response.setStatus(status.value());
ModelAndView modelAndView = resolveErrorView(request, response, status, model);
return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
}

/**其他请求头返回obj*/
@RequestMapping
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
Map<String, Object> body = getErrorAttributes(request,
isIncludeStackTrace(request, MediaType.ALL));
HttpStatus status = getStatus(request);
return new ResponseEntity<>(body, status);
}

/**
* Determine if the stacktrace attribute should be included.
* @param request the source request
* @param produces the media type produced (or {@code MediaType.ALL})
* @return if the stacktrace attribute should be included
*/
protected boolean isIncludeStackTrace(HttpServletRequest request,
MediaType produces) {
IncludeStacktrace include = getErrorProperties().getIncludeStacktrace();
if (include == IncludeStacktrace.ALWAYS) {
return true;
}
if (include == IncludeStacktrace.ON_TRACE_PARAM) {
return getTraceParameter(request);
}
return false;
}

/**
* Provide access to the error properties.
* @return the error properties
*/
protected ErrorProperties getErrorProperties() {
return this.errorProperties;
}

}

发现它本身就是一个控制器,这个类是默认处理​​/error​​请求的。那么响应页面的时候是怎么找到页面的呢?

这里有一个关键类 DefaultErrorViewResolver

Spring boot异常处理机制_spring_04

/*
* Copyright 2012-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.boot.autoconfigure.web.servlet.error;

import java.util.Collections;
import java.util.EnumMap;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider;
import org.springframework.boot.autoconfigure.template.TemplateAvailabilityProviders;
import org.springframework.boot.autoconfigure.web.ResourceProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.core.Ordered;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpStatus;
import org.springframework.http.HttpStatus.Series;
import org.springframework.http.MediaType;
import org.springframework.util.Assert;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.View;

/**
* Default {@link ErrorViewResolver} implementation that attempts to resolve error views
* using well known conventions. Will search for templates and static assets under
* {@code '/error'} using the {@link HttpStatus status code} and the
* {@link HttpStatus#series() status series}.
* <p>
* For example, an {@code HTTP 404} will search (in the specific order):
* <ul>
* <li>{@code '/<templates>/error/404.<ext>'}</li>
* <li>{@code '/<static>/error/404.html'}</li>
* <li>{@code '/<templates>/error/4xx.<ext>'}</li>
* <li>{@code '/<static>/error/4xx.html'}</li>
* </ul>
*
* @author Phillip Webb
* @author Andy Wilkinson
* @since 1.4.0
*/
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作为视图名
String errorViewName = "error/" + viewName;
TemplateAvailabilityProvider provider = this.templateAvailabilityProviders
.getProvider(errorViewName, this.applicationContext);
if (provider != null) {
//模板引擎可用的时候返回配置的,默认为error/状态码.html。
return new ModelAndView(errorViewName, model);
}
//返回默认的errorViewName下的
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());
}

}

}

一但系统出现4xx或者5xx之类的错误;

​ErrorPageCustomizer​​就会生效(定制错误的响应规则);

就会来到/​​error​​请求;

就会被​​BasicErrorController​​处理。

3、异常页面查找

创建一下目录

Spring boot异常处理机制_java_05

页面定义以及查找规则(默认)

如上图定义一个404的异常界面。访问一个不存在的页面(触发404)。查找顺序如下

1)有模板引擎的情况下templates/error/状态码

Spring boot异常处理机制_spring_06

将错误页面命名为 错误状态码.html 放在模板引擎文件夹里面的error文件夹下,发生此状态码的错误就会来到 对应的页面。

2)没有模板引擎(模板引擎找不到这个错误页面),静态资源文件夹下找(也就是static文件夹); static/error/状态码

Spring boot异常处理机制_ide_07

3)以上都没有错误页面,就是默认来到SpringBoot默认的错误提示页面

4、全局自定义异常处理

import com.example.demo.common.UserNotExistException;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;

import java.util.HashMap;
import java.util.Map;

/**
* 全局异常捕获
*
* @Author: Mr_li
* @CreateDate: 2019-05-02 15:07
* @Version: 1.0
*/
@ControllerAdvice
public class MyExceptionHandler {

@ResponseBody
@ExceptionHandler(UserNotExistException.class)
public Map<String, Object> handleException(Exception e) {
Map<String, Object> map = new HashMap<>();
map.put("code", "not_exist");
map.put("message", e.getMessage());
return map;
}
}

@ControllerAdvice是controller的一个辅助类,最常用的就是作为全局异常处理的切面类

@ControllerAdvice可以指定扫描范围

@ControllerAdvice约定了几种可行的返回值,如果是直接返回model类的话,需要使用@ResponseBody进行json转换

返回String,表示跳到某个view

返回modelAndView

返回model + @ResponseBody (不加@ResponseBody)会抛出异常(would dispatch back to the current handler URL [/test1] again. Check your ViewResolver setup! (Hint: This may be the result of an unspecified view, due to default view name generation.)

创建自定义异常

import lombok.Data;

/**
* 自定义异常
*
* @Author: Mr_li
* @CreateDate: 2019-05-02 13:33
* @Version: 1.0
*/
@Data
public class UserNotExistException extends RuntimeException {

private String message;

public UserNotExistException(String message) {
super(message);
this.message = message;
}
}

创建控制器

import com.example.demo.common.UserNotExistException;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import java.util.HashMap;
import java.util.Map;

/**
* 测试
*
* @Author: Mr_li
* @CreateDate: 2019-05-02 12:05
* @Version: 1.0
*/
@Controller
public class TestController {

@RequestMapping(value = "/test1")
public void testException() {
throw new UserNotExistException("用户不存在");
}
}

执行结果

Spring boot异常处理机制_spring_08