前言
最近学习Sring-Cloud,遇到的一些问题,其中发现feign这个组件是遇到的坑是最多的,特此记录一下。
1.使用httpclient连接池
示例使用Apache httpclient,pom文件引入依赖
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>
复制代码
配置文件application.yml
feign:
name: saber-auth-provider
httpclient:
enabled: true
复制代码
这样就算是配置好了,验证的话我是用的idea,找到ApacheHttpClient类,在execute方法打个断点,如果请求能走进来说明配置成功。
2.Post请求Form表单支持
pom文件依赖
<dependency>
<groupId>io.github.openfeign.form</groupId>
<artifactId>feign-form-spring</artifactId>
<version>3.5.0</version>
</dependency>
复制代码
创建Feign的配置文件
@Configuration
public class SbFeignConfiguration {
/**
spring初始化的时候创建的,不需要自己初始化这个bean
*/
@Autowired
private ObjectFactory<HttpMessageConverters> messageConverters;
/**
开启feign请求日志
*/
@Bean
public Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
/**
使用formEncoder支持form表单
*/
@Bean
public Encoder feignFormEncoder() {
return new SpringFormEncoder(new SpringEncoder(messageConverters));
}
}
复制代码
使用的时候需要注意post请求,然后参数只能用 Map<String, ?>,并且需要注意设置请求头'application/x-www-form-urlencoded' 类似这样的。
@FeignClient(value = "${feign.name}", path = "/dept")
public interface DeptCloudService {
@PostMapping(value = "/save", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
JsonResult<Integer> saveDept(Map<String, ?> deptDto);
}
复制代码
但是这里我参数想使用POJO类而不是一个map,找了一段时间找到一个方法,就是把feign-form-spring升级到3.5.0版本.在3.5.0版本的 FormEncoder 类的 encode 方法中,贴一下源码
/**
* 这段是3.5.0的代码
* object 请求参数,也就是上边的map或者pojo类
* bodyType 请求的类型
* template 请求模板
*/
public void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException {
String contentTypeValue = this.getContentTypeValue(template.headers());
ContentType contentType = ContentType.of(contentTypeValue);
if (!this.processors.containsKey(contentType)) {
this.delegate.encode(object, bodyType, template);
} else {
Map data;
if (MAP_STRING_WILDCARD.equals(bodyType)) {
data = (Map)object;
} else {
/**
* 这一段是3.5.0之前才有的,之前的版本只判断了Map类
*/
if (!PojoUtil.isUserPojo(object)) {
this.delegate.encode(object, bodyType, template);
return;
}
data = PojoUtil.toMap(object);
}
Charset charset = this.getCharset(contentTypeValue);
((ContentProcessor)this.processors.get(contentType)).process(template, charset, data);
}
}
/**
* 这段是3.4.1的代码,这是3.5.0后边最近的版本了
*/
public void encode (Object object, Type bodyType, RequestTemplate template) throws EncodeException {
String contentTypeValue = getContentTypeValue(template.headers());
val contentType = ContentType.of(contentTypeValue);
//这里只对Map<String,?>进行处理
//MAP_STRING_WILDCARD:Type literal for {@code Map<String, ?>}.
if (!MAP_STRING_WILDCARD.equals(bodyType) || !processors.containsKey(contentType)) {
delegate.encode(object, bodyType, template);
return;
}
val charset = getCharset(contentTypeValue);
val data = (Map<String, Object>) object;
try {
processors.get(contentType).process(template, charset, data);
} catch (Exception ex) {
throw new EncodeException(ex.getMessage());
}
}
复制代码
把feign-form-spring升级到3.5.0后就可以直接使用pojo类传递参数,不过限制是3.5.0版本对应的spring-cloud版本也要升级到Greenwich.RELEASE,那对应的spring-boot又要升级,如果是正式点项目可能太大,可以考虑参考3.5.0的FormEncoder实现自己的Encoder。
3.Get请求使用pojo类
没错,又是pojo类,feign对pojo类的支持简直丧心病狂(个人认为). 一般的正常用法
@FeignClient(value = "${feign.name}", path = "/dept")
public interface DeptCloudService {
@GetMapping("/list")
JsonResult<PageDto> findDeptList(@QueryMap Map query);
}
复制代码
使用注解 @QueryMap 修饰一个map是没问题的,但是将Map换成pojo类就不行了,所幸前边升级了spring-cloud,新增了一个注解 @SpringQueryMap 可以用来修饰pojo类达到传递参数的需求.
@FeignClient(value = "${feign.name}", path = "/dept")
public interface DeptCloudService {
@GetMapping("/list")
JsonResult<PageDto> findDeptList(@SpringQueryMap DeptParam deptParam);
}
复制代码
但是,又是但是,我的pojo类里边有一些参数我希望是公共的,但是传递多个pojo类又不识别,所以我选择了继承的方式来实现,但是默认的传递方式又不能识别父属性的参数,所以又得想办法,最后翻代码执行过程和在github上的文档上乱翻,找到一个方法。
我的pojo类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class DeptParam extends PageParam {
private Long deptId;
@NotBlank(message = "部门名字name不能为空")
private String name;
private Long parentId;
private String remark;
private Integer seq;
}
//我希望的公共参数,可以的话其实分两个参数传递是最好的
@Data
public class PageParam {
/**
* 页码,从1开始
*/
private int pageNum = 1;
/**
* 页面大小
*/
private int pageSize = 10;
private String sortKey;
private String sortValue;
public String sortStr() {
return sortStr(null);
}
public String sortStr(String tableAlias) {
if (StringUtils.isBlank(tableAlias)) {
return sortKey + " " + sortValue;
}
return tableAlias + "." + sortKey + " " + sortValue;
}
}
复制代码
feign配置文件增加配置
@Configuration
public class SbFeignConfiguration {
/**
spring初始化的时候创建的,不需要自己初始化这个bean
*/
@Autowired
private ObjectFactory<HttpMessageConverters> messageConverters;
/**
开启feign请求日志
*/
@Bean
public Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
/**
使用formEncoder支持form表单
*/
@Bean
public Encoder feignFormEncoder() {
return new SpringFormEncoder(new SpringEncoder(messageConverters));
}
//替换解析queryMap的类
@Bean
public Feign.Builder feignBuilder() {
return Feign.builder()
.queryMapEncoder(new BeanQueryMapEncoder())
.retryer(Retryer.NEVER_RETRY);
}
}
复制代码
根据代码断点走下去会发现默认的queryMap是使用 FieldQueryMapEncoder 解析的,而这个类是不会解析父类属性。BeanQueryMapEncoder是feign提供的另一个解析器,配置替换一下。