【一起学源码-微服务】Feign 源码一:源码初探,通过Demo Debug Feign源码
2020/4/9 17:02:42
本文主要是介绍【一起学源码-微服务】Feign 源码一:源码初探,通过Demo Debug Feign源码,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
申明
本文章首发自本人公众号:壹枝花算不算浪漫,如若转载请标明来源!
感兴趣的小伙伴可关注个人公众号:壹枝花算不算浪漫
前言
前情回顾
上一讲深入的讲解了Ribbon的初始化过程及Ribbon与Eureka的整合代码,与Eureka整合的类就是DiscoveryEnableNIWSServerList
,同时在DynamicServerListLoadBalancer
中会调用PollingServerListUpdater
进行定时更新Eureka注册表信息到BaseLoadBalancer
中,默认30s调度一次。
本讲目录
这一讲主要是讲Fegin Demo以及通过入口注解@EnableFeignCliets和@FeignClient来进行源码初探。
目录如下:
- Feign代码Demo
- Feign调用原理
- @EnableEurekaClient和@FeignClient注解扫描
源码分析
Feign代码Demo
Fegin的Demo还是延续之前讲解的Eureka的代码。地址为: github.com/barrywangme…
如上图所示,ServiceB调用ServiceA的服务,定义了一个@FeignClient标注的ServiceAFeignClient接口,里面定义了ServiceA中Controller提供的接口信息。
Feign调用原理
接着我们可以启动所有服务,调用ServiceBController,然后在serviceAFeignClient处打上断点看一下:
我们发现这里serviceAFeignClient显示的是:ReflectiveFeign$FeignInvocationHandler@7642
,这里其实是使用了动态代理,因为ServiceAFeignClient
是一个接口,所以这里可以猜测到底层使用的是JDK动态代理。
接着可以简单地梳理下Feign请求简单原理图:
@EnableEurekaClient和@FeignClient注解扫描
先看下@EnableFeignClients注解代码:
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @Import(FeignClientsRegistrar.class) public @interface EnableFeignClients { //等价于basePackages属性,更简洁的方式 String[] value() default {}; //指定多个包名进行扫描 String[] basePackages() default {}; //指定多个类或接口的class,扫描时会在这些指定的类和接口所属的包进行扫描 Class<?>[] basePackageClasses() default {}; //为所有的Feign Client设置默认配置类 Class<?>[] defaultConfiguration() default {}; //指定用@FeignClient注释的类列表。如果该项配置不为空,则不会进行类路径扫描 Class<?>[] clients() default {}; } 复制代码
接着看下@FeignClient注解代码:
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface FeignClient { //指定Feign Client的名称,如果项目使用了 Ribbon,name属性会作为微服务的名称,用于服务发现 @AliasFor("name") String value() default ""; //用serviceId做服务发现已经被废弃,所以不推荐使用该配置 @Deprecated String serviceId() default ""; //指定Feign Client的serviceId,如果项目使用了 Ribbon,将使用serviceId用于服务发现,但上面可以看到serviceId做服务发现已经被废弃,所以也不推荐使用该配置 @AliasFor("value") String name() default ""; //为Feign Client 新增注解@Qualifier String qualifier() default ""; //请求地址的绝对URL,或者解析的主机名 String url() default ""; //调用该feign client发生了常见的404错误时,是否调用decoder进行解码异常信息返回,否则抛出FeignException boolean decode404() default false; //Feign Client设置默认配置类 Class<?>[] configuration() default {}; //定义容错的处理类,当调用远程接口失败或超时时,会调用对应接口的容错逻辑,fallback 指定的类必须实现@FeignClient标记的接口。实现的法方法即对应接口的容错处理逻辑 Class<?> fallback() default void.class; //工厂类,用于生成fallback 类示例,通过这个属性我们可以实现每个接口通用的容错逻辑,减少重复的代码 Class<?> fallbackFactory() default void.class; //定义当前FeignClient的所有方法映射加统一前缀 String path() default ""; //是否将此Feign代理标记为一个Primary Bean,默认为ture boolean primary() default true; } 复制代码
接着我们看下@EnableFeignClients中注入的 @Import(FeignClientsRegistrar.class) 源码:
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware { // patterned after Spring Integration IntegrationComponentScanRegistrar // and RibbonClientsConfigurationRegistgrar private ResourceLoader resourceLoader; private Environment environment; public FeignClientsRegistrar() { } @Override public void setResourceLoader(ResourceLoader resourceLoader) { this.resourceLoader = resourceLoader; } //在这个重载的方法里面做了两件事情: //1.将EnableFeignClients注解对应的配置属性注入 //2.将FeignClient注解对应的属性注入 @Override public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { //注入EnableFeignClients注解对应的配置属性 registerDefaultConfiguration(metadata, registry); //注入FeignClient注解对应的属性 registerFeignClients(metadata, registry); } /** * 拿到 EnableFeignClients注解 defaultConfiguration 字段的值 * 然后进行注入 * **/ 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")); } } public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { // 获取ClassPath扫描器 ClassPathScanningCandidateComponentProvider scanner = getScanner(); // 为扫描器设置资源加载器 scanner.setResourceLoader(this.resourceLoader); Set<String> basePackages; // 1. 从@EnableFeignClients注解中获取到配置的各个属性值 // 这里可以获取到配置的:basePackages=com.barrywang.service.feign Map<String, Object> attrs = metadata .getAnnotationAttributes(EnableFeignClients.class.getName()); // 2. 注解类型过滤器,只过滤@FeignClient AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter( FeignClient.class); // 3. 从1. 中的属性值中获取clients属性的值 final Class<?>[] clients = attrs == null ? null : (Class<?>[]) attrs.get("clients"); if (clients == null || clients.length == 0) { // 扫描器设置过滤器且获取需要扫描的基础包集合 scanner.addIncludeFilter(annotationTypeFilter); basePackages = getBasePackages(metadata); }else { // clients属性值不为null,则将其clazz路径转为包路径 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))); } // 3. 扫描基础包,且满足过滤条件下的接口封装成BeanDefinition for (String basePackage : basePackages) { // 找到basePackage下定义的@FeignClient接口列表 Set<BeanDefinition> candidateComponents = scanner .findCandidateComponents(basePackage); // 遍历扫描到的bean定义 for (BeanDefinition candidateComponent : candidateComponents) { if (candidateComponent instanceof AnnotatedBeanDefinition) { // 并校验扫描到的bean定义类是一个接口 AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent; AnnotationMetadata annotationMetadata = beanDefinition.getMetadata(); Assert.isTrue(annotationMetadata.isInterface(), "@FeignClient can only be specified on an interface"); // 获取@FeignClient注解上的各个属性值 Map<String, Object> attributes = annotationMetadata .getAnnotationAttributes( FeignClient.class.getCanonicalName()); String name = getClientName(attributes); // 可以看到这里也注册了一个FeignClient的配置bean // 这个方法是创建了一个FeignClientFactoryBean的工厂类,里面保存@FeignClient注解的所有属性值 registerClientConfiguration(registry, name, attributes.get("configuration")); // 注册bean定义到spring中 registerFeignClient(registry, annotationMetadata, attributes); } } } } /** * 注册bean **/ private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map<String, Object> attributes) { // 1.获取类名称,也就是本例中的FeignService接口 String className = annotationMetadata.getClassName(); // 2.BeanDefinitionBuilder的主要作用就是构建一个AbstractBeanDefinition // AbstractBeanDefinition类最终被构建成一个BeanDefinitionHolder // 然后注册到Spring中 // 注意:beanDefinition类为FeignClientFactoryBean,故在Spring获取类的时候实际返回的是 // FeignClientFactoryBean类 BeanDefinitionBuilder definition = BeanDefinitionBuilder .genericBeanDefinition(FeignClientFactoryBean.class); validate(attributes); // 3.添加FeignClientFactoryBean的属性, // 这些属性也都是我们在@FeignClient中定义的属性 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); // 4.设置别名 name就是我们在@FeignClient中定义的name属性 String alias = name + "FeignClient"; AbstractBeanDefinition beanDefinition = definition.getBeanDefinition(); boolean primary = (Boolean)attributes.get("primary"); // has a default, won't be null beanDefinition.setPrimary(primary); String qualifier = getQualifier(attributes); if (StringUtils.hasText(qualifier)) { alias = qualifier; } // 5.定义BeanDefinitionHolder BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, new String[] { alias }); BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry); } private void validate(Map<String, Object> attributes) { AnnotationAttributes annotation = AnnotationAttributes.fromMap(attributes); // This blows up if an aliased property is overspecified // FIXME annotation.getAliasedString("name", FeignClient.class, null); Assert.isTrue( !annotation.getClass("fallback").isInterface(), "Fallback class must implement the interface annotated by @FeignClient" ); Assert.isTrue( !annotation.getClass("fallbackFactory").isInterface(), "Fallback factory must produce instances of fallback classes that implement the interface annotated by @FeignClient" ); } private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name, Object configuration) { // 创建一个使用FeignClientSpecification构建的BeanDefinitionBuilder的类 BeanDefinitionBuilder builder = BeanDefinitionBuilder .genericBeanDefinition(FeignClientSpecification.class); builder.addConstructorArgValue(name); builder.addConstructorArgValue(configuration); registry.registerBeanDefinition( name + "." + FeignClientSpecification.class.getSimpleName(), builder.getBeanDefinition()); } ...... ...... ...... } 复制代码
在这里做了两件事情:
- 将EnableFeignClients注解对应的配置属性注入;
- 将FeignClient注解对应的属性注入。
生成FeignClient对应的bean,注入到Spring 的IOC容器。
总结
在我们查看处理@EnableFeignClients和@FeignClient注解的地方,最后调用registerFeignClient()
会构造一个:
BeanDefinitionBuilder definition = BeanDefinitionBuilder .genericBeanDefinition(FeignClientFactoryBean.class); 复制代码
所以我们后续会重点查看FeignClientFactoryBean
这个类。
这篇关于【一起学源码-微服务】Feign 源码一:源码初探,通过Demo Debug Feign源码的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-01后台管理开发学习:新手入门指南
- 2024-11-01后台管理系统开发学习:新手入门教程
- 2024-11-01后台开发学习:从入门到实践的简单教程
- 2024-11-01后台综合解决方案学习:从入门到初级实战教程
- 2024-11-01接口模块封装学习入门教程
- 2024-11-01请求动作封装学习:新手入门教程
- 2024-11-01登录鉴权入门:新手必读指南
- 2024-11-01动态面包屑入门:轻松掌握导航设计技巧
- 2024-11-01动态权限入门:新手必读指南
- 2024-11-01动态主题处理入门:新手必读指南