背景
项目中,微服务环境下,有很多时候,都需要调用其他服务,而且其他服务基本上都有一个骨架类(如下图),为了不用每次调用都去判断是否成功,所以需要统一处理接口返回的结果
思考
跟踪代码发现,Fegin中有个数据解析器(如下图),默认是使用SpringDecoder实现,我们需要自己封装该类,将逻辑替换
思路
由此,就展开了,需要自定义解析器,并配置解析器生效的思考,如果这么的路走通了,就能统一处理数据了
骨架类
package com.a.mybatis.common.response;
import java.io.Serializable;
import java.util.Objects;
public final class ResponseData<T> implements Serializable {
private static final long serialVersionUID = 7824278330465676943L;
public static final Integer ERROR = 0;
public static final Integer SUCCESS = 1;
private static final String ERROR_MSG = "服务器错误";
private static final String SUCCESS_MSG = "success";
private Integer code;
private String msg;
private T data;
}
自定义解析类
package com.a.common.feign.config;
import com.a.mybatis.common.exception.CustomMessageException;
import com.a.mybatis.common.response.ResponseData;
import com.alibaba.fastjson2.JSON;
import feign.FeignException;
import feign.Response;
import feign.codec.Decoder;
import org.springframework.cloud.openfeign.support.SpringDecoder;
import org.springframework.util.Assert;
import java.io.IOException;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
public class MyDataDecoder implements Decoder {
private final SpringDecoder decoder;
public MyDataDecoder(SpringDecoder decoder) {
this.decoder = decoder;
}
@Override
public Object decode(Response response, Type type) throws IOException, FeignException {
ResponseData<?> responseData = JSON.parseObject(response.body().asReader(StandardCharsets.UTF_8), ResponseData.class);
Assert.notNull(responseData, "feign invoke failed");
if (ResponseData.SUCCESS.equals(responseData.getCode())) {
return responseData.getData();
}
throw new CustomMessageException("数据解析失败");
}
}
配置类
package com.a.common.feign.config;
import feign.codec.Decoder;
import feign.optionals.OptionalDecoder;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.cloud.openfeign.support.HttpMessageConverterCustomizer;
import org.springframework.cloud.openfeign.support.ResponseEntityDecoder;
import org.springframework.cloud.openfeign.support.SpringDecoder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FeignConfig {
/**
* 自定义解析器
*
* @param msgConverters 信息转换
* @param customizers 自定义参数
* @return 解析器
*/
@Bean
public Decoder decoder(ObjectFactory<HttpMessageConverters> msgConverters, ObjectProvider<HttpMessageConverterCustomizer> customizers) {
return new OptionalDecoder((new ResponseEntityDecoder(new MyDataDecoder(new SpringDecoder(msgConverters, customizers)))));
}
}
FeginCilent
package com.a.common.feign;
import com.a.common.feign.config.FeignConfig;
import io.swagger.annotations.ApiOperation;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
import java.util.Map;
@Order(Ordered.HIGHEST_PRECEDENCE)
@Component
@FeignClient(value = "data-accessor-server", url = "http://192.168.0.1:8080", configuration = FeignConfig.class)
public interface DataAccessorFeignClient {
/**
* 获取学生的编号
*
* @param id 唯一主键
* @return 学生编号
*/
@GetMapping(value = "query/student/no")
@ApiOperation(value = "获取学生的编号")
String getStudentNo(Long id);
}
访问类
package com.a.data.controller;
import com.a.common.feign.DataAccessorFeignClient;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
@Api(tags = "Tests-测试-相关接口")
@RequestMapping(value = "/test/test", produces = MediaType.APPLICATION_JSON_VALUE)
public class TestController {
@Resource
private DataAccessorFeignClient dataAccessorFeignClient;
@GetMapping("")
@ApiOperation(value = "test-测试")
public Object test(Long id) {
return dataAccessorFeignClient.getStudentNo(id);
}
}
此时可以愉快的进行接口调用,而又不需要做多余的判断了
通用位置配置
如果把对应的FeignClient放在公共位置,需要对该类是否使用进行配置,能满足该功能的官方注解有两个
1.@ConditionalOnExpression -根据表达式确定是否满足使用
2.@ConditionalOnProperty -根据having值确定是否满足使用
使用@ConditionalOnExpression,发现没有生效,跟踪代码发现,是因为该注解的生效优先级比较低,而 @ConditionalOnProperty 优先级比较高,导致@EnableFeignClients扫描的时候,该注解还没执行,所以要么是使用@ConditionalOnProperty注解,要么是将优先级问题解决掉
记录被查