专栏首页码匠的流水账聊聊spring boot的ErrorWebFluxAutoConfiguration

聊聊spring boot的ErrorWebFluxAutoConfiguration

本文主要研究一下spring boot的ErrorWebFluxAutoConfiguration

ErrorWebFluxAutoConfiguration

spring-boot-autoconfigure-2.1.5.RELEASE-sources.jar!/org/springframework/boot/autoconfigure/web/reactive/error/ErrorWebFluxAutoConfiguration.java

@Configuration
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
@ConditionalOnClass(WebFluxConfigurer.class)
@AutoConfigureBefore(WebFluxAutoConfiguration.class)
@EnableConfigurationProperties({ ServerProperties.class, ResourceProperties.class })
public class ErrorWebFluxAutoConfiguration {

private final ServerProperties serverProperties;

private final ApplicationContext applicationContext;

private final ResourceProperties resourceProperties;

private final List<ViewResolver> viewResolvers;

private final ServerCodecConfigurer serverCodecConfigurer;

public ErrorWebFluxAutoConfiguration(ServerProperties serverProperties,
ResourceProperties resourceProperties,
ObjectProvider<ViewResolver> viewResolversProvider,
ServerCodecConfigurer serverCodecConfigurer,
ApplicationContext applicationContext) {
this.serverProperties = serverProperties;
this.applicationContext = applicationContext;
this.resourceProperties = resourceProperties;
this.viewResolvers = viewResolversProvider.orderedStream()
.collect(Collectors.toList());
this.serverCodecConfigurer = serverCodecConfigurer;
}

@Bean
@ConditionalOnMissingBean(value = ErrorWebExceptionHandler.class,
search = SearchStrategy.CURRENT)
@Order(-1)
public ErrorWebExceptionHandler errorWebExceptionHandler(
ErrorAttributes errorAttributes) {
DefaultErrorWebExceptionHandler exceptionHandler = new DefaultErrorWebExceptionHandler(
errorAttributes, this.resourceProperties,
this.serverProperties.getError(), this.applicationContext);
exceptionHandler.setViewResolvers(this.viewResolvers);
exceptionHandler.setMessageWriters(this.serverCodecConfigurer.getWriters());
exceptionHandler.setMessageReaders(this.serverCodecConfigurer.getReaders());
return exceptionHandler;
}

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

}
  • ErrorWebFluxAutoConfiguration注册了DefaultErrorAttributes、ErrorWebExceptionHandler

ErrorAttributes

spring-boot-2.1.5.RELEASE-sources.jar!/org/springframework/boot/web/reactive/error/ErrorAttributes.java

public interface ErrorAttributes {

/**
 * Return a {@link Map} of the error attributes. The map can be used as the model of
 * an error page, or returned as a {@link ServerResponse} body.
 * @param request the source request
 * @param includeStackTrace if stack trace elements should be included
 * @return a map of error attributes
 */
Map<String, Object> getErrorAttributes(ServerRequest request,
boolean includeStackTrace);

/**
 * Return the underlying cause of the error or {@code null} if the error cannot be
 * extracted.
 * @param request the source ServerRequest
 * @return the {@link Exception} that caused the error or {@code null}
 */
Throwable getError(ServerRequest request);

/**
 * Store the given error information in the current {@link ServerWebExchange}.
 * @param error the {@link Exception} that caused the error
 * @param exchange the source exchange
 */
void storeErrorInformation(Throwable error, ServerWebExchange exchange);

}
  • ErrorAttributes接口定义了getErrorAttributes、getError、storeErrorInformation三个方法

DefaultErrorAttributes

spring-boot-2.1.5.RELEASE-sources.jar!/org/springframework/boot/web/reactive/error/DefaultErrorAttributes.java

public class DefaultErrorAttributes implements ErrorAttributes {

private static final String ERROR_ATTRIBUTE = DefaultErrorAttributes.class.getName()
+ ".ERROR";

private final boolean includeException;

/**
 * Create a new {@link DefaultErrorAttributes} instance that does not include the
 * "exception" attribute.
 */
public DefaultErrorAttributes() {
this(false);
}

/**
 * Create a new {@link DefaultErrorAttributes} instance.
 * @param includeException whether to include the "exception" attribute
 */
public DefaultErrorAttributes(boolean includeException) {
this.includeException = includeException;
}

@Override
public Map<String, Object> getErrorAttributes(ServerRequest request,
boolean includeStackTrace) {
Map<String, Object> errorAttributes = new LinkedHashMap<>();
errorAttributes.put("timestamp", new Date());
errorAttributes.put("path", request.path());
Throwable error = getError(request);
HttpStatus errorStatus = determineHttpStatus(error);
errorAttributes.put("status", errorStatus.value());
errorAttributes.put("error", errorStatus.getReasonPhrase());
errorAttributes.put("message", determineMessage(error));
handleException(errorAttributes, determineException(error), includeStackTrace);
return errorAttributes;
}

private HttpStatus determineHttpStatus(Throwable error) {
if (error instanceof ResponseStatusException) {
return ((ResponseStatusException) error).getStatus();
}
ResponseStatus responseStatus = AnnotatedElementUtils
.findMergedAnnotation(error.getClass(), ResponseStatus.class);
if (responseStatus != null) {
return responseStatus.code();
}
return HttpStatus.INTERNAL_SERVER_ERROR;
}

private String determineMessage(Throwable error) {
if (error instanceof WebExchangeBindException) {
return error.getMessage();
}
if (error instanceof ResponseStatusException) {
return ((ResponseStatusException) error).getReason();
}
ResponseStatus responseStatus = AnnotatedElementUtils
.findMergedAnnotation(error.getClass(), ResponseStatus.class);
if (responseStatus != null) {
return responseStatus.reason();
}
return error.getMessage();
}

private Throwable determineException(Throwable error) {
if (error instanceof ResponseStatusException) {
return (error.getCause() != null) ? error.getCause() : error;
}
return error;
}

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 handleException(Map<String, Object> errorAttributes, Throwable error,
boolean includeStackTrace) {
if (this.includeException) {
errorAttributes.put("exception", error.getClass().getName());
}
if (includeStackTrace) {
addStackTrace(errorAttributes, error);
}
if (error instanceof BindingResult) {
BindingResult result = (BindingResult) error;
if (result.hasErrors()) {
errorAttributes.put("errors", result.getAllErrors());
}
}
}

@Override
public Throwable getError(ServerRequest request) {
return (Throwable) request.attribute(ERROR_ATTRIBUTE)
.orElseThrow(() -> new IllegalStateException(
"Missing exception attribute in ServerWebExchange"));
}

@Override
public void storeErrorInformation(Throwable error, ServerWebExchange exchange) {
exchange.getAttributes().putIfAbsent(ERROR_ATTRIBUTE, error);
}

}
  • DefaultErrorAttributes实现了ErrorAttributes接口,它的getErrorAttributes方法会返回timestamp、path、status、error、message、exception(includeException)、trace(includeStackTrace)等信息;getError方法会从ServerRequest的ERROR_ATTRIBUTE中获取Throwable;storeErrorInformation则是把Throwable存放到ServerWebExchange的attributes中

WebExceptionHandler

spring-web-5.1.7.RELEASE-sources.jar!/org/springframework/web/server/WebExceptionHandler.java

public interface WebExceptionHandler {

/**
 * Handle the given exception. A completion signal through the return value
 * indicates error handling is complete while an error signal indicates the
 * exception is still not handled.
 * @param exchange the current exchange
 * @param ex the exception to handle
 * @return {@code Mono<Void>} to indicate when exception handling is complete
 */
Mono<Void> handle(ServerWebExchange exchange, Throwable ex);

}
  • WebExceptionHandler定义了handle方法

ErrorWebExceptionHandler

spring-boot-2.1.5.RELEASE-sources.jar!/org/springframework/boot/web/reactive/error/ErrorWebExceptionHandler.java

@FunctionalInterface
public interface ErrorWebExceptionHandler extends WebExceptionHandler {

}
  • ErrorWebExceptionHandler继承了WebExceptionHandler接口,仅仅是通过类名来标识它用来render errors

AbstractErrorWebExceptionHandler

public abstract class AbstractErrorWebExceptionHandler
implements ErrorWebExceptionHandler, InitializingBean {

/**
 * Currently duplicated from Spring WebFlux HttpWebHandlerAdapter.
 */
private static final Set<String> DISCONNECTED_CLIENT_EXCEPTIONS;
static {
Set<String> exceptions = new HashSet<>();
exceptions.add("AbortedException");
exceptions.add("ClientAbortException");
exceptions.add("EOFException");
exceptions.add("EofException");
DISCONNECTED_CLIENT_EXCEPTIONS = Collections.unmodifiableSet(exceptions);
}

private static final Log logger = HttpLogging
.forLogName(AbstractErrorWebExceptionHandler.class);

private final ApplicationContext applicationContext;

private final ErrorAttributes errorAttributes;

private final ResourceProperties resourceProperties;

private final TemplateAvailabilityProviders templateAvailabilityProviders;

private List<HttpMessageReader<?>> messageReaders = Collections.emptyList();

private List<HttpMessageWriter<?>> messageWriters = Collections.emptyList();

private List<ViewResolver> viewResolvers = Collections.emptyList();

public AbstractErrorWebExceptionHandler(ErrorAttributes errorAttributes,
ResourceProperties resourceProperties,
ApplicationContext applicationContext) {
Assert.notNull(errorAttributes, "ErrorAttributes must not be null");
Assert.notNull(resourceProperties, "ResourceProperties must not be null");
Assert.notNull(applicationContext, "ApplicationContext must not be null");
this.errorAttributes = errorAttributes;
this.resourceProperties = resourceProperties;
this.applicationContext = applicationContext;
this.templateAvailabilityProviders = new TemplateAvailabilityProviders(
applicationContext);
}

//......

@Override
public void afterPropertiesSet() throws Exception {
if (CollectionUtils.isEmpty(this.messageWriters)) {
throw new IllegalArgumentException("Property 'messageWriters' is required");
}
}

/**
 * Create a {@link RouterFunction} that can route and handle errors as JSON responses
 * or HTML views.
 * <p>
 * If the returned {@link RouterFunction} doesn't route to a {@code HandlerFunction},
 * the original exception is propagated in the pipeline and can be processed by other
 * {@link org.springframework.web.server.WebExceptionHandler}s.
 * @param errorAttributes the {@code ErrorAttributes} instance to use to extract error
 * information
 * @return a {@link RouterFunction} that routes and handles errors
 */
protected abstract RouterFunction<ServerResponse> getRoutingFunction(
ErrorAttributes errorAttributes);

@Override
public Mono<Void> handle(ServerWebExchange exchange, Throwable throwable) {
if (exchange.getResponse().isCommitted()
|| isDisconnectedClientError(throwable)) {
return Mono.error(throwable);
}
this.errorAttributes.storeErrorInformation(throwable, exchange);
ServerRequest request = ServerRequest.create(exchange, this.messageReaders);
return getRoutingFunction(this.errorAttributes).route(request)
.switchIfEmpty(Mono.error(throwable))
.flatMap((handler) -> handler.handle(request))
.doOnNext((response) -> logError(request, response, throwable))
.flatMap((response) -> write(exchange, response));
}

//......

}
  • AbstractErrorWebExceptionHandler声明实现ErrorWebExceptionHandler以及InitializingBean接口;其handle方法首先把throwable存储到errorAttributes汇总,然后通过getRoutingFunction进行route;afterPropertiesSet主要是确保messageWriters不为空;它定义了getRoutingFunction要子类去实现

DefaultErrorWebExceptionHandler

spring-boot-autoconfigure-2.1.5.RELEASE-sources.jar!/org/springframework/boot/autoconfigure/web/reactive/error/DefaultErrorWebExceptionHandler.java

public class DefaultErrorWebExceptionHandler extends AbstractErrorWebExceptionHandler {

private static final Map<HttpStatus.Series, String> SERIES_VIEWS;

static {
Map<HttpStatus.Series, String> views = new EnumMap<>(HttpStatus.Series.class);
views.put(HttpStatus.Series.CLIENT_ERROR, "4xx");
views.put(HttpStatus.Series.SERVER_ERROR, "5xx");
SERIES_VIEWS = Collections.unmodifiableMap(views);
}

private final ErrorProperties errorProperties;

/**
 * Create a new {@code DefaultErrorWebExceptionHandler} instance.
 * @param errorAttributes the error attributes
 * @param resourceProperties the resources configuration properties
 * @param errorProperties the error configuration properties
 * @param applicationContext the current application context
 */
public DefaultErrorWebExceptionHandler(ErrorAttributes errorAttributes,
ResourceProperties resourceProperties, ErrorProperties errorProperties,
ApplicationContext applicationContext) {
super(errorAttributes, resourceProperties, applicationContext);
this.errorProperties = errorProperties;
}

@Override
protected RouterFunction<ServerResponse> getRoutingFunction(
ErrorAttributes errorAttributes) {
return route(acceptsTextHtml(), this::renderErrorView).andRoute(all(),
this::renderErrorResponse);
}

/**
 * Render the error information as an HTML view.
 * @param request the current request
 * @return a {@code Publisher} of the HTTP response
 */
protected Mono<ServerResponse> renderErrorView(ServerRequest request) {
boolean includeStackTrace = isIncludeStackTrace(request, MediaType.TEXT_HTML);
Map<String, Object> error = getErrorAttributes(request, includeStackTrace);
HttpStatus errorStatus = getHttpStatus(error);
ServerResponse.BodyBuilder responseBody = ServerResponse.status(errorStatus)
.contentType(MediaType.TEXT_HTML);
return Flux
.just("error/" + errorStatus.value(),
"error/" + SERIES_VIEWS.get(errorStatus.series()), "error/error")
.flatMap((viewName) -> renderErrorView(viewName, responseBody, error))
.switchIfEmpty(this.errorProperties.getWhitelabel().isEnabled()
? renderDefaultErrorView(responseBody, error)
: Mono.error(getError(request)))
.next();
}

/**
 * Render the error information as a JSON payload.
 * @param request the current request
 * @return a {@code Publisher} of the HTTP response
 */
protected Mono<ServerResponse> renderErrorResponse(ServerRequest request) {
boolean includeStackTrace = isIncludeStackTrace(request, MediaType.ALL);
Map<String, Object> error = getErrorAttributes(request, includeStackTrace);
return ServerResponse.status(getHttpStatus(error))
.contentType(MediaType.APPLICATION_JSON_UTF8)
.body(BodyInserters.fromObject(error));
}

/**
 * Determine if the stacktrace attribute should be included.
 * @param request the source request
 * @param produces the media type produced (or {@code MediaType.ALL})
 * @return if the stacktrace attribute should be included
 */
protected boolean isIncludeStackTrace(ServerRequest request, MediaType produces) {
ErrorProperties.IncludeStacktrace include = this.errorProperties
.getIncludeStacktrace();
if (include == ErrorProperties.IncludeStacktrace.ALWAYS) {
return true;
}
if (include == ErrorProperties.IncludeStacktrace.ON_TRACE_PARAM) {
return isTraceEnabled(request);
}
return false;
}

/**
 * Get the HTTP error status information from the error map.
 * @param errorAttributes the current error information
 * @return the error HTTP status
 */
protected HttpStatus getHttpStatus(Map<String, Object> errorAttributes) {
int statusCode = (int) errorAttributes.get("status");
return HttpStatus.valueOf(statusCode);
}

/**
 * Predicate that checks whether the current request explicitly support
 * {@code "text/html"} media type.
 * <p>
 * The "match-all" media type is not considered here.
 * @return the request predicate
 */
protected RequestPredicate acceptsTextHtml() {
return (serverRequest) -> {
try {
List<MediaType> acceptedMediaTypes = serverRequest.headers().accept();
acceptedMediaTypes.remove(MediaType.ALL);
MediaType.sortBySpecificityAndQuality(acceptedMediaTypes);
return acceptedMediaTypes.stream()
.anyMatch(MediaType.TEXT_HTML::isCompatibleWith);
}
catch (InvalidMediaTypeException ex) {
return false;
}
};
}

}
  • DefaultErrorWebExceptionHandler继承了AbstractErrorWebExceptionHandler;其getRoutingFunction方法会对acceptsTextHtml的renderErrorView,其他的通过renderErrorResponse来返回json格式的错误信息

ExceptionHandlingWebHandler

spring-web-5.1.7.RELEASE-sources.jar!/org/springframework/web/server/handler/ExceptionHandlingWebHandler.java

public class ExceptionHandlingWebHandler extends WebHandlerDecorator {


private final List<WebExceptionHandler> exceptionHandlers;


public ExceptionHandlingWebHandler(WebHandler delegate, List<WebExceptionHandler> handlers) {
super(delegate);
this.exceptionHandlers = Collections.unmodifiableList(new ArrayList<>(handlers));
}


/**
 * Return a read-only list of the configured exception handlers.
 */
public List<WebExceptionHandler> getExceptionHandlers() {
return this.exceptionHandlers;
}


@Override
public Mono<Void> handle(ServerWebExchange exchange) {

Mono<Void> completion;
try {
completion = super.handle(exchange);
}
catch (Throwable ex) {
completion = Mono.error(ex);
}

for (WebExceptionHandler handler : this.exceptionHandlers) {
completion = completion.onErrorResume(ex -> handler.handle(exchange, ex));
}

return completion;
}

}
  • ExceptionHandlingWebHandler继承了WebHandlerDecorator,它会挨个调用WebExceptionHandler的handle方法

小结

  • ErrorWebFluxAutoConfiguration注册了DefaultErrorAttributes、ErrorWebExceptionHandler
  • DefaultErrorAttributes实现了ErrorAttributes接口,它的getErrorAttributes方法会返回timestamp、path、status、error、message、exception(includeException)、trace(includeStackTrace)等信息;getError方法会从ServerRequest的ERROR_ATTRIBUTE中获取Throwable;storeErrorInformation则是把Throwable存放到ServerWebExchange的attributes中
  • DefaultErrorWebExceptionHandler继承了AbstractErrorWebExceptionHandler;其getRoutingFunction方法会对acceptsTextHtml的renderErrorView,其他的通过renderErrorResponse来返回json格式的错误信息

doc

  • ErrorWebFluxAutoConfiguration

本文分享自微信公众号 - 码匠的流水账(geek_luandun)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-07-09

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • CopyOnWriteArrayList实现原理及源码分析

    CopyOnWriteArrayList是Java并发包中提供的一个并发容器,它是个线程安全且读操作无锁的ArrayList,写操作则通过创建底层数组的新副本来...

    胖虎
  • ActiveMQ 客户端的开发

    上篇文章 ActiveMQ 服务器的部署 实现了 ActiveMQ 服务器的部署,本文分别以官方 API、Spring、SpringBoot 三种方式,实现 A...

    AiSmart4J
  • 单例模式

    这篇是我开始进入设计模式的第一篇文章,Java一共有23种设计模式,我曾看过《大话设计模式》,书的内容当然都是干货满满,我当时看的是电子版,让我一度从入门到放弃...

    胖虎
  • 抽象类和接口的区别

    定义一个抽象类AbstractDoor,作为门的基类,同时定义一个alarmable的接口.(alarmable是我自己写的,我不知道有没有这个单词,大家懂就好...

    呼延十
  • LeetCode-8 字符串转换整数

    今天我们学习第8题字符串转换整数,这是一个字符串的中等题,像这样字符串的题目经常作为面试题来考察面试者算法能力和写代码能力,因此最好能手写出该题。下面我...

    用户3470542
  • dubbo 的 spi 思想

    spi,简单来说,就是 service provider interface,说白了是什么意思呢,比如你有个接口,现在这个接口有 3 个实现类,那么在系统运行的...

    AiSmart4J
  • Arraylist和linkedlist的区别

    ArrayList和LinkedList可以说是日常业务开发中最常使用的容器类了,同时,他们的区别也是面试高发区,虽然很简单,但是我们总是不能说的完整,今天就通...

    呼延十
  • JavaBean基于注解实现校验

    前言 上一文我通过传递不合法参数触发异常,进行了统一拦截,那么这篇文章主要介绍JSR303,Hibernate Validator详细讲解及如何优雅的对参数进行...

    胖虎
  • ActiveMQ 服务器的部署

    ActiveMQ 是 Apache 流行、强大的开源消息传递和集成模式服务器,它完全支持 JMS 1.1(严格实现“点对点”和“发布/订阅”两种消息模型) 和 ...

    AiSmart4J
  • LeetCode-22括号生成

    今天我们学习第22题括号生成,这是一道中等题。像这样字符串的题目经常作为面试题来考察面试者算法能力和写代码能力,因此最好能手写出该题。下面我们看看这道题...

    用户3470542

扫码关注云+社区

领取腾讯云代金券