Commit c98d8823 authored by liaozan's avatar liaozan 🏀

Polish ExceptionTranslator

parent b7bab0b7
package com.schbrain.common.util;
import cn.hutool.extra.spring.SpringUtil;
import com.schbrain.common.exception.BaseException;
import org.springframework.core.env.Environment;
import org.springframework.core.env.EnvironmentCapable;
......@@ -17,11 +16,11 @@ public class ApplicationName {
return Optional.ofNullable(SpringUtil.getApplicationContext())
.map(EnvironmentCapable::getEnvironment)
.map(ApplicationName::get)
.orElseThrow(() -> new BaseException("Could not get application name"));
.orElseThrow();
}
public static String get(Environment environment) {
return environment.getRequiredProperty("spring.application.name");
}
}
\ No newline at end of file
}
......@@ -55,6 +55,25 @@ public class TreeUtils {
return doBuildTree(keyExtractor, childrenSetter, childMapper, parentWithSubNodes, childrenComparator, parentId);
}
public static <T, E> List<E> buildNodeList(List<T> treeList,
Function<T, List<T>> childrenGetter,
Function<T, E> mapper) {
List<E> nodes = new ArrayList<>();
doBuildNodeList(treeList, childrenGetter, mapper, nodes);
return nodes;
}
private static <E, T> void doBuildNodeList(List<T> treeList,
Function<T, List<T>> childrenGetter,
Function<T, E> mapper,
List<E> nodes) {
if (CollectionUtils.isEmpty(treeList)) {
return;
}
nodes.addAll(StreamUtils.toList(treeList, mapper));
treeList.forEach(tree -> doBuildNodeList(childrenGetter.apply(tree), childrenGetter, mapper, nodes));
}
private static <E, K, T> List<E> doBuildTree(Function<T, K> keyExtractor,
BiConsumer<E, List<E>> childrenSetter,
Function<T, E> childMapper,
......
......@@ -5,6 +5,7 @@ import com.schbrain.common.web.exception.ExceptionHandingWebMvcConfigurer;
import com.schbrain.common.web.exception.ExceptionTranslator;
import com.schbrain.common.web.exception.GlobalExceptionHandler;
import com.schbrain.common.web.properties.WebProperties;
import com.schbrain.common.web.result.ResponseDTO;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
......@@ -23,13 +24,13 @@ public class ExceptionHandingConfiguration {
@Bean
@ConditionalOnMissingBean
public ExceptionTranslator defaultExceptionTranslator() {
public ExceptionTranslator<ResponseDTO<String>> defaultExceptionTranslator() {
return new DefaultExceptionTranslator();
}
@Bean
@ConditionalOnMissingBean
public GlobalExceptionHandler defaultGlobalExceptionHandler(ObjectProvider<ExceptionTranslator> exceptionTranslators) {
public GlobalExceptionHandler defaultGlobalExceptionHandler(ObjectProvider<ExceptionTranslator<?>> exceptionTranslators) {
return new GlobalExceptionHandler(exceptionTranslators.orderedStream().collect(Collectors.toList()));
}
......@@ -39,4 +40,4 @@ public class ExceptionHandingConfiguration {
return new ExceptionHandingWebMvcConfigurer(webProperties, exceptionHandler);
}
}
\ No newline at end of file
}
......@@ -3,6 +3,7 @@ package com.schbrain.common.web;
import com.schbrain.common.web.argument.BodyParamArgumentResolverWebMvcConfigurer;
import com.schbrain.common.web.properties.WebProperties;
import com.schbrain.common.web.result.ResponseBodyHandler;
import com.schbrain.common.web.support.converter.Jsr310DateTimeWebMvcConfigurer;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfigurationPackages;
......@@ -40,6 +41,12 @@ public class WebCommonAutoConfiguration {
return new BodyParamArgumentResolverWebMvcConfigurer();
}
@Bean
@ConditionalOnMissingBean
public Jsr310DateTimeWebMvcConfigurer defaultJsr310DateTimeWebMvcConfigurer() {
return new Jsr310DateTimeWebMvcConfigurer();
}
@Bean
@Lazy
@ConditionalOnMissingBean
......
......@@ -10,7 +10,7 @@ import org.springframework.core.Ordered;
* @author liaozan
* @since 2023-06-01
*/
public class DefaultExceptionTranslator implements ExceptionTranslator {
public class DefaultExceptionTranslator implements ExceptionTranslator<ResponseDTO<String>> {
private final boolean isProduction;
......@@ -34,4 +34,4 @@ public class DefaultExceptionTranslator implements ExceptionTranslator {
return Ordered.LOWEST_PRECEDENCE;
}
}
\ No newline at end of file
}
......@@ -32,8 +32,6 @@ import java.util.concurrent.ConcurrentHashMap;
@EqualsAndHashCode(callSuper = true)
public class DefaultGlobalExceptionResolver extends AbstractHandlerMethodExceptionResolver {
private final boolean wrapResponse;
private final GlobalExceptionHandler exceptionHandler;
private final ExceptionHandlerMethodResolver handlerMethodResolver;
......@@ -44,8 +42,7 @@ public class DefaultGlobalExceptionResolver extends AbstractHandlerMethodExcepti
private final Map<Class<?>, ExceptionHandlerMethodResolver> exceptionHandlerMethodResolvers = new ConcurrentHashMap<>(64);
public DefaultGlobalExceptionResolver(ExceptionHandlerExceptionResolver handlerMethodResolver, boolean wrapResponse, GlobalExceptionHandler exceptionHandler) {
this.wrapResponse = wrapResponse;
public DefaultGlobalExceptionResolver(ExceptionHandlerExceptionResolver handlerMethodResolver, GlobalExceptionHandler exceptionHandler) {
this.exceptionHandler = exceptionHandler;
this.handlerMethodResolver = new ExceptionHandlerMethodResolver(exceptionHandler.getClass());
this.argumentResolverComposite = handlerMethodResolver.getArgumentResolvers();
......@@ -55,10 +52,6 @@ public class DefaultGlobalExceptionResolver extends AbstractHandlerMethodExcepti
@Override
protected boolean shouldApplyTo(HttpServletRequest request, @Nullable Object handler) {
if (!wrapResponse) {
return false;
}
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
ResponseWrapOption responseWrapOption = HandlerMethodAnnotationUtils.getAnnotation(handlerMethod, ResponseWrapOption.class);
......@@ -147,4 +140,4 @@ public class DefaultGlobalExceptionResolver extends AbstractHandlerMethodExcepti
return arguments;
}
}
\ No newline at end of file
}
......@@ -49,7 +49,7 @@ public class ExceptionHandingWebMvcConfigurer implements WebMvcConfigurer {
}
protected HandlerExceptionResolver createExceptionResolver(ExceptionHandlerExceptionResolver adviceExceptionResolver) {
return new DefaultGlobalExceptionResolver(adviceExceptionResolver, webProperties.isWrapResponse(), globalExceptionHandler);
return new DefaultGlobalExceptionResolver(adviceExceptionResolver, globalExceptionHandler);
}
}
\ No newline at end of file
}
......@@ -8,16 +8,16 @@ import org.springframework.core.Ordered;
* @since 2023-06-01
*/
@FunctionalInterface
public interface ExceptionTranslator extends Ordered {
public interface ExceptionTranslator<T> extends Ordered {
/**
* Translate the exception to {@link ResponseDTO}
*/
ResponseDTO<String> translate(Throwable throwable, int code, int action, String message);
T translate(Throwable throwable, int code, int action, String message);
@Override
default int getOrder() {
return Ordered.LOWEST_PRECEDENCE - 100;
}
}
\ No newline at end of file
}
......@@ -42,78 +42,78 @@ import static com.schbrain.common.util.support.ValidationMessageBuilder.buildCon
@ResponseStatus(HttpStatus.OK)
public class GlobalExceptionHandler {
private final List<ExceptionTranslator> exceptionTranslators;
private final List<ExceptionTranslator<?>> exceptionTranslators;
public GlobalExceptionHandler(List<ExceptionTranslator> exceptionTranslators) {
public GlobalExceptionHandler(List<ExceptionTranslator<?>> exceptionTranslators) {
this.exceptionTranslators = exceptionTranslators;
}
/************************************* Base Exception Handing *************************************/
@ExceptionHandler(BaseException.class)
public ResponseDTO<String> handleBaseException(BaseException ex) {
public Object handleBaseException(BaseException ex) {
logError(ex);
return buildResponse(ex, ex.getCode(), ex.getAction(), ex.getMessage());
}
/************************************* Common Exception Handing *************************************/
@ExceptionHandler(Throwable.class)
public ResponseDTO<String> handleAll(Throwable ex) {
public Object handleAll(Throwable ex) {
return loggingThenBuildResponse(ex, SERVER_ERROR);
}
@ExceptionHandler(NullPointerException.class)
public ResponseDTO<String> handleNullPointerException(NullPointerException ex) {
public Object handleNullPointerException(NullPointerException ex) {
return loggingThenBuildResponse(ex, SERVER_ERROR);
}
@ExceptionHandler(IllegalArgumentException.class)
public ResponseDTO<String> handleIllegalArgumentException(IllegalArgumentException ex) {
public Object handleIllegalArgumentException(IllegalArgumentException ex) {
return loggingThenBuildResponse(ex, SERVER_ERROR);
}
@ExceptionHandler(IllegalStateException.class)
public ResponseDTO<String> handleIllegalStateException(IllegalStateException ex) {
public Object handleIllegalStateException(IllegalStateException ex) {
return loggingThenBuildResponse(ex, SERVER_ERROR);
}
@ExceptionHandler(NoHandlerFoundException.class)
public ResponseDTO<String> handleNoHandlerFoundException(NoHandlerFoundException ex) {
public Object handleNoHandlerFoundException(NoHandlerFoundException ex) {
return loggingThenBuildResponse(ex, PARAM_INVALID);
}
@ExceptionHandler(AsyncRequestTimeoutException.class)
public ResponseDTO<String> handleAsyncRequestTimeoutException(AsyncRequestTimeoutException ex) {
public Object handleAsyncRequestTimeoutException(AsyncRequestTimeoutException ex) {
return loggingThenBuildResponse(ex, SERVER_ERROR);
}
/************************************* SQL Exception Handing *************************************/
@ExceptionHandler(SQLException.class)
public ResponseDTO<String> handleSQLException(SQLException ex) {
public Object handleSQLException(SQLException ex) {
return loggingThenBuildResponse(ex, SERVER_ERROR);
}
@ExceptionHandler(DataAccessException.class)
public ResponseDTO<String> handleDataAccessException(DataAccessException ex) {
public Object handleDataAccessException(DataAccessException ex) {
return loggingThenBuildResponse(ex, SERVER_ERROR);
}
/************************************* Http Request Exception Handing *************************************/
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public ResponseDTO<String> handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException ex) {
public Object handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException ex) {
String errorMsg = StrUtil.format("不支持该HTTP方法: {}, 请使用 {}", ex.getMethod(), Arrays.toString(ex.getSupportedMethods()));
log.error(errorMsg);
return buildResponse(ex, PARAM_INVALID, errorMsg);
}
@ExceptionHandler(HttpMediaTypeNotSupportedException.class)
public ResponseDTO<String> handleHttpMediaTypeNotSupportedException(HttpMediaTypeNotSupportedException ex) {
public Object handleHttpMediaTypeNotSupportedException(HttpMediaTypeNotSupportedException ex) {
String errorMsg = StrUtil.format("不支持该媒体类型: {}, 请使用 {}", ex.getContentType(), ex.getSupportedMediaTypes());
log.error(errorMsg);
return buildResponse(ex, PARAM_INVALID, errorMsg);
}
@ExceptionHandler(HttpMediaTypeNotAcceptableException.class)
public ResponseDTO<String> handleHttpMediaTypeNotAcceptableException(HttpMediaTypeNotAcceptableException ex) {
public Object handleHttpMediaTypeNotAcceptableException(HttpMediaTypeNotAcceptableException ex) {
String errorMsg = StrUtil.format("不支持的媒体类型, 请使用 {}", ex.getSupportedMediaTypes());
log.error(errorMsg);
return buildResponse(ex, PARAM_INVALID, errorMsg);
......@@ -121,14 +121,14 @@ public class GlobalExceptionHandler {
/************************************* Method Parameter Exception Handing *************************************/
@ExceptionHandler(HttpMessageNotReadableException.class)
public ResponseDTO<String> handleHttpMessageNotReadableException(HttpMessageNotReadableException ex) {
public Object handleHttpMessageNotReadableException(HttpMessageNotReadableException ex) {
String errorMsg = StrUtil.format("参数解析失败, {}", ex.getMessage());
log.error(errorMsg);
return buildResponse(ex, PARAM_INVALID, errorMsg);
}
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
public ResponseDTO<String> handlerMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException ex) {
public Object handlerMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException ex) {
Object value = ex.getValue();
String variableName = ex.getName();
Class<?> requiredTypeClass = ex.getRequiredType();
......@@ -140,42 +140,42 @@ public class GlobalExceptionHandler {
}
@ExceptionHandler(MissingPathVariableException.class)
public ResponseDTO<String> handleMissingPathVariableException(MissingPathVariableException ex) {
public Object handleMissingPathVariableException(MissingPathVariableException ex) {
String errorMsg = StrUtil.format("丢失路径参数, 参数名: {}, 参数类型: {}", ex.getVariableName(), ex.getParameter().getParameterType());
log.error(errorMsg);
return buildResponse(ex, PARAM_INVALID, errorMsg);
}
@ExceptionHandler(MissingRequestCookieException.class)
public ResponseDTO<String> handleMissingRequestCookieException(MissingRequestCookieException ex) {
public Object handleMissingRequestCookieException(MissingRequestCookieException ex) {
String errorMsg = StrUtil.format("丢失Cookie参数, 参数名: {}, 参数类型: {}", ex.getCookieName(), ex.getParameter().getParameterType());
log.error(errorMsg);
return buildResponse(ex, PARAM_INVALID, errorMsg);
}
@ExceptionHandler(MissingRequestHeaderException.class)
public ResponseDTO<String> handleMissingRequestHeaderException(MissingRequestHeaderException ex) {
public Object handleMissingRequestHeaderException(MissingRequestHeaderException ex) {
String errorMsg = StrUtil.format("丢失Header参数, 参数名: {}, 参数类型: {}", ex.getHeaderName(), ex.getParameter().getParameterType());
log.error(errorMsg);
return buildResponse(ex, PARAM_INVALID, errorMsg);
}
@ExceptionHandler(MissingServletRequestPartException.class)
public ResponseDTO<String> handleMissingServletRequestPartException(MissingServletRequestPartException ex) {
public Object handleMissingServletRequestPartException(MissingServletRequestPartException ex) {
String errorMsg = StrUtil.format("丢失参数: {}", ex.getRequestPartName());
log.error(errorMsg);
return buildResponse(ex, PARAM_INVALID, errorMsg);
}
@ExceptionHandler(MissingServletRequestParameterException.class)
public ResponseDTO<String> handleServletRequestParameterException(MissingServletRequestParameterException ex) {
public Object handleServletRequestParameterException(MissingServletRequestParameterException ex) {
String errorMsg = StrUtil.format("丢失Query参数, 参数名: {}, 参数类型: {}", ex.getParameterName(), ex.getParameterType());
log.error(errorMsg);
return buildResponse(ex, PARAM_INVALID, errorMsg);
}
@ExceptionHandler(ServletRequestBindingException.class)
public ResponseDTO<String> handleServletRequestBindingException(ServletRequestBindingException ex) {
public Object handleServletRequestBindingException(ServletRequestBindingException ex) {
String errorMsg = StrUtil.format("参数绑定失败: {}", ex.getMessage());
log.error(errorMsg);
return buildResponse(ex, PARAM_INVALID, errorMsg);
......@@ -183,42 +183,43 @@ public class GlobalExceptionHandler {
/************************************* Parameter Binding Exception Handing *************************************/
@ExceptionHandler(BindException.class)
public ResponseDTO<String> handleBindException(BindException ex) {
public Object handleBindException(BindException ex) {
String errorMsg = buildBindingErrorMsg(ex.getBindingResult());
log.error(errorMsg);
return buildResponse(ex, PARAM_INVALID, errorMsg);
}
@ExceptionHandler(ConstraintViolationException.class)
public ResponseDTO<String> handleConstraintViolationException(ConstraintViolationException ex) {
public Object handleConstraintViolationException(ConstraintViolationException ex) {
String errorMsg = buildConstraintViolationErrorMsg(ex.getConstraintViolations());
log.error(errorMsg);
return buildResponse(ex, PARAM_INVALID, errorMsg);
}
private ResponseDTO<String> loggingThenBuildResponse(Throwable throwable, int code) {
private Object loggingThenBuildResponse(Throwable throwable, int code) {
Throwable rootCause = ExceptionUtil.getRootCause(throwable);
logError(rootCause);
return buildResponse(rootCause, code, rootCause.getMessage());
}
private ResponseDTO<String> buildResponse(Throwable throwable, int code, String message) {
private Object buildResponse(Throwable throwable, int code, String message) {
return buildResponse(throwable, code, ResponseActionConstants.ALERT, message);
}
private ResponseDTO<String> buildResponse(Throwable throwable, int code, int action, String message) {
ResponseDTO<String> responseDTO = translateException(throwable, code, action, message);
if (responseDTO != null) {
return responseDTO;
private Object buildResponse(Throwable throwable, int code, int action, String message) {
Object translated = translateException(throwable, code, action, message);
if (translated != null) {
return translated;
}
// fallback
return ResponseDTO.error(message, code, action);
}
private ResponseDTO<String> translateException(Throwable throwable, int code, int action, String message) {
for (ExceptionTranslator exceptionTranslator : exceptionTranslators) {
ResponseDTO<String> responseDTO = exceptionTranslator.translate(throwable, code, action, message);
if (responseDTO != null) {
return responseDTO;
private Object translateException(Throwable throwable, int code, int action, String message) {
for (ExceptionTranslator<?> exceptionTranslator : exceptionTranslators) {
Object translated = exceptionTranslator.translate(throwable, code, action, message);
if (translated != null) {
return translated;
}
}
return null;
......
package com.schbrain.common.web.support.converter;
import org.springframework.core.convert.converter.Converter;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.List;
import static java.time.LocalDateTime.ofInstant;
import static java.time.ZoneId.systemDefault;
/**
* @author liaozan
* @since 2023/8/16
*/
public class Jsr310Converters {
public static List<Converter<?, ?>> getConverters() {
List<Converter<?, ?>> converters = new ArrayList<>();
converters.add(LongToLocalDateConverter.INSTANCE);
converters.add(LongToLocalTimeConverter.INSTANCE);
converters.add(LongToLocalDateTimeConverter.INSTANCE);
converters.add(StringToLocalDateConverter.INSTANCE);
converters.add(StringToLocalTimeConverter.INSTANCE);
converters.add(StringToLocalDateTimeConverter.INSTANCE);
return converters;
}
public enum LongToLocalDateConverter implements Converter<Long, LocalDate> {
INSTANCE;
@Override
public LocalDate convert(Long source) {
return LongToLocalDateTimeConverter.INSTANCE.convert(source).toLocalDate();
}
}
public enum LongToLocalTimeConverter implements Converter<Long, LocalTime> {
INSTANCE;
@Override
public LocalTime convert(Long source) {
return LongToLocalDateTimeConverter.INSTANCE.convert(source).toLocalTime();
}
}
public enum LongToLocalDateTimeConverter implements Converter<Long, LocalDateTime> {
INSTANCE;
@Override
public LocalDateTime convert(Long source) {
return ofInstant(Instant.ofEpochMilli(source), systemDefault());
}
}
public enum StringToLocalDateConverter implements Converter<String, LocalDate> {
INSTANCE;
@Override
public LocalDate convert(String source) {
return StringToLocalDateTimeConverter.INSTANCE.convert(source).toLocalDate();
}
}
public enum StringToLocalTimeConverter implements Converter<String, LocalTime> {
INSTANCE;
@Override
public LocalTime convert(String source) {
return StringToLocalDateTimeConverter.INSTANCE.convert(source).toLocalTime();
}
}
public enum StringToLocalDateTimeConverter implements Converter<String, LocalDateTime> {
INSTANCE;
@Override
public LocalDateTime convert(String source) {
return ofInstant(Instant.ofEpochMilli(Long.parseLong(source)), systemDefault());
}
}
}
package com.schbrain.common.web.support.converter;
import org.springframework.format.FormatterRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* @author liaozan
* @since 2023/8/16
*/
public class Jsr310DateTimeWebMvcConfigurer implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
Jsr310Converters.getConverters().forEach(registry::addConverter);
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment