feign原理+源码解析

1 架构图。

image.png

2 feign的初始化扫包原理。

(1)feign使用,是main上的@EnableFeignClients 和 api的@FeignClient(value = "serviceXXX") 搭配使用。
(2)@FeignClient 是个自定义注解没啥东西只是个feign定义,
(3)@EnableFeignClients核心是@Import(FeignClientsRegistrar.class),在这里完成对@FeignClient的扫描和beanDefination的定义。spring

3 feign扫包和注册细节

image.png
(1)FeignClientsRegistrar的核心方法 registerBeanDefinitions(),包含
registerDefaultConfiguration(metadata, registry); 拿到@EnableFeignClients的属性冰注册配置bean,
 registerFeignClients (metadata, registry); 扫@feignClient并注册,是核心api

(2)registerFeignClients细节,
    a 获得scanner:ClassPathScanningCandidateComponentProvider
    b 根据getBasePackages()获得包,默认是SpringbootApplication同路径的包。
    c 声明FeignClient.class的AnnotationTypeFilter,加入到scanner
    d 遍历packages,findCandidateComponents(),经过判断isCandidateComponent拿到class,判断依据是是否知足注解filter(已经加入FeignClient条件)
    e registerFeignClient 注册获得的feign的bd,是用factorybean,FeignClientFactoryBean,扩展了不少feign属性。beanname是feign类的全类名。最终register到容器。缓存

4 FeignClientFactoryBean 解析

image.png
(1)经过getObject()返回bean,调用getTarget()架构

(2)首先FeignContext context = applicationContext.getBean(FeignContext.class); 拿到feign环境,bean定义是在org.springframework.cloud.openfeign.FeignAutoConfiguration, 核心this.configurations.put(client.getName(), client) 是为每一个feign维护了一个环境,放在map中。每一个feign实例都能从feignContext里拿到独立的spring容器。T instance = context.getInstance(this.name, type);这种,name就是feignclient注解的 “servicexxxx”。mvc

(3)Feign.Builder builder = get(context, Feign.Builder.class),结合环境生成一个builder,app

这个builder也是从spring容器拿的,FeignClientsConfiguration中有两个,一个hystrix的,一个retryer(默认)的。从feignContext里拿到servicename对应的容器,再根据类型拿到Encoder、Decoder、Contract设置builder。这些bean都在FeignClientsConfiguraiton中定义:SpringEncoder、ResponseEntityDecoder、SpringMvcContract负载均衡

configureFeign(),把application.yml的属性配置进builder中,ide

(4)核心,处理url后,手动写一个HardCoudeTarget,包含服务名称、接口类名、url,调用loadBalance(target),在这里面经过容器拿到一个Client,client定义在DefaultFeignLoadBalancedConfiguration,实际类型是LoadBalancerFeignClient。微服务

(5)targeter.target(this, builder, context, target) 实现代理。ui

5 代理的实现过程。

image.png
Feign.Builder和HardCodedTarget,来最终基于feign的动态代理。

(1) build().newInstance(target);

build()融合了FeignBuilder全部内容建立了RefrectiveFeign,先建立SynchronousMethodHandler.Factory,包含LoadBalancerFeignClient、Retryer(负责请求重试)、请求拦截器、日志打印等,用来建立methodHandler。

(2)Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
根据contract,解析target里面全部方法,包含属性和各类springmvc注解解析,造成SynchronousMethodHandler

(3)Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
遍历target的method,造成method和SynchronousMethodHandler的对应map。

(4)InvocationHandler handler = factory.create(target, methodToHandler)-》ReflectiveFeign.FeignInvocationHandler(target, dispatch)  造成代理。最终是SynchronousMethodHandler的invoke方法,实现最终Proxy.newProxyInstance代理过程。

(5)invoke方法: dispatch.get(method).invoke(args) 根据方法自己,拿到MethodHandler,调用MethodHandler的invoke,包含了ribbon的RequestTemplate,负载均衡,融合重试机制。

6 代理invoke的实际执行过程

image.png
(1)核心在FeignInvocationHandler的invoke,dispatch.get(method).invoke(args);实际是SynchronousMethodHandler的invoke,进而executeAndDecode(template);

(2)    Request request = targetRequest(template); 获得request,其实就是target的feignService信息(service名称,类型等)和请求的springmvc方法,结合出
“GET http://service2/serviceInfo HTTP/1.1”这种请求信息。

(3)response = client.execute(request, options);是LoadBalanceFeignClient,先建立一个RibbonRequest,在拿到ribbon的IClientConfig。

(4)lbClient(clientName),从CachingSpringLoadBalancerFactory中拿到FeignLoadBalancer,FeignLoadBalancer里经过spring容器拿到并封装了ribbon的ILoadBalancer(ribbon用的ZoneAwareLoadBalancer)。CachingSpringLoadBalancerFactory采用了缓存。

(5)lbClient(clientName).executeWithLoadBalancer(ribbonRequest,requestConfig).toResponse() 的第二步
    executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse();  
    调用路线:submit()->selectServer()->ServerOption.call() 发送http请求。selectServer使用了ribbon的select过程。

7 feign的编解码方式

HttpMessageConverters 默认使用jackson2方式进行序列化和反序列化。可是若是是微服务间频频通讯,使用jackson2序列化和反序列化会占用很多系统资源,而且效率较差。能够手动扩展编解码方式,继承AbstractHttpMessageConverter<Object> ,使用@FeignClient(value = "service",path ="/nafosRemoteCall/test" , configuration = ProtoFeignConfiguration.class) 进行配置。