【OpenFeign】@FeignClient 注入过程源码分析
1 前言
微服务之间的调用,OpenFeign 是一种选择,并且还提供了很多功能,比如我们有多个节点,它能负载均衡,当服务发生异常时,它还能提供熔断机制。所以它是怎么实现的,因为我们平时只需要写 @FeignClient 是个接口,所以它势必会走代理,所以是不是要从我们的 @FeignClient 下手。那么这节我们就先简单看下OpenFeign 中 @FeignClient 代理的创建过程,当代理的创建过程知道了,然后我们就可以深入它的增强逻辑,继而能看到它的负载均衡、熔断又是如何做的对吧。
如果你之前看过我的 【Mybatis】【二】源码分析-Mapper 接口都是怎么注入到 Spring容器中的? 以及【Mybatis】【三】源码分析- MapperFactoryBean 的创建过程以及 Mapper 接口代理的生成过程详解 那么这节你就能很好的理解,因为他俩的注入过程太像了,基本都差不多。大体都是先扫描包、然后注入FactoryBean 形式的 BeanDefinition 来实现的。
那么废话不多说,我们直接看。
2 源码分析
2.1 入口 EnableFeignClients
起点在哪里,是不是我们的启动类上,是不是会配置基本的扫描包,@EnableFeignClients 它就是入口。
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @Import(FeignClientsRegistrar.class) public @interface EnableFeignClients {
可以看到它引入了 FeignClientsRegistrar,从名称上就可以看出,它就是 FeignClient 的注册器。
2.2 注册器 FeignClientsRegistrar
那我们看看注册器里都干了什么:
// 可以看到它实现了 ImportBeanDefinitionRegistrar class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware { // 注册 BeanDefinition public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { // 注册默认配置 @FeignClient 注解有个属性是 defaultConfiguration 自定义全局默认配置比如可以配置你自己的重试机制、编码器、解码器等也就是控制 @FeignClient 的行为的(通常不需要配) this.registerDefaultConfiguration(metadata, registry); // 注册 @FeignClient this.registerFeignClients(metadata, registry); } }
如上可以看到大概两部分,一部分是默认的全局配置、一部分是注册我们的 @FeignClient。
2.2.1 全局默认配置类 registerDefaultConfiguration
这个我们就先草草略过:
// @see FeignClientsRegistrar#registerDefaultConfiguration 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(); } this.registerClientConfiguration(registry, name, defaultAttrs.get("defaultConfiguration")); } } // @see FeignClientsRegistrar#registerClientConfiguration private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name, Object configuration) { // FeignClientSpecification BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(FeignClientSpecification.class); builder.addConstructorArgValue(name); builder.addConstructorArgValue(configuration); registry.registerBeanDefinition(name + "." + FeignClientSpecification.class.getSimpleName(), builder.getBeanDefinition()); }
2.2.2 注册 registerFeignClients
我们瞅瞅具体都做了哪些内容:
// 开始注册 FeignClient public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { // 存放候选的 FeignClient 的 BeanDefinition LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet(); // 获取注解信息 Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName()); // @EnableFeignClients 可以直接配置 Client 我们平时基本不配这个,因为当存在大量的 Client 的话那不是费劲么,每新加一个还要配一个多麻烦 所以我们平时配一个或者多个包名 Class<?>[] clients = attrs == null ? null : (Class[])((Class[])attrs.get("clients")); // 当配置了 Client 属性值的话,用 Annotated 的 BeanDefinition 包起来 if (clients != null && clients.length != 0) { Class[] var12 = clients; int var14 = clients.length; for(int var16 = 0; var16 < var14; ++var16) { Class<?> clazz = var12[var16]; candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz)); } } else { // 扫描器 ClassPathScanningCandidateComponentProvider scanner = this.getScanner(); scanner.setResourceLoader(this.resourceLoader); // 指定扫描的筛子 也就是过滤出 @FeignClient 注解的类 scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class)); // 获取注解上配的包名 Set<String> basePackages = this.getBasePackages(metadata); // 逐个循环扫描包下的类 Iterator var8 = basePackages.iterator(); while(var8.hasNext()) { String basePackage = (String)var8.next(); // 都加入到集合中 candidateComponents.addAll(scanner.findCandidateComponents(basePackage)); } } // 对集合中的每个类进行注入 Iterator var13 = candidateComponents.iterator(); while(var13.hasNext()) { BeanDefinition candidateComponent = (BeanDefinition)var13.next(); // 对每一个进行基础检查 然后进行注入 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 = this.getClientName(attributes); // 每个 @FeignClient 也可以设置自己的配置 this.registerClientConfiguration(registry, name, attributes.get("configuration")); // 注册 FeignClient this.registerFeignClient(registry, annotationMetadata, attributes); } } } 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()); }
可以看到会从我们配置的包下扫描出 @FeignClient 的接口,然后进行注入,并且每个 Client 也都会注入一个自己的 ClientConfiguration,即使没有配置也会注入一个这样的 BeanDefinition 哈。
那我们继续看下 FeignClient 的具体注入:
// annotationMetadata 就是 @FeignClient 注解信息 // attributes 就是每个属性的值 private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map<String, Object> attributes) { // 我们的 @FeignClient 下的接口名 String className = annotationMetadata.getClassName(); Class clazz = ClassUtils.resolveClassName(className, (ClassLoader)null); ConfigurableBeanFactory beanFactory = registry instanceof ConfigurableBeanFactory ? (ConfigurableBeanFactory)registry : null; // 我们常设置的 contextId 和 name contextId没有设置的会拿 name 属性的值 name 属性的值又会先获取 serviceId 的值,没有的话拿 name 再没有的话会拿 value 并且他俩都可以设置表达式 ${}这种写法 会根据环境变量等配置属性解析的 String contextId = this.getContextId(beanFactory, attributes); String name = this.getName(attributes); // 嘿嘿, 外壳是用 FeignClientFactoryBean 来包装的 FeignClientFactoryBean factoryBean = new FeignClientFactoryBean(); factoryBean.setBeanFactory(beanFactory); factoryBean.setName(name); factoryBean.setContextId(contextId); factoryBean.setType(clazz); // 是否开启配置动态刷新这个跟 feign.client.refresh-enabled 有关默认是 false factoryBean.setRefreshableClient(this.isClientRefreshEnabled()); BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(clazz, () -> { factoryBean.setUrl(this.getUrl(beanFactory, attributes)); factoryBean.setPath(this.getPath(beanFactory, attributes)); factoryBean.setDecode404(Boolean.parseBoolean(String.valueOf(attributes.get("decode404")))); Object fallback = attributes.get("fallback"); // 两个异常钩子 if (fallback != null) { factoryBean.setFallback(fallback instanceof Class ? (Class)fallback : ClassUtils.resolveClassName(fallback.toString(), (ClassLoader)null)); } Object fallbackFactory = attributes.get("fallbackFactory"); if (fallbackFactory != null) { factoryBean.setFallbackFactory(fallbackFactory instanceof Class ? (Class)fallbackFactory : ClassUtils.resolveClassName(fallbackFactory.toString(), (ClassLoader)null)); } return factoryBean.getObject(); }); // 装配模式=2 也就是根据类型装配 和 mabatis 里的 MapperFactoryBean 异曲同工 definition.setAutowireMode(2); // 延迟加载的 也就是没有人依赖这个 FeignClient 它就不会创建 很合理 definition.setLazyInit(true); // 校验属性 主要是检查两个失败回调的设置 this.validate(attributes); AbstractBeanDefinition beanDefinition = definition.getBeanDefinition(); beanDefinition.setAttribute("factoryBeanObjectType", className); beanDefinition.setAttribute("feignClientsRegistrarFactoryBean", factoryBean); // 哟 还能设置主的 boolean primary = (Boolean)attributes.get("primary"); beanDefinition.setPrimary(primary); String[] qualifiers = this.getQualifiers(attributes); if (ObjectUtils.isEmpty(qualifiers)) { qualifiers = new String[]{contextId + "FeignClient"}; } BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, qualifiers); BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry); // 如果开启了动态配置刷新的话 会更改 BeanDefinition 的 scope 来进行增强 this.registerOptionsBeanDefinition(registry, contextId); }
别看这么二三十行代码,它东西还挺全乎,我们看看它取了哪些重要属性哈:
(1)contextId
取的就是注解里的 contextId 属性值,没有的话就等于 name 的属性值,并且它配有 BeanFactory 的 resolve,也就是说支持 spring 的表达式 类似我们平时 @Value 或者配置文件里的表达式写法
private String getContextId(ConfigurableBeanFactory beanFactory, Map<String, Object> attributes) { String contextId = (String)attributes.get("contextId"); // 没有 contextId 那么 contextId 就等于 name 属性的值 if (!StringUtils.hasText(contextId)) { return this.getName(attributes); } else { // 设置了的话,通过 getName 检查一下 检查的方式主要是 加上协议前缀然后 URI.host 一下 这是为什么的 我不得其解 contextId = this.resolve(beanFactory, contextId); return getName(contextId); } } static String getName(String name) { if (!StringUtils.hasText(name)) { return ""; } else { String host = null; try { String url; if (!name.startsWith("http://") && !name.startsWith("https://")) { url = "http://" + name; } else { url = name; } host = (new URI(url)).getHost(); } catch (URISyntaxException var3) { } Assert.state(host != null, "Service id not legal hostname (" + name + ")"); return name; } }
(2)name
我们继续看看 name 的获取,可以看到顺序获取的优先级:serviceId > name > value:
String getName(Map<String, Object> attributes) { return this.getName((ConfigurableBeanFactory)null, attributes); } // 重载 String getName(ConfigurableBeanFactory beanFactory, Map<String, Object> attributes) { // 先获取 serviceId 的属性值 String name = (String)attributes.get("serviceId"); // 没有的话 再获取 name的值 if (!StringUtils.hasText(name)) { name = (String)attributes.get("name"); } // 还没有的话 获取 value的值 if (!StringUtils.hasText(name)) { name = (String)attributes.get("value"); } // 表达式解析 虽然beanFactory为空,它有 environment 进行解析 name = this.resolve(beanFactory, name); // 一样还是要检查一下 URI.host 还是不得其解 return getName(name); } static String getName(String name) { if (!StringUtils.hasText(name)) { return ""; } else { String host = null; try { String url; if (!name.startsWith("http://") && !name.startsWith("https://")) { url = "http://" + name; } else { url = name; } host = (new URI(url)).getHost(); } catch (URISyntaxException var3) { } Assert.state(host != null, "Service id not legal hostname (" + name + ")"); return name; } }
(3)url 的获取
private String getUrl(ConfigurableBeanFactory beanFactory, Map<String, Object> attributes) { // 一样解析表达式 String url = this.resolve(beanFactory, (String)attributes.get("url")); // url 通过 URL 解析 这个我能理解 上边那俩我就不太理解为什么要用 URL.host return getUrl(url); } static String getUrl(String url) { if (StringUtils.hasText(url) && (!url.startsWith("#{") || !url.contains("}"))) { // 会补 http:// if (!url.contains("://")) { url = "http://" + url; } try { new URL(url); } catch (MalformedURLException var2) { throw new IllegalArgumentException(url + " is malformed", var2); } } return url; }
(4)path 路径的获取
private String getPath(ConfigurableBeanFactory beanFactory, Map<String, Object> attributes) { String path = this.resolve(beanFactory, (String)attributes.get("path")); return getPath(path); } static String getPath(String path) { if (StringUtils.hasText(path)) { // 去除多余空格 path = path.trim(); // 贴心自动补 / 前缀 if (!path.startsWith("/")) { path = "/" + path; } // 去掉最后的 / if (path.endsWith("/")) { path = path.substring(0, path.length() - 1); } } return path; }
BeanDefinition 是 FactoryBean 方式的,类型为FeignClientFactoryBean,还有 lazyInit 为 true 表示懒加载开启,装配模式=2表示根据类型自动装配,当多个冲突的时候还可以设置 Primary,最后还有一个重要的就是配置热加载的,这个就涉及到 spring 中Scope 的原理哈。这里就不详细解释了,后序单独讲这个 Scope。
那么最后我们画个图小小的捋一下:
大家看源码一定要画图,真的,不画图的话你后续再回忆的时候,又要一点点再看起,这样当你看完画个思路图,下次看的时候直接看图就大概能想起来怎么个过程,是不是。