这篇文章主要介绍了SpringBoot 错误处理机制与自定义错误处理实现详解,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
【1】springboot的默认错误处理
① 浏览器访问
请求头如下:
② 使用“postman”访问{
"timestamp": 1529479254647,
"status": 404,
"error": "not found",
"message": "no message available",
"path": "/aaa1"
}
请求头如下:
总结:如果是浏览器访问,则springboot默认返回错误页面;如果是其他客户端访问,则默认返回json数据。
【2】默认错误处理原理
springboot默认配置了许多xxxautoconfiguration,这里我们找errormvcautoconfiguration。
其注册部分组件如下:
① defaulterrorattributes@bean
@conditionalonmissingbean(value = errorattributes.class, search = searchstrategy.current)
public defaulterrorattributes errorattributes() {
return new defaulterrorattributes();
}
跟踪其源码如下: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@bean
@conditionalonmissingbean(value = errorcontroller.class, search = searchstrategy.current)
public basicerrorcontroller basicerrorcontroller(errorattributes errorattributes) {
return new basicerrorcontroller(errorattributes, this.serverproperties.geterror(),
this.errorviewresolvers);
}
跟踪其源码:@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);方法跟踪如下: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@bean
public errorpagecustomizer errorpagecustomizer() {
return new errorpagecustomizer(this.serverproperties);
}
跟踪其源码:@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@bean
@conditionalonbean(dispatcherservlet.class)
@conditionalonmissingbean
public defaulterrorviewresolver conventionerrorviewresolver() {
return new defaulterrorviewresolver(this.applicationcontext,
this.resourceproperties);
}
跟踪其源码: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源码如下:@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的异常处理器@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请求进行自适应效果处理@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()方法获取,如下所示:@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方法将我们的错误数据添加进去。
示例如下://给容器中加入我们自己定义的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;
}
}
异常处理器修改如下:@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页面代码如下://...
<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测试效果如下:
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持CodeAE代码之家 。
原文链接:https://blog.csdn.net/j080624/article/details/80747669