Feign 踩坑指南 (接口返回泛型设置属性为null)

Feign 踩坑指南 (接口返回泛型设置属性为null)

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;
	}

}

应用场景

  1. 序列化以及反序列化采用jackson

  2. 调用第三方采用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;
   
   ...

本文由 在码圈 创作,如果您觉得本文不错,请随意赞赏
采用 知识共享署名4.0 国际许可协议进行许可
您可以自由的转载和修改,但请务必注明文章来源并且不可用于商业目的。
本站部分内容收集于互联网,如果有侵权内容、不妥之处,请联系我们删除。敬请谅解!
原文链接:https://www.arcinbj.com/archives/openfeign
最后更新于:2020-09-08 01:50:41

请博主喝咖啡 ☕.