【1】springboot的默認錯誤處理
① 瀏覽器訪問
請求頭如下:
② 使用“postman”訪問
1
2
3
4
5
6
7
|
{ "timestamp" : 1529479254647 , "status" : 404 , "error" : "not found" , "message" : "no message available" , "path" : "/aaa1" } |
請求頭如下:
總結:如果是瀏覽器訪問,則springboot默認返回錯誤頁面;如果是其他客戶端訪問,則默認返回json數據。
【2】默認錯誤處理原理
springboot默認配置了許多xxxautoconfiguration,這里我們找errormvcautoconfiguration。
其注冊部分組件如下:
① defaulterrorattributes
1
2
3
4
5
|
@bean @conditionalonmissingbean (value = errorattributes. class , search = searchstrategy.current) public defaulterrorattributes errorattributes() { return new defaulterrorattributes(); } |
跟蹤其源碼如下:
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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
|
public class defaulterrorattributes implements errorattributes, handlerexceptionresolver, ordered { private static final string error_attribute = defaulterrorattributes. class .getname() + ".error" ; @override public int getorder() { return ordered.highest_precedence; } @override public modelandview resolveexception(httpservletrequest request, httpservletresponse response, object handler, exception ex) { storeerrorattributes(request, ex); return null ; } private void storeerrorattributes(httpservletrequest request, exception ex) { request.setattribute(error_attribute, ex); } @override public map<string, object> geterrorattributes(requestattributes requestattributes, boolean includestacktrace) { map<string, object> errorattributes = new linkedhashmap<string, object>(); errorattributes.put( "timestamp" , new date()); addstatus(errorattributes, requestattributes); adderrordetails(errorattributes, requestattributes, includestacktrace); addpath(errorattributes, requestattributes); return errorattributes; } private void addstatus(map<string, object> errorattributes, requestattributes requestattributes) { integer status = getattribute(requestattributes, "javax.servlet.error.status_code" ); if (status == null ) { errorattributes.put( "status" , 999 ); errorattributes.put( "error" , "none" ); return ; } errorattributes.put( "status" , status); try { errorattributes.put( "error" , httpstatus.valueof(status).getreasonphrase()); } catch (exception ex) { // unable to obtain a reason errorattributes.put( "error" , "http status " + status); } } private void adderrordetails(map<string, object> errorattributes, requestattributes requestattributes, boolean includestacktrace) { throwable error = geterror(requestattributes); if (error != null ) { while (error instanceof servletexception && error.getcause() != null ) { error = ((servletexception) error).getcause(); } errorattributes.put( "exception" , error.getclass().getname()); adderrormessage(errorattributes, error); if (includestacktrace) { addstacktrace(errorattributes, error); } } object message = getattribute(requestattributes, "javax.servlet.error.message" ); if ((!stringutils.isempty(message) || errorattributes.get( "message" ) == null ) && !(error instanceof bindingresult)) { errorattributes.put( "message" , stringutils.isempty(message) ? "no message available" : message); } } private void adderrormessage(map<string, object> errorattributes, throwable error) { bindingresult result = extractbindingresult(error); if (result == null ) { errorattributes.put( "message" , error.getmessage()); return ; } if (result.geterrorcount() > 0 ) { errorattributes.put( "errors" , result.getallerrors()); errorattributes.put( "message" , "validation failed for object='" + result.getobjectname() + "'. error count: " + result.geterrorcount()); } else { errorattributes.put( "message" , "no errors" ); } } private bindingresult extractbindingresult(throwable error) { if (error instanceof bindingresult) { return (bindingresult) error; } if (error instanceof methodargumentnotvalidexception) { return ((methodargumentnotvalidexception) error).getbindingresult(); } return 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 = getattribute(requestattributes, "javax.servlet.error.request_uri" ); if (path != null ) { errorattributes.put( "path" , path); } } @override public throwable geterror(requestattributes requestattributes) { throwable exception = getattribute(requestattributes, error_attribute); if (exception == null ) { exception = getattribute(requestattributes, "javax.servlet.error.exception" ); } return exception; } @suppresswarnings ( "unchecked" ) private <t> t getattribute(requestattributes requestattributes, string name) { return (t) requestattributes.getattribute(name, requestattributes.scope_request); } } |
即,填充錯誤數據!
② basicerrorcontroller
1
2
3
4
5
6
|
@bean @conditionalonmissingbean (value = errorcontroller. class , search = searchstrategy.current) public basicerrorcontroller basicerrorcontroller(errorattributes errorattributes) { return new basicerrorcontroller(errorattributes, this .serverproperties.geterror(), this .errorviewresolvers); } |
跟蹤其源碼:
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
|
@controller @requestmapping ( "${server.error.path:${error.path:/error}}" ) public class basicerrorcontroller extends abstracterrorcontroller { //產生html類型的數據;瀏覽器發送的請求來到這個方法處理 @requestmapping (produces = "text/html" ) 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 ? new modelandview( "error" , model) : modelandview); } //產生json數據,其他客戶端來到這個方法處理; @requestmapping @responsebody 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<map<string, object>>(body, status); } //... } |
其中 resolveerrorview(request, response, status, model);方法跟蹤如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
public abstract class abstracterrorcontroller implements errorcontroller { protected modelandview resolveerrorview(httpservletrequest request, httpservletresponse response, httpstatus status, map<string, object> model) { //拿到所有的錯誤視圖解析器 for (errorviewresolver resolver : this .errorviewresolvers) { modelandview modelandview = resolver.resolveerrorview(request, status, model); if (modelandview != null ) { return modelandview; } } return null ; } //... } |
③ errorpagecustomizer
1
2
3
4
|
@bean public errorpagecustomizer errorpagecustomizer() { return new errorpagecustomizer( this .serverproperties); } |
跟蹤其源碼:
1
2
3
4
5
6
7
8
9
10
11
12
|
@override public void registererrorpages(errorpageregistry errorpageregistry) { errorpage errorpage = new errorpage( this .properties.getservletprefix() + this .properties.geterror().getpath()); errorpageregistry.adderrorpages(errorpage); } //getpath()->go on /** * path of the error controller. */ @value ( "${error.path:/error}" ) private string path = "/error" ; |
即,系統出現錯誤以后來到error請求進行處理(web.xml注冊的錯誤頁面規則)。
④ defaulterrorviewresolver
1
2
3
4
5
6
7
|
@bean @conditionalonbean (dispatcherservlet. class ) @conditionalonmissingbean public defaulterrorviewresolver conventionerrorviewresolver() { return new defaulterrorviewresolver( this .applicationcontext, this .resourceproperties); } |
跟蹤其源碼:
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
41
42
43
44
45
46
47
48
49
50
51
|
public class defaulterrorviewresolver implements errorviewresolver, ordered { private static final map<series, string> series_views; //錯誤狀態碼 static { map<series, string> views = new hashmap<series, string>(); views.put(series.client_error, "4xx" ); views.put(series.server_error, "5xx" ); series_views = collections.unmodifiablemap(views); } //... @override public modelandview resolveerrorview(httpservletrequest request, httpstatus status, map<string, object> model) { // 這里如果沒有拿到精確狀態碼(如404)的視圖,則嘗試拿4xx(或5xx)的視圖 modelandview modelandview = resolve(string.valueof(status), 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) { //默認springboot可以去找到一個頁面? error/404||error/4xx string errorviewname = "error/" + viewname; //模板引擎可以解析這個頁面地址就用模板引擎解析 templateavailabilityprovider provider = this .templateavailabilityproviders .getprovider(errorviewname, this .applicationcontext); if (provider != null ) { //模板引擎可用的情況下返回到errorviewname指定的視圖地址 return new modelandview(errorviewname, model); } //模板引擎不可用,就在靜態資源文件夾下找errorviewname對應的頁面 error/404.html 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 ; } |
總結如下:
一但系統出現4xx或者5xx之類的錯誤,errorpagecustomizer就會生效(定制錯誤的響應規則),就會來到/error請求,然后被basicerrorcontroller處理返回modelandview或者json。
【3】定制錯誤響應
① 定制錯誤響應頁面
1)有模板引擎的情況下
error/狀態碼–將錯誤頁面命名為 錯誤狀態碼.html 放在模板引擎文件夾里面的error文件夾下,發生此狀態碼的錯誤就會來到 對應的頁面。
我們可以使用4xx和5xx作為錯誤頁面的文件名來匹配這種類型的所有錯誤,精確優先(優先尋找精確的狀態碼.html)。
如下圖所示:
頁面能獲取的信息;
timestamp:時間戳
status:狀態碼
error:錯誤提示
exception:異常對象
message:異常消息
errors:jsr303數據校驗的錯誤都在這里
2)沒有模板引擎(模板引擎找不到這個錯誤頁面),靜態資源文件夾下找。
3)以上都沒有錯誤頁面,就是默認來到springboot默認的錯誤提示頁面。
webmvcautoconfiguration源碼如下:
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
|
@configuration @conditionalonproperty (prefix = "server.error.whitelabel" , name = "enabled" , matchifmissing = true ) @conditional (errortemplatemissingcondition. class ) protected static class whitelabelerrorviewconfiguration { private final spelview defaulterrorview = new spelview( "<html><body><h1>whitelabel error page</h1>" + "<p>this application has no explicit mapping for /error, so you are seeing this as a fallback.</p>" + "<div id='created'>${timestamp}</div>" + "<div>there was an unexpected error (type=${error}, status=${status}).</div>" + "<div>${message}</div></body></html>" ); @bean (name = "error" ) @conditionalonmissingbean (name = "error" ) public view defaulterrorview() { return this .defaulterrorview; } // if the user adds @enablewebmvc then the bean name view resolver from // webmvcautoconfiguration disappears, so add it back in to avoid disappointment. @bean @conditionalonmissingbean (beannameviewresolver. class ) public beannameviewresolver beannameviewresolver() { beannameviewresolver resolver = new beannameviewresolver(); resolver.setorder(ordered.lowest_precedence - 10 ); return resolver; } } |
② 定制錯誤響應數據
第一種,使用springmvc的異常處理器
1
2
3
4
5
6
7
8
9
10
11
12
13
|
@controlleradvice public class myexceptionhandler { //瀏覽器客戶端返回的都是json @responsebody @exceptionhandler (usernotexistexception. class ) public map<string,object> handleexception(exception e){ map<string,object> map = new hashmap<>(); map.put( "code" , "user.notexist" ); map.put( "message" ,e.getmessage()); return map; } } |
這樣無論瀏覽器還是postman返回的都是json!
第二種,轉發到/error請求進行自適應效果處理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
@exceptionhandler (usernotexistexception. class ) public string handleexception(exception e, httpservletrequest request){ map<string,object> map = new hashmap<>(); //傳入我們自己的錯誤狀態碼 4xx 5xx /** * integer statuscode = (integer) request .getattribute("javax.servlet.error.status_code"); */ request.setattribute( "javax.servlet.error.status_code" , 500 ); map.put( "code" , "user.notexist" ); map.put( "message" , "用戶出錯啦" ); //轉發到/error return "forward:/error" ; } |
但是此時沒有將自定義 code message傳過去!
第三種,注冊myerrorattributes繼承自defaulterrorattributes(推薦)
從第【2】部分(默認錯誤處理原理)中知道錯誤數據都是通過defaulterrorattributes.geterrorattributes()
方法獲取,如下所示:
1
2
3
4
5
6
7
8
9
10
|
@override public map<string, object> geterrorattributes(requestattributes requestattributes, boolean includestacktrace) { map<string, object> errorattributes = new linkedhashmap<string, object>(); errorattributes.put( "timestamp" , new date()); addstatus(errorattributes, requestattributes); adderrordetails(errorattributes, requestattributes, includestacktrace); addpath(errorattributes, requestattributes); return errorattributes; } |
我們可以編寫一個myerrorattributes繼承自defaulterrorattributes重寫其geterrorattributes方法將我們的錯誤數據添加進去。
示例如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
//給容器中加入我們自己定義的errorattributes @component public class myerrorattributes extends defaulterrorattributes { //返回值的map就是頁面和json能獲取的所有字段 @override public map<string, object> geterrorattributes(requestattributes requestattributes, boolean includestacktrace) { //defaulterrorattributes的錯誤數據 map<string, object> map = super .geterrorattributes(requestattributes, includestacktrace); map.put( "company" , "springboot" ); //我們的異常處理器攜帶的數據 map<string,object> ext = (map<string, object>) requestattributes.getattribute( "ext" , 0 ); map.put( "ext" ,ext); return map; } } |
異常處理器修改如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
@exceptionhandler (usernotexistexception. class ) public string handleexception(exception e, httpservletrequest request){ map<string,object> map = new hashmap<>(); //傳入我們自己的錯誤狀態碼 4xx 5xx /** * integer statuscode = (integer) request .getattribute("javax.servlet.error.status_code"); */ request.setattribute( "javax.servlet.error.status_code" , 500 ); map.put( "code" , "user.notexist" ); map.put( "message" , "用戶出錯啦" ); //將自定義錯誤數據放入request中 request.setattribute( "ext" ,map); //轉發到/error return "forward:/error" ; } |
5xx.html頁面代碼如下:
1
2
3
4
5
6
7
8
9
10
|
//... <main role= "main" class = "col-md-9 ml-sm-auto col-lg-10 pt-3 px-4" > <h1>status:[[${status}]]</h1> <h2>timestamp:[[${timestamp}]]</h2> <h2>exception:[[${exception}]]</h2> <h2>message:[[${message}]]</h2> <h2>ext:[[${ext.code}]]</h2> <h2>ext:[[${ext.message}]]</h2> </main> //... |
瀏覽器測試效果如下:
postman測試效果如下:
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持服務器之家。
原文鏈接:https://blog.csdn.net/j080624/article/details/80747669