感謝不知名朋友的打賞,感謝你的支持!java
在追尋Feign源碼的過程當中發現了一些套路,既然是套路,就能夠觸類旁通,因此值得關注。
這篇會詳細解析Feign Client配置和初始化的方式,這些方式大多依賴Spring的遊戲規則,在和Spring相關的各個組件中均可以看到相似的玩法,都是能夠觸類旁通。因此熟悉這些套路大有益處。spring
在上一篇中,咱們提到了註解FeignClient引入了FeignClientsRegistrar,它繼承ImportBeanDefinitionRegistrar。
在Spring中,使用ImportBeanDefinitionRegistrar動態組裝註冊BeanDefinition,就是套路之一,像FeignClientsRegistrar同樣的類還有不少,好比:org.springframework.cloud.netflix.ribbon.RibbonClientConfigurationRegistrar,org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesRegistrarelasticsearch
FeignClientsRegistrar實現ImportBeanDefinitionRegistrar的registerBeanDefinitions方法:ide
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { registerDefaultConfiguration(metadata, registry); registerFeignClients(metadata, registry); }
從入口代碼調用的兩個方法看,從方法名上也能夠看出來,要作的事能夠分爲兩個:函數
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()); }
registerClientConfiguration方法中,使用FeignClientSpecification生成BeanDefinitionBuilder,放入構造函數的兩個參數,而後構造了bean註冊的名稱。
這裏的名稱是相似這樣的:default.xxx.TestApplication.FeignClientSpecification。
假如你還記得在@FeignClient中有一個屬性:configuration,這個屬性是表示各個FeignClient自定義的配置類,後面也會經過調用registerClientConfiguration方法來註冊成FeignClientSpecification到容器。
因此,這裏能夠徹底理解在@EnableFeignClients中配置的是作爲兜底的配置,在各個@FeignClient配置的就是自定義的狀況。ui
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) { // 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()); String name = getClientName(attributes); registerClientConfiguration(registry, name, attributes.get(「configuration")); registerFeignClient(registry, annotationMetadata, attributes); } } } }
咱們知道@FeignClient的掃描路徑在@EnableFeignClients上是能夠經過basePackages,basePackageClasses,client這三個參數進行配置的。因此在掃描@FeignClient以前就都是這個邏輯。確認好basePackages後,就遍歷basePackages,利用掃描器掃出各個路徑下的@FeignClient註解。this
特別注意,@FeignClient是須要在定義掃描位置才能被解析的,若是你的feign客戶端接口不在掃描範圍是不會被入住到容器中,從而沒法被使用。並且沒有入口能夠更改配置的掃描路徑,在實際開發中須要注意。url
這個掃描器ClassPathScanningCandidateComponentProvider又是spring的套路,經過配置的filters,找出須要的結果類。這個能力在自定義註解+掃描路徑可配置的場景很是合適。
在確認好@FeignClient註解的是否爲接口後,最後會解析配置,先調用registerClientConfiguration方法,後調用registerFeignClient方法。.net
registerClientConfiguration方法,前面已經提到過,這裏會對每一個FeignClient都進行調用,因此會把@FeignClient上配置的configuration包裝成FeignClientSpecification到容器中。
registerFeignClient方法把FeignClientFactoryBean注入到容器,FeignClientFactoryBean用於生產FeignClient,後續詳細。code
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"); // has a default, won't be null 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); }
FeignClientSpecification繼承NamedContextFactory.Specification,而NamedContextFactory用與建立子上下文將NamedContextFactory.Specification放入其中。
NamedContextFactory中維護一個context的map,value是AnnotationConfigApplicationContext即子上下文。
在feign中定義了一個FeignContext繼承NamedContextFactory,來統一維護feign中各個feign客戶端相互隔離的上下文。
類相互依賴圖:
FeignContext的代碼:
public class FeignContext extends <FeignClientSpecification> { public FeignContext() { super(FeignClientsConfiguration.class, "feign", "feign.client.name"); } }
NamedContextFactory中的defaultConfigType被設置爲FeignClientsConfiguration。
這裏咱們先看一下NamedContextFactory中的createContext方法的實現:
protected AnnotationConfigApplicationContext createContext(String name) { // 每次調用new一個AnnotationConfigApplicationContext AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); //在子上下文上就註冊name對應的configuration if (this.configurations.containsKey(name)) { for (Class<?> configuration : this.configurations.get(name) .getConfiguration()) { context.register(configuration); } } //註冊default configuration for (Map.Entry<String, C> entry : this.configurations.entrySet()) { if (entry.getKey().startsWith("default.")) { for (Class<?> configuration : entry.getValue().getConfiguration()) { context.register(configuration); } } } // 將this.defaultConfigType即FeignClientsConfiguration也註冊上 context.register(PropertyPlaceholderAutoConfiguration.class, this.defaultConfigType); context.getEnvironment().getPropertySources().addFirst(new MapPropertySource( this.propertySourceName, Collections.<String, Object> singletonMap(this.propertyName, name))); // 父上下文設置,全部的子上下文都是一個父上下文,當子上下文找不到時,就去父上下文找 if (this.parent != null) { // Uses Environment from parent as well as beans context.setParent(this.parent); } context.refresh(); return context; }
FeignContext註冊到容器是在FeignAutoConfiguration上完成的:
@Autowired(required = false) private List<FeignClientSpecification> configurations = new ArrayList<>(); @Bean public FeignContext feignContext() { FeignContext context = new FeignContext(); context.setConfigurations(this.configurations); return context; }
在初始化FeignContext時,會把configurations在容器中放入FeignContext中。configurations的來源就是在前面registerFeignClients方法中將@FeignClient的配置configuration。
關鍵須要理解的是在feign中爲每個client準備了FeignContext,內部維護這個自定義配置的內容好比Encoder,Decoder等,從而實現對每一個client自定義能力。