Feign的组件简介 Feign负责简化接口调用,发起Http请求,Feign也包含了几个核心组件
编码器和解码器:Encoder和Decoder。 Encoder:如果调用接口的时候,传递的参数是个对象,feign需要将这个对象进行encode解码,json序列化,把一个对象转成json格式。 Decoder:json反序列化,收到json以后,将json转换成本地的一个Java对象。
Logger:用于打印接口请求相关的调用日志
Contract:feign注解和spring web mvc 支持的@PathVariable,@RequesstMapping,@RequestParam等注解结合起来使用了。feign本来是没法支持spring web mvc的注解的,但是有了contract(契约组件)支持后,这个组件负责解释其他的注解,让feign可以跟其他注解结合起来使用。
Feign.Builder:Feign客户端的一个实例构造器,基于构建器模式的,Ribbon也有。
FeignClient:最核心的入口,和RibbonLoadBalancerClient类似,包含以上这些核心的组件,基于这些组件去协作调用。
默认配置 Spring Cloud对feign的默认组件
Encoder:SpringEncoder
Decoder:ResponseEntityDecoder
Logger:Sl4jLogger
Contract:SpringMvcContract,解析Spring web mvc的注解
Feign.Builder:HystrixFeign.Builder,和Hystrix整合使用
FeignClient:LoadBalancerFeignClient,底层还是和Ribbon整合
自定义配置 可以通过自定义配置覆盖一些默认的组件,也可以定义拦截器配置,可实现对feign的请求进行拦截,可用于在发起请求之前动态添加请求头,或者打印日志等。
1 2 3 4 5 6 7 8 9 10 11 @FeignClient(name="serviceA",configuration=MyConfiguration.class) public interface ServiceAClient { } public class MyConfiguration { @Bean public RequestInterceptor requestInterceptor () { return new MyRequestInterceptor(); } }
配置文件配置 feign配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 feign: client: config: ServiceA: connectTimeout: 5000 readTimeout: 5000 loggerLevel: full decode404: false feign: client: config: default: connectTimeout: 5000 readTimeout: 5000 loggerLevel: full
启用feign的压缩 1 2 3 4 5 6 7 8 feign: compression: request: enabled: true mime-types: text/xml,application/xml,application/json min-request-size: 2048 response: enabled: true
启用请求日志 1 logging.level.com.zhss.service.ServiceAClient: DEBUG
大体流程画图 大体分析一下Feign是如何完成请求的,包含动态代理,路径和参数的拼装,与Ribbon的整合等。
Feign源码入口 在分析feign源码之前,应该从哪里入手?那么我们在接入feign的时候,有2个很重要的注解,分别是@EnableFeignClients开启feign,和每个接口上的@FeignClient。
分别看一下注解源码上的javadoc。
@FeignClient 用@FeignClient注解标记的接口,会被创建为一个Rest Client,可以被其他组件注入使用。
如果Ribbon启用的话,就会采用负载均衡的方式发送http请求。负载均衡器可以用@RibbonClient来配置,RibbonClient的和名字要和FeignClient的名字一样。就是@FeignClient(“serviceA”)可以通过下面的配置来指定对应服务Ribbon的配置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 @RibbonClient(name = "serviceA", configuration = ServiceAConfiguration.class) public class XXXConfiguration { } public class ServiceAConfiguration { @Value("${ribbon.client.name}") private String name = "client" ; @Autowired private PropertiesFactory propertiesFactory; @Bean @ConditionalOnMissingBean public ILoadBalancer ribbonLoadBalancer (IClientConfig config, ServerList<Server> serverList, ServerListFilter<Server> serverListFilter, IRule rule, IPing ping, ServerListUpdater serverListUpdater) { if (this .propertiesFactory.isSet(ILoadBalancer.class, name)) { return this .propertiesFactory.get(ILoadBalancer.class, config, name); } return new ZoneAwareLoadBalancer<>(config, rule, ping, serverList, serverListFilter, serverListUpdater); } }
@EnableFeignClients 扫描那些标记了@FeignClient的接口,指定要扫描哪些包下面的接口。
1 2 3 4 @Import(FeignClientsRegistrar.class) public @interface EnableFeignClients { ... }
FeignClientsRegistrar是非常重要的一个类,SpringBoot大多数的EnableXXX注解都是通过@Import来完成功能开启的,所以我们猜测,SpringBoot项目启动后,在识别到EnableFeignClients注解后,FeignClientsRegistrar肯定是扫描了标记@FeignClient的接口,完成了@FeignClient的注册。这部分代码和RibbonClientConfigurationRegistrar的相似的,都是先加载了一个default开头的默认配置,然后将每个服务对应的client的配置再加载一些,包装了FeignClientSpecification类放在spring上下文中。
1 2 3 4 5 6 @Override public void registerBeanDefinitions (AnnotationMetadata metadata, BeanDefinitionRegistry registry) { registerDefaultConfiguration(metadata, registry); registerFeignClients(metadata, registry); }
registerDefaultConfiguration 注册默认配置和注册FeignClient,这儿和Ribbon的代码差不多。
1、Application启动类的全的限定名
2、获取@EnableFeignClients注解里配置的defaultConfiguration属性
3、利用以上2个属性,构建一个FeignClientSpecification,注册到了Spring上下文中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 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" )); } } private void registerClientConfiguration (BeanDefinitionRegistry registry, Object name, Object configuration) { BeanDefinitionBuilder builder = BeanDefinitionBuilder .genericBeanDefinition(FeignClientSpecification.class); builder.addConstructorArgValue(name); builder.addConstructorArgValue(configuration); registry.registerBeanDefinition( name + "." + FeignClientSpecification.class.getSimpleName(), builder.getBeanDefinition()); }
registerFeignClients 这个方法会扫描配置的包,然后将标注了@FeignClient注解的接口,进行配置的注册。
获得组件扫描器ClassPathScanningCandidateComponentProvider,这是个内部类。
如果没有配置clients属性,设置扫描的组件为标记了@FeignClient注解类或者接口。读取@EnableFeignClients的basePackages属性
如果没有配置basePackages属性,就会根据注解所在的类设置为扫描的包,例如DemoApplication所在的包
如果配置了clients属性,则不会开启扫描,直接使用配置的clients。一般不会配置
遍历basePackages,扫描所有注解了@FeignClient的类或者接口。判断的逻辑在内部匿名类ClassPathScanningCandidateComponentProvider.isCandidateComponent方法里
得到标记了@FeignClient的接口
根据@FeignClient的配置注册serviceId对应的个性化配置
根据配置的属性,构建器模式构建基于FeignClientFactoryBean的BeanDefinition并注册到BeanDefinitionRegistry中。此时FeignClient类的实例并没有生成,只是构建了一个FeignClientFactoryBean的BeanDefinition,并将其注册到了BeanDefinitionRegistry(也就是Spring上下文)里。大胆猜一下,应该是在后面才会用动态代理去创建FeignClient接口的实例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 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()); AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter( FeignClient.class); final Class<?>[] clients = attrs == null ? null : (Class<?>[]) attrs.get("clients" ); if (clients == null || clients.length == 0 ) { scanner.addIncludeFilter(annotationTypeFilter); basePackages = getBasePackages(metadata); } 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); for (BeanDefinition candidateComponent : candidateComponents) { if (candidateComponent instanceof AnnotatedBeanDefinition) { 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()); String name = getClientName(attributes); registerClientConfiguration(registry, name, attributes.get("configuration" )); registerFeignClient(registry, annotationMetadata, attributes); } } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 private void registerFeignClient (BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map<String, Object> attributes) { String className = annotationMetadata.getClassName(); BeanDefinitionBuilder definition = BeanDefinitionBuilder .genericBeanDefinition(FeignClientFactoryBean.class); validate(attributes); definition.addPropertyValue("url" , getUrl(attributes)); definition.addPropertyValue("path" , getPath(attributes)); String name = getName(attributes); definition.addPropertyValue("name" , name); definition.addPropertyValue("type" , className); definition.addPropertyValue("decode404" , attributes.get("decode404" )); definition.addPropertyValue("fallback" , attributes.get("fallback" )); definition.addPropertyValue("fallbackFactory" , attributes.get("fallbackFactory" )); definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); String alias = name + "FeignClient" ; AbstractBeanDefinition beanDefinition = definition.getBeanDefinition(); boolean primary = (Boolean)attributes.get("primary" ); beanDefinition.setPrimary(primary); String qualifier = getQualifier(attributes); if (StringUtils.hasText(qualifier)) { alias = qualifier; } BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, new String[] { alias }); BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry); }
画图总结流程