1. 程式人生 > 其它 >dubbo自動裝配記錄-盲點記錄

dubbo自動裝配記錄-盲點記錄

記錄下dubbo的自動裝配分析遇到的盲點

盲點一

自動裝配在dubbo-spring-boot-autoconfigure-0.2.1.RELEASE.jar\META-INF\spring.factories檔案內

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.alibaba.boot.dubbo.autoconfigure.DubboAutoConfiguration

DubboAutoConfiguration在有dubbo開頭的配置下啟用,裡面的@Bean很容易理解,這裡只是記錄它的兩個內部類SingleDubboConfigConfiguration、MultipleDubboConfigConfiguration的裝配過程,涉及到自己知識盲點。

/**
     * Single Dubbo Config Configuration
     *
     * @see EnableDubboConfig
     * @see DubboConfigConfiguration.Single
     */
@EnableDubboConfig
protected static class SingleDubboConfigConfiguration {
}

/**
     * Multiple Dubbo Config Configuration , equals @EnableDubboConfig.multiple() == <code>true</code>
     *
     * @see EnableDubboConfig
     * @see DubboConfigConfiguration.Multiple
     */
@ConditionalOnProperty(name = MULTIPLE_CONFIG_PROPERTY_NAME, havingValue = "true")
@EnableDubboConfig(multiple = true)
protected static class MultipleDubboConfigConfiguration {
}

在分析時候,發現兩個內部類@EnableDubboConfig上的@Import(DubboConfigConfigurationRegistrar.class)比DubboAutoConfiguration類內的@Bean註解的方法先執行,這個原因涉及到了以前分析springboot自動裝配機制當時沒有完全理解的地方。

springboot 原始碼分析見https://www.cnblogs.com/zhangyjblogs/p/14438444.html#configurationclasspostprocessor原始碼分析

自己以前詳細分析了springboot原理,但是現在在複習dubbo自動裝配啟動時候,對於兩個內部類SingleDubboConfigConfiguration、MultipleDubboConfigConfiguration在什麼時候進行裝配的,竟然不清楚,因此詳細分析下。

springboot的自動裝配是在ConfigurationClassPostProcessor進行處理,在org.springframework.context.annotation.ConfigurationClassParser.doProcessConfigurationClass(ConfigurationClass, SourceClass)方法內processMemberClasses方法實際就是處理內部類,詳細分析下這個方法。

/**
	 * Register member (nested) classes that happen to be configuration classes themselves.
	 */
private void processMemberClasses(ConfigurationClass configClass, SourceClass sourceClass) throws IOException {//傳入的配置類是DubboAutoConfiguration
    Collection<SourceClass> memberClasses = sourceClass.getMemberClasses();//獲取DubboAutoConfiguration內部類,即SingleDubboConfigConfiguration、MultipleDubboConfigConfiguration
    if (!memberClasses.isEmpty()) {//存在內部類
        List<SourceClass> candidates = new ArrayList<>(memberClasses.size());
        for (SourceClass memberClass : memberClasses) {
            if (ConfigurationClassUtils.isConfigurationCandidate(memberClass.getMetadata()) && //內部類被@Configuration、@Component、@Import、@Bean註解
                !memberClass.getMetadata().getClassName().equals(configClass.getMetadata().getClassName())) {//內部類className不等於配置類className名稱
                candidates.add(memberClass);//內部類加入到候選集合
            }
        }
        OrderComparator.sort(candidates);
        //對每個內部類進行處理
        for (SourceClass candidate : candidates) {
            if (this.importStack.contains(configClass)) {
                this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
            }
            else {
                this.importStack.push(configClass);
                try {
                    processConfigurationClass(candidate.asConfigClass(configClass));//處理DubboAutoConfiguration內部類上的配置類、@Import
                }
                finally {
                    this.importStack.pop();
                }
            }
        }
    }
}

關鍵程式碼是Collection<SourceClass> memberClasses = sourceClass.getMemberClasses(),獲取內部類,下面看這個方法

//org.springframework.context.annotation.ConfigurationClassParser.SourceClass.getMemberClasses()
//獲取內部類
public Collection<SourceClass> getMemberClasses() throws IOException {
    Object sourceToProcess = this.source;
    if (sourceToProcess instanceof Class) {
        Class<?> sourceClass = (Class<?>) sourceToProcess;
        try {
            Class<?>[] declaredClasses = sourceClass.getDeclaredClasses();//獲取內部類
            List<SourceClass> members = new ArrayList<>(declaredClasses.length);
            for (Class<?> declaredClass : declaredClasses) {
                members.add(asSourceClass(declaredClass));//內部類包裝為SourceClass並新增到集合返回
            }
            return members;
        }
        catch (NoClassDefFoundError err) {
            // getDeclaredClasses() failed because of non-resolvable dependencies
            // -> fall back to ASM below
            sourceToProcess = metadataReaderFactory.getMetadataReader(sourceClass.getName());
        }
    }

    // ASM-based resolution - safe for non-resolvable classes as well
    //this.source非class型別,比如是 com.alibaba.boot.dubbo.autoconfigure.DubboAutoConfiguration.MultipleDubboConfigConfiguration包裝而成的SimpleMetadataReader
    MetadataReader sourceReader = (MetadataReader) sourceToProcess;
    String[] memberClassNames = sourceReader.getClassMetadata().getMemberClassNames();//獲取內部類名稱
    List<SourceClass> members = new ArrayList<>(memberClassNames.length);
    for (String memberClassName : memberClassNames) {
        try {
            members.add(asSourceClass(memberClassName));//內部類包裝為SourceClass並新增到集合返回
        }
        catch (IOException ex) {
            // Let's skip it if it's not resolvable - we're just looking for candidates
            if (logger.isDebugEnabled()) {
                logger.debug("Failed to resolve member class [" + memberClassName +
                             "] - not considering it as a configuration class candidate");
            }
        }
    }
    return members;
}

如果是Class型別,直接通過Class方法getDeclaredClasses()獲取內部類,這個簡單。

如果非Class型別,直接通過sourceReader.getClassMetadata().getMemberClassNames()獲取內部類名稱,sourceReader.getClassMetadata()返回的是AnnotationMetadataReadingVisitor,獲取內部類程式碼是

//org.springframework.core.type.classreading.ClassMetadataReadingVisitor.getMemberClassNames()
@Override
public String[] getMemberClassNames() {
    return StringUtils.toStringArray(this.memberClassNames);
}

發現內部類名已經是在AnnotationMetadataReadingVisitor存在的。那麼就需要找AnnotationMetadataReadingVisitor的例項化階段內部類的來源了。

通過查詢發現ClassMetadata註釋發現,該介面是由MetadataReader#getClassMetadata()建立,具體是SimpleMetadataReader例項化時候建立。程式碼如下

final class SimpleMetadataReader implements MetadataReader {

	private final Resource resource;

	private final ClassMetadata classMetadata;

	private final AnnotationMetadata annotationMetadata;

	SimpleMetadataReader(Resource resource, @Nullable ClassLoader classLoader) throws IOException {//resource是org.springframework.core.io.Resource
		InputStream is = new BufferedInputStream(resource.getInputStream());
		ClassReader classReader;
		try {
			classReader = new ClassReader(is);//建立位元組碼讀取工具類ClassReader
		}
		catch (IllegalArgumentException ex) {
			throw new NestedIOException("ASM ClassReader failed to parse class file - " +
					"probably due to a new Java class file version that isn't supported yet: " + resource, ex);
		}
		finally {
			is.close();
		}

		AnnotationMetadataReadingVisitor visitor = new AnnotationMetadataReadingVisitor(classLoader);//建立AnnotationMetadataReadingVisitor,是個ClassMetadata,也是AnnotationMetadata
		classReader.accept(visitor, ClassReader.SKIP_DEBUG);//位元組碼處理,解析位元組碼,把類結構解析到AnnotationMetadataReadingVisitor

		this.annotationMetadata = visitor;
		// (since AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisitor)
		this.classMetadata = visitor;
		this.resource = resource;
	}
}    

ClassMetadata、AnnotationMetadata介面類關係圖如下:

org.springframework.core.io.Resource:表示類資原始檔

ClassReader:位元組碼讀取工具類,建立例子,new ClassReader(new BufferedInputStream(org.springframework.core.io.Resource.getInputStream())); 讀取位元組碼並把類結構解析到AnnotationMetadataReadingVisitor,程式碼:classReader.accept(new AnnotationMetadataReadingVisitor(classLoader), ClassReader.SKIP_DEBUG); 類似ASM的ClassReader

MetadataReader:訪問類元資料的外觀模式介面,使用org.springframework.asm.ClassReader進行讀取

SimpleMetadataReader:MetadataReader的具體實現,簡單的元資料讀取實現。使用ClassReader讀取位元組流並解析類結構到AnnotationMetadataReadingVisitor

ClassMetadata:class元資料介面,提供了獲取類屬性資訊的能力。

​ getClassName:獲取類名

​ isInterface:是否是介面

​ isAnnotation:是否是註解

​ isAbstract:是否是抽象類

​ isConcrete:是否是具體類,即非介面非抽象類

​ isFinal:是否被Final標識

​ isIndependent:是否是個獨立類,確定基礎類是否獨立,即是否它是頂級類或巢狀類(靜態內部類),可以與封閉類無關地構造

​ hasEnclosingClass:是否是內部類

​ getEnclosingClassName:返回內部類名

​ hasSuperClass:是否有超類

​ getSuperClassName:返回超類

​ getInterfaceNames:返回當前類實現的介面列表

​ getMemberClassNames:返回此類內部的成員類/介面(即該類裡面的內部類或內部介面)

AnnotationMetadata:註解類的元資訊,註解相較於普通類,多一些屬性,因此又增加了此介面用於描述註解類元資訊。

​ getAnnotationTypes:獲取此註解類上的所有註解的全限名稱

​ getMetaAnnotationTypes:根據傳入的元註解的完全限定的類名獲取此註解類上的所有元註解型別名稱

​ hasAnnotation:此註解類是否被傳入引數annotationName註解

​ hasMetaAnnotation:此註解類是否被指定的metaAnnotationName元註解註解

​ hasAnnotatedMethods:此註解類任何方法被指定的annotationName註解

​ getAnnotatedMethods:返回此註解類內被annotationName註解方法集合

AnnotatedTypeMetadata:註解型別元資料

​ isAnnotated:指定的元素是否被指定的annotationName註解

​ getAnnotationAttributes:檢索給定型別的註釋的屬性(如果有的話),同時還要考慮對組合註釋的屬性覆蓋。

​ getAllAnnotationAttributes:與getAnnotationAttributes功能相同,但是不進行屬性覆蓋

ClassMetadataReadingVisitor:ASM類訪問者,該訪問者僅查詢類名稱和實現的型別,並通過org.springframework.core.type.ClassMetadata介面將其公開。僅僅表示普通類,它的屬性是表示類的資訊。以DubboAutoConfiguration為例

​ 屬性className:類名,比如是com.alibaba.boot.dubbo.autoconfigure.DubboAutoConfiguration

​ 屬性isInterface:是否是介面,false

​ 屬性isAnnotation:是否是註解,false

​ 屬性isAbstract:是否是abstract,false

​ 屬性isFinal:是否標註了Final,false

​ 屬性enclosingClassName:null

​ 屬性independentInnerClass:false

​ 屬性superClassName:父類,java.lang.Object

​ 屬性interfaces:實現的介面,null

​ 屬性memberClassNames:成員內部類/介面,[com.alibaba.boot.dubbo.autoconfigure.DubboAutoConfiguration$MultipleDubboConfigConfiguration, com.alibaba.boot.dubbo.autoconfigure.DubboAutoConfiguration$SingleDubboConfigConfiguration]

AnnotationMetadataReadingVisitor:功能同ClassMetadataReadingVisitor,但是這個又支援註解類。繼續以com.alibaba.boot.dubbo.autoconfigure.DubboAutoConfiguration為例

​ 屬性annotationSet:此註解類上的註解集合 [org.springframework.context.annotation.Configuration, org.springframework.boot.autoconfigure.condition.ConditionalOnProperty, org.springframework.boot.autoconfigure.condition.ConditionalOnClass]

​ 屬性metaAnnotationMap:此註解類上註解的註解集合(不包含元註解) {org.springframework.context.annotation.Configuration=[org.springframework.stereotype.Component, org.springframework.stereotype.Indexed], org.springframework.boot.autoconfigure.condition.ConditionalOnProperty=[org.springframework.context.annotation.Conditional], org.springframework.boot.autoconfigure.condition.ConditionalOnClass=[org.springframework.context.annotation.Conditional]}

​ 屬性attributesMap:此註解類所有註解的屬性集合(不包含元註解) 比如{org.springframework.context.annotation.Configuration=[{value=org.springframework.core.annotation.AnnotationUtils$DefaultValueHolder@f91da5e}], org.springframework.stereotype.Component=[{value=}], org.springframework.stereotype.Indexed=[{}], org.springframework.boot.autoconfigure.condition.ConditionalOnProperty=[{prefix=dubbo, name=[enabled], matchIfMissing=true, havingValue=true, value=org.springframework.core.annotation.AnnotationUtils$DefaultValueHolder@eca6a74}], org.springframework.context.annotation.Conditional=[{value=[class org.springframework.boot.autoconfigure.condition.OnPropertyCondition]}, {value=[class org.springframework.boot.autoconfigure.condition.OnClassCondition]}], org.springframework.boot.autoconfigure.condition.ConditionalOnClass=[{value=[Lcom/alibaba/dubbo/config/AbstractConfig;], name=org.springframework.core.annotation.AnnotationUtils$DefaultValueHolder@79fd6f95}]}

​ 屬性methodMetadataSet:此註解類上的方法集合 [org.springframework.core.type.classreading.MethodMetadataReadingVisitor@6c9320c2, org.springframework.core.type.classreading.MethodMetadataReadingVisitor@3414a8c3, org.springframework.core.type.classreading.MethodMetadataReadingVisitor@cf518cf] 因為DubboAutoConfiguration只有三個方法

StandardClassMetadata:使用標準反射生成一個Class,僅支援普通類

​ 屬性introspectedClass:使用標準反射生成的class

StandardAnnotationMetadata:使用標準反射生成一個Class,支援註解類

ClassVisitor:功能同ASM的ClassVisitor,提供訪問java class的能力。

以上是spring對ClassMetadata的處理過程,現在回到dubbo自動裝配,在ConfigurationClassPostProcessor執行processMemberClasses的時候,DubboAutoConfiguration有兩個member類,而且這兩個內部類上有@Import,因此@Import(DubboConfigConfigurationRegistrar.class)優先於@Bean serviceAnnotationBeanPostProcessor(Environment environment)方法執行。這也揭開了自己的疑惑。

總結下獲取ClassMetadata整個流程,程式碼如下

//className=com.alibaba.boot.dubbo.autoconfigure.DubboAutoConfiguration
String resourcePath = "classpath:" + ClassUtils.convertClassNameToResourcePath(className) + ".class";
Resource resource = this.resourceLoader.getResource(resourcePath);	//resourceLoader即DefaultResourceLoader
InputStream is = new BufferedInputStream(resource.getInputStream());
ClassReader classReader = new ClassReader(is);
AnnotationMetadataReadingVisitor visitor = new AnnotationMetadataReadingVisitor(classLoader);
classReader.accept(visitor, ClassReader.SKIP_DEBUG);//解析位元組流,儲存class資訊到AnnotationMetadataReadingVisitor
//結果visitor就是ClassMetadata,可以獲取內部類等資訊

Class獲取內部類參考 JDK反射類Class記錄 https://www.cnblogs.com/zhangyjblogs/p/14819554.html

盲點二

DubboAutoConfiguration自動裝配了DubboConfigConfiguration.Single,那麼DubboConfigConfiguration.Single上的註解@EnableDubboConfigBindings是如何裝配的呢?答案是在org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(BeanDefinitionRegistry)內的do{...}while內,判斷註冊的bean數量有變化,則會進一步解析配置類。程式碼如圖

因為自動裝配類DubboAutoConfiguration有內部類SingleDubboConfigConfiguration,上面有註解@EnableDubboConfig引入了@Import(DubboConfigConfigurationRegistrar.class),在DubboConfigConfigurationRegistrar自動註冊了bean DubboConfigConfiguration.Single,因此bean的數量發生變化,繼而遞迴去解析com.alibaba.dubbo.config.spring.context.annotation.DubboConfigConfiguration.Single上的@EnableDubboConfigBindings註解,最終執行DubboConfigBindingsRegistrar註冊dubbo的內部XxxConfig bean,比如ApplicationConfig,RegistryConfig。

解答:

  1. SingleDubboConfigConfiguration、MultipleDubboConfigConfiguration有什麼作用?

    目的是為了註冊dubbo的config bean。這兩個類作為自動裝配類DubboAutoConfiguration有內部類,會引入註冊bean DubboConfigConfiguration.Single、DubboConfigConfiguration.Multiple,從而註冊dubbo配置bean,比如ApplicationConfig、RegistryConfig、ProtocolConfig等。

  2. @EnableDubboConfigBindings是如何繫結的?

    在ConfigurationClassPostProcessor.processConfigBeanDefinitions(BeanDefinitionRegistry)的do{...}while內,判斷註冊的bean數量有變化,則會進一步解析配置類DubboConfigConfiguration.Single、DubboConfigConfiguration.Multiple,繼而引入DubboConfigBindingsRegistrar註冊dubbo config bean。