dubbo指定provider(dubbo.provider配置),本文通过数据整理汇集了dubbo指定provider(dubbo.provider配置)相关信息,下面一起看看。

在Dubbo的最新版本中,服务bean的注册可以通过注释来完成。声明方法是用@ org . Apache . dubbo . config . annotation . Service标记目标bean(注意包路径和spring的@Service不同,后面提到的@Service注释指的是这个Dubbo路径的注释)。使用注释后,当前bean将被注册为由spring容器管理的bean,并且可以远程调用它。本文主要解释Dubbo如何注册这些bean。

对于@Service标记的类的注册,Dubbo主要通过ServiceNotationBeanPostProcessor实现。该类实现了BeanFinitionRegistry接口,在注册了当前容器中的默认BeanDefinition后,将调用它的postprocessBeanFinitionRegistry()方法。Dubbo用这个方法将标有@Service的类的实例注册到Spring容器中。我们先来看看这个方法的源代码:

@ override public void postprocessbean definition registry(bean definition registry)Throws beans exception {//PackagesToScan保存要扫描的指定dubbo类路径,这里主要处理这些路径中的//占位符,用对应的值setstring resolvedpackagestoscan=resolvedpackagestoscan(PackagesToScan)替换;如果(!collect utils . isempty(resolvedpackagestoscan)){//这里主要有两个动作。一种是扫描当前类路径中所有标有//@ org . Apache . dubbo . config . annotation . service注释的类,注册为bean//另一个是为引用该类的服务beans的每个目标类注册一个BeanDefinition。这些服务beans是//Dubbo能够向外界提供远程服务调用的关键。至于它的实现原理,我们后面会详细解释RegisterServiceBeans(ResolvedPageScan,Registry);} else { if(logger . iswarneabled()){ logger . warn(' packagesToScan为空,ServiceBean注册表将被忽略!');}}}如您所见,这里的第一步是获取需要扫描Dubbo bean的配置路径,并处理该路径中的占位符。然后将特定的注册操作委托给registerServiceBeans()方法。让我们继续阅读这个方法的源代码:

private void registerServiceBeans(SetString packagesToScan,bean定义注册表注册表){//创建一个dubboclasspathbeanditionscanner,该类的作用主要是扫描类路径中指定路径//下的所有班级文件,根据其过滤器指定的条件判断扫描到的班级文件是否符合当前条件, //最后将符合条件的班级封装为一个豆定义注册到BeanDefinitionRegistry注册表注册表中dubboclasspathbeanditionscanner scanner=new dubboclasspathbeanditionscanner(注册表,环境,资源加载器);//获取一个BeanNameGenerator,主要作用是为目标类创建要给豆名称bean name generator bean name generator=resolveBeanNameGenerator(注册表);扫描仪。setbeannamegenerator(bean name generator);//添加一个过滤器,这里添加的过滤器就是如果目标类上标注了@服务注解, //那么就会将其注册到BeanDefinitionRegistry注册表注册表中扫描仪。addincludefilter(新的annotationtype筛选器(服务。类));for(String packageToScan:packagesToScan){//对每一个文件夹进行扫描,判断扫描到的班级是否符合过滤器指定的条件, //符合则将其封装为一个BeanDefinition,并且注册到BeanDefinitionRegistry注册表注册表中扫描仪。扫描(打包toscan);//查找所有前一步注册的杜博服务bean setbeandeditionholder beandeditionholders=findservicebeandeditionholders(scanner,packageToScan,registry,beanNameGenerator);如果(!收集工具。isempty(bean定义持有者)){ for(bean定义持有者bean定义持有者:bean定义持有者){//为每一个注册的使用@服务标注的豆声明一个ServiceBean的豆定义对象, //并且指定其裁判员属性为当前豆子,该ServiceBean会将其引用的豆注册为一个//可用于远程调用的bean registerServiceBean(bean定义持有者、注册表、扫描器);} } }}上述注册服务bean()就是注册杜博提供者豆的主要流程方法,简要来说就是分为两步:

查找指定目录下使用@服务注解标注的所有班级文件,将该班级封装为一个BeanDefinition,并且注册到BeanDefinitionRegistry注册表注册表中;为每一个@服务注解标注的豆注册一个ServiceBean,指定其裁判员属性为@服务注解标注的豆;1.封装并注册豆定义这里我们首先看dubboclasspathbeanditionscanner。扫描()方法是如何扫描目标路径,并且注册相关豆的。如下是该方法的源码:

公共(同Internationalorganizations)国际组织扫描(字符串.基础包){ //获取当前已经注册的豆定义数量int beanCountAtScanStart=this。注册表。get bean definition count();//扫描指定路径下的dubbo bean doScan(基本包装);如果(这个。includeannotationconfig){//注册用于处理诸如@配置,@自动连线,@必需等注解的类或属性的处理器注释配置实用程序。registerannotationconfigprocessors(this。注册表);} //返回注册的杜博提供程序豆定义的数量返回(这个。注册表。getbean定义count()-beanCountAtScanStart);}受保护的SetBeanDefinitionHolder doScan(字符串.基础包){ assert。notempty(基础包,'至少必须指定一个基包');setbeandeditionholder bean定义=new linkedhashsetbeandeditionholder();for(字符串基础包:基础包){//在指定目录下查找所有的符合条件的类,并且将其封装为一个豆定义对象setbean定义candidatecomponents=findcindiandatecomponents(基础包);for(bean定义候选项:候选项){//检查目标类是否标注了@范围注解,如果标注了该注解,则将其属性封装为一个ScopeMetadata,//并且将配置的范围名称设置到当前豆定义中范围元数据范围元数据=范围元数据解析器。resolvescopemetadata(候选);候选人。设置范围(范围元数据。getscopename());//按照指定的策略为当前豆定义生成一个名称字符串bean名称=this。bean名称生成器。生成bean名称(候选,this。注册表);抽象定义的候选实例){ //为当前豆定义设置lazyInit,autowireMode等属性的默认值postprocessbean定义((abstractbean定义)候选项,bean名称);} if(annotated bean定义的候选实例){//检查当前豆是否标注了诸如@懒惰,@初级等注解,如果标注了,则获取其值, //并且将其设置到当前的豆定义中AnnotationConfigUtils .processcommondefinitionnations((annotatedbean定义)候选);} //检查当前BeanDefinitionRegistry注册表注册表中是否已经存在该名称的豆子,如果已经存在, //则表示当前豆定义不能进行注册,但这里会判断已经存在的BeanDefinition //与当前豆定义是不是匹配的,如果不是匹配的, 则抛出异常,如果是匹配的, //说明两者是同一个BeanDefinition,则返回假的,而不对当前豆定义进行处理if (checkCandidate(beanName,candidate)) { //将当前豆定义封装为一个bean定义持有者bean定义持有者定义持有者=新bean定义持有者(候选,bean名称);定义持有者=AnnotationConfigUtils .applyScopedProxyMode(作用域元数据,定义Holder,this。注册表);bean定义。添加(定义持有者);//将封装得到的BeanDefinitionHolder注册到BeanDefinitionRegistry注册表注册表中registerbean定义(定义持有者,this。注册表);} } }返回beanDefinitions}在多扫描()方法中,首先通过findCandidateComponents()方法查找指定目录下的所有符合条件的类,并且将其封装为一个BeanDefinition,然后为得到的豆定义设置默认的属性,最后将其注册到BeanDefinitionRegistry注册表注册表中。这里我们主要看findCandidateComponents()是如何查找并封装目标豆定义的:

public setbean definition findccandidatecomponents(String base package){ setbean definition candidates=new linkedhashsetbean definition();Try {//这里,resolveBasePackage()方法将处理目标路径中的占位符,用真实路径替换它。//这里的组装方式其实就是在目标路径前面加上classpath*:加上**/*。最后一节课。//处理后的路径看起来像:class path *:com/xiaowanzi/service/* */*。班级。经过这样的处理,//我们可以使用Ant将目标类路径与这个路径进行匹配。如果匹配,说明是类字符串包搜索路径=resourcepatternsolver . class path _ all _ URL _ prefix resolvesepackage(base package)'/' this . resource pattern;//这里主要是获取目标路径下的所有类文件,封装成资源对象Resource[]resources=resourceparterresolver . get resources(包搜索路径);For(Resource Resource:resources){ if(Resource . is readable()){ try {//这里的MetadataReader是用来读取当前资源所代表的类的一些元数据,//比如批注metadata reader metadata reader=metadata reader Factory。由此类标记的get metadata reader(resource);//isCandidate()方法本质上是通过includeFilter和excluding filter来判断当前资源是否满足这些过滤器设置的条件,这些过滤器是之前为当前Scanner//设置的。只有符合条件的资源才是我们最终将封装为BeanDefinition的类if (iscandidate component(元数据读取器))。{//这说明当前资源代表的类是目标类。然后封装成bean定义扫描的genericbeandefinition SBD=新扫描的genericbeandefinition(元数据读取器);sbd.setResource(资源);sbd.setSource(资源);//判断当前类class是具体类,还是抽象类,用@Lookup标注,用来指定子类if(iscanditecomponent(SBD)){ candidatecomponent . add(SBD);//将当前类添加到结果集} } catch(throwableex){ throw new beanditionstoreexception('读取候选组件类失败:' resource,ex);} } } } catch(io exception ex){ throw new bean definitionstoreexception(' class path扫描期间I/O失败',ex);}返回候选人;}你可以看到,这里的findCandidateComponents()的主要作用是首先在指定的目录下找到资源文件,然后依次判断资源文件是否满足当前扫描器指定的过滤条件。如果是,它将被封装成一个BeanDefinition并添加到结果集中以供返回。在这里,我们继续来看看resourcepatherresolver . get resources()是如何查找资源文件的。下面是这个方法的源代码:

//genericapplicationcontext . get resources()最后将查找动作委托给//pathmatchingresourcepatternresolver . get resources()方法,我们直接看这个方法的源代码:public resource[]get resources(string location pattern)抛出io异常{ assert . not null(location pattern,' location pattern '不能为null ');//判断目标路径是否以classpath*:开头,如果是,则通过读取类路径文件If(location pattern . starts with(class path _ all _ URL _ prefix)){//如果目标路径是Ant形式,则以Ant形式匹配。这里的Ant形式意味着类路径包含*或?If (getpathmatcher()。ispattern (locationpattern。substring(class path _ all _ URL _ prefix . length()){//通过Ant pattern匹配指定路径下的资源文件Return findpathmatchingreasures(location pattern);} else {//如果不是Ant的形式,那么会被当作路径前缀。这里将直接读取该路径下的所有文件,并返回findallclasssthresources(location pattern . substring(class path _ all _ URL _ prefix . length());}} else {//处理路径。如果以war:开头,说明是用Tomcat协议写的,那么只取*/之后的部分。//否则取以下部分:int前缀end=(location pattern . starts with(' war:')?location pattern . index of(' */')1:location pattern . index of(':')1);//如果路径是Ant的形式,使用Ant查找对应的资源文件if (getpathmatcher()。ispattern (locationpattern。substring(前缀end)){返回目标目录中的findpathmatching资源(location pattern));} else {//如果路径不是Ant的形式,就当作完整路径,然后直接读取文件return new resource[]{ get resource loader()。路径下的get resource(location pattern)};}}}这里主要是根据路径的不同形式,使用不同的方式读取路径下的资源文件。由于之前已经在路径中添加了Ant形式的后缀,Dubbo使用Ant的形式来匹配路径,所以我们继续读取findPathMatchingResources()方法的源代码:

受保护的资源[]findpathmatchingeresources(字符串位置模式)引发IOException { //这里根目录路径就是获取当前蚂蚁形式路径的根路径,所谓的根路径就是不包含蚂蚁通配符的路径前缀部分。 //比如类路径*:com/xiaowanzi/service/* */* .班级的根路径是//类路径*:com/xiaowanzi/服务字符串rootDirPath=限定词rootdir(位置模式);//子模式就是整个路径去除根路径的包含蚂蚁通配符的部分,比如上面的**/*.类字符串子模式=位置模式。子字符串(rootdirpath。length());//获取根路径下的所有资源文件resource[]rootDirResources=获取资源(rootDirPath);set resource result=new LinkedHashSetResource(16);for(资源根目录资源:根目录资源){//这里只是一个钩方法,并没有做任何处理rootDirResource=resolveRootDirResource(rootDirResource);URL rootDirUrl=rootdirresource。geturl();//如果equinoxResolveMethod方法方法不为空,则通过该方法加载该资源文件, //这里equinoxResolveMethod方法方法是org。月食。核心。运行时。文件定位器。解决()方法, //这里其实就是判断当前工程中是否存在该类,以便借助该类进行处理if (equinoxResolveMethod!=null){ if(root dirurl。获取协议().以(' bundle '){ rootDirUrl=(URL)反射实用程序开头.invoke方法(equinox resolve方法,null,rootDirUrl);rootDirResource=新的URL资源(rootDirUrl);} } //如果目标资源是虚拟文件系统文件,则交由VfsResourceMatchingDelegate读取该资源文件if (rootDirUrl.getProtocol().startsWith(ResourceUtils .网址_协议_ VFS)){结果。addall(VfsResourceMatchingDelegate .findMatchingResources(root dirurl,subPattern,getPathMatcher());//如果目标资源存在于冲突包中,则以冲突包的形式读取资源文件} else if(资源利用率。isjarurl(rootDirUrl)| | isJarResource(rootDirResource)){ result。addall(doFindPathMatchingJarResources(rootDirResource,rootDirUrl,subPattern));} else { //这里说明目标资源文件是普通的存在与当前类路径中的班级文件,那么就直接读取该资源文件结果。addall(doFindPathMatchingFileResources(根目录资源,子模式));} } //将资源文件转换为一个数组返回返回result.toArray(新资源【结果。size()]);}这里查找资源文件的方式就是查找指定路径下的所有文件,得到的一个一系列的统一资源定位器对象,然后对这些统一资源定位器对象进行判断,按照其存储的不同的形式进行读取。这里我们以查找类路径下的普通班级文件的方式进行讲解,如下是doFindPathMatchingFileResources()方法的源码:

受保护的设置资源doFindPathMatchingFileResources(资源根目录资源,字符串子模式)引发IOException { //根据根路径获取其对应的文件文件目录句柄文件根目录=根目录资源。getfile().getAbsoluteFile();//获取根目录下的所有文件,并且对其进行匹配返回doFindMatchingFileSystemResources(rootDir,subPattern);}受保护集资源doFindMatchingFileSystemResources(文件根目录,字符串子模式)抛出IOException { //获取根路径下所有匹配的文件set file matching files=retrieveMatchingFiles(rootDir,subPattern);set resource result=new LinkedHashSetResource(匹配文件。size());对于(文件文件:匹配文件){ //将匹配的文件封装为一个文件系统资源对象,然后将其添加到结果集中结果。add(新文件系统资源(文件));}返回结果;}受保护的SetFile检索匹配文件(文件根目录,字符串模式)抛出IOException { //如果根目录文件不存在,则直接返回如果(!rootdir。exists()){返回集合。空集();} //如果根目录文件不是一个目录,则直接返回如果(!rootdir。is directory()){返回集合。空集();} //如果根目录文件不可读,则直接返回如果(!rootdir。可以读取()){返回集合。空集();} //将根目录文件的路径中的文件分隔符全部替换为反斜杠"/",以便与目标模式路径进行匹配字符串完整模式=字符串实用程序。替换(根目录。getabsolutepath(),File.separator,'/');如果(!模式。以('/')开头{完整模式='/';} //对模式路径进行处理,将其目录分隔符替换为反斜杠"/"完整模式=完整模式字符串实用程序。replace(pattern,File.separator,'/');SetFile result=new LinkedHashSetFile(8);//获取目标根目录下的所有文件,并且根据模式路径对这些文件进行匹配doRetrieveMatchingFiles(全模式,根目录,结果);返回结果;}受保护的void doRetrieveMatchingFiles(字符串完整模式,文件目录,设置文件结果)抛出IOException { //获取目标目录下的所有文件文件[]目录内容=目录。列出文件();if(dir contents==null){ return;}数组。排序(dir contents);for(文件内容:目录内容){ String currPath=String utils。替换(内容。getabsolutepath(),File.separator,'/');//如果当前获取到的子文件还是一个目录,则递归的调用当前方法以获取该目录下的文件如果(内容。是directory()getpath matcher().matchStart(fullPattern,curr path '/'){ if(content。可以读取()){ doRetrieveMatchingFiles(全模式,内容,结果);} } //如果当前文件不是一个目录,则将当前文件路径与模式路径进行匹配,如果匹配上了,则将其添加到结果集中if (getPathMatcher().match(fullPattern,curr path)){ result。添加(内容);} }}可以看到,这里就是进行匹配的主要流程,主要就是查找指定目录下的所有文件,并且将该文件的路径与设置的模式路径进行匹配,匹配上了则说明该文件是我们所需要的文件,此时会将其添加到结果集中返回。

前面我们讲到,在获取所有的资源文件之后,会将资源文件封装为一个元数据阅读器对象,然后判断其是否符合当前扫描仪所设置的过滤器条件,符合条件的才是我们所需要的班级.这里的判断过程在Scanner.isCandidateComponent()方法中,如下是该方法的源码:

受保护的布尔值isCandidateComponent(元数据读取器元数据读取器)引发IOException { //通过排除过滤器判断当前元数据阅读器是否需要被过滤掉,是则返回假的,表示应该被排除for(type filter TF:this。排除过滤器){ if(TF。match(元数据读取器,this.metadataReaderFactory)) {返回false} } //通过包括过滤器判断当前元数据阅读器是否是符合条件的,是则判断其是否标注了@Conditional //注解,如果标注了该注解,则根据该注解所指定的条件,判断当前班级是否符合该条件,符合才说明当前//类是我们所需要的班级文件for(type filter TF:this。包含过滤器){ if(TF。匹配(元数据阅读器,这个。元数据读取器工厂)){ return isConditionMatch(元数据读取器);} }返回false}由于我们在声明扫描仪对象时,只指定了包括过滤器为AnnotationTypeFilter,并且指定了注解为服务,我们这里直接看其匹配()方法:

@Overridepublic布尔匹配(元数据读取器元数据读取器,元数据读取器工厂元数据读取器工厂)引发IOException { //matchSelf()就是判断当前班级是否标注了目标注解,这里是@服务,如果标注了,则返回true if(匹配自身(元数据读取器)){返回真实;}元数据元数据=元数据读取器。getclass元数据();if(匹配类名(元数据。get class name()){//该方法默认返回假的,主要是提供给子类实现的返回true} //如果设置了考虑父类,则会递归的判断各个父类是否有标注目标注解,如果标注了,则返回真的如果(这个。考虑继承){ if(元数据。hassuperclass()){布尔超类match=match超类(元数据。getsuperclassname());if (superClassMatch!=null){ if(超类匹配。布尔值()){返回true} } else { if(match(元数据。getsuperclassname()、metadataReaderFactory)){ return true;} } } } //如果设置了考虑接口,则会递归的判断各个接口是否有标注目标注解,如果标注了,则返回真的如果(这个。考虑接口){ for(String IFC:metadata。getinterfacenames()){布尔接口匹配=匹配接口(IFC);if (interfaceMatch!=null){ if(接口匹配。布尔值()){返回true} } else { if (match(ifc,元数据读取器工厂)){ return true} } } }返回false}可以看到,这里就是对目标豆进行过滤的主要逻辑,其判断方式就是通过查找目标班级上是否标注了@服务注解来进行,如果标注了,则是一个目标豆子.通过这里的判断的班级文件,最后会被封装为一个BeanDefinitionHolder,然后注册到BeanDefinitionRegistry注册表注册表中。

2.ServiceBean注册对于ServiceBean,其是杜博提供对外服务的核心,该类会将每一个杜博类型的豆都注册到动物园管理员上,以便其他的服务通过动物园管理员获取该类的信息,然后通过传输控制协议(传输控制协议)协议进行远程调用。关于ServiceBean的工作原理,我们后续会进行详细讲解,这里主要讲解其是如何注册到BeanDefinitionRegistry注册表注册表中的。

前面serviceannotationbeanpostprocessor。注册服务bean()方法中,在注册了所有@服务声明的所有豆定义之后,会通过findservicebean定义holders()方法查找得到所有这些BeanDefinition,然后在registerServiceBean()方法中为这每一个豆定义创建一个ServiceBean的BeanDefinition,并且其裁判员属性指向了这些@服务标注的班级对应的实例。这里我们主要查看其是如何进行ServiceBean的注册的:

private void registerServiceBean(Bean定义持有者bean定义持有者、BeanDefinitionRegistry注册表、dubboclasspathbean定义扫描器scanner){//获取目标豆的班级对象班级?bean class=resolve class(bean定义持有者);//查找该班级上标注的@服务注解对象service service=find annotation(bean类,service。类);//获取目标豆所实现的接口对象班级?interface class=resolveServiceInterfaceClass(bean类,服务);string annotatedServiceBeanName=bean定义持有者。获取bean name();//根据@服务注解中的各个属性,为ServiceBean构造一个豆定义对象abstractbean定义servicebean定义=buildservicebean定义(service,interfaceClass,annotatedservicebean名称);//为当前ServiceBean生成一个名称string bean name=generateservicebean name(service,interfaceClass,annotatedservicebean name);//判断当前BeanDefinitionRegistry注册表注册表中是否已经存在了当前名称的豆子,如果存在,则不进行注册如果(扫描仪。检查候选项(bean名称,serviceBeanDefinition)) { //将当前ServiceBean对应的豆定义注册到BeanDefinitionRegistry注册表注册表中注册表。registerbean定义(bean名称,servicebean定义);}}这里封装ServiceBean的过程,主要就是读取目标班级文件上的@服务注解所设置的各个属性值,然后根据该属性值将其封装为一个豆定义对象,并且其班级设置为ServiceBean。封装完成后,就为当前ServiceBean对应的豆定义生成一个名称,并且将其注册到BeanDefinitionRegistry注册表注册表中。

3.小结本文首先从源码的角度讲解了杜博是如何生成并且注册提供者豆对应的豆定义的,然后讲解了将提供者豆封装为ServiceBean对应的豆定义的过程。

更多dubbo指定provider(dubbo.provider配置)相关信息请关注本站,本文仅仅做为展示!