SpringBoot自定义starter之标准化处理
前言
许久没更新博客了, 甚是抱歉。工作中需要下沉一些通用组件, 所以这里先做了一个接口标准化包。功能包括:
统一异常处理
统一接口返回格式
统一参数校验(业务无需加@Valid和Validated注解)
实现
pom.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 <dependencies > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-configuration-processor</artifactId > <optional > true</optional > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-autoconfigure</artifactId > <optional > true</optional > </dependency > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-webmvc</artifactId > <optional > true</optional > </dependency > <dependency > <groupId > com.fasterxml.jackson.core</groupId > <artifactId > jackson-databind</artifactId > <optional > true</optional > </dependency > <dependency > <groupId > org.slf4j</groupId > <artifactId > slf4j-api</artifactId > <scope > provided</scope > <optional > true</optional > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-validation</artifactId > </dependency > <dependency > <groupId > cn.idea360</groupId > <artifactId > idea360-core</artifactId > <version > 0.0.1</version > </dependency > <dependency > <groupId > javax.servlet</groupId > <artifactId > javax.servlet-api</artifactId > <scope > provided</scope > <optional > true</optional > </dependency > <dependency > <groupId > commons-io</groupId > <artifactId > commons-io</artifactId > <version > 2.8.0</version > <scope > compile</scope > </dependency > </dependencies >
spring.factories
/resources/META-INF/spring.factories
目录下
1 2 3 4 5 org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ cn.idea360.unified.UnifiedResponseBodyAdvice,\ cn.idea360.unified.UnifiedExceptionHandler,\ cn.idea360.unified.filter.FilterAutoConfig,\ cn.idea360.unified.interceptor.InterceptorAutoConfig
通用异常处理
返回格式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 public class UnifiedResult <T > implements Serializable { public static final int SUCCESS = 0 ; public static final int ERROR = -1 ; private String msg; private int code = SUCCESS; private T data; public UnifiedResult (int code, String msg, T data) { this .code = code; this .msg = msg; this .data = data; } public String getMsg () { return msg; } public void setMsg (String msg) { this .msg = msg; } public int getCode () { return code; } public void setCode (int code) { this .code = code; } public T getData () { return data; } public void setData (T data) { this .data = data; } public static class Builder <T > { private String msg = "OK" ; private int code = SUCCESS; private T data; public String getMsg () { return msg; } public void setMsg (String msg) { this .msg = msg; } public int getCode () { return code; } public void setCode (int code) { this .code = code; } public T getData () { return data; } public void setData (T data) { this .data = data; } public Builder<T> data (T data) { this .data = data; return this ; } public Builder<T> error (int code, String msg) { this .code = code; this .msg = msg; return this ; } public Builder<T> error (int code) { this .code = code; return this ; } public UnifiedResult<T> build () { return new UnifiedResult<T>(this .code, this .msg, this .data); } public Builder<T> message (String msg) { this .msg = msg; return this ; } } }
异常拦截
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 @RestControllerAdvice public class UnifiedExceptionHandler { private final Logger log = LoggerFactory.getLogger(UnifiedExceptionHandler.class); @ExceptionHandler({BindException.class}) public UnifiedResult exceptionHandler (BindException e) { log.error("BindException:" , e); BindingResult bindingResult = e.getBindingResult(); return new UnifiedResult.Builder<>().error(UnifiedResult.ERROR, Objects.requireNonNull(bindingResult.getFieldError()).getDefaultMessage()).build(); } @ExceptionHandler({MethodArgumentNotValidException.class}) public UnifiedResult exceptionHandler (MethodArgumentNotValidException e) { log.error("MethodArgumentNotValidException:" , e); BindingResult bindingResult = e.getBindingResult(); return new UnifiedResult.Builder<>().error(UnifiedResult.ERROR, Objects.requireNonNull(bindingResult.getFieldError()).getDefaultMessage()).build(); } @ExceptionHandler(value = ConstraintViolationException.class) public UnifiedResult handler (ConstraintViolationException e) { return new UnifiedResult.Builder<>().error(UnifiedResult.ERROR, e.getMessage()).build(); } @ExceptionHandler(HttpMessageConversionException.class) public UnifiedResult handler (HttpMessageConversionException e) { return new UnifiedResult.Builder<>().error(UnifiedResult.ERROR, e.getMessage()).build(); } @ExceptionHandler(value = HttpMessageNotReadableException.class) public UnifiedResult handler (HttpMessageNotReadableException e) { return new UnifiedResult.Builder<>().error(UnifiedResult.ERROR, e.getMessage()).build(); } @ExceptionHandler(value = HttpRequestMethodNotSupportedException.class) public UnifiedResult handler (HttpRequestMethodNotSupportedException e) { return new UnifiedResult.Builder<>().error(UnifiedResult.ERROR, "请求方式错误" ).build(); } @ExceptionHandler(value = HttpMediaTypeNotSupportedException.class) public UnifiedResult handler (HttpMediaTypeNotSupportedException e) { return new UnifiedResult.Builder<>().error(UnifiedResult.ERROR, "媒体类型错误" ).build(); } @ExceptionHandler({MissingServletRequestParameterException.class}) public UnifiedResult handler (MissingServletRequestParameterException e) { return new UnifiedResult.Builder<>().error(UnifiedResult.ERROR, e.getMessage()).build(); } @ExceptionHandler({BsException.class}) public UnifiedResult exceptionHandler (BsException e) { log.error("BsException:" , e); return new UnifiedResult.Builder<>().error(e.getCode(), e.getMessage()).build(); } @ExceptionHandler(value = Throwable.class) public UnifiedResult exceptionHandler (Throwable e) { log.error("UnifiedExceptionHandler: {}" , ExceptionUtils.getStackTrace(e)); return new UnifiedResult.Builder<>().error(UnifiedResult.ERROR, e.getMessage()).build(); } }
自定义业务异常
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 public class BsException extends RuntimeException { private Integer code; private String message; private String stackTrace; private transient Throwable throwable; public BsException () { } public BsException (String message) { super (message); this .code = this .formatErrCode(-1 ); this .message = message; } public BsException (int code, String message) { super (message); this .code = this .formatErrCode(code); this .message = message; } public BsException (int code, Throwable throwable) { super (throwable); this .code = this .formatErrCode(code); this .message = throwable.getMessage(); this .stackTrace = ExceptionUtils.getStackTrace(throwable); this .throwable = throwable; } public BsException (int code, String message, Throwable throwable) { super (message, throwable); this .code = this .formatErrCode(code); this .message = message; this .throwable = throwable; this .stackTrace = ExceptionUtils.getStackTrace(throwable); } private int formatErrCode (int errCode) { return errCode == 0 ? -1 : errCode; } public Integer getCode () { return this .code; } public String getMessage () { if (!StringUtils.isEmpty(this .message)) { return this .message; } else { return this .throwable != null ? this .throwable.getMessage() : "" ; } } public String getStackTrace2String () { return this .stackTrace == null ? "" : this .stackTrace; } public String toString () { Integer var10000 = this .getCode(); return "ErrCode:" + var10000 + ", ErrMsg:" + this .getMessage(); } }
统一包装接口格式
全局格式
首先, 我们应该允许上层配置白名单, 所以先从配置文件读取配置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @ConfigurationProperties(prefix = UnifiedProperties.UNIFIED_PREFIX) public class UnifiedProperties { public static final String UNIFIED_PREFIX = "easyliao.framework.unified" ; private final List<String> ignores = new ArrayList<>(); public List<String> getIgnores () { return ignores; } }
然后配置全局返回格式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 @EnableConfigurationProperties(UnifiedProperties.class) @RestControllerAdvice public class UnifiedResponseBodyAdvice implements ResponseBodyAdvice <Object >, BeanFactoryAware { @Resource private UnifiedProperties unifiedProperties; private static ObjectMapper objectMapper; private static final String[] innerIgnores = new String[]{ "/swagger-resources" , "/swagger-ui" , "/v3/api-docs" }; @Override public boolean supports (MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) { return !returnType.getGenericParameterType().equals(UnifiedResult.class) && !returnType.hasMethodAnnotation(UnifiedIgnore.class); } @Override public Object beforeBodyWrite (Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { if (this .ignoring(request.getURI().toString())) { return body; } if (returnType.getGenericParameterType().equals(String.class)) { try { response.getHeaders().set("Content-Type" , "application/json;charset=utf-8" ); return objectMapper.writeValueAsString(new UnifiedResult.Builder<>().data(body).build()); } catch (JsonProcessingException e) { e.printStackTrace(); throw new RuntimeException("返回String类型错误" ); } } return new UnifiedResult.Builder<>().data(body).build(); } private boolean ignoring (String uri) { for (String string : innerIgnores) { if (uri.contains(string)) { return true ; } } for (String string : unifiedProperties.getIgnores()) { if (uri.contains(string)) { return true ; } } return false ; } @Override public void setBeanFactory (BeanFactory beanFactory) throws BeansException { objectMapper = beanFactory.getBean(ObjectMapper.class); } }
无需按标准返回的方法注解
1 2 3 4 5 6 7 8 9 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface UnifiedIgnore {}
参数统一校验
目前只处理了RequestBody类型的参数。 由于Filter拿不到方法相关信息, 所以只能基于 Interceptor
或者 AOP
实现, AOP
实现需要指定 Controller
切面, 需要提取配置参数, 故选择 Interceptor
拦截所有。
注入拦截器
1 2 3 4 5 6 7 8 9 10 11 12 13 @Configuration public class InterceptorAutoConfig implements WebMvcConfigurer { @Override public void addInterceptors (InterceptorRegistry registry) { registry.addInterceptor(new ValidateInterceptor()).addPathPatterns("/**" ); } }
拦截器实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 @Component public class ValidateInterceptor implements HandlerInterceptor , BeanFactoryAware { private final Logger log = LoggerFactory.getLogger(getClass()); private static final Validator validator = Validation.byProvider(HibernateValidator.class) .configure() .failFast(true ) .buildValidatorFactory() .getValidator(); private static ObjectMapper objectMapper; @Override public boolean preHandle (HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (HandlerMethod.class.isInstance(handler)) { HandlerMethod handlerMethod = (HandlerMethod) handler; MethodParameter[] methodParameters = handlerMethod.getMethodParameters(); for (MethodParameter arg : methodParameters){ if (arg != null && arg.hasParameterAnnotation(RequestBody.class) && !arg.getParameterType().isAssignableFrom(String.class)) { RequestWrapper requestWrapper = new RequestWrapper(request); String body = getRequestBody(requestWrapper); Object o = objectMapper.readValue(body, arg.getParameterType()); Set<ConstraintViolation<Object>> constraintViolations = validator.validate(o); if (!constraintViolations.isEmpty()){ throw new ConstraintViolationException(constraintViolations); } } } } return HandlerInterceptor.super .preHandle(request, response, handler); } private String getRequestBody (HttpServletRequest request) throws IOException { return request.getReader().lines().collect(Collectors.joining(System.lineSeparator())); } @Override public void setBeanFactory (BeanFactory beanFactory) throws BeansException { objectMapper = beanFactory.getBean(ObjectMapper.class); } }
由于流只能被消费一次, 所有此处需要处理流消费的问题。这里不能在拦截器包装 HttpServletRequestWrapper
处理。因为流在拦截器消费一次后不会再向下传递。而在 Filter
做 HttpServletRequestWrapper
处理, 流因为中间载体可以被多次消费, 而且向下游传递的是 HttpServletRequestWrapper
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Configuration public class FilterAutoConfig { @Bean public FilterRegistrationBean<RepeatStreamFilter> traceFilterRegistration () { FilterRegistrationBean<RepeatStreamFilter> registration = new FilterRegistrationBean<>(); registration.setOrder(Ordered.HIGHEST_PRECEDENCE + 100 ); registration.addUrlPatterns("/*" ); registration.setFilter(new RepeatStreamFilter()); return registration; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class RepeatStreamFilter implements Filter { @Override public void doFilter (ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { ServletRequest requestWrapper=null ; if (request instanceof HttpServletRequest) { requestWrapper = new RequestWrapper((HttpServletRequest)request); } if (requestWrapper==null ) { chain.doFilter(request, response); }else { chain.doFilter(requestWrapper, response); } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 public class RequestWrapper extends HttpServletRequestWrapper { private ByteArrayOutputStream cachedBytes; public RequestWrapper (HttpServletRequest request) { super (request); } @Override public ServletInputStream getInputStream () throws IOException { if (cachedBytes == null ) cacheInputStream(); return new RequestWrapper.CachedServletInputStream(); } @Override public BufferedReader getReader () throws IOException { return new BufferedReader(new InputStreamReader(getInputStream())); } private void cacheInputStream () throws IOException { cachedBytes = new ByteArrayOutputStream(); IOUtils.copy(super .getInputStream(), cachedBytes); } public class CachedServletInputStream extends ServletInputStream { private ByteArrayInputStream input; public CachedServletInputStream () { input = new ByteArrayInputStream(cachedBytes.toByteArray()); } @Override public int read () throws IOException { return input.read(); } @Override public boolean isFinished () { return false ; } @Override public boolean isReady () { return false ; } @Override public void setReadListener (ReadListener listener) { } } }
最后
本文到此结束,感谢阅读。如果您觉得不错,请关注公众号【当我遇上你】, 您的支持是我写作的最大动力。