需求分析
- 接口请求格式的统一设计
- 接口响应格式的统一设计
- 灵活配置统一接口
统一API的格式及配置方式
将请求体和响应体,分为公有域和私有域(data数据体)两部分。请求体的公有域主要包含私有域(data数据体)。响应体的公有域中包含响应状态码、响应状态信息、错误详情和私有域(data数据体)。
对于需要统一API处理的接口,可以通过注解进行配置
公有域的定义
package com.example.demo.api;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class PublicDomain implements Serializable {
private T data;
}
请求体可直接使用公有域格式
响应体的定义
package com.example.demo.api;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import lombok.Data;
@Data
@JsonPropertyOrder({"resCode", "resMessage", "errorMessage", "data"})
public class ResultVo extends PublicDomain {
// 响应状态码
private int resCode;
// 错误详情
@JsonInclude(JsonInclude.Include.NON_NULL)
private String errorMessage;
public ResultVo() {
}
public ResultVo(T data) {
super(data);
}
public ResultVo(int resCode, T data) {
super(data);
this.resCode = resCode;
}
public ResultVo(RuntimeException e) {
this.resCode = 2;
this.errorMessage = e.getMessage();
}
public ResultVo(Throwable throwable) {
this.resCode = 3;
this.errorMessage = throwable.getMessage();
}
public ResultVo(int resCode, String errorMessage, T data) {
super(data);
this.resCode = resCode;
this.errorMessage = errorMessage;
}
public int getResCode() {
return resCode;
}
// 响应状态信息
public String getResMessage() {
switch (resCode) {
case 0:
return "success";
case 1:
return "fail";
case 2:
return "validate fail";
default:
return "error";
}
}
public String getErrorMessage() {
return errorMessage;
}
}
- @JsonPropertyOrder注解表示属性顺序
- @JsonInclude(JsonInclude.Include.NON_NULL)注解表示,只有该属性不为null时才显示
样例中的响应状态码设计比较简单:
- 0:响应成功,对应的resMessage为:success
- 1:处理失败,对应的resMessage为:fail
- 2:校验失败,对应的resMessage为:validate fail
- 3:处理异常,对应的resMessage为:error
API接口配置注解
package com.example.demo.api;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Documented
@Target({ElementType.TYPE, ElementType.METHOD})
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface Api {
boolean request() default true;
boolean response() default true;
}
- request属性表示在请求时将data数据体转化为请求体对象
- response属性表示响应时将响应体对象使用公有域类封装
统一API的处理
请求体的处理
请求体的处理需要基于RequestBodyAdvice实现
RequestBodyAdvice接口
该接口用于分别在请求体读取之前和读取后并且尚未转化为请求体对象时,对请求进行定制化处理。该接口存在一个适配器类RequestBodyAdviceAdapter,为接口定义的部分方法提供了默认实现。该接口定义了以下4个方法:
- boolean supports(MethodParameter methodParameter, Type targetType, Class extends HttpMessageConverter>> converterType)
- HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class extends HttpMessageConverter>> converterType) throws IOException
- Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class extends HttpMessageConverter>> converterType)
- Object handleEmptyBody(@Nullable Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class extends HttpMessageConverter>> converterType)
- supports 方法用于判断是否支持对于当前请求的拦截处理。MethodParameter是请求参数(详情见下文)。Type是请求体对象的类型,MethodParameter也提供了一个getParameterType()的方法判断请求体对象类型,区别在于targetType可能是一个泛型类型,MethodParameter.getParameterType()返回的是一个没有泛型参数的类对象。Class extends HttpMessageConverter>>是http消息转化器类型,默认的是MappingJackson2HttpMessageConverter
- beforeBodyRead 方法在请求体被读取前调用。HttpInputMessage是对请求的封装,包括请求头和请求体。其中请求体是以输入流InputStream的形式表示的
- afterBodyRead 方法在请求体被读取后并且尚未被转化为对象时被调用
- handleEmptyBody 方法在请求体为空时被调用
MethodParameter类
对方法参数及其上下文的封装,包括方法的入参和返回参数,并提供了一些相应的辅助方法。经常用于表示请求体对象和响应体对象。主要方法如下:
- public Class> getParameterType()
- public Method getMethod()
- public boolean hasMethodAnnotation(Class annotationType)
- public A getMethodAnnotation(Class annotationType)
- public Class> getDeclaringClass()
- public Class> getContainingClass()
- getParameterType方法获取的是声明参数的的类对象
- getMethod方法是获取参数所在方法
- hasMethodAnnotation方法是判断参数所在方法是否存在某个类型的注解
- getMethodAnnotation方法是获取参数所在方法某个类型的注解
- getDeclaringClass方法是获取声明参数所在的类
- getContainingClass方法是获取参数实际所在的类。与前一个方法的区别是,实际执行时,参数可能由于接口、动态代理等原因,参数实际所在的类是声明的类的子类或实现类
方案
- 在beforeBodyRead方法中对HttpInputMessage进行定制,修改请求体的输入流,将统一的基于公有域的输入流变成基于请求体对象的输入流(说直白点就是只把data属性的数据的流)
- 在afterBodyRead方法中使用PublicDomain和请求体类动态生成一个泛型类型(ParamererizedType),用于将原请求体解析为公有域对象,然后将其私有域对象返回
样例代码:
package com.example.demo.api;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.util.StreamUtils;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdviceAdapter;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
@RestControllerAdvice(basePackages = "com.example.demo")
public class RequesstApiAdvice extends RequestBodyAdviceAdapter {
@Autowired
private ObjectMapper objectMapper;
@Override
public boolean supports(MethodParameter methodParameter, Type targetType, Class extends HttpMessageConverter>> converterType) {
Api apiAnn = methodParameter.hasMethodAnnotation(Api.class) ?
methodParameter.getMethodAnnotation(Api.class) : methodParameter.getDeclaringClass().getAnnotation(Api.class);
return apiAnn != null && apiAnn.request() && !PublicDomain.class.equals(methodParameter.getParameterType());
}
@Override
public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter,
Type targetType, Class extends HttpMessageConverter>> converterType) throws IOException {
JsonNode jsonNode = objectMapper.readTree(inputMessage.getBody());
JsonNode dataJsonNode = jsonNode.get("data");
return new HttpInputMessage() {
@Override
public InputStream getBody() {
if (dataJsonNode == null) {
return StreamUtils.emptyInput();
}
return new ByteArrayInputStream(dataJsonNode.toString().getBytes(StandardCharsets.UTF_8));
}
@Override
public HttpHeaders getHeaders() {
return inputMessage.getHeaders();
}
};
}
}
项目中加入该组件后,凡是在控制类或接口方法上定义了@Api注解的接口,都必须将请求体放在data属性中了
响应体的处理
响应体的处理可以基于ResponseBodyAdvice,也可以基于HandlerMethodReturnValueHandler
ResponseBodyAdvice接口
对于响应的定制处理,有以下两个方法:
- boolean supports(MethodParameter returnType, Class extends HttpMessageConverter>> converterType)
- T beforeBodyWrite(@Nullable T body, MethodParameter returnType, MediaType selectedContentType,
Class extends HttpMessageConverter>> selectedConverterType,
ServerHttpRequest request, ServerHttpResponse response)
- supports:该组件是否支持给定的控制器方法返回类型和选择的HttpMessageConverter类型。用于判断是否需要做处理
- beforeBodyWrite:在选择HttpMessageConverter之后调用,在调用其写方法之前调用。用于做返回处理
基于ResponseBodyAdvice的样例代码:
package com.example.demo.api;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
@RestControllerAdvice(basePackages = "com.example.demo")
public class ResponseApiAdvice implements ResponseBodyAdvice {
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
Api apiAnn = returnType.hasMethodAnnotation(Api.class) ?
returnType.getMethodAnnotation(Api.class) : returnType.getDeclaringClass().getAnnotation(Api.class);
return apiAnn != null && apiAnn.response();
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
return body instanceof ResultVo ? body : new ResultVo<>(body);
}
}
HandlerMethodReturnValueHandler接口
HandlerMethodReturnValueHandler是处理控制器方法返回值的策略接口,定义的方法有以下两个:
- boolean supportsReturnType(MethodParameter returnType)
- void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception
- supportsReturnType方法用于判断是否支持声明的响应体类型
- handleReturnValue方法用于对响应进行处理
基于HandlerMethodReturnValueHandler的样例代码
package com.example.demo.api;
import org.springframework.core.MethodParameter;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor;
import java.util.ArrayList;
import java.util.List;
@Component
public class ApiHandlerMethodReturnValueHandler implements HandlerMethodReturnValueHandler {
private HandlerMethodReturnValueHandler handler;
public ApiHandlerMethodReturnValueHandler(RequestMappingHandlerAdapter requestMappingHandlerAdapter) {
List originHandlers = requestMappingHandlerAdapter.getReturnValueHandlers();
List newHandlers = new ArrayList<>(originHandlers.size());
for (HandlerMethodReturnValueHandler originHandler : originHandlers) {
if (originHandler instanceof RequestResponseBodyMethodProcessor) {
newHandlers.add(this);
handler = originHandler;
} else {
newHandlers.add(originHandler);
}
}
requestMappingHandlerAdapter.setReturnValueHandlers(newHandlers);
}
@Override
public boolean supportsReturnType(MethodParameter returnType) {
Api apiAnn = returnType.hasMethodAnnotation(Api.class) ?
returnType.getMethodAnnotation(Api.class) : returnType.getDeclaringClass().getAnnotation(Api.class);
return handler.supportsReturnType(returnType) && apiAnn != null && apiAnn.response();
}
@Override
public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
Object response = returnValue instanceof ResultVo ? returnValue : new ResultVo<>(returnValue);
handler.handleReturnValue(response, returnType, mavContainer, webRequest);
}
}
异常情况的统一处理
异常情况的全局处理主要通过ExceptionHandler注解
package com.example.demo.api;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice(basePackages = "com.example.demo")
public class GlobalExceptionResolver {
@ExceptionHandler(RuntimeException.class)
public ResultVo throwableHandle(RuntimeException e) {
return new ResultVo(e);
}
@ExceptionHandler(Throwable.class)
public ResultVo throwableHandle(Throwable throwable) {
return new ResultVo(throwable);
}
}
如果是特定控制器内的处理,可以把throwableHandle方法写在Controller内