欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Feign源码解析系列-注册套路

程序员文章站 2022-07-10 13:44:27
感谢不知名朋友的打赏,感谢你的支持! 开始 在追寻Feign源码的过程中发现了一些套路,既然是套路,就可以举一反三,所以值得关注。 这篇会详细解析Feign Client配置和初始化的方式,这些方式大多依赖Spring的游戏规则,在和Spring相关的各个组件中都可以看到类似的玩法,都是可以举一反三 ......

感谢不知名朋友的打赏,感谢你的支持!

开始

在追寻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客户端相互隔离的上下文。

类相互依赖图:
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自定义能力。