好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

Spring Boot统一异常处理最佳实践(拓展篇)

前言

之前一篇文章介绍了基本的 统一 异常处理 思路: spring mvc/boot 统一异常处理最佳实践 .

上篇文章也有许多人提出了一些问题:

如何区分 ajax 请求和普通页面请求, 以分别返回 json 错误信息和错误页面. 如何结合 http 状态码进行统一异常处理.

今天这篇文章就主要来讲讲这些, 以及其他的一些拓展点.

区分请求方式

其实 spring boot 本身是内置了一个异常处理机制的, 会判断请求头的参数来区分要返回 json 数据还是错误页面. 源码为: org.springframework.boot.autoconfigure.web.servlet.error.basicerrorcontroller , 他会处理 /error 请求. 核心处理代码如下:

?

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

@requestmapping (

  produces = { "text/html" }

)

// 如果请求头是 text/html, 则找到错误页面, 并返回

public modelandview errorhtml(httpservletrequest request, httpservletresponse response) {

  // 1. 获取 http 错误状态码

  httpstatus status = this .getstatus(request);

  // 2. 调用 geterrorattributes 获取响应的 map 结果集.

  map<string, object> model = collections.unmodifiablemap( this .geterrorattributes(request, this .isincludestacktrace(request, mediatype.text_html)));

  // 3. 设置响应头的状态码

  response.setstatus(status.value());

  // 4. 获取错误页面的路径

  modelandview modelandview = this .resolveerrorview(request, response, status, model);

  return modelandview != null ? modelandview : new modelandview( "error" , model);

}

 

@requestmapping

@responsebody

public responseentity<map<string, object>> error(httpservletrequest request) {

  // 调用 geterrorattributes 获取响应的 map 结果集.

  map<string, object> body = this .geterrorattributes(request, this .isincludestacktrace(request, mediatype.all));

  // 获取 http 错误状态码

  httpstatus status = this .getstatus(request);

  // 返回给页面 json 信息.

  return new responseentity(body, status);

}

这两个方法的共同点是: 他们都调用了 this.geterrorattributes(…) 方法来获取响应信息.

然后来看看他默认情况下对于 ajax 请求和 html 请求, 分别的返回结果是怎样的:

对于返回错误页面, 其中还调用了一个非常重要的方法: this.resolveerrorview(...) 方法, 源码我就不带大家看了, 他的作用就是根据 http 状态码来去找错误页面, 如 500 错误会去找 /error/500.html, 403 错误回去找 /error/403.html, 如果找不到则再找 /error/4xx.html 或 /error/5xx.html 页面. 还找不到的话, 则会去找 /error.html 页面, 如果都没有配置, 则会使用 spring boot 默认的页面. 即:

看到这里, 应该就清楚了, 我们主要需要做四件事:

发送异常后, 重定向到 basicerrorcontroller 来处理 (既然spring boot 都已经写好了区分请求的功能, 我们就不必要再写这些判断代码了) 自定义 http 错误状态码 他返回的信息格式可能不是我们想要的, 所以必须要改造 geterrorattributes(...) 方法, 以自定义我们向页面返回的数据. (自定义错误信息) 创建我们自己的 /error/4xx.html 或 /error/5xx.html 等页面, (自定义错误页面)

basicerrorcontroller

第一点很简单, basicerrorcontroller 他处理 /error 请求, 我们只需要将页面重定向到 /error 即可, 在 controlleradvice 中是这样的:

?

1

2

3

4

5

6

7

8

9

@controlleradvice

public class webexceptionhandler {

 

  @exceptionhandler

  public string methodargumentnotvalid(bindexception e) {

  // do something

  return "/error" ;

  }

}

自定义 http 错误状态码

我们来看下 this.getstatus(request); 的源码, 看他原来时如何获取错误状态码的:

?

1

2

3

4

5

6

7

8

9

10

11

12

protected httpstatus getstatus(httpservletrequest request) {

  integer statuscode = (integer)request.getattribute( "javax.servlet.error.status_code" );

  if (statuscode == null ) {

  return httpstatus.internal_server_error;

  } else {

  try {

   return httpstatus.valueof(statuscode);

  } catch (exception var4) {

   return httpstatus.internal_server_error;

  }

  }

}

简单来说就是从 request 域中获取 javax.servlet.error.status_code 的值, 如果为 null 或不合理的值, 都返回 500. 既然如何在第一步, 重定向到 /error 之前将其配置到 request 域中即可, 如:

?

1

2

3

4

5

6

7

8

9

10

@controlleradvice

public class webexceptionhandler {

 

  @exceptionhandler

  public string methodargumentnotvalid(bindexception e, httpservletrequest request) {

  request.setattribute( "javax.servlet.error.status_code" , 400 );

  // do something

  return "forward:/error" ;

  }

}

自定义错误信息

也就是 geterrorattributes 方法, 默认的代码是这样的:

?

1

2

3

4

5

6

7

8

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;

}

他获取了时间戳, 错误状态码, 错误信息, 错误路径等信息, 和我们之前看到默认的返回内容是一致的:

?

1

2

3

4

5

6

7

{

  "timestamp" : "2019-01-27t07:08:30.011+0000" ,

  "status" : 500 ,

  "error" : "internal server error" ,

  "message" : "/ by zero" ,

  "path" : "/user/index"

}

同样的思路, 我们将错误信息也放到 request 域中, 然后在 geterrorattributes 中从 request 域中获取:

?

1

2

3

4

5

6

7

8

9

10

11

12

@controlleradvice

public class webexceptionhandler {

 

  @exceptionhandler

  public string methodargumentnotvalid(bindexception e, httpservletrequest request) {

  request.setattribute( "javax.servlet.error.status_code" , 400 );

  request.setattribute( "code" , 1 );

  request.setattribute( "message" , "参数校验失败, xxx" );

  // do something

  return "forward:/error" ;

  }

}

再继承 defaulterrorattributes 类, 重写 geterrorattributes 方法:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

//@component

public class mydefaulterrorattributes extends defaulterrorattributes {

 

  @override

  //重写 geterrorattributes方法-添加自己的项目数据

  public map<string, object> geterrorattributes(webrequest webrequest,

        boolean includestacktrace) {

  map<string, object> map = new hashmap<>();

  // 从 request 域中获取 code

  object code = webrequest.getattribute( "code" , requestattributes.scope_request);

  // 从 request 域中获取 message

  object message = webrequest.getattribute( "message" , requestattributes.scope_request);

  map.put( "code" , code);

  map.put( "message" , message);

  return map;

  }

}

自定义错误页面

我们遵循 springboot 的规则, 在 /error/ 下建立 400.html, 500.html 等页面细粒度的错误, 并配置一个 /error.html 用来处理细粒度未处理到的其他错误.

/error/400.html

?

1

2

3

4

5

6

7

8

9

10

11

12

<!doctype html>

<html lang= "en" xmlns:th= "http://www.thymeleaf.org" >

<head>

  <meta charset= "utf-8" >

  <title> 400 </title>

</head>

<body>

  <h1> 400 </h1>

  <h1 th:text= "$[code]" ></h1>

  <h1 th:text= "${message}" ></h1>

</body>

</html>

/error/500.html

?

1

2

3

4

5

6

7

8

9

10

11

12

<!doctype html>

<html lang= "en" xmlns:th= "http://www.thymeleaf.org" >

<head>

  <meta charset= "utf-8" >

  <title> 500 </title>

</head>

<body>

  <h1> 500 </h1>

  <h1 th:text= "$[code]" ></h1>

  <h1 th:text= "${message}" ></h1>

</body>

</html>

/error.html

?

1

2

3

4

5

6

7

8

9

10

11

12

<!doctype html>

<html lang= "en" xmlns:th= "http://www.thymeleaf.org" >

<head>

  <meta charset= "utf-8" >

  <title>系统出现了错误</title>

</head>

<body>

  <h1>error page</h1>

  <h1 th:text= "$[code]" ></h1>

  <h1 th:text= "${message}" ></h1>

</body>

</html>

测试效果

到此位置, 大功告成, 然后来创造一个异常来测试一下效果:

前端 error 处理

现在使用了 http 状态码, 所以 ajax 请求出现错误后, 需要在每个 ajax 请求方法中都写 error: function() {} 方法, 甚至麻烦. 好在 jquery 为我们提供了全局处理 ajax 的 error 结果的方法 ajaxerror() :

?

1

2

3

4

5

$(document).ajaxerror(function(event, response){

  console.log( "错误响应状态码: " ,response.status);

  console.log( "错误响应结果: " ,response.responsejson);

  alert( "an error occurred!" );

});

结语

回顾一下讲到的这些内容:

理解 springboot 默认提供的 basicerrorcontroller 自定义 http 错误状态码, (通过 request 域的 javax.servlet.error.status_code 参数) 自定义错误信息, (将我们自定义的错误信息放到 request 域中, 并重写 defaulterrorattributes 的 geterrorattributes 方法, 从 request 域中获取这些信息). 自定义错误页面, (根据 springboot 查找错误页面的逻辑来自定义错误页面: /error/500.html, /error/400.html, /error.html)

可以自己根据文章一步一步走一遍, 或者看我写好的演示项目先看看效果, 总是动手实践, 而不是收藏文章并封存。

演示项目地址: https://github.com/zhaojun1998/exception-handler-demo

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对的支持。

原文链接:http://www.zhaojun.im/springboot-exception-expand/

查看更多关于Spring Boot统一异常处理最佳实践(拓展篇)的详细内容...

  阅读:13次