preface
Sorry I haven’t updated my blog for a long time. There are some common components that need to be sunk in the work, so I made an interface standardization package first. Features include:
- Unified Exception Handling
- Unified interface return format
- Unified parameter verification (services do not need to be annotated with @Valid and Validated)
implementation
pom.xml
<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>
Copy the code
spring.factories
/ resources/meta-inf/spring. Factories directory
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.idea360.unified.UnifiedResponseBodyAdvice,\
cn.idea360.unified.UnifiedExceptionHandler,\
cn.idea360.unified.filter.FilterAutoConfig,\
cn.idea360.unified.interceptor.InterceptorAutoConfig
Copy the code
General exception handling
- Returns the format
/ * * *@author cuishiying
* @dateThe 2021-01-22 * /
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(a) {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public int getCode(a) {
return code;
}
public void setCode(int code) {
this.code = code;
}
public T getData(a) {
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(a) {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public int getCode(a) {
return code;
}
public void setCode(int code) {
this.code = code;
}
public T getData(a) {
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(a) {
return new UnifiedResult<T>(this.code, this.msg, this.data);
}
public Builder<T> message(String msg) {
this.msg = msg;
return this; }}}Copy the code
- Abnormal intercept
/ * * *@author cuishiying
* @dateThe 2021-01-22 * /
@RestControllerAdvice
public class UnifiedExceptionHandler {
private final Logger log = LoggerFactory.getLogger(UnifiedExceptionHandler.class);
/** * Parameter binding exception */
@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();
}
/** * Parameter verification exception */
@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();
}
/** * Parameter verification exception */
@ExceptionHandler(value = ConstraintViolationException.class)
public UnifiedResult handler(ConstraintViolationException e) {
return new UnifiedResult.Builder<>().error(UnifiedResult.ERROR, e.getMessage()).build();
}
/** * Argument type conversion error */
@ExceptionHandler(HttpMessageConversionException.class)
public UnifiedResult handler(HttpMessageConversionException e) {
return new UnifiedResult.Builder<>().error(UnifiedResult.ERROR, e.getMessage()).build();
}
/** * The parameter format is abnormal */
@ExceptionHandler(value = HttpMessageNotReadableException.class)
public UnifiedResult handler(HttpMessageNotReadableException e) {
return new UnifiedResult.Builder<>().error(UnifiedResult.ERROR, e.getMessage()).build();
}
/** * The request mode is abnormal */
@ExceptionHandler(value = HttpRequestMethodNotSupportedException.class)
public UnifiedResult handler(HttpRequestMethodNotSupportedException e) {
return new UnifiedResult.Builder<>().error(UnifiedResult.ERROR, "Request mode error").build();
}
/** * Media type is abnormal */
@ExceptionHandler(value = HttpMediaTypeNotSupportedException.class)
public UnifiedResult handler(HttpMediaTypeNotSupportedException e) {
return new UnifiedResult.Builder<>().error(UnifiedResult.ERROR, "Wrong media type").build();
}
/** * Request parameters are missing */
@ExceptionHandler({MissingServletRequestParameterException.class})
public UnifiedResult handler(MissingServletRequestParameterException e) {
return new UnifiedResult.Builder<>().error(UnifiedResult.ERROR, e.getMessage()).build();
}
/** * Unified service exception */
@ExceptionHandler({BsException.class})
public UnifiedResult exceptionHandler(BsException e) {
log.error("BsException:", e);
return new UnifiedResult.Builder<>().error(e.getCode(), e.getMessage()).build();
}
/** * Default exception */
@ExceptionHandler(value = Throwable.class)
public UnifiedResult exceptionHandler(Throwable e) {
log.error("UnifiedExceptionHandler: {}", ExceptionUtils.getStackTrace(e));
return newUnifiedResult.Builder<>().error(UnifiedResult.ERROR, e.getMessage()).build(); }}Copy the code
Unified packaging interface format
- The global format
/ * * *@author cuishiying
* @dateThe 2021-01-22 * /
@RestControllerAdvice
public class UnifiedResponseBodyAdvice implements ResponseBodyAdvice<Object> {
private static final String[] ignores = new String[]{
// Filter the interface for swagger related requests, otherwise Swagger will indicate that base-URL is blocked
"/swagger-resources"."/swagger-ui"."/v3/api-docs"
};
@Override
public boolean supports(MethodParameter returnType, Class
> converterType) {
// return MappingJackson2HttpMessageConverter.class.isAssignableFrom(converterType) &&
// (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) || returnType.hasMethodAnnotation(ResponseBody.class));
return! returnType.getGenericParameterType().equals(UnifiedResult.class) && ! returnType.hasMethodAnnotation(UnifiedIgnore.class); }@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class
> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
if (this.ignoring(request.getURI().toString())) {
return body;
}
if (body instanceof String) {
ObjectMapper objectMapper = new ObjectMapper();
try {
return objectMapper.writeValueAsString(new UnifiedResult.Builder<>().data(body).build());
} catch (JsonProcessingException e) {
throw new RuntimeException("Mandatory String Type error"); }}return new UnifiedResult.Builder<>().data(body).build();
}
private boolean ignoring(String uri) {
for (String string : ignores) {
if (uri.contains(string)) {
return true; }}return false; }}Copy the code
- Method annotations returned as standard are not required
/ * * *@author cuishiying
* @dateThe 2021-05-25 * /
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface UnifiedIgnore {
}
Copy the code
Unified check of parameters
Currently, only parameters of type RequestBody are handled. Because Filter does not get method information, it can only be implemented based on Interceptor or AOP. AOP implementation needs to specify the Controller aspect and extract configuration parameters, so select Interceptor to intercept all.
- Injection interceptor
/ * * *@author cuishiying
* @dateThe 2021-01-22 * /
@Configuration
public class InterceptorAutoConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new ValidateInterceptor()).addPathPatterns("/ * *"); }}Copy the code
- Interceptor implementation
/ * * *@author cuishiying
* @dateThe 2021-01-22 * /
@Component
public class ValidateInterceptor implements HandlerInterceptor {
private final Logger log = LoggerFactory.getLogger(getClass());
private static final Validator validator = Validation.byProvider(HibernateValidator.class)
.configure()
.failFast(true)
.buildValidatorFactory()
.getValidator();
ObjectMapper mapper = new 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)) {
RequestWrapper requestWrapper = new RequestWrapper(request);
String body = getRequestBody(requestWrapper);
Object o = mapper.readValue(body, arg.getParameterType());
Set<ConstraintViolation<Object>> constraintViolations = validator.validate(o);
if(! constraintViolations.isEmpty()){throw newConstraintViolationException(constraintViolations); }}}}return HandlerInterceptor.super.preHandle(request, response, handler);
}
private String getRequestBody (HttpServletRequest request) throws IOException {
returnrequest.getReader().lines().collect(Collectors.joining(System.lineSeparator())); }}Copy the code
- Since streams can only be consumed once, the issue of stream consumption needs to be addressed here. This cannot be wrapped in interceptor
HttpServletRequestWrapper
To deal with. Because the stream is not passed down once consumed by the interceptor. And in theFilter
做HttpServletRequestWrapper
Because the intermediate carrier can be consumed multiple times and is passed downstreamHttpServletRequestWrapper
.
/ * * *@author cuishiying
* @dateThe 2021-01-22 * /
@Configuration
public class FilterAutoConfig {
@Bean
public FilterRegistrationBean<RepeatStreamFilter> traceFilterRegistration(a) {
FilterRegistrationBean<RepeatStreamFilter> registration = new FilterRegistrationBean<>();
registration.setOrder(Ordered.HIGHEST_PRECEDENCE + 100);
registration.addUrlPatterns("/ *");
registration.setFilter(new RepeatStreamFilter());
returnregistration; }}Copy the code
/ * * *@author cuishiying
* @dateThe 2021-01-22 * /
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); }}}Copy the code
/ * * *@author cuishiying
* @dateThe 2021-01-22 * /
public class RequestWrapper extends HttpServletRequestWrapper {
private ByteArrayOutputStream cachedBytes;
public RequestWrapper(HttpServletRequest request) {
super(request);
}
@Override
public ServletInputStream getInputStream(a) throws IOException {
if (cachedBytes == null)
cacheInputStream();
return new RequestWrapper.CachedServletInputStream();
}
@Override
public BufferedReader getReader(a) throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
private void cacheInputStream(a) throws IOException {
cachedBytes = new ByteArrayOutputStream();
IOUtils.copy(super.getInputStream(), cachedBytes);
}
/* An inputstream which reads the cached request body */
public class CachedServletInputStream extends ServletInputStream {
private ByteArrayInputStream input;
public CachedServletInputStream(a) {
/* create a new input stream from the cached request body */
input = new ByteArrayInputStream(cachedBytes.toByteArray());
}
@Override
public int read(a) throws IOException {
return input.read();
}
@Override
public boolean isFinished(a) {
return false;
}
@Override
public boolean isReady(a) {
return false;
}
@Override
public void setReadListener(ReadListener listener) {}}}Copy the code
The last
This article ends, thanks for reading. If you feel good, please pay attention to the public account [when I meet You], your support is the biggest motivation for my writing.