Feign 简介
Feign 的英文表意为“假装,伪装,变形”, 是一个http请求调用的轻量级框架,可以以Java接口注解的方式调用Http请求,而不用像Java中通过封装HTTP请求报文的方式直接调用。Feign通过处理注解,将请求模板化,当实际调用的时候,传入参数,根据参数再应用到请求上,进而转化成真正的请求,这种请求相对而言比较直观。
Feign和OpenFeign的关系
Feign本身不支持Spring MVC的注解,它有一套自己的注解
OpenFeign是Spring Cloud 在Feign的基础上支持了Spring MVC的注解,如@RequesMapping等等。
OpenFeign的@FeignClient
可以解析SpringMVC的@RequestMapping注解下的接口,
并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。
使用
依赖
<!-- 引入open-feign --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <!-- Feign默认所有带参数的请求都是Post,想要使用指定的提交方式需引入依赖 --> <dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-okhttp</artifactId> </dependency>
权限拦截器
这里针对服务与服务之间权限验证
// 定义拦截器 public class MyBasicAuthRequestInterceptor implements RequestInterceptor { @Override public void apply(RequestTemplate template) { // TODO Auto-generated method stub template.header("Authorization", "Basic cm9vdDpyb290"); } }
配置文件 feign: client: config: service-valuation: request-interceptors: - com.online.taxi.passenger.feign.interceptor.MyBasicAuthRequestInterceptor
通用配置
feign: compression: # 配置请求GZIP压缩 request: enabled: true # 配置压缩支持的MIME TYPE mime-types: text/xml,application/xml,application/json # 配置压缩数据大小的下限 min-request-size: 2048 # 配置响应GZIP压缩 response: enabled: true # 采用 apache的 okhttp 作为 http访问 okhttp: enabled: true # feign 客户端配置 client: config: # 默认配置 -> 可单独指定 feignName default: # 链接超时时间 connectTimeout: 5000 # 读取超时时间 readTimeout: 5000 # 日志等级 loggerLevel: full
调用
脱离注册中心
@FeignClient(name = "single",url = "192.168.0.102:7601") public interface ConsumerApiBySingle { // 针对 Feign 的 单独调用 @GetMapping("getHi") String getHi(); }
Eureka 调用 (Feign - Ribbon 实现的负载均衡,RestTemplate发起的调用)
@FeignClient(name = "provider") public interface UserApi { @GetMapping("/users/getUser") ResultDto<User> getUser(); /** * 三种 传参格式 * 1、 默认 @RequestParam * 2、 指定参数名 @RequestParam(name = "xxx") * 3、 传输一个Json格式对象 @RequestBody * @param userName * @return */ @PostMapping("/users/saveUser") ResultDto<User> saveUser(@RequestParam(name = "userName") String userName); }
踩坑
在做 SpringCloud 多服务调用时,有些人可能习惯直接使用Entity来做返回值。但是针对 熔断、降级、隔离问题时,需要做好异常状态判断就很麻烦了
于是,封装了DTO层来做服务之间的数据传输
DTO 范型大坑 ⭐️⭐️⭐️
中间数据传输层为了方便数据的传输 , 使用范型可以很好的封装起来传输对象 , 并且可以封装数据状态类型和其他信息, 坑就在这里 !!!
import com.fasterxml.jackson.annotation.JsonProperty; import org.springframework.http.HttpStatus; import java.io.Serializable; import java.util.HashMap; import java.util.Map; /** * 统一返回参数 * * @date 2020年5月15日10:40:54 * @author Parker * * 在 Feign 的调用过程中,无法直接序列化数据 * * 所以要加上 @JsonProperty ,否者返回则为一个null * */ public class ResultDto<T> implements Serializable { /** Map 容器 */ private final Map<String,Object> resultMap = new HashMap<>(3); /** 数据 */ private T data; public ResultDto(){ resultMap.put("success", true); resultMap.put("code", HttpStatus.OK.value()); resultMap.put("msg", "操作成功"); } /** * 获得编号 * @return */ public int getCode() { return (int)resultMap.get("code"); } /** * 设置编号 * @param code */ public void setCode(int code) { resultMap.put("code", code); } /** * 获得信息 * @return */ public String getMsg() { return (String)resultMap.get("msg"); } /** * 设置信息 * @param msg */ public void setMsg(String msg) {//向json中添加属性,在js中访问,请调用data.msg resultMap.put("msg", msg); } /** * 获得状态 * @return */ public boolean isSuccess() { return (boolean)resultMap.get("success"); } /** * 设置状态 * @param success */ public void setSuccess(boolean success) { resultMap.put("success", success); } // --------------------------------- /** * 设置值 * @param value * @return */ public ResultDto<T> put(T value) { data = value; return this; } /** * 获得值 * @return */ public T get(){ return data; } }
应用场景
- 序列化以及反序列化采用jackson
- 调用第三方采用feign注解式接口
问题分析
ResultDto 是一个api通用接口返回泛型类,User 为传入的具体泛型类
在Feign接口中返回泛型类时,由于Java的泛型机制,在实例化之前无法得到具体的类型 ,因此,虽然服务提供方返回的是具体实例的数据,但是在客户端decode时,无法转化为具体的类。
解决方案
针对范型的字段必须要用@JsonProperty("字段名")
或者```@JsonSetter("字段名")``注解来显示声明属性名字,尤其是首字母为大写的情况,否则反序列化后的数据就为空值。
/** * 统一返回参数 * * @date 2020年5月15日10:40:54 * @author Parker * * 在 Feign 的调用过程中,无法直接序列化数据 * * 所以要加上 @JsonProperty ,否者返回则为一个null * */ public class ResultDto<T> implements Serializable { /** Map 容器 */ @JsonProperty("resultMap") private final Map<String,Object> resultMap = new HashMap<>(3); /** 数据 */ @JsonProperty("data") private T data; ...