Feign源码解析系列-注册套路
感谢不知名朋友的打赏,感谢你的支持!
开始
在追寻feign源码的过程中发现了一些套路,既然是套路,就可以举一反三,所以值得关注。
这篇会详细解析feign client配置和初始化的方式,这些方式大多依赖spring的游戏规则,在和spring相关的各个组件中都可以看到类似的玩法,都是可以举一反三。所以熟悉这些套路大有益处。
内容
在上一篇中,我们提到了注解feignclient引入了feignclientsregistrar,它继承importbeandefinitionregistrar。
在spring中,使用importbeandefinitionregistrar动态组装注册beandefinition,就是套路之一,像feignclientsregistrar一样的类还有很多,比如:org.springframework.cloud.netflix.ribbon.ribbonclientconfigurationregistrar,org.springframework.boot.autoconfigure.data.elasticsearch.elasticsearchrepositoriesregistrar
feignclientsregistrar实现importbeandefinitionregistrar的registerbeandefinitions方法:
public void registerbeandefinitions(annotationmetadata metadata, beandefinitionregistry registry) { registerdefaultconfiguration(metadata, registry); registerfeignclients(metadata, registry); }
从入口代码调用的两个方法看,从方法名上也可以看出来,要做的事可以分为两个:
1,注册@enablefeignclients中定义defaultconfiguration属性下的类,包装成feignclientspecification,注册到spring容器。
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配置的就是自定义的情况。
2,对于每个@feignclient进行解析,并将他们注册到spring容器。
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注解。
特别注意,@feignclient是需要在定义扫描位置才能被解析的,如果你的feign客户端接口不在扫描范围是不会被入住到容器中,从而无法被使用。而且没有入口可以更改配置的扫描路径,在实际开发中需要注意。
这个扫描器classpathscanningcandidatecomponentprovider又是spring的套路,通过配置的filters,找出需要的结果类。这个能力在自定义注解+扫描路径可配置的场景非常合适。
在确认好@feignclient注解的是否为接口后,最后会解析配置,先调用registerclientconfiguration方法,后调用registerfeignclient方法。
registerclientconfiguration方法,前面已经提到过,这里会对每个feignclient都进行调用,所以会把@feignclient上配置的configuration包装成feignclientspecification到容器中。
registerfeignclient方法把feignclientfactorybean注入到容器,feignclientfactorybean用于生产feignclient,后续详细。
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自定义能力。
上一篇: 如果刘备派别人去北伐,最后能够成功吗?
推荐阅读
-
死磕 java同步系列之CyclicBarrier源码解析——有图有真相
-
Asp.Net Core2.2 源码阅读系列——控制台日志源码解析
-
jsp基于XML实现用户登录与注册的实例解析(附源码)
-
Java 并发系列(一) ThreadPoolExecutor源码解析及理解
-
死磕 java同步系列之ReentrantLock源码解析(二)——条件锁
-
Feign源码解析系列-核心初始化
-
源码系列【springboot之@Import注解多个类引入同一个类源码解析】
-
死磕 java同步系列之ReentrantLock源码解析(一)——公平锁、非公平锁
-
Tomcat系列(二)- EndPoint源码解析
-
死磕Tomcat系列(2)——EndPoint源码解析