Spring Cloud Gateway-自定义异常处理
参考 https://blog.csdn.net/suyuaidan/article/details/132663141,写法不同于注入方式不一样
ErrorWebFluxAutoConfiguration
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
@ConditionalOnClass(WebFluxConfigurer.class)
@AutoConfigureBefore(WebFluxAutoConfiguration.class)
@EnableConfigurationProperties({ ServerProperties.class, ResourceProperties.class })
public class ErrorWebFluxAutoConfiguration {
private final ServerProperties serverProperties;
public ErrorWebFluxAutoConfiguration(ServerProperties serverProperties) {
this.serverProperties = serverProperties;
}
@Bean
@ConditionalOnMissingBean(value = ErrorWebExceptionHandler.class, search = SearchStrategy.CURRENT)
@Order(-1)
public ErrorWebExceptionHandler errorWebExceptionHandler(ErrorAttributes errorAttributes,
ResourceProperties resourceProperties, ObjectProvider<ViewResolver> viewResolvers,
ServerCodecConfigurer serverCodecConfigurer, ApplicationContext applicationContext) {
DefaultErrorWebExceptionHandler exceptionHandler = new DefaultErrorWebExceptionHandler(errorAttributes,
resourceProperties, this.serverProperties.getError(), applicationContext);
exceptionHandler.setViewResolvers(viewResolvers.orderedStream().collect(Collectors.toList()));
exceptionHandler.setMessageWriters(serverCodecConfigurer.getWriters());
exceptionHandler.setMessageReaders(serverCodecConfigurer.getReaders());
return exceptionHandler;
}
@Bean
@ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)
public DefaultErrorAttributes errorAttributes() {
return new DefaultErrorAttributes();
}
}
DefaultErrorWebExceptionHandler
public class DefaultErrorWebExceptionHandler extends AbstractErrorWebExceptionHandler {
private static final MediaType TEXT_HTML_UTF8 = new MediaType("text", "html", StandardCharsets.UTF_8);
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) {
Map<String, Object> error = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.TEXT_HTML));
int errorStatus = getHttpStatus(error);
ServerResponse.BodyBuilder responseBody = ServerResponse.status(errorStatus).contentType(TEXT_HTML_UTF8);
return Flux.just(getData(errorStatus).toArray(new String[] {}))
.flatMap((viewName) -> renderErrorView(viewName, responseBody, error))
.switchIfEmpty(this.errorProperties.getWhitelabel().isEnabled()
? renderDefaultErrorView(responseBody, error) : Mono.error(getError(request)))
.next();
}
private List<String> getData(int errorStatus) {
List<String> data = new ArrayList<>();
data.add("error/" + errorStatus);
HttpStatus.Series series = HttpStatus.Series.resolve(errorStatus);
if (series != null) {
data.add("error/" + SERIES_VIEWS.get(series));
}
data.add("error/error");
return data;
}
/**
* 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) {
Map<String, Object> error = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL));
return ServerResponse.status(getHttpStatus(error)).contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromValue(error));
}
protected ErrorAttributeOptions getErrorAttributeOptions(ServerRequest request, MediaType mediaType) {
ErrorAttributeOptions options = ErrorAttributeOptions.defaults();
if (this.errorProperties.isIncludeException()) {
options = options.including(Include.EXCEPTION);
}
if (isIncludeStackTrace(request, mediaType)) {
options = options.including(Include.STACK_TRACE);
}
if (isIncludeMessage(request, mediaType)) {
options = options.including(Include.MESSAGE);
}
if (isIncludeBindingErrors(request, mediaType)) {
options = options.including(Include.BINDING_ERRORS);
}
return options;
}
/**
* 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
*/
@SuppressWarnings("deprecation")
protected boolean isIncludeStackTrace(ServerRequest request, MediaType produces) {
switch (this.errorProperties.getIncludeStacktrace()) {
case ALWAYS:
return true;
case ON_PARAM:
case ON_TRACE_PARAM:
return isTraceEnabled(request);
default:
return false;
}
}
/**
* Determine if the message attribute should be included.
* @param request the source request
* @param produces the media type produced (or {@code MediaType.ALL})
* @return if the message attribute should be included
*/
protected boolean isIncludeMessage(ServerRequest request, MediaType produces) {
switch (this.errorProperties.getIncludeMessage()) {
case ALWAYS:
return true;
case ON_PARAM:
return isMessageEnabled(request);
default:
return false;
}
}
/**
* Determine if the errors attribute should be included.
* @param request the source request
* @param produces the media type produced (or {@code MediaType.ALL})
* @return if the errors attribute should be included
*/
protected boolean isIncludeBindingErrors(ServerRequest request, MediaType produces) {
switch (this.errorProperties.getIncludeBindingErrors()) {
case ALWAYS:
return true;
case ON_PARAM:
return isBindingErrorsEnabled(request);
default:
return false;
}
}
/**
* Get the HTTP error status information from the error map.
* @param errorAttributes the current error information
* @return the error HTTP status
*/
protected int getHttpStatus(Map<String, Object> errorAttributes) {
return (int) errorAttributes.get("status");
}
/**
* 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.removeIf(MediaType.ALL::equalsTypeAndSubtype);
MediaType.sortBySpecificityAndQuality(acceptedMediaTypes);
return acceptedMediaTypes.stream().anyMatch(MediaType.TEXT_HTML::isCompatibleWith);
}
catch (InvalidMediaTypeException ex) {
return false;
}
};
}
}
观察上面的代码,我们可以知道我们要做两步工作
- 全局处理异常怎么操作(实现ErrorWebExceptionHandler,或者直接继承他的子类DefaultErrorWebExceptionHandler)
- 如何把异常处理注入到框架中(自定CustomErrorWebFluxAutoConfiguration)
自定义ErrorWebExceptionHandler
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.autoconfigure.web.ResourceProperties;
import org.springframework.boot.autoconfigure.web.reactive.error.DefaultErrorWebExceptionHandler;
import org.springframework.boot.web.reactive.error.ErrorAttributes;
import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.*;
import reactor.core.publisher.Mono;
import java.util.HashMap;
import java.util.Map;
/**
* 自定义异常处理器
*/
@Slf4j
public class CustomErrorWebExceptionHandler extends DefaultErrorWebExceptionHandler {
public CustomErrorWebExceptionHandler(ErrorAttributes errorAttributes, ResourceProperties resourceProperties,
ErrorProperties errorProperties, ApplicationContext applicationContext) {
super(errorAttributes, resourceProperties, errorProperties, applicationContext);
}
@Override
protected Mono<ServerResponse> renderErrorResponse(ServerRequest request) {
// 原始的异常信息可以用getError方法取得
Throwable throwable = getError(request);
// 这里和父类的做法一样,取得DefaultErrorAttributes整理出来的所有异常信息
Map<String, Object> errorAttributes = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL));
HttpStatus status = HttpStatus.valueOf((Integer) errorAttributes.get("status"));
// todo 写你自己的逻辑也可根据status封装不同的结果集枚举
Map<String, Object> responseBodyMap = new HashMap<>();
responseBodyMap.put("code", "my error code");
responseBodyMap.put("msg", throwable.getMessage());
return ServerResponse
// http返回码
.status(HttpStatus.INTERNAL_SERVER_ERROR)
// 类型和以前一样
.contentType(MediaType.APPLICATION_JSON)
// 响应body的内容
.body(BodyInserters.fromValue(responseBodyMap));
}
@Override
protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse);
}
}
自定义CustomErrorWebFluxAutoConfiguration
import com.hatzi.gateway.plus.handler.CustomErrorWebExceptionHandler;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.SearchStrategy;
import org.springframework.boot.autoconfigure.web.ResourceProperties;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.reactive.error.DefaultErrorAttributes;
import org.springframework.boot.web.reactive.error.ErrorAttributes;
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.web.reactive.config.WebFluxConfigurer;
import org.springframework.web.reactive.result.view.ViewResolver;
import java.util.List;
import java.util.stream.Collectors;
/**
* @Description
* @Author weiwenbin
* @Date 2024/6/6 15:01
*/
@Configuration
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
@ConditionalOnClass(WebFluxConfigurer.class)
@AutoConfigureBefore(WebFluxAutoConfiguration.class)
@EnableConfigurationProperties({ServerProperties.class, ResourceProperties.class})
public class CustomErrorWebFluxAutoConfiguration {
private final ServerProperties serverProperties;
private final ApplicationContext applicationContext;
private final ResourceProperties resourceProperties;
private final List<ViewResolver> viewResolvers;
private final ServerCodecConfigurer serverCodecConfigurer;
public CustomErrorWebFluxAutoConfiguration(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(Ordered.HIGHEST_PRECEDENCE)
public ErrorWebExceptionHandler errorWebExceptionHandler(ErrorAttributes errorAttributes) {
CustomErrorWebExceptionHandler exceptionHandler = new CustomErrorWebExceptionHandler(
errorAttributes,
resourceProperties,
this.serverProperties.getError(),
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();
}
}