前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >聊聊spring boot的ErrorWebFluxAutoConfiguration

聊聊spring boot的ErrorWebFluxAutoConfiguration

原创
作者头像
code4it
修改2019-07-10 11:02:22
1.2K0
修改2019-07-10 11:02:22
举报
文章被收录于专栏:码匠的流水账码匠的流水账

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

ErrorWebFluxAutoConfiguration

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

代码语言:javascript
复制
@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

代码语言:javascript
复制
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

代码语言:javascript
复制
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

代码语言:javascript
复制
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

代码语言:javascript
复制
@FunctionalInterface
public interface ErrorWebExceptionHandler extends WebExceptionHandler {
​
}
  • ErrorWebExceptionHandler继承了WebExceptionHandler接口,仅仅是通过类名来标识它用来render errors

AbstractErrorWebExceptionHandler

代码语言:javascript
复制
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

代码语言:javascript
复制
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

代码语言:javascript
复制
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

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • ErrorWebFluxAutoConfiguration
  • ErrorAttributes
    • DefaultErrorAttributes
    • WebExceptionHandler
      • ErrorWebExceptionHandler
        • AbstractErrorWebExceptionHandler
          • DefaultErrorWebExceptionHandler
          • ExceptionHandlingWebHandler
          • 小结
          • doc
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档