目录
1.Feign简介
Feign是一种声明式的web service客户端,使用Feign只需定义一个接口并加上相应注解即可使用。通过Feign只需要简单的几行配置,即可实现调用远程服务如调用本地服务般简单
2.入门案例
服务名 | 服务端口 | 作用 |
---|---|---|
192.168.1.100 | 8500 | 注册中心 |
eureka-client | 8762,8763 | 服务提供者 |
eureka-feign-client | 8765 | feign客户端 |
-
新建服务
eureka-feign-client
-
引入依赖
从此章开始,后续章节均使用consul作为注册中心
<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-consul-all</artifactId> </dependency> </dependencies>
-
配置
bootstrap.yml
server: port: 8765 spring: application: # 服务注册名 name: eureka-feign-service cloud: consul: config: # 设置配置文件夹 prefix: config # 设置配置文件夹位置 default-context: application # 设置配置文件名称 data-key: data # 设置配置文件显示样式,有properties和yaml两种格式 format: yaml # 开启监听,动态刷新配置,默认每秒触发一次 watch: enabled: true # 修改动态刷新配置时间 # delay: 2000 # 应用和profile之间的分割符,与spring.profiles.active配合使用 # profile-separator: , enabled: true # consul服务器地址 host: 192.168.1.100 # 默认端口号 port: 8500 # 是否注册,默认为true discovery: register: true # 健康检查时间 health-check-timeout: 2m enabled: true heartbeat: enabled: true # 优先ip注册 prefer-ip-address: true instance-id: ${spring.application.name}:${spring.cloud.client.hostname}:${spring.application.instance_id:${server.port}}
-
书写启动类
注意启动类中需引入注解
@EnableDiscoveryClient
和@EnableFeignClients
@EnableDiscoveryClient:服务注册至注册中心可被发现
@EnableFeignClients:开启feign
@SpringBootApplication @EnableFeignClients @EnableDiscoveryClient public class EurekaFeignclientApplication { public static void main(String[] args) { SpringApplication.run(EurekaFeignclientApplication.class, args); } }
-
书写一个FeignClient,开始远程调用
@FeignClient(name = "eureka-client") public interface EurekaFeignclientService { @GetMapping("/hi") String sayHello(@RequestParam String name); }
-
访问
http://localhost:8765/hi
出现如下结果hi 张三, i am from port:8763
hi 张三, i am from port:8762
3.Feign工作原理简单分析
Feign通过包扫描FeignClient的Bean,该源码在FeignClientsRegistar类中。程序启动时,会检查是否有@EnableFeignClients注解,如果有该注解,则开启包扫描,扫描被@FeignClient注解的接口
- 3.1 FeignClientsRegistrar分析
class FeignClientsRegistrar
implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {}
- 3.2 查看
registerBeanDefinitions()
方法可知注册bean时需处理下述两个方法
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
registerDefaultConfiguration(metadata, registry);
registerFeignClients(metadata, registry);
}
- 3.3 继续分析其中的两个方法
registerDefaultConfiguration
:扫描是否开启@EnableFeignClients,如果开启,通过调用registerClientConfiguration
方法,将该类加载进spring容器中
private void registerDefaultConfiguration(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
Map<String, Object> defaultAttrs = metadata
.getAnnotationAttributes(EnableFeignClients.class.getName(), true);
if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
String name;
if (metadata.hasEnclosingClass()) {
name = "default." + metadata.getEnclosingClassName();
}
else {
name = "default." + metadata.getClassName();
}
registerClientConfiguration(registry, name,
defaultAttrs.get("defaultConfiguration"));
}
}
registerFeignClients
:扫描项目中配置了@FeignClient注解的对象,如果存在,则将该对象加载进spring容器中
-
3.4 ReflectiveFeign分析
注入BeanDefinition之后,通过JDK代理,当调用Feign Client接口里边的方法时,该方法被拦截,从而根据参数生成RequestTemplate对象,最终实现请求下游服务
-
3.5 负载均衡分析
FeignRibbonClientAutoConfiguration
中注入了HttpClientFeignLoadBalancedConfiguration
@ConditionalOnClass({ ILoadBalancer.class, Feign.class }) @Configuration @AutoConfigureBefore(FeignAutoConfiguration.class) @EnableConfigurationProperties({ FeignHttpClientProperties.class }) @Import({ HttpClientFeignLoadBalancedConfiguration.class, OkHttpFeignLoadBalancedConfiguration.class, DefaultFeignLoadBalancedConfiguration.class }) public class FeignRibbonClientAutoConfiguration {}
查看
HttpClientFeignLoadBalancedConfiguration
源码中通过feignClient加载Client对象,最终将给LoadBalancerFeignClient
对象进行初始化@Bean @ConditionalOnMissingBean(Client.class) public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory, SpringClientFactory clientFactory, HttpClient httpClient) { ApacheHttpClient delegate = new ApacheHttpClient(httpClient); return new LoadBalancerFeignClient(delegate, cachingFactory, clientFactory); }
查看
LoadBalancerFeignClient
对象中的execute()
,该方法主要解析url,服务名,最终将请求信息交由executeWithLoadBalancer()
处理@Override public Response execute(Request request, Request.Options options) throws IOException { try { URI asUri = URI.create(request.url()); String clientName = asUri.getHost(); URI uriWithoutHost = cleanUrl(request.url(), clientName); FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest( this.delegate, request, uriWithoutHost); IClientConfig requestConfig = getClientConfig(options, clientName); return lbClient(clientName) .executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse(); } //省略 }
继续查看
executeWithLoadBalancer()
源码中submit()中selectServer()方法为具体选择下游服务实现private Observable<Server> selectServer() { return Observable.create(new OnSubscribe<Server>() { @Override public void call(Subscriber<? super Server> next) { try { Server server = loadBalancerContext.getServerFromLoadBalancer(loadBalancerURI, loadBalancerKey); next.onNext(server); next.onCompleted(); } catch (Exception e) { next.onError(e); } } });3.5 负载均衡分析 }
最终通过
loadBalancerContext
对象实现负载均衡,而loadBalancerContext
则通过上篇文章中ILoadBalancer
s获取下游服务器列表实现,最终根据选择的服务列表实现负载均衡 -
3.6 源码实现总结
(1) 首先通过
@EnableFeignClients
注解开启FeignClient功能,只有注解存在,才会扫描@FeignClient(2) 根据扫描到的@FeignClient接口,将接口创建交给spring容器管理
(3) 当调用@FeignClient修饰的接口时,通过JDK代理生成具体的RequestTemplate模板对象
(4) 根据RequestTemplate再生成Http请求的Request对象
(5) Request对象交给Client去处理,其中的Client网络请求框架可以是HttpURLConnection、HttpClient和OKHttp
(6) 最后Client被封装到LoadBalancerClient类中,通过Ribbon实现负载均衡
4. Feign基础功能及配置
4.1 默认配置文件
Feign | default | 用途 |
---|---|---|
Decoder | ResponseEntityDecoder | 编码 |
Logger | Slf4jLogger | 日志 |
Encoder | SpringEncoder | 解码 |
Contract | SpringMvcContract | 协议 |
Feign.Builder | HystrixFeign.Builder | |
Client | 如果Ribbon可用则为LoadBalancerFeignClient,否则为默认Feign client |
- 使用自定义配置
可以自定义配置,使用默认值,但要需要注意,如果使用自定义配置,则不能将该配置置至@ComponentScan之内
@Configuration
public class FooConfiguration {
@Bean
public Contract feignContract() {
return new feign.Contract.Default();
}
@Bean
public BasicAuthRequestInterceptor basicAuthRequestInterceptor() {
return new BasicAuthRequestInterceptor("user", "password");
}
}
- 使用配置文件,配置单个FeignClient
feign:
client:
config:
feignName:
connectTimeout: 5000
readTimeout: 5000
loggerLevel: full
errorDecoder: com.example.SimpleErrorDecoder
retryer: com.example.SimpleRetryer
requestInterceptors:
- com.example.FooRequestInterceptor
- com.example.BarRequestInterceptor
decode404: false
encoder: com.example.SimpleEncoder
decoder: com.example.SimpleDecoder
contract: com.example.SimpleContract
- 所有FeignClient使用一套配置
feign:
client:
config:
default:
connectTimeout: 5000
readTimeout: 5000
loggerLevel: basic
注意:如果配置文件和@Configuration同时存在,优先使用配置文件,如果想使用@Configuration则可使用feign.client.default-to-properties=false
4.2 Feign开启GZIP压缩
具体用法大家可以查看官方Feign官方文档
feign:
compression:
response:
enabled: true
request:
enabled: true
4.3Feign日志
日志级别 | 说明 |
---|---|
NONE | 不打印日志(默认) |
BASIC | 只打印请求方式及url和响应码,执行时间 |
HEADERS | 只打印请求及响应头 |
FULL | 表示展示所有信息 |
Feign默认未开启日志,如果需要开启日志只需执行如下几步
1.调用方启动类或配置文件创建日志Bean,配置日志输出信息
2.配置文件中配置日志级别
下图为配置日志级别
箭头所指处即为对外提供的api全路径,当服务消费者调用该api时,则会以debug日志级别打印
debug日志级别打印信息
4.4 @QueryMap
使用request请求时,接收对象,可使用@SpringQueryMap进行接收
@FeignClient("demo")
public class DemoTemplate {
@GetMapping(path = "/demo")
String demoEndpoint(@SpringQueryMap Params params);
}
6.参考资料
- sprincloud官方文档
- 《深入理解Spring Cloud与微服务构建》