深入学习ErrorWebExceptionHandler
Posted aofengdaxia
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入学习ErrorWebExceptionHandler相关的知识,希望对你有一定的参考价值。
我们来学习和了解下GatewayExceptionHandler
有助于我们处理spring gateway和webFlux中的异常自定义处理。
它继承自ErrorWebExceptionHandler
,
类关系图如下:
通过类上述关系图,我们可以看到,DefaultErrorWebExceptionHandler
是ErrorWebExceptionHandler
的实现类,如果我们不自定义类异常处理器,系统将自动装配DefaultErrorWebExceptionHandler
。
我们就来庖丁解牛,一步步地来学习下这一组类和接口。
AbstractErrorWebExceptionHandler
AbstractErrorWebExceptionHandler
实现了ErrorWebExceptionHandler
接口,其中handle
方法是其核心方法。我们来看下其实现代码:
public Mono<Void> handle(ServerWebExchange exchange, Throwable throwable)
// 判断response是否已经提交,并且调用isDisconnectedClientError方法判断是否是客户端断开连接。
// 如果已经断开连接,已经无法将消息发送给客户端,直接Mono.error(throwable)抛出异常。
if (!exchange.getResponse().isCommitted() && !this.isDisconnectedClientError(throwable))
// exchange.getAttributes().putIfAbsent(ERROR_INTERNAL_ATTRIBUTE, error);
this.errorAttributes.storeErrorInformation(throwable, exchange);
// 创建request
ServerRequest request = ServerRequest.create(exchange, this.messageReaders);
return this.getRoutingFunction(this.errorAttributes).route(request).switchIfEmpty(Mono.error(throwable)).flatMap((handler) ->
return handler.handle(request);
).doOnNext((response) ->
this.logError(request, response, throwable);
).flatMap((response) ->
return this.write(exchange, response);
);
else
return Mono.error(throwable);
通过Handle方法的代码我们看出主要是实现了以下的事项:
- 判断response是否已经提交,并且调用isDisconnectedClientError方法判断是否是客户端断开连接。
- 如果已经断开连接,已经无法将消息发送给客户端,直接Mono.error(throwable)抛出异常。
- 将异常信息存储到exchange中。
- 创建request。
- 获取路由函数。
- 调用路由函数的handle方法。
- 然后执行日志记录。
- 最后将response写入到exchange中。
isDisconnectedClientError方法
private boolean isDisconnectedClientError(Throwable ex)
return DISCONNECTED_CLIENT_EXCEPTIONS.contains(ex.getClass().getSimpleName()) ||this.isDisconnectedClientErrorMessage(NestedExceptionUtils.getMostSpecificCause(ex).getMessage());
DISCONNECTED_CLIENT_EXCEPTIONS是一个集合,里面存放了一些异常信息,如果是这些异常信息, 就认为是客户端断开连接了。 或者通过isDisconnectedClientErrorMessage方法判断是否是客户端断开连接的异常信息。
下面我们来看看isDisconnectedClientErrorMessage方法的实现。
isDisconnectedClientErrorMessage方法:
private boolean isDisconnectedClientErrorMessage(String message)
message = message != null ? message.toLowerCase() : "";
return message.contains("broken pipe") || message.contains("connection reset by peer");
上述代码的含义是:如果异常信息中包含“broken pipe”或者“connection reset by peer”,就认为是客户端断开连接了。
总结
综合起来,isDisconnectedClientError方法的含义是:如果DISCONNECTED_CLIENT_EXCEPTIONS集合中包含异常信息的类名或者异常信息中包含“broken pipe”或者“connection reset by peer”,就认为是客户端断开连接了。
NestedExceptionUtils
而isDisconnectedClientErrorMessage方法的参数message来自于NestedExceptionUtils.getMostSpecificCause(ex).getMessage()。我们进一步跟踪源码,可以看到NestedExceptionUtils.getMostSpecificCause(ex)方法的源码如下:
public static Throwable getMostSpecificCause(Throwable ex)
Throwable cause;
Throwable result = ex;
while(null != (cause = result.getCause()) && (result != cause))
result = cause;
return result;
上述代码的含义是:如果ex.getCause()不为空,并且ex.getCause()不等于ex,就将ex.getCause()赋值给result,然后继续执行while循环。直到ex.getCause()为空或者ex.getCause()等于ex,就返回result。
getRoutingFunction
我们看到获得路由调用的是getRoutingFunction
方法,而这个方法是一个抽象方法,需要在具体的继承类中实现。
logError
protected void logError(ServerRequest request, ServerResponse response, Throwable throwable)
if (logger.isDebugEnabled())
logger.debug(request.exchange().getLogPrefix() + this.formatError(throwable, request));
if (HttpStatus.resolve(response.rawStatusCode()) != null && response.statusCode().equals(HttpStatus.INTERNAL_SERVER_ERROR))
logger.error(LogMessage.of(() ->
return String.format("%s 500 Server Error for %s", request.exchange().getLogPrefix(), this.formatRequest(request));
), throwable);
上述代码的含义:
- 如果是debug级别的日志,就以debug的方式打印异常信息。
- 如果response的状态码是500,就打印异常信息。
用到的formatError方法就是简单的讲错误格式化,代码不再一一分析。
write
在把内容写入的时候用到了私有方法write,我们看看write方法的代码如下:
private Mono<? extends Void> write(ServerWebExchange exchange, ServerResponse response)
exchange.getResponse().getHeaders().setContentType(response.headers().getContentType());
return response.writeTo(exchange, new AbstractErrorWebExceptionHandler.ResponseContext());
上述代码的含义是:将response的contentType设置到exchange的response中,然后调用response的writeTo方法,将response写入到exchange中。
ResponseContext是一个内部类,主要是携带了外部类中的viewResolvers和messageWriters属性。
其他的方法
afterPropertiesSet
public void afterPropertiesSet() throws Exception
if (CollectionUtils.isEmpty(this.messageWriters))
throw new IllegalArgumentException("Property 'messageWriters' is required");
afterPropertiesSet主要是通过实现InitializingBean接口,实现afterPropertiesSet方法,判断messageWriters是否为空,如果为空就抛出异常。
renderDefaultErrorView
protected Mono<ServerResponse> renderDefaultErrorView(BodyBuilder responseBody, Map<String, Object> error)
StringBuilder builder = new StringBuilder();
Date timestamp = (Date)error.get("timestamp");
Object message = error.get("message");
Object trace = error.get("trace");
Object requestId = error.get("requestId");
builder.append("<html><body><h1>Whitelabel Error Page</h1>").append("<p>This application has no configured error view, so you are seeing this as a fallback.</p>").append("<div id='created'>").append(timestamp).append("</div>").append("<div>[").append(requestId).append("] There was an unexpected error (type=").append(this.htmlEscape(error.get("error"))).append(", status=").append(this.htmlEscape(error.get("status"))).append(").</div>");
if (message != null)
builder.append("<div>").append(this.htmlEscape(message)).append("</div>");
if (trace != null)
builder.append("<div style='white-space:pre-wrap;'>").append(this.htmlEscape(trace)).append("</div>");
builder.append("</body></html>");
return responseBody.bodyValue(builder.toString());
renderDefaultErrorView方法主要是渲染默认的错误页面,如果没有自定义的错误页面,就会使用这个方法渲染默认的错误页面。
renderErrorView
protected Mono<ServerResponse> renderErrorView(String viewName, BodyBuilder responseBody, Map<String, Object> error)
if (this.isTemplateAvailable(viewName))
return responseBody.render(viewName, error);
else
Resource resource = this.resolveResource(viewName);
return resource != null ? responseBody.body(BodyInserters.fromResource(resource)) : Mono.empty();
上述代码的含义是:
- 如果viewName对应的模板存在,就使用模板渲染错误页面。
- 否则就使用静态资源渲染错误页面。
在使用资源渲染的时候,调用resolveResource方法,代码如下:
private Resource resolveResource(String viewName)
// 获得所有的静态资源的路径
String[] var2 = this.resources.getStaticLocations();
int var3 = var2.length;
// 遍历所有的静态资源
for(int var4 = 0; var4 < var3; ++var4)
String location = var2[var4];
try
// 获得静态资源
Resource resource = this.applicationContext.getResource(location);
// 获得静态资源的文件
resource = resource.createRelative(viewName + ".html");
if (resource.exists())
return resource;
catch (Exception var7)
return null;
DefaultErrorWebExceptionHandler
DefaultErrorWebExceptionHandler是ErrorWebExceptionHandler的默认实现,继承了AbstractErrorWebExceptionHandler。通过对AbstractErrorWebExceptionHandler的分析,我们知道了大致实现原理,下面我们来看看DefaultErrorWebExceptionHandler的具体实现。
getRoutingFunction
在AbstractErrorWebExceptionHandler中,getRoutingFunction方法是一个抽象方法,需要子类实现。DefaultErrorWebExceptionHandler的getRoutingFunction方法的实现如下:
protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes)
深入了解机器学习