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发起请求: