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) 進行配置。

相關文章
相關標籤/搜索