一、概述
Feign是Netflix开发的声明式,模板化的HTTP客户端,其灵感来自Retrofit,JAXRS-2.0以及WebSocket.
Feign可帮助我们更加便捷,优雅的调用HTTP API。
在SpringCloud中,使用Feign非常简单——创建一个接口,并在接口上添加一些注解,代码就完成了。
Feign支持多种注解,例如Feign自带的注解或者JAX-RS注解等。
SpringCloud对Feign进行了增强,使Feign支持了SpringMVC注解,并整合了Ribbon和Eureka,从而让Feign的使用更加方便。
1.1 工作原理
1、 启动类添加@EnableFeignClients注解,Spring会扫描标记了@FeignClient注解的接口,并生成此接口的代理对象
2、 @FeignClient(value = )即指定了服务名称,Feign会从注册中心获取服务列表,并通过负载均衡算法进行服务调用。
3、在接口方法 中使用注解@GetMapping("/cms/page/get/{id}"),指定调用的url,Feign将根据url进行远程调用。
1.2 注意事项
1、feignClient接口 有参数在参数必须加@PathVariable("XXX")@RequestParam("XXX")
2、feignClient返回值为复杂对象时其类型必须有无参构造函数。
二、使用Demo
2.1 基本使用
依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring‐cloud‐starter‐openfeign</artifactId>
</dependency>
<dependency>
<groupId>com.netflix.feign</groupId>
<artifactId>feign‐okhttp</artifactId>
</dependency>配置
feign:
hystrix: # sentinel
enabled: true # 开启Feign的熔断功能
hystrix:
command:
default:
execution:
timeout:
#设置熔断超时时间
enabled:false
loggerLevel: full
isolation:
thread:
timeoutInMilliseconds:30000
threadpool:
default:
#并发执行的最大线程数,默认10个,这里改成15个
coreSize:15
#BlockingQueue的最大队列数,默认值为-1,这里改成50个
maxQueueSize:100
#即使maxQueueSize没有达到,达到queueSizeRejectionThreshold该值后,请求也会被拒绝,默认值5,这里改成100
queueSizeRejectionThreshold:100
ribbon:
ReadTimeout:60000
ConnectTimeout:2000应用
//指定需要调用的微服务名称
@FeignClient(name="shop-service-product")
public interface ProductFeginClient {
//调用的请求路径
@RequestMapping(value = "/product/{id}",method = RequestMethod.GET)
public Product findById(@PathVariable("id") Long id);
}定义各参数绑定时,@PathVariable、@RequestParam、@RequestHeader等可以指定参数属性,在Feign中绑定参数必须通过value属性来指明具体的参数名,不然会抛出异常
@FeignClient:注解通过name指定需要调用的微服务的名称,用于创建Ribbon的负载均衡器。所以Ribbon会把 shop-service-product 解析为注册中心的服务。
启用
启动类添加@EnableFeignClients注解
2.2 自定义构建
import feign.Feign;
import feign.Logger;
import feign.codec.Decoder;
import feign.codec.Encoder;
import org.springframework.cloud.openfeign.support.SpringMvcContract;
import org.springframework.context.annotation.Import;
import org.springframework.stereotype.Component;
/**
* feign工具类
* 可借助缓存优化feign工具类调用。
* @author wangxp
* @Date 2020/9/27
*/
@Component
//@Import(FastJsonConfigurer.class)
public class FeignHelper {
//Feign 原生构造器
private Feign.Builder builder;
public FeignHelper(Decoder decoder, Encoder encoder) {
this.builder = Feign.builder()
.encoder(encoder)
.decoder(decoder)
.logLevel(Logger.Level.HEADERS)
.contract(new SpringMvcContract());
}
/**
* 自定义Feign Target<br/>
* 与注解自动生成的Target区别:可以手动指定服务实例
*
* @param apiType
* @param url
* @param <T>
* @return
*/
public <T> T target(Class<T> apiType, String url) {
return this.builder.target(apiType, url);
}
public Feign.Builder getBuilder() {
return this.builder;
}
}三、配置
3.1 feign组件配置
Feign可以支持很多的自定义配置,如下表所示:
| 类型 | 作用 | 说明 |
|---|---|---|
| feign.Logger.Level | 修改日志级别 | 包含四种不同的级别:NONE、BASIC、HEADERS、FULL |
| feign.codec.Decoder | 响应结果的解析器 | http远程调用的结果做解析,例如解析json字符串为java对象 |
| feign.codec.Encoder | 请求参数编码 | 将请求参数编码,便于通过http请求发送 |
| feign. Contract | 支持的注解格式 | 默认是SpringMVC的注解 |
| feign. Retryer | 失败重试机制 | 请求失败的重试机制,默认是没有,不过会使用Ribbon的重试 |
一般情况下,默认值就能满足我们使用,如果要自定义时,只需要创建自定义的@Bean覆盖默认Bean即可。
自定义配置
配置文件方式
feign:
client:
config:
default/demoservice: # 针对某个微服务的配置 使用default为全局
loggerLevel: FULL # 日志级别 相当于代码配置方式中的Logger Fegin的日志级别
connectTimeout: 5000 # 相当于Request.Options 建立链接的超时时长
readTimeout: 5000 # 相当于Request.Options 读取超时时长
# Feign的错误解码器,相当于代码配置方式中的ErrorDecoder Feign的错误解码器
errorDecoder: com.example.SimpleErrorDecoder
# 配置重试,相当于代码配置方式中的Retryer
retryer: com.example.SimpleRetryer
# 配置拦截器,相当于代码配置方式中的RequestInterceptor
requestInterceptors:
- com.example.FooRequestInterceptor
- com.example.BarRequestInterceptor
decode404: false # 配置熔断不处理404异常
feign:
compression:
request:
enabled: true # 开启请求压缩
mime-types: text/html,application/xml,application/json # 设置压缩的数据类型
min-request-size: 2048 # 设置触发压缩的大小下限
response:
enabled: true # 开启响应压缩java类配置
public class DefaultFeignConfiguration {
@Bean
public Logger.Level feignLogLevel(){
return Logger.Level.BASIC; // 日志级别为BASIC
}
}如果要全局生效,将其放到启动类的@EnableFeignClients这个注解中:
@EnableFeignClients(defaultConfiguration = DefaultFeignConfiguration .class)如果是局部生效,则把它放到对应的@FeignClient这个注解中:
@FeignClient(value = "demoservice", configuration = DefaultFeignConfiguration .class)3.2 配置连接池
Feign底层发起http请求,依赖于其它的框架。其底层客户端实现包括:
•URLConnection:默认实现,不支持连接池
•Apache HttpClient :支持连接池
•OKHttp:支持连接池
以HttpClient为例:
依赖
<!--httpClient的依赖 -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>配置
feign:
client:
config:
default: # default全局的配置
loggerLevel: BASIC # 日志级别,BASIC就是基本的请求和响应信息 日志级别尽量用basic
httpclient:
enabled: true # 开启feign对HttpClient的支持
max-connections: 200 # 最大的连接数
max-connections-per-route: 50 # 每个路径的最大连接数3.3 feign接口抽取
feign服务提供方和服务消费方都有相关重复代码,可以将方法抽取为接口,提供方实现,消费方继承。实现代码共享。
缺点:
务提供方、服务消费方紧耦合
参数列表中的注解映射并不会继承,因此Controller中必须再次声明方法、参数列表、注解
抽取后问题:
可能会找不到具体的类。因为添加@EnableFeignClients注解的包可能与api的包路径不一致。
// 扫描包路径
@EnableFeignClients(basePackages = "cn.itcast.feign.clients")
// 指定需要加载的接口
@EnableFeignClients(clients = {UserClient.class})四、拓展配置
4.1 编码解码转换为fastjson
fastjson的编码解码器参考fastjsonhelper工具类
package com.lly.core.feign;
import com.lly.core.fastjson.FastJsonHelper;
import com.lly.core.feign.config.FeignRequestInterceptor;
import feign.RequestInterceptor;
import feign.codec.Encoder;
import feign.form.spring.SpringFormEncoder;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.cloud.openfeign.support.ResponseEntityDecoder;
import org.springframework.cloud.openfeign.support.SpringDecoder;
import org.springframework.cloud.openfeign.support.SpringEncoder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
/**
* @Classname FeignAutoConfiguration
* @Description feign的公用配置
* @Date 2021/08/14 17:06
* @Created by wxp
*/
@Configuration
public class FeignAutoConfiguration {
// @Bean
// public RequestInterceptor requestInterceptor() {
// return new FeignRequestInterceptor();
// }
@Bean
public ResponseEntityDecoder feignDecoder() {
HttpMessageConverter fastJsonConverter = FastJsonHelper.getFastJsonConverter();
ObjectFactory<HttpMessageConverters> objectFactory = () -> new HttpMessageConverters(fastJsonConverter);
return new ResponseEntityDecoder(new SpringDecoder(objectFactory));
}
@Bean
public Encoder feignEncoder() {
return new SpringFormEncoder(new SpringEncoder(() -> new HttpMessageConverters(FastJsonHelper.getFastJsonConverter())));
// return new SpringEncoder(() -> new HttpMessageConverters(getFastJsonConverter()));
}
}4.2 请求拦截器
package com.lly.core.feign.config;
import com.lly.common.utils.StringUtils;
import com.lly.core.constant.CacheConstants;
import com.lly.core.utils.IpUtils;
import com.lly.core.utils.ServletUtils;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.util.Map;
/**
* feign 请求拦截器
*
* @author ruoyi
*/
@Component
public class FeignRequestInterceptor implements RequestInterceptor
{
@Override
public void apply(RequestTemplate requestTemplate)
{
HttpServletRequest httpServletRequest = ServletUtils.getRequest();
if (StringUtils.isNotNull(httpServletRequest))
{
Map<String, String> headers = ServletUtils.getHeaders(httpServletRequest);
// 传递用户信息请求头,防止丢失
String userId = headers.get(CacheConstants.DETAILS_USER_ID.getValue());
if (StringUtils.isNotEmpty(userId))
{
requestTemplate.header(CacheConstants.DETAILS_USER_ID.getValue(), userId);
}
String userName = headers.get(CacheConstants.DETAILS_USERNAME.getValue());
if (StringUtils.isNotEmpty(userName))
{
requestTemplate.header(CacheConstants.DETAILS_USERNAME.getValue(), userName);
}
String authentication = headers.get(CacheConstants.AUTHORIZATION_HEADER.getValue());
if (StringUtils.isNotEmpty(authentication))
{
requestTemplate.header(CacheConstants.AUTHORIZATION_HEADER.getValue(), authentication);
}
// 配置客户端IP
requestTemplate.header("X-Forwarded-For", IpUtils.getIpAddr(ServletUtils.getRequest()));
}
}
}4.3 日期格式序列化
package com.lly.core.feign;
import com.lly.common.utils.DateUtils;
import org.springframework.cloud.openfeign.FeignFormatterRegistrar;
import org.springframework.core.convert.converter.Converter;
import org.springframework.format.FormatterRegistry;
import org.springframework.stereotype.Component;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* feign日期格式序列化
* @ClassName FeignDateFormatRegister
* @Author wuzm
* @Date 2020/12/1 19:04
* @Version 1.0
*/
@Component
public class FeignDateFormatRegister implements FeignFormatterRegistrar {
@Override
public void registerFormatters(FormatterRegistry registry) {
registry.addConverter(Date.class, String.class, new Date2StringConverter());
}
private class Date2StringConverter implements Converter<Date, String> {
@Override
public String convert(Date source) {
SimpleDateFormat sdf = new SimpleDateFormat(DateUtils.ISO_DATE_TIME);
return sdf.format(source);
}
}
}