Spring IOC(十四)Dubbo 與Spring 集成 - @Reference和@Service 原理

上一篇文章圍繞 @EnableDubbo 進行了深入的分析,本篇文章將重點看@Service@Reference 原理。
與上面兩個註解相關聯兩個Bean類分別爲:

  1. ServiceAnnotationBeanPostProcessor
  2. ReferenceAnnotationBeanPostProcessor

ServiceAnnotationBeanPostProcessor

上一篇文章中,ServiceAnnotationBeanPostProcessor 是在 DubboComponentScan 中,在 DubboComponentScanRegistrar 中,被註冊到Spring 容器中。

public class ServiceAnnotationBeanPostProcessor implements BeanDefinitionRegistryPostProcessor, EnvironmentAware,
        ResourceLoaderAware, BeanClassLoaderAware {
        ...
        }

ServiceAnnotationBeanPostProcessor 最核心的角色是 一個 BeanDefinitionRegistryPostProcessor,這樣能夠在refresh中對BeanFactoryPostProcessor處理時,對 Spring 容器中bean定義進行刪改。

裏面主要方法爲 postProcessBeanDefinitionRegistry:

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {

        // 註冊 DubboBootstrapApplicationListener
        registerBeans(registry, DubboBootstrapApplicationListener.class);
		// 獲取掃描路徑
        Set<String> resolvedPackagesToScan = resolvePackagesToScan(packagesToScan);

        if (!CollectionUtils.isEmpty(resolvedPackagesToScan)) {
        // 具體掃描
            registerServiceBeans(resolvedPackagesToScan, registry);
        } else {
            if (logger.isWarnEnabled()) {
                logger.warn("packagesToScan is empty , ServiceBean registry will be ignored!");
            }
        }

    }

上面方法則中,普通獲取掃描包路徑,最終調用 registerServiceBeans 進行掃描:

    private void registerServiceBeans(Set<String> packagesToScan, BeanDefinitionRegistry registry) {
		// 構建一個 DubboClassPathBeanDefinitionScanner,父類爲 Spring 的 ClassPathBeanDefinitionScanner
        DubboClassPathBeanDefinitionScanner scanner =
                new DubboClassPathBeanDefinitionScanner(registry, environment, resourceLoader);
		// 獲取一個beanGenerator
        BeanNameGenerator beanNameGenerator = resolveBeanNameGenerator(registry);
        scanner.setBeanNameGenerator(beanNameGenerator);
        // 添加過濾類,即只掃描@Service註解的類。
        scanner.addIncludeFilter(new AnnotationTypeFilter(Service.class));
		// 添加對舊版本支持
        scanner.addIncludeFilter(new AnnotationTypeFilter(com.alibaba.dubbo.config.annotation.Service.class));

        for (String packageToScan : packagesToScan) {

            // 掃描,並且會將@Service 註解的類都註冊進Spring 
            scanner.scan(packageToScan);

            // 獲取所有的 @Service 的 BeanDefinitionHolders 
            Set<BeanDefinitionHolder> beanDefinitionHolders =
                    findServiceBeanDefinitionHolders(scanner, packageToScan, registry, beanNameGenerator);

            if (!CollectionUtils.isEmpty(beanDefinitionHolders)) {

                for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders) {
                	// 將上一步中獲取到的所有 @Service 註解bean,都註冊到ServiceBean類型到 Spirng中
                    registerServiceBean(beanDefinitionHolder, registry, scanner);
                }
                if (logger.isInfoEnabled()) {
                    logger.info(beanDefinitionHolders.size() + " annotated Dubbo's @Service Components { " +
                            beanDefinitionHolders +
                            " } were scanned under package[" + packageToScan + "]");
                }
            } else {
                if (logger.isWarnEnabled()) {
                    logger.warn("No Spring Bean annotating Dubbo's @Service was found under package["
                            + packageToScan + "]");
                }
            }
        }
    }

上面方法中,主要有以下幾步操作:

  1. 構建一個 DubboClassPathBeanDefinitionScanner,父類爲 SpringClassPathBeanDefinitionScanner,這是Spring提供出來一個通用掃描器,其內置的 @Conponent 掃描,以及 Mybatis 中掃描,也是以這個類爲主。
  2. 將 所有掃描出 的 @Service Bean類,通過registerServiceBean 註冊進成爲Spring的Bean。

registerServiceBean :

    private void registerServiceBean(BeanDefinitionHolder beanDefinitionHolder, BeanDefinitionRegistry registry,
                                     DubboClassPathBeanDefinitionScanner scanner) {
		// 獲取到BeanClass
        Class<?> beanClass = resolveClass(beanDefinitionHolder);
        Annotation service = findServiceAnnotation(beanClass);
		// 獲取 @Service 註解的參數
        AnnotationAttributes serviceAnnotationAttributes = getAnnotationAttributes(service, false, false);
		// 獲取接口名
        Class<?> interfaceClass = resolveServiceInterfaceClass(serviceAnnotationAttributes, beanClass);
		// 獲取ServiceBean名字
        String annotatedServiceBeanName = beanDefinitionHolder.getBeanName();
		// 構造一個 serviceBean
        AbstractBeanDefinition serviceBeanDefinition =
                buildServiceBeanDefinition(service, serviceAnnotationAttributes, interfaceClass, annotatedServiceBeanName);

        // ServiceBean Bean name
        String beanName = generateServiceBeanName(serviceAnnotationAttributes, interfaceClass);

        if (scanner.checkCandidate(beanName, serviceBeanDefinition)) { 
        	// 如果沒有註冊過,那麼久進行註冊
            registry.registerBeanDefinition(beanName, serviceBeanDefinition);
            if (logger.isInfoEnabled()) {
                logger.info("The BeanDefinition[" + serviceBeanDefinition +
                        "] of ServiceBean has been registered with name : " + beanName);
            }
        } else {
            if (logger.isWarnEnabled()) {
                logger.warn("The Duplicated BeanDefinition[" + serviceBeanDefinition +
                        "] of ServiceBean[ bean name : " + beanName +
                        "] was be found , Did @DubboComponentScan scan to same package in many times?");
            }
        }
    }

上面註冊方法中,包括以下三個邏輯:

  1. 獲取@Service 參數
  2. 獲取ServiceBean名字
  3. 構造一個 ServiceBean 類型 BeanDefinition
  4. ServiceBean註冊進Spring 容器

整個過程最終要的就是 ServiceBean 構造過程,即 buildServiceBeanDefinition 方法:

    private AbstractBeanDefinition buildServiceBeanDefinition(Annotation serviceAnnotation,
                                                              AnnotationAttributes serviceAnnotationAttributes,
                                                              Class<?> interfaceClass,
                                                              String annotatedServiceBeanName) {
        BeanDefinitionBuilder builder = rootBeanDefinition(ServiceBean.class);
        AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
        MutablePropertyValues propertyValues = beanDefinition.getPropertyValues();
        String[] ignoreAttributeNames = of("provider", "monitor", "application", "module", "registry", "protocol",
                "interface", "interfaceName", "parameters");
        propertyValues.addPropertyValues(new AnnotationPropertyValuesAdapter(serviceAnnotation, environment, ignoreAttributeNames));
        // References "ref" property to annotated-@Service Bean
        addPropertyReference(builder, "ref", annotatedServiceBeanName);
        // Set interface
        builder.addPropertyValue("interface", interfaceClass.getName());
        // Convert parameters into map
        builder.addPropertyValue("parameters", convertParameters(serviceAnnotationAttributes.getStringArray("parameters")));
        // Add methods parameters
        List<MethodConfig> methodConfigs = convertMethodConfigs(serviceAnnotationAttributes.get("methods"));
        if (!methodConfigs.isEmpty()) {
            builder.addPropertyValue("methods", methodConfigs);
        }
        /**
         * Add {@link org.apache.dubbo.config.ProviderConfig} Bean reference
         */
        String providerConfigBeanName = serviceAnnotationAttributes.getString("provider");
        if (StringUtils.hasText(providerConfigBeanName)) {
            addPropertyReference(builder, "provider", providerConfigBeanName);
        }
        /**
         * Add {@link org.apache.dubbo.config.MonitorConfig} Bean reference
         */
        String monitorConfigBeanName = serviceAnnotationAttributes.getString("monitor");
        if (StringUtils.hasText(monitorConfigBeanName)) {
            addPropertyReference(builder, "monitor", monitorConfigBeanName);
        }
        /**
         * Add {@link org.apache.dubbo.config.ApplicationConfig} Bean reference
         */
        String applicationConfigBeanName = serviceAnnotationAttributes.getString("application");
        if (StringUtils.hasText(applicationConfigBeanName)) {
            addPropertyReference(builder, "application", applicationConfigBeanName);
        }
        /**
         * Add {@link org.apache.dubbo.config.ModuleConfig} Bean reference
         */
        String moduleConfigBeanName = serviceAnnotationAttributes.getString("module");
        if (StringUtils.hasText(moduleConfigBeanName)) {
            addPropertyReference(builder, "module", moduleConfigBeanName);
        }
        /**
         * Add {@link org.apache.dubbo.config.RegistryConfig} Bean reference
         */
        String[] registryConfigBeanNames = serviceAnnotationAttributes.getStringArray("registry");
        List<RuntimeBeanReference> registryRuntimeBeanReferences = toRuntimeBeanReferences(registryConfigBeanNames);
        if (!registryRuntimeBeanReferences.isEmpty()) {
            builder.addPropertyValue("registries", registryRuntimeBeanReferences);
        }
        /**
         * Add {@link org.apache.dubbo.config.ProtocolConfig} Bean reference
         */
        String[] protocolConfigBeanNames = serviceAnnotationAttributes.getStringArray("protocol");
        List<RuntimeBeanReference> protocolRuntimeBeanReferences = toRuntimeBeanReferences(protocolConfigBeanNames);
        if (!protocolRuntimeBeanReferences.isEmpty()) {
            builder.addPropertyValue("protocols", protocolRuntimeBeanReferences);
        }
        return builder.getBeanDefinition();
    }

圍繞兩個問題說明:

  1. @Service 註解 的bean,最終注入到Spring中,是以 ServiceBean 方式存在。
  2. 註冊進Spring時候,需要將對應屬性填充到 BeanDefinition 中,大概有以下:
  • 包括基本的組件 "provider", "monitor", "application", "module", "registry", "protocol","interface", "interfaceName", "parameters"
  • ref 屬性,默認爲實現類名字
  • interface屬性,暴露接口對應的接口類
  • parameters 屬性
  • methods 屬性
  • provider 屬性,即 ProviderConfig,即確定使用哪一份ProviderConfig
  • monitor 屬性,同上,可以指定哪一份配置
  • application 屬性
  • module 屬性
  • registry(registries) 屬性
  • protocol(protocols) 屬性

最終,這就是一個 @Service 在Spring 中初始化過程,但是似乎還少了些什麼,讀過博主以前博客同學應該知道,Dubbo的接口暴露過程遠不止這些,最重要的還要執行export方法呀。

其實這個問題的答案,在上一篇文章最後已經給出,在Spring容器完全初始化完之後,由 DubboBootstrapexportServices 將所有ServiceBean 執行其 export 方法。

還有一個問題,如果使用 @Service 暴露後,在本地,可以使用 @Autowired 之類的注入註解 引用到嗎?
答案是可以,因爲Dubbo會往Spring中注入兩種類型bean:

  1. 對應接口 實現類的bean(掃描時注入)
  2. ServiceBean類型bean

ReferenceAnnotationBeanPostProcessor

ReferenceAnnotationBeanPostProcessor 是爲了處理 @Reference 註解,運行原理和 AutowiredAnnotationBeanPostProcessor (處理@Autowired)很相似。

public class ReferenceAnnotationBeanPostProcessor extends AbstractAnnotationBeanPostProcessor implements
        ApplicationContextAware, ApplicationListener {
	...
	}

對應的父類爲:

public abstract class AbstractAnnotationBeanPostProcessor extends
        InstantiationAwareBeanPostProcessorAdapter implements MergedBeanDefinitionPostProcessor, PriorityOrdered,
        BeanFactoryAware, BeanClassLoaderAware, EnvironmentAware, DisposableBean {
	}

ReferenceAnnotationBeanPostProcessor 關鍵的接口超類爲: InstantiationAwareBeanPostProcessorAdapterMergedBeanDefinitionPostProcessor
從調用關係來看,MergedBeanDefinitionPostProcessor 在實例化前被調用(具體原因看博主前面文章哦),postProcessMergedBeanDefinition 優先調用,先看其方法:

    @Override
    public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
        if (beanType != null) {
        	// 獲取所有的@Reference註解字段並緩存
            InjectionMetadata metadata = findInjectionMetadata(beanName, beanType, null);
            // 校驗
            metadata.checkConfigMembers(beanDefinition);
        }
    }

上面方法中,重要的是 findInjectionMetadata 方法,主要目的:

  1. 掃描所有非static 的字段和方法,並進行緩存。
    這一步和 @AutowiredAutowiredAnnotationBeanPostProcessor 處理邏輯一樣,可以參考博主以前文章:
    https://blog.csdn.net/anLA_/article/details/104507941

當在每個Bean實例化前,找完其所有的@Reference 註解過的字段和方法後,就進入到 InstantiationAwareBeanPostProcessorAdapter 的作用範圍了。
InstantiationAwareBeanPostProcessorAdapterpostProcessPropertyValues 會在實例化時被調用:

    public PropertyValues postProcessPropertyValues(
            PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeanCreationException {
        InjectionMetadata metadata = findInjectionMetadata(beanName, bean.getClass(), pvs);
        try {
            metadata.inject(bean, beanName, pvs);
        } catch (BeanCreationException ex) {
            throw ex;
        } catch (Throwable ex) {
            throw new BeanCreationException(beanName, "Injection of @" + getAnnotationType().getSimpleName()
                    + " dependencies is failed", ex);
        }
        return pvs;
    }

上面就是實例化之後,具體的初始化動作中執行,還是那個方法 findInjectionMetadata 獲取到 InjectionMetadata, 而後進行注入。
對 獲取到的 InjectMetadata 進行注入:

	public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
		Collection<InjectedElement> checkedElements = this.checkedElements;
		Collection<InjectedElement> elementsToIterate =
				(checkedElements != null ? checkedElements : this.injectedElements);
		if (!elementsToIterate.isEmpty()) {
			for (InjectedElement element : elementsToIterate) {
				if (logger.isTraceEnabled()) {
					logger.trace("Processing injected element of bean '" + beanName + "': " + element);
				}
				element.inject(target, beanName, pvs);
			}
		}
	}

此處的 InjectedElement 包括 兩種類型 AnnotatedMethodElementAnnotatedFieldElement。這兩種都是在 findInjectionMetadata 已經獲取到的數據。
AnnotatedFieldElement 爲例:

    public class AnnotatedFieldElement extends InjectionMetadata.InjectedElement {
        private final Field field;
        private final AnnotationAttributes attributes;
        private volatile Object bean;
        protected AnnotatedFieldElement(Field field, AnnotationAttributes attributes) {
            super(field, null);
            this.field = field;
            this.attributes = attributes;
        }
        @Override
        protected void inject(Object bean, String beanName, PropertyValues pvs) throws Throwable {
            Class<?> injectedType = field.getType();
            Object injectedObject = getInjectedObject(attributes, bean, beanName, injectedType, this);
            ReflectionUtils.makeAccessible(field);
            field.set(bean, injectedObject);
        }
    }

對應 inject 方法邏輯,首先是獲取對應類型,而後調用具體子類的 getInjectedObject 獲取bean實例,最後反射調用set方法注入值。
AbstactAnnotationBeanPostProcessorgetInjectedObject 中,則是定義了一層緩存,如果緩存中沒有,則再從子類中獲取:

    protected Object getInjectedObject(AnnotationAttributes attributes, Object bean, String beanName, Class<?> injectedType,
                                       InjectionMetadata.InjectedElement injectedElement) throws Exception {
        String cacheKey = buildInjectedObjectCacheKey(attributes, bean, beanName, injectedType, injectedElement);
        // 從緩存中獲取
        Object injectedObject = injectedObjectsCache.get(cacheKey);
        if (injectedObject == null) {
        // 調用子類創建
            injectedObject = doGetInjectedBean(attributes, bean, beanName, injectedType, injectedElement);
            // Customized inject-object if necessary
            injectedObjectsCache.putIfAbsent(cacheKey, injectedObject);
        }
        return injectedObject;
    }

最終還是回到了 ReferenceAnnotationBeanPostProcessor,看它的 doGetInjectedBean 方法:

    protected Object doGetInjectedBean(AnnotationAttributes attributes, Object bean, String beanName, Class<?> injectedType,
                                       InjectionMetadata.InjectedElement injectedElement) throws Exception {
         // 創建一個ServiceBean對應名字,例如    ServiceBean:com.anla.rpc.configcenter.provider.service.HelloService:1.0.0
        String referencedBeanName = buildReferencedBeanName(attributes, injectedType);
        // 生成一個@Reference bean對應名字,如果有id屬性,則直接用id屬性替代。 @Reference(check=false,version=1.0.0) com.anla.rpc.configcenter.provider.service.HelloService
        String referenceBeanName = getReferenceBeanName(attributes, injectedType);
        // 以 injectedElement 爲藍本,創建一個 @ReferenceBean實例
        ReferenceBean referenceBean = buildReferenceBeanIfAbsent(referenceBeanName, attributes, injectedType);
        // 註冊到Spring中
        registerReferenceBean(referencedBeanName, referenceBean, attributes, injectedType);
        // 緩存一份bean實例
        cacheInjectedReferenceBean(referenceBean, injectedElement);
        // 返回
        return getOrCreateProxy(referencedBeanName, referenceBeanName, referenceBean, injectedType);
    }

上面方法有以下幾點關鍵邏輯:

  1. buildReferenceBeanIfAbsent 方法裏面其實也做了挺多複雜邏輯,包括檢查依賴,檢查Spring 各層級組件等:
    ReferenceBeanBuilder 的 build方法
    public final C build() throws Exception {
        checkDependencies();
        C configBean = doBuild();
        configureBean(configBean);
        if (logger.isInfoEnabled()) {
            logger.info("The configBean[type:" + configBean.getClass().getSimpleName() + "] has been built.");
        }
        return configBean;
    }
  1. 構建出對應的@Service 方法的bean名字,用於判斷是否 @Reference 的對象就在本地,如果就在本地,則註冊一個 @Reference 對象的別名。這一點在 registerReferenceBean 有體現:
    private void registerReferenceBean(String referencedBeanName, ReferenceBean referenceBean,
                                       AnnotationAttributes attributes,
                                       Class<?> interfaceClass) {

        ConfigurableListableBeanFactory beanFactory = getBeanFactory();

        String beanName = getReferenceBeanName(attributes, interfaceClass);

        if (existsServiceBean(referencedBeanName)) { // If @Service bean is local one
            /**
             * Get  the @Service's BeanDefinition from {@link BeanFactory}
             * Refer to {@link ServiceAnnotationBeanPostProcessor#buildServiceBeanDefinition}
             */
            AbstractBeanDefinition beanDefinition = (AbstractBeanDefinition) beanFactory.getBeanDefinition(referencedBeanName);
            RuntimeBeanReference runtimeBeanReference = (RuntimeBeanReference) beanDefinition.getPropertyValues().get("ref");
            // The name of bean annotated @Service
            String serviceBeanName = runtimeBeanReference.getBeanName();
            // register Alias rather than a new bean name, in order to reduce duplicated beans
            beanFactory.registerAlias(serviceBeanName, beanName);
        } else { // Remote @Service Bean
            if (!beanFactory.containsBean(beanName)) {
                beanFactory.registerSingleton(beanName, referenceBean);
            }
        }
  1. 如果不是在本地,則直接注入單例到Spring容器中。beanFactory.registerSingleton(beanName, referenceBean);
  2. 緩存注入的bean,以及inject對象。
  3. 在最後一步的 getOrCreateProxy 中,仍然會以 是否就是 @Reference 本地對象爲基礎:
    private Object getOrCreateProxy(String referencedBeanName, String referenceBeanName, ReferenceBean referenceBean, Class<?> serviceInterfaceType) {
        if (existsServiceBean(referencedBeanName)) { // If the local @Service Bean exists, build a proxy of ReferenceBean
        // 如果存在@Service對象,則返回一個代理對象
            return newProxyInstance(getClassLoader(), new Class[]{serviceInterfaceType},
                    wrapInvocationHandler(referenceBeanName, referenceBean));
        } else {
        // 默認是立刻獲取
            return referenceBean.get();
        }

referenceBean.get(); 就是dubbo接口的refer過程,這個看博主dubbo系列文章即可清楚。

ReferenceBean 本身是一個 FactoryBean 類型,其bean實例可以動態制定,主要在 getObject方法中體現:

    public Object getObject() {
        return get();
    }

整個 @Service 暴露和 @Reference 注入原理過程即已講清楚,
但是感覺目前最新版本(2.7.7-SNAPSHOT) 在 @Reference 處理上,還有點小缺陷,已經有了處理,打算提交Issue和PR反饋給社區,等博主後面文章具體分析。

覺得博主寫的有用,不妨關注博主公衆號: 六點A君。
哈哈哈,一起研究Spring:
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章