使用 Spring boot + WebFlux 的全局错误处理
Global error handling using Spring boot + WebFlux
在 Spring boot rest controller 中使用响应式编程时,我们如何全局处理异常?
我认为 @ControllerAdvice
不会起作用,因为我已经尝试过但没有成功。
我目前的另一个尝试是这个选项,使用自定义属性:
@Component
public class OsvcErrorAttributes extends DefaultErrorAttributes {
public OsvcErrorAttributes() {
super(true);
}
@Override
public Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) {
return assembleError(request);
}
private Map<String, Object> assembleError(ServerRequest request) {
ServerException serverException = (ServerException)getError(request);
Map<String, Object> errorAttributes = new HashMap<>();
errorAttributes.put("message", serverException.getMessage());
errorAttributes.put("errors", serverException.getErrorMap());
return errorAttributes;
}
}
WebExceptionHandler 是这样的:
@Component
@Order(-2)
public class OsvcErrorHandler extends AbstractErrorWebExceptionHandler {
public OsvcErrorHandler(ErrorAttributes errorAttributes,
ResourceProperties resourceProperties,
ApplicationContext applicationContext) {
super(errorAttributes, resourceProperties, applicationContext);
// TODO: 25.06.2019 temporary workaround
ServerCodecConfigurer serverCodecConfigurer = new DefaultServerCodecConfigurer();
setMessageWriters(serverCodecConfigurer.getWriters());
setMessageReaders(serverCodecConfigurer.getReaders());
}
@Override
protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse);
}
private Mono<ServerResponse> renderErrorResponse(ServerRequest serverRequest) {
final Map<String, Object> errorAttributes = getErrorAttributes(serverRequest, true);
return ServerResponse.status(HttpStatus.BAD_REQUEST)
.contentType(MediaType.APPLICATION_JSON_UTF8)
.body(BodyInserters.fromObject(errorAttributes));
}
}
产生错误的代码:
@Data
@Service
public class ContactService {
private final ContactRepository contactRepository;
public Mono<Business> saveNewContact(Business business) {
return contactRepository.save(business)
.onErrorMap(throwable ->
ServerException.create(throwable.getMessage())
.persistError("ico", business.getIco(), "ICO is probably duplicate"));
}
}
问题是这也不起作用。我确实遵循了本教程,但我看不出我是否有错误。
尝试注入 ServerCodecConfigurer 而不是实例化它。我在执行此操作时还注入了一个 ViewResolversProvider
,尽管可能没有必要。
public OsvcErrorHandler(
final CustomErrorAttributes customAttributes,
final ResourceProperties resourceProperties,
final ObjectProvider<List<ViewResolver>> viewResolversProvider,
final ServerCodecConfigurer serverCodecConfigurer,
final ApplicationContext applicationContext
) {
super(customAttributes, resourceProperties, applicationContext);
this.setViewResolvers(viewResolversProvider.getIfAvailable(Collections::emptyList));
this.setMessageWriters(serverCodecConfigurer.getWriters());
this.setMessageReaders(serverCodecConfigurer.getReaders());
}
您只需像这样在全局错误处理程序构造函数中使用 ServerCodecConfigurer 注入。
public OsvcErrorHandler(GlobalErrorAttributes errorAttributes, ApplicationContext applicationContext,
ServerCodecConfigurer serverCodecConfigurer) {
super(errorAttributes, new ResourceProperties(), applicationContext);
super.setMessageWriters(serverCodecConfigurer.getWriters());
super.setMessageReaders(serverCodecConfigurer.getReaders());
}
请在 git repository 中找到代码示例。
您需要将 ErrorWebExceptionHandler
定义和实现为一个 bean,并设置一个 @Order
值小于 -1
的注解,因为这是 Spring 的默认值DefaultErrorWebExceptionHandler
这是一个示例实现:
public class GlobalErrorHandler extends DefaultErrorWebExceptionHandler {
public GlobalErrorHandler(
final ErrorAttributes errorAttributes,
final WebProperties.Resources resources,
final ErrorProperties errorProperties,
final ApplicationContext applicationContext) {
super(errorAttributes, resources, errorProperties, applicationContext);
}
@Override
public Mono<Void> handle(final ServerWebExchange exchange, final Throwable ex) {
final ServerHttpResponse response = exchange.getResponse();
if (ex instanceof IllegalStateException
&& StringUtils.equals("Session was invalidated", ex.getMessage())
&& response.getStatusCode().is3xxRedirection()) {
final DataBufferFactory bufferFactory = exchange.getResponse().bufferFactory();
return response.writeWith(Mono.just(bufferFactory.wrap("Redirecting...".getBytes())));
}
return super.handle(exchange, ex);
}
}
这是基于 org.springframework.boot.autoconfigure.web.reactive.error.ErrorWebFluxAutoConfiguration
class:
的示例配置
@Configuration
public class ErrorWebFluxAutoConfiguration {
private final ServerProperties serverProperties;
public ErrorWebFluxAutoConfiguration(final ServerProperties serverProperties) {
this.serverProperties = serverProperties;
}
@Bean
@Order(-2)
public ErrorWebExceptionHandler errorWebExceptionHandler(
final ErrorAttributes errorAttributes,
final org.springframework.boot.autoconfigure.web.ResourceProperties resourceProperties,
final WebProperties webProperties,
final ObjectProvider<ViewResolver> viewResolvers,
final ServerCodecConfigurer serverCodecConfigurer,
final ApplicationContext applicationContext) {
final GlobalErrorHandler exceptionHandler =
new GlobalErrorHandler(
errorAttributes,
resourceProperties.hasBeenCustomized()
? resourceProperties
: webProperties.getResources(),
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();
}
}
感谢 this 文章指出我可以使用 ErrorWebExceptionHandler
。
在 Spring boot rest controller 中使用响应式编程时,我们如何全局处理异常?
我认为 @ControllerAdvice
不会起作用,因为我已经尝试过但没有成功。
我目前的另一个尝试是这个选项,使用自定义属性:
@Component
public class OsvcErrorAttributes extends DefaultErrorAttributes {
public OsvcErrorAttributes() {
super(true);
}
@Override
public Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) {
return assembleError(request);
}
private Map<String, Object> assembleError(ServerRequest request) {
ServerException serverException = (ServerException)getError(request);
Map<String, Object> errorAttributes = new HashMap<>();
errorAttributes.put("message", serverException.getMessage());
errorAttributes.put("errors", serverException.getErrorMap());
return errorAttributes;
}
}
WebExceptionHandler 是这样的:
@Component
@Order(-2)
public class OsvcErrorHandler extends AbstractErrorWebExceptionHandler {
public OsvcErrorHandler(ErrorAttributes errorAttributes,
ResourceProperties resourceProperties,
ApplicationContext applicationContext) {
super(errorAttributes, resourceProperties, applicationContext);
// TODO: 25.06.2019 temporary workaround
ServerCodecConfigurer serverCodecConfigurer = new DefaultServerCodecConfigurer();
setMessageWriters(serverCodecConfigurer.getWriters());
setMessageReaders(serverCodecConfigurer.getReaders());
}
@Override
protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse);
}
private Mono<ServerResponse> renderErrorResponse(ServerRequest serverRequest) {
final Map<String, Object> errorAttributes = getErrorAttributes(serverRequest, true);
return ServerResponse.status(HttpStatus.BAD_REQUEST)
.contentType(MediaType.APPLICATION_JSON_UTF8)
.body(BodyInserters.fromObject(errorAttributes));
}
}
产生错误的代码:
@Data
@Service
public class ContactService {
private final ContactRepository contactRepository;
public Mono<Business> saveNewContact(Business business) {
return contactRepository.save(business)
.onErrorMap(throwable ->
ServerException.create(throwable.getMessage())
.persistError("ico", business.getIco(), "ICO is probably duplicate"));
}
}
问题是这也不起作用。我确实遵循了本教程,但我看不出我是否有错误。
尝试注入 ServerCodecConfigurer 而不是实例化它。我在执行此操作时还注入了一个 ViewResolversProvider
,尽管可能没有必要。
public OsvcErrorHandler(
final CustomErrorAttributes customAttributes,
final ResourceProperties resourceProperties,
final ObjectProvider<List<ViewResolver>> viewResolversProvider,
final ServerCodecConfigurer serverCodecConfigurer,
final ApplicationContext applicationContext
) {
super(customAttributes, resourceProperties, applicationContext);
this.setViewResolvers(viewResolversProvider.getIfAvailable(Collections::emptyList));
this.setMessageWriters(serverCodecConfigurer.getWriters());
this.setMessageReaders(serverCodecConfigurer.getReaders());
}
您只需像这样在全局错误处理程序构造函数中使用 ServerCodecConfigurer 注入。
public OsvcErrorHandler(GlobalErrorAttributes errorAttributes, ApplicationContext applicationContext,
ServerCodecConfigurer serverCodecConfigurer) {
super(errorAttributes, new ResourceProperties(), applicationContext);
super.setMessageWriters(serverCodecConfigurer.getWriters());
super.setMessageReaders(serverCodecConfigurer.getReaders());
}
请在 git repository 中找到代码示例。
您需要将 ErrorWebExceptionHandler
定义和实现为一个 bean,并设置一个 @Order
值小于 -1
的注解,因为这是 Spring 的默认值DefaultErrorWebExceptionHandler
这是一个示例实现:
public class GlobalErrorHandler extends DefaultErrorWebExceptionHandler {
public GlobalErrorHandler(
final ErrorAttributes errorAttributes,
final WebProperties.Resources resources,
final ErrorProperties errorProperties,
final ApplicationContext applicationContext) {
super(errorAttributes, resources, errorProperties, applicationContext);
}
@Override
public Mono<Void> handle(final ServerWebExchange exchange, final Throwable ex) {
final ServerHttpResponse response = exchange.getResponse();
if (ex instanceof IllegalStateException
&& StringUtils.equals("Session was invalidated", ex.getMessage())
&& response.getStatusCode().is3xxRedirection()) {
final DataBufferFactory bufferFactory = exchange.getResponse().bufferFactory();
return response.writeWith(Mono.just(bufferFactory.wrap("Redirecting...".getBytes())));
}
return super.handle(exchange, ex);
}
}
这是基于 org.springframework.boot.autoconfigure.web.reactive.error.ErrorWebFluxAutoConfiguration
class:
@Configuration
public class ErrorWebFluxAutoConfiguration {
private final ServerProperties serverProperties;
public ErrorWebFluxAutoConfiguration(final ServerProperties serverProperties) {
this.serverProperties = serverProperties;
}
@Bean
@Order(-2)
public ErrorWebExceptionHandler errorWebExceptionHandler(
final ErrorAttributes errorAttributes,
final org.springframework.boot.autoconfigure.web.ResourceProperties resourceProperties,
final WebProperties webProperties,
final ObjectProvider<ViewResolver> viewResolvers,
final ServerCodecConfigurer serverCodecConfigurer,
final ApplicationContext applicationContext) {
final GlobalErrorHandler exceptionHandler =
new GlobalErrorHandler(
errorAttributes,
resourceProperties.hasBeenCustomized()
? resourceProperties
: webProperties.getResources(),
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();
}
}
感谢 this 文章指出我可以使用 ErrorWebExceptionHandler
。