1.測試環境搭建:
1.1 架構圖:
product服務提供一個接口:
order服務通過feign的方式來調用product的接口:
order服務需要引入依賴:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
order服務對外的controller :
order服務主啟動類:
2. 源碼分析
2.1: spring 是如何找到@FeignClient標注的接口?
我們在order服務啟動類中加了一個注解:@EnableFeignClients,點擊該注解進入看看
由圖可知,該注解向容器導入了FeignClientsRegistrar.class類,然后我們跟蹤進入該類:
該類實現了ImportBeanDefinitionRegistrar接口,該接口有個向spring容器注冊的組件的方法:
debug調試:發現metadata就是主啟動類的信息
先跟蹤第一個方法:registerDefaultConfiguration(metadata, registry);
上圖:通過主啟動類獲取@EnableFeignClients注解的defaultConfiguration屬性,同時創建了一個名叫name的變量,值為:default.+主啟動類的全限定類名
繼續跟進去:
上圖:向sprin容器中注冊了一個FeignClientSpecification類,beanName為:default.com.yang.xiao.hui.order.OrderApplication.FeignClientSpecification
@EnableFeignClients注解的defaultConfiguration屬性我並沒有配置信息,所以FeignClientSpecification的屬性值是一個空的數組
第一個方法分析完后,下面分析第二個:
public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { ClassPathScanningCandidateComponentProvider scanner = getScanner(); //創建一個掃描器 scanner.setResourceLoader(this.resourceLoader); Set<String> basePackages; Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName()); //獲取EnableFeignClients注解的元素據,因為該注解可以包含你要掃描的路徑 AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(FeignClient.class); //注解過濾器,代表要過濾含有FeignClient.class的類 final Class<?>[] clients = attrs == null ? null : (Class<?>[]) attrs.get("clients"); if (clients == null || clients.length == 0) { //由於我沒有配置對應的屬性,所以會走這個分支 scanner.addIncludeFilter(annotationTypeFilter); //掃描器將注解過濾器新增進來了,這個是重點了 basePackages = getBasePackages(metadata); //EnableFeignClients注解的value,basePackages,basePackageClasses屬性有值,就掃描這些配置的包名,如果沒設置就掃描主啟動類的包名,此處我沒有配置,所以這里得到的是主啟動類的報名 } else { final Set<String> clientClasses = new HashSet<>(); basePackages = new HashSet<>(); for (Class<?> clazz : clients) { basePackages.add(ClassUtils.getPackageName(clazz)); clientClasses.add(clazz.getCanonicalName()); } AbstractClassTestingTypeFilter filter = new AbstractClassTestingTypeFilter() { @Override protected boolean match(ClassMetadata metadata) { String cleaned = metadata.getClassName().replaceAll("\\$", "."); return clientClasses.contains(cleaned); } }; scanner.addIncludeFilter( new AllTypeFilter(Arrays.asList(filter, annotationTypeFilter))); }
//遍歷所有要掃描的包名 for (String basePackage : basePackages) { Set<BeanDefinition> candidateComponents = scanner .findCandidateComponents(basePackage); //通過掃描器,讀取包名下所有的類,然后看看哪些是有FeignClient.class注解的,將這些類轉成beanDefinition對象 for (BeanDefinition candidateComponent : candidateComponents) { if (candidateComponent instanceof AnnotatedBeanDefinition) { //這些BeanDefinition 都含有FeignClient.class注解 // verify annotated class is an interface AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent; AnnotationMetadata annotationMetadata = beanDefinition.getMetadata(); Assert.isTrue(annotationMetadata.isInterface(), "@FeignClient can only be specified on an interface"); Map<String, Object> attributes = annotationMetadata.getAnnotationAttributes( FeignClient.class.getCanonicalName()); //獲取FeignClient.class注解元信息 String name = getClientName(attributes);//獲取FeignClient.class的name值,我這里配置的是@FeignClient(name ="product" ),所以name就是product registerClientConfiguration(registry, name, attributes.get("configuration"));//這個跟之前分析的第一個方法邏輯是一樣的:向容器注入了一個name=product.feignClientSpecification 類型feignClientSpecification的bean registerFeignClient(registry, annotationMetadata, attributes);//注冊所有的帶有@FeignClient(name ="product" )注解的bean } } } }
上面代碼總結:主要做了:
1.創建了一個掃描器ClassPathScanningCandidateComponentProvider scanner,並將AnnotationTypeFilter過濾器傳入掃描器中(scanner.addIncludeFilter(annotationTypeFilter)),用於過濾掃描到的帶有@FeignClient注解的類
2.獲取要掃描的包路徑:basePackages = getBasePackages(metadata);
3.遍歷包名獲取候選的帶有FeignClient注解的類的信息 Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(basePackage);
因為掃描器之前加了一個注解過濾器,這里isCandidateComponent(metadataReader)的邏輯是使用注解過濾器來過濾出帶有FeignClien注解的類
4.遍歷掃描到的BeanDefinition,向ioc容器中注冊對應的bean,這里每個帶有@FeignClient的類都會注冊2個bean
registerClientConfiguration(registry, name,attributes.get("configuration")); 向容器中注冊了name=product.feignClientSpecification 類型feignClientSpecification的bean
重點是: registerFeignClient(registry, annotationMetadata, attributes);方法
所以,該方法向spring容器注入的bean,名字為:com.yang.xiao.hui.order.controller.ProductService,類型為FeignClientFactoryBean,而FactoryBean都會提供一個getObject方法獲取對應的實例
spring如何獲取帶有@FeignClient注解標准的接口,總結如下圖:
3.springboot自動裝配機制:springboot啟動時會加載類路徑下/META-INF/spring.factories中key為org.springframework.boot.autoconfigure.EnableAutoConfiguration的類:
因此有四個相關的配置類會被加載,我們主要關注2個FeignRibbonClientAutoConfiguration 和FeignAutoConfiguration
//再看看DefaultFeignLoadBalancedConfiguration這個類:
之后看看這個bean: FeignAutoConfiguration
總結:自動裝配機制注入的bean:
4.獲取第三方服務實例
4.1由前面分析,每個帶有@FeignClient注解的接口都會被注入到spring容器,名字為接口的權限定類名,類型為FeignClientFactoryBean
4.2我們debug調試,從容器中根據beanName獲取對應的實例看看,我這里的beanName=com.yang.xiao.hui.order.controller.ProductService,order服務的controller方法先修改下:
跟進該方法:
對於factoryBean,如果beanName前面帶上“&” 符號,獲取的bean就是原始的factoryBean,如果沒帶該符號,獲取的是factoryBean.getObject()方法返回的bean,我們這次調試是沒帶上該符合的
至此,我們可以知道,所有帶有@FeignClient注解的接口獲取實例,最終都是調用FeignClientFactoryBean的getObject方法,該方法才是我們的重點
4.3 FeignClientFactoryBean的getObject()方法的講解:
分析: Feign.Builder builder = feign(context);
//上面很多bean都是通過get(Context,xxx.class)方法獲取,而context就是FeignContext,在springBoot自動裝配時注入了spring容器,那么我們跟蹤第一個方法看看:
FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);

這里有2個疑問:這個容器是怎么創建的,為何能通過該容器獲取對應的bean
由於上面的獲取子容器的方法是在FeignContext中的,所以我們要來分析下FeignContext的創建過程:
FeignContext創建時調用了父類的有參構造
此時們再回到,通過服務名稱獲取子容器的方法,獲取不到就創建一個子容器:
由圖可知,子容器注入了一個FeignClientsConfiguration配置類,該類是由FeignContext初始化時,調用父類有參構造器傳入的,所以分析下該配置類:
@Configuration(proxyBeanMethods = false) public class FeignClientsConfiguration { @Autowired private ObjectFactory<HttpMessageConverters> messageConverters; @Autowired(required = false) private List<AnnotatedParameterProcessor> parameterProcessors = new ArrayList<>(); @Autowired(required = false) private List<FeignFormatterRegistrar> feignFormatterRegistrars = new ArrayList<>(); @Autowired(required = false) private Logger logger; @Autowired(required = false) private SpringDataWebProperties springDataWebProperties; @Bean @ConditionalOnMissingBean public Decoder feignDecoder() { return new OptionalDecoder( new ResponseEntityDecoder(new SpringDecoder(this.messageConverters))); } @Bean @ConditionalOnMissingBean @ConditionalOnMissingClass("org.springframework.data.domain.Pageable") public Encoder feignEncoder() { return new SpringEncoder(this.messageConverters); } @Bean @ConditionalOnClass(name = "org.springframework.data.domain.Pageable") @ConditionalOnMissingBean public Encoder feignEncoderPageable() { PageableSpringEncoder encoder = new PageableSpringEncoder( new SpringEncoder(this.messageConverters)); if (springDataWebProperties != null) { encoder.setPageParameter( springDataWebProperties.getPageable().getPageParameter()); encoder.setSizeParameter( springDataWebProperties.getPageable().getSizeParameter()); encoder.setSortParameter( springDataWebProperties.getSort().getSortParameter()); } return encoder; } @Bean @ConditionalOnMissingBean public Contract feignContract(ConversionService feignConversionService) { return new SpringMvcContract(this.parameterProcessors, feignConversionService); } @Bean public FormattingConversionService feignConversionService() { FormattingConversionService conversionService = new DefaultFormattingConversionService(); for (FeignFormatterRegistrar feignFormatterRegistrar : this.feignFormatterRegistrars) { feignFormatterRegistrar.registerFormatters(conversionService); } return conversionService; } @Bean @ConditionalOnMissingBean public Retryer feignRetryer() { return Retryer.NEVER_RETRY; } @Bean @Scope("prototype") @ConditionalOnMissingBean public Feign.Builder feignBuilder(Retryer retryer) { return Feign.builder().retryer(retryer); } @Bean @ConditionalOnMissingBean(FeignLoggerFactory.class) public FeignLoggerFactory feignLoggerFactory() { return new DefaultFeignLoggerFactory(this.logger); } @Bean @ConditionalOnClass(name = "org.springframework.data.domain.Page") public Module pageJacksonModule() { return new PageJacksonModule(); } @Configuration(proxyBeanMethods = false) @ConditionalOnClass({ HystrixCommand.class, HystrixFeign.class }) protected static class HystrixFeignConfiguration { @Bean @Scope("prototype") @ConditionalOnMissingBean @ConditionalOnProperty(name = "feign.hystrix.enabled") //這里可以發現,如果要想讓hystrix生效,需要配置一下屬性feign.hystrix.enabled public Feign.Builder feignHystrixBuilder() { return HystrixFeign.builder(); } } }
通過該配置類,可以知道,子容器中注入了Decoder,Encoder,Contract,FormattingConversionService,Retryer,FeignLoggerFactory,Module
此時我們再看看父容器和子容器注入的主要類:
有了上面的分析,我們回到之前的方法繼續分析:
我們看到該方法:configureUsingProperties(properties.getConfig().get(this.contextId),builder);,通過一個ContextId也就是服務名,我這里是product,獲取該服務的專有配置,說明每一個服務都可以擁有自己的特殊配置;
看看這個FeignClientProperties類:
我們再次回到FactoryBean的getObject()方法進行分析:
上圖,我們看到從容器中獲取了一個client和targeter,這2個bean是在父容器中獲取的,前面有分析過了:
繼續跟進:
可見是調用了ReflectiveFeign的newInstance(target);方法
至此,我們可以發現,最終是調用了JDK動態代理機制來生成代理類,下面再來分析上面2個方法:
Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);和InvocationHandler handler = factory.create(target, methodToHandler);
先看第一個方法:targetToHandlersByName.apply(target)

我這里只有一個方法:
所以跟進去:
可見,校驗是對RequestMaping注解的校驗
我們分析對方法上的處理:
之后我們再分析InvocationHandler handler = factory.create(target, methodToHandler);方法:
對上述所有分析做個總結:
5.我們是如何通過feign調用到第三方服務的,在這里就是我們是如何通過order服務調用到product服務:
5.1:調用任何服務都要知道ip和端口,因此,如何獲取ip和端口,如果服務提供者有多個,如何進行負載均衡,構建測試代碼如下,在order服務下:
我們看看server是如何獲取到的,這里用到ribbon進行負載均衡,點擊command.submit
上圖是ribbon獲取負載均衡獲取單個服務實例的過程,可以參考我之前的博客,有詳細源碼講解:https://www.cnblogs.com/yangxiaohui227/p/12614343.html
繼續跟蹤,到達了:
最終是使用HttpURLConnection發起請求: