在项目中使用Feign替代HttpClient

背景

在当前业务中,项目需要远程http服务时,往往使用HttpClient进行post/get调用。但是这种编码方式经常需要try catch包裹住这段调用,避免在httpClient调用时,目标服务器出错不可用。为了改善编码,并且兼容现有的微服务框架,我将项目里的httpClient调用替换成Feign调用。

方案

  1. 老的HttpClient调用暂不修改,让业务继续跑。
  2. 新的外部Http服务调用,使用Feign。
  3. 在日常迭代与优化中,逐步替代掉旧的调用方式。

实行

1) 引入依赖
 	<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>io.github.openfeign</groupId>
            <artifactId>feign-okhttp</artifactId>
        </dependency>
2) 定义feign调用时的传参

(为了方便feign调用时候的参数传递)

@Configurable
@AllArgsConstructor
public class FeignBasicAuthRequestInterceptor implements RequestInterceptor {

    private final ObjectMapper objectMapper;

    @Override
    public void apply(RequestTemplate template) {
        // get-pojo贯穿
        if (template.method().equals(HttpMethod.GET.name()) && template.body() != null) {
            try {//template.requestBody().asBytes()
                JsonNode jsonNode = objectMapper.readTree(template.body());
                Map<String, Collection<String>> queries = new HashMap<>();
                //feign 不支持 GET 方法传 POJO, json body转query
                buildQuery(jsonNode, "", queries);
                template.queries(queries);
                template.body(Request.Body.empty());
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    //处理 get-pojo贯穿
    private void buildQuery(JsonNode jsonNode, String path, Map<String, Collection<String>> queries) {
        if (!jsonNode.isContainerNode()) { //叶子节点
            if (jsonNode.isNull()) {
                return;
            }
            Collection<String> values = queries.computeIfAbsent(path, k -> new ArrayList<>());
            values.add(jsonNode.asText());
            return;
        }
        if (jsonNode.isArray()) { //数组节点
            Iterator<JsonNode> it = jsonNode.elements();
            while (it.hasNext()) {
                buildQuery(it.next(), path, queries);
            }
        } else {
            Iterator<Map.Entry<String, JsonNode>> it = jsonNode.fields();
            while (it.hasNext()) {
                Map.Entry<String, JsonNode> entry = it.next();
                if (StringUtils.hasText(path)) {
                    buildQuery(entry.getValue(), path + SymbolEnum.DOT.getValue() + entry.getKey(), queries);
                } else { //根节点
                    buildQuery(entry.getValue(), entry.getKey(), queries);
                }
            }
        }
    }
}
3) 定义序列化bean
@Configuration
public class FeignAutoConfiguration {
    @Bean
    public FeignBasicAuthRequestInterceptor interceptor () {
        return new FeignBasicAuthRequestInterceptor(new ObjectMapper());
    }
    
    @Bean
    feign.Logger.Level feignLoggerLevel() {
        return Logger.Level.FULL;
    }
    
}
4) 声明feign接口
@FeignClient(name = "test",
        contextId = "testRemoteService",
        url = "${host}",
        path = "通用路径")
public interface TestRemoteService {

    @GetMapping("/test")
    ApiResponse<String> test(
            @RequestParam("testId") Long testId, @RequestParam("userId") Long userId);
}
5) 启动feign
> 在启动类上加 @EnableFeignClients(basePackages = { "包名" })
6) 使用feign接口
> 直接在spring里@Autowrid注入, 正常调用于使用
7) 如果需要对接口做熔断,直接使用hystrix。

(这一块先略)