Loading

SpringCloud OpenFeign配置

前言

  • 原生OpenFeignSpringCloud OpenFeign在配置上有些区别,主要关注点在Contract、Encoder、Decoder的不同,而Contract最主要作用是对feignClient接口与方法上注解的解析(例如原生OpenFeign使用@RequestLine,而SpringCloud OpenFeign则是使用@RequestMapping),如果配置混了就会造成一些问题
  • 原生OpenFeign都是默认:Contract.Default、Encoder.Default、Decoder.Default
  • SpringCloud OpenFeign则是:SpringMvcContract、SpringEncoder、SpringDecoder
  • 本文主要关注于SpringCloud OpenFeign的配置,对于原生OpenFeign的配置,请看笔者另一篇文章原生Feign进行HTTP调用

依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
    <version>2.1.3.RELEASE</version>
</dependency>
<!--因为使用的是jackson自定义反序列化,所以引入此包-->
<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-jackson</artifactId>
    <version>10.10.1</version>
</dependency>

配置

application.yml

feign:
  hystrix: 
    enabled: true #开启hystrix
  httpclient:
    enabled: true #使用httpclient客户端
  client:
    config:
      default:
        connectTimeout: 3000 #连接超时
        readTimeout: 10000 #返回超时
hystrix:
  threadpool:
    default:
      coreSize: 50
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 9000 #fallback超时时间

FeignClient

  • 因为公共路径和公共headers,接口上使用了@RequestMapping注解,所以不能使用FallBackImpl实现接口的fallback,否则会报错

    java.lang.IllegalStateException: Ambiguous mapping. Cannot map 'com.wf.itao.auth.core.client.AspClient' method...
    
  • @RequestMappingheaders设置需要使用=连接,而不能使用:,这点在org.springframework.cloud.openfeign.support.SpringMvcContract#parseHeaders方法中体现

  • headers中可以使用变量形式,从配置中获取值

  • @RequestHeader注解支持动态传入header

package com.wf.itao.auth.core.client;

import com.wf.itao.auth.core.client.fallback.AspClientFallbackFactory;
import com.wf.itao.auth.core.common.util.AspResponseDecoder;
import com.wf.itao.auth.core.entity.Role;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

import java.util.List;

/**
 * @author wf
 * @date 2022年09月23日 11:14
 * @description
 */
@RequestMapping(value = "dev", headers = {"systemKey=${sysKey}"})
@FeignClient(name = "asp", url = "${gwUrl}", decode404 = true, configuration = {AspResponseDecoder.class}, fallbackFactory = AspClientFallbackFactory.class)
public interface AspClient {

    String QUERY_ROLES_URI = "/queryRoles";

    String QUERY_BTNS = "/queryBtns";

    /**
     * 查询角色
     *
     * @param userId
     * @return
     */
    @GetMapping(value = QUERY_ROLES_URI)
    List<Role> queryRoles(@RequestHeader("sgs-userid") String userId);

    @GetMapping(value = QUERY_BTNS)
    List<String> queryBtns(@RequestHeader("sgs-userid") String userId, @RequestParam("moduleId") Long moduleId, @RequestParam("roleId") Long roleId, @RequestParam("time") Long time);

FallbackFactory

package com.wf.itao.auth.core.client.fallback;

import com.wf.itao.auth.core.client.AspClient;
import com.wf.itao.auth.core.entity.Role;
import feign.hystrix.FallbackFactory;
import org.springframework.stereotype.Component;
import org.testng.collections.Lists;

import java.util.List;

/**
 * @author wf
 * @date 2022年09月23日 11:14
 * @description
 */
@Component
public class AspClientFallbackFactory implements FallbackFactory<AspClient> {

    @Override
    public AspClient create(Throwable cause) {
        return new AspClient() {
            @Override
            public List<Role> queryRoles(String userId) {
                return Lists.newArrayList();
            }

            @Override
            public List<String> queryBtns(String userId, Long moduleId, Long roleId, Long time) {
                return Lists.newArrayList();
            }
        };
    }
}

自定义Response

package com.wf.itao.auth.core.common.asp;

/**
 * @author wf
 * @date 2021年04月27日 14:28
 * @description
 */
public class AspResponse<T> {

    private String msg;
    private String succ;
    private T result;

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public String getSucc() {
        return succ;
    }

    public void setSucc(String succ) {
        this.succ = succ;
    }

    public T getResult() {
        return result;
    }

    public void setResult(T result) {
        this.result = result;
    }
}

自定义Decoder

package com.wf.itao.auth.core.common.util;

import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.wf.boot.base.exception.BusinessException;
import com.wf.itao.auth.core.common.asp.AspResponse;
import feign.FeignException;
import feign.Response;
import feign.jackson.JacksonDecoder;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpStatus;
import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl;

import java.io.IOException;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.Map;

/**
 * @author wf
 * @date 2021年04月27日 14:10
 * @description
 */
@Slf4j
public class AspResponseDecoder extends JacksonDecoder {

    private static final String OK = "ok";

    private static final ObjectMapper MAPPER = new ObjectMapper();

    static {
        MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    }

    @Override
    public Object decode(Response response, Type type) throws IOException {
        Type decodeType = ParameterizedTypeImpl.make(AspResponse.class, new Type[]{Object.class}, null);
        Object decodeObj = super.decode(response, decodeType);
        if (decodeObj != null && decodeObj instanceof AspResponse) {
            AspResponse aspResponse = (AspResponse) decodeObj;
            if (!OK.equals(aspResponse.getSucc())) {
                log.error("接口返回失败 url : {}, msg : {}", response.request().url(), aspResponse.getMsg());
                throw FeignException.errorStatus("接口返回失败 url :" + response.request().url(), response);
            }
            if (aspResponse.getResult() == null) {
                JavaType javaType = MAPPER.constructType(type);
                Class<?> rawClass = javaType.getRawClass();
                if (Collection.class.isAssignableFrom(rawClass) ||
                        Map.class.isAssignableFrom(rawClass)) {
                    try {
                        return rawClass.newInstance();
                    } catch (Exception e) {
                        log.error("实例化aspResponse异常", e);
                    }
                }
            }
            return MAPPER.convertValue(aspResponse.getResult(), MAPPER.constructType(type));
        }
        throw new BusinessException(JSON.toJSONString(decodeObj));
    }
}

Application

  • @EnableFeignClients开启Feign

总结

  • SpringCloud OpenFeign是在原生OpenFeign的基础上构建起来,更加易用
  • SpringCloud OpenFeign不支持@Headers注解,@Headers原生OpenFeign支持,在Contract.Default中解析

参考

posted @ 2022-12-15 21:24  FynnWang  阅读(734)  评论(0编辑  收藏  举报