项目中的接口返回结果一般都是规定的固定格式,例如Controller层统一返回Result对象,对象中的data用于存放接口返回的数据,这样在通过feign进行远程调用时一般也用Result对象作为返回值,然后在代码中调用getData获取返回结果。getData这一步完全是冗余的,可以通过ResponseBodyAdvice和Feign的Decoder来解决此问题。
一、ResponseBodyAdvice动态的统一返回格式
在项目中我们通常会创建一个通用的对象来统一接口返回的数据格式,例如:
@Accessors(chain = true)
@Data
public class Result<T> {
private Integer code;
private String msg;
private Object data;
public Result(ResultStatusEnum enums){
this.code = enums.code;
this.msg = enums.msg;
}
public Result<T> setData(T data){
this.data = data;
return this;
}
public Result<T> setData(Collection<T> data){
this.data = data;
return this;
}
public Result<T> setMsg(String msg){
this.msg = msg;
return this;
}
public T getData(){
return (T) data;
}
}
Controller统一返回Result对象,这样虽然做虽然能控制返回的数据格式,但是在分布式中多个微服务互相调用时就显得很不方便,业务代码就会变成这样
public void test(){
List<User> userList = userFeignService.userList().getData();
}
如果要解决这个问题,我们可以让Controller直接返回集合或者其他对象,然后借助SpringMVC提供的ResponseBodyAdvice动态的统一返回格式。
- 自定义个注解,来标识是否对接口返回的数据进行统一格式(默认是)
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface ApiResult {
String successMsg() default "成功";
boolean format() default true;
}
- 实现ResponseBodyAdvice,自定义返回的sjon格式
@ConditionalOnClass(ResponseBodyAdvice.class)
@RestControllerAdvice
public class GlobalResponseFormatConfig implements ResponseBodyAdvice {
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
ApiResult apiResult = returnType.getMethodAnnotation(ApiResult.class);
if(apiResult!=null && !apiResult.format()){
return false;
}
return true;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
if(body.getClass() == Result.class){
return body;
}
Result result = new Result(ResultStatusEnum.SUCCESS).setData(body);
Optional.ofNullable(returnType.getMethodAnnotation(ApiResult.class))
.ifPresent( apiResult -> result.setMsg(apiResult.successMsg()) );
return result;
}
}
这样controller返回的数据就动态的封装为Result对象了。
二、自定义Feign解析器Decoder
在微服务中,多个服务会进行Feign互相调用,在调用其他服务的接口时,返回结果通常是一个封装了结果数据的对象,我们还需要获取到对象内的某个属性才能获取到我们想要的值,虽然代码并不复杂,但还是会有点冗余代码。如:
public void test(){
List<User> userList = userFeignService.userList().getData();
}
我们可以通过自定义Feign的Decoder来自动解析接口的数据
- 自定义Decoder 注意不要加@Component等注解
public class ResponseResultDecoder implements Decoder {
private final SpringDecoder decoder;
public ResponseResultDecoder(SpringDecoder decoder) {
this.decoder = decoder;
}
@Override
public Object decode(Response response, Type type) throws IOException, DecodeException, FeignException {
Method method = response.request().requestTemplate().methodMetadata().method();
ApiResult apiResult = method.getAnnotation(ApiResult.class);
if( apiResult!=null && !apiResult.format() ) {
return this.decoder.decode(response, type);
}
boolean isResultObj = method.getReturnType() == Result.class;
if (!isResultObj) {
String json = IoUtil.read(response.body().asReader(CharsetUtil.CHARSET_UTF_8));
return JSONUtil.parse(json).getByPath(apiResult==null ? "data": apiResult.dataField(),method.getReturnType());
}
return this.decoder.decode(response, type);
}
}
- 编写配置类
@Configuration
public class GlobalFeignConfig {
@Bean
public Decoder decoder(ObjectProvider<HttpMessageConverters> messageConverters){
return new OptionalDecoder((new ResponseEntityDecoder(new ResponseResultDecoder(new SpringDecoder(messageConverters)))));
}
}
这样会使Decoder全局生效,如果只想对某个接口生效时不能这样编写,直接在@FeignService内配置即可
@FeignClient(contextId = "goodsService",
value = ServerNameConstant.GOODS_SERVICE,
path = "/goods",
configuration = GoodsFeignConfig.class
)
这样feignfeign的接口即可直接使用集合或者entity作为返回值了。
|