# 一、SpringBoot 默认的错误处理机制

【1】浏览器返回的默认错误页面如下:

配置

浏览器发送请求的请求头信息如下: text/html会在后面的源码分析中说到。

配置

【2】如果是其他客户端,默认则响应错误的 JSON字符串,如下所示:

配置

其他客户端发送请求的请求头信息如下: */* 源码中解释。

配置

# 二、原理分析

参照ErrorMvcAutoConfiguration类: 错误处理的自动配置类,以下4项为此类的重要信息。

【1】ErrorMvcAutoConfiguration.ErrorPageCustomizer: 当系统出现 4xx或者 5xx之类的错误时,ErrorPageCustomizer就会生效(定制错误的响应规则),根据如下源码可知,将会来到/error请求。

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

//进入ErrorPageCustomizer方法,发现registerErrorPages方法:注册一个错误也
private static class ErrorPageCustomizer implements ErrorPageRegistrar, Ordered {
	private final ServerProperties properties;

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

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

//进入this.properties.getError().getPath()方法,获取如下信息,得到/error请求。
@Value("${error.path:/error}")
private String path = "/error";//系统出现错误以后来到error请求进行处理;(web.xml注册的错误页面规则)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

【2】BasicErrorController 处理 /error错误请求: 注意:text/html*/*就是在此处生效。

@Bean
@ConditionalOnMissingBean(
    value = {ErrorController.class},
    search = SearchStrategy.CURRENT
)
public BasicErrorController basicErrorController(ErrorAttributes errorAttributes) {
    return new BasicErrorController(errorAttributes, this.serverProperties.getError(), this.errorViewResolvers);
}

//进入BasicErrorController对象,获取如下信息
@Controller
@RequestMapping({"${server.error.path:${error.path:/error}}"})
public class BasicErrorController extends AbstractErrorController {
    private final ErrorProperties errorProperties;

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

    @RequestMapping(
        produces = {"text/html"}//产生html类型的数据;浏览器发送的请求来到这个方法处理
    )
    public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
        HttpStatus status = this.getStatus(request);
        Map<String, Object> model = Collections.unmodifiableMap(this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.TEXT_HTML)));
        response.setStatus(status.value());

        //去哪个页面作为错误页面;包含页面地址和页面内容
        ModelAndView modelAndView = this.resolveErrorView(request, response, status, model);
        return modelAndView != null?modelAndView:new ModelAndView("error", model);
    }

    @RequestMapping
    @ResponseBody//产生json数据,其他客户端来到这个方法处理;
    public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
        Map<String, Object> body = this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.ALL));
        HttpStatus status = this.getStatus(request);
        return new ResponseEntity(body, status);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

如上代码中提到的错误页面解析代码,进入此方法: this.resolveErrorView(request, response, status, model);

protected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, HttpStatus status, Map<String, Object> model) {
	Iterator var5 = this.errorViewResolvers.iterator();
	ModelAndView modelAndView;
	do {
		//从所有的ErrorViewResolver得到ModelAndView
		if(!var5.hasNext()) {
			return null;
		}

		ErrorViewResolver resolver = (ErrorViewResolver)var5.next();
		modelAndView = resolver.resolveErrorView(request, status, model);
	} while(modelAndView == null);

	return modelAndView;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

【3】最终的响应页面是由DefaultErrorViewResolver解析得到的: 最重要的信息是,SpringBoot默认模板引擎的/error目录下获取 ‘status’.xml 错误页面,也可以通过4xx.xml来统配 404.xml和 400.xml等等,但是优先获取精准的页面。如果模板引擎中不存在,则会从静态页面中获取错误页面。否则返回系统默认错误页面。

 @Bean
 @ConditionalOnBean({DispatcherServlet.class})
 @ConditionalOnMissingBean
 public DefaultErrorViewResolver conventionErrorViewResolver() {
		return new DefaultErrorViewResolver(this.applicationContext, this.resourceProperties);
	}

//进入DefaultErrorViewResolver类中
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
	ModelAndView modelAndView = this.resolve(String.valueOf(status), model);
	if(modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {

		//调用时viewname = status ***重要
		modelAndView = this.resolve((String)SERIES_VIEWS.get(status.series()), model);
	}

	return modelAndView;
}

private ModelAndView resolve(String viewName, Map<String, Object> model) {

	//默认SpringBoot可以去找到一个页面? error/404
	String errorViewName = "error/" + viewName;

	//模板引擎可以解析这个页面地址就用模板引擎解析
	TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.
	getProvider(errorViewName, this.applicationContext);

	//模板引擎可用的情况下返回到errorViewName指定的视图地址,
	//当模板引擎不可用,就在静态资源文件夹下找errorViewName对应的页面 error/404.html
	return provider != null?new ModelAndView(errorViewName, model):this.resolveResource(errorViewName, model);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

【4】DefaultErrorAttributes: 在页面添加错误信息,供我们使用。

@Bean
@ConditionalOnMissingBean(
	value = {ErrorAttributes.class},
	search = SearchStrategy.CURRENT
)
public DefaultErrorAttributes errorAttributes() {
	return new DefaultErrorAttributes();
}

//进入DefaultErrorAttributes类中,发现此方法给视图中添加了status状态等信息,供我们使用。
public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) {
	Map<String, Object> errorAttributes = new LinkedHashMap();
	errorAttributes.put("timestamp", new Date());
	this.addStatus(errorAttributes, requestAttributes);
	this.addErrorDetails(errorAttributes, requestAttributes, includeStackTrace);
	this.addPath(errorAttributes, requestAttributes);
	return errorAttributes;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 三、定制错误 JSON数据

【1】自定义异常处理类,返回定制的 JSON数据。通过上述的分析,我们得知:
 ①、可以完全编写一个ErrorController的实现类,或者继承AbstractErrorController的子类,放入容器中。
 ②、也可以自定义异常处理类,返回 JSON数据。
 ③、页面上的数据或 JSON返回的数据都是可以通过errorAttributes.getErrorAttributes得到的。我们可以自定义属于自己的ErrorAttributes

//首先我们可以通过自定义异常处理,来确定返回的数据,但这个不够灵活,我们可以与③结合使用
/**
 * @RequestMapping启动应用后,被 @ExceptionHandler、@InitBinder、@ModelAttribute 注解的方法,都会作用在 被 @RequestMapping
 * 注解的方法上。
 */
@ControllerAdvice
public class MyExceptionHandler {
    @ResponseBody
    @ExceptionHandler(UserNotExistException.class)
    public Map<String,Object> handlerException(Exception e, HttpServletRequest request){
        Map<String,Object> map = new HashMap<String,Object>();
        request.setAttribute("javax.servlet.error.status_code","500");
        map.put("code","user.notexist");
        map.put("message",e.getMessage());
        return map;
    }
}

//③自定义ErrorAttributes,一定要加入容器
@Component
public class MyErrorAttributes extends DefaultErrorAttributes{
    @Override
    public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) {
        //获取默认的配置,在此基础上添加自己的需求
        Map<String, Object> map = super.getErrorAttributes(requestAttributes, includeStackTrace);
        //自定义自己需要的属性
        map.put("company","yintong");

        //获取我们在异常处理类中添加的信息,
        /*注意:当我们需要结合使用的时候异常处理必须return "forward:/error";将请求转发出去,不能直接返回map对象,
        同时要去掉@responseBody注解,否则ErrorAttributes不生效*/
        map.put("ext",requestAttributes.getAttribute("ext",requestAttributes.SCOPE_REQUEST));
        return map;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

【2】效果展示:

配置
(adsbygoogle = window.adsbygoogle || []).push({});