聊聊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(www.yfzc7.com ) 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 www.yuechaoyule.com the pipeline and can be processed by other
  
   * {@link org.springframework. www.kunlunyule.com.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(www.htaizx2012.com )
  
  || 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) -http://www.zzhehong.com/chaoyue/> handler.handle(request))
  
  .doOnNext((response) http://www.wanxinyulept.com/chaoyue/-> 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

转载于:https://www.cnblogs.com/qwangxiao/p/11161331.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值