前言
- 原生OpenFeign与SpringCloud 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...
-
@RequestMapping的headers设置需要使用
=
连接,而不能使用:
,这点在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中解析
参考
- 为 springcloud feign 添加自定义headers
- fallback实现与fallbackFactory
- @FeignClient用fallback绑定熔断器,加上@RequestMapping时发生错误