Dubbo系列之 (四)服務訂閱(1)


輔助鏈接

Dubbo系列之 (一)SPI擴展

Dubbo系列之 (二)Registry注冊中心-注冊(1)

Dubbo系列之 (三)Registry注冊中心-注冊(2)

Dubbo系列之 (四)服務訂閱(1)

介紹

dubbo的服務訂閱可以通過2種方式: 1)通過xml文件的標簽<dubbo:reference /> ;2)通過注解@DubboReference。
這2種服務訂閱在使用上基本沒區別,因為標簽<dubbo:reference />上的屬性字段都可以在注解@DubboReference上對應的找到。一般使用XML的配置文件方式來訂閱服務。
但是這2種的源碼實現上有一定的區別和公用。
首先,這2種的實現最終都是調用ReferenceBean#get()方法來產生代理對象和訂閱服務。所以ReferenceBean#get()方法是我們分析的重點。
其次,標簽<dubbo:reference />的實現方式是通過Spring自定義的標簽來實現的,當一個<dubbo:reference />標簽被DubboBeanDefinitionParser處理轉化后,會變成一個RootBeanDefinition,接着注冊到Spring容器中。而這個RootBeanDefinition對應的類就是ReferenceBean,這個ReferenceBean 實現了工廠Bean接口FactoryBean,所以在具體創建這個Bean得對象時候,會調用FactoryBean#getObject()來返回具體類對象。剛好這個ReferenceBean的getObject()方法就是調用ReferenceBean#get()來返回具體引用服務類型對象和訂閱服務。
再次,注解@DubboReference的實現方式則有所不同,它是通過BeanPostProcessor來實現的。一般我們把注解@DubboReference打在某個被Spring托管的類的成員變量上,例如:

@Component("demoServiceComponent")
public class DemoServiceComponent implements DemoService {

    @DubboReference(check = false, mock = "true" ,version = "2.0.0")
    private DemoService demoService;
    ......
}

所以可以知道@DubboReference注解和@Resource,@Autowired等注入注解類似,都是注入一個DemoService這種類型的對象。
而Dubbo框架正是通過自己編寫類似於AutowiredAnnotationBeanPostProcessor的處理器ReferenceAnnotationBeanPostProcessor,通過這個處理器來處理@DubboReference注解。讀者可以2者結合起來看。

ReferenceAnnotationBeanPostProcessor 分析

通過xml配置的方式的解析沒有太多的價值,所以我們直接分析注解的方式。接下來我們分析Spring擴展點和dubbo框架的相結合內容。

1、Spring相關:一個Bean屬性依賴注入的擴展點在哪

在遠程服務引用的解析前,我們需要先了解下Spring的一個特殊的BeanPostProcessor,即
InstantiationAwareBeanPostProcessor,該后置處理器主要有三個作用:
1)、Bean實例化前的一個回調:postProcessBeforeInstantiation。
2)、Bean實例化后屬性顯式填充和自動注入前的回調:postProcessAfterInstantiation
3)、給Bean對象屬性自動注入的機會:postProcessPropertyValues
顯而易見,被打上@DubboReference 注解的屬性值注入,就是利用了這個后置處理器的第三個作用(給Bean對象屬性自動注入的機會postProcessPropertyValues)。就是在這個方法內處理Bean對象屬性成員的注入。
有了以上Spring擴展點的基礎,我們來看下AbstractAnnotationBeanPostProcessor的postProcessPropertyValues。
因為ReferenceAnnotationBeanPostProcessor繼承了AbstractAnnotationBeanPostProcessor.

 public PropertyValues postProcessPropertyValues(
            PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeanCreationException {

        /**
         * 查找bean 下需要注入的相關成員(包括成員變量和方法,即被@DubboReference標注的成員,並把這些這些屬性集合封裝為一個對象InjectionMetadata,)
         * InjectionMetadata 對象內部for 循環,一次注入相關的屬性值。
         */
        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()得到查找需要注入的相關成員。接着通過Spring內置的對象InjectionMetadata來注入bean的相關屬性。這里需要注意點,metdata對象封裝了該bean 需要注入的所有屬性成員,在inject內部for循環一致注入。查看代碼類似如下:

public void inject(Object target, String beanName, PropertyValues pvs){
		Collection<InjectedElement> elementsToIterate =
				(this.checkedElements != null ? this.checkedElements : this.injectedElements);
		if (!elementsToIterate.isEmpty()) {
			for (InjectedElement element : elementsToIterate) {
				element.inject(target, beanName, pvs);
			}
		}
	}

2、Spring相關:一個引用了遠程服務的Bean,是如何找到打上@DubboReference注解的成員

通過第一個問題,我們知道當一個Spring Service的對象引用了遠程對象(通過注解@DubboReference),是通過postProcessPropertyValues注入的。接着我們需要找到它是如何需要注入的屬性成員。

/**
     * 找到相關的需要注入的成員元信息,並封裝為InjectionMetadata
     *
     * @param beanName 當前需要被注入的 beanName
     * @param clazz    當前需要被注入的 類的Class
     * @param pvs      當前 需要被注入bean 的參數
     * @return
     */
    private InjectionMetadata findInjectionMetadata(String beanName, Class<?> clazz, PropertyValues pvs) {
        // Fall back to class name as cache key, for backwards compatibility with custom callers.
        //獲取緩存key,默認為beanName,否則為className
        String cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName());
        // Quick check on the concurrent map first, with minimal locking.
        //從緩存中拿,如果未null 或者注入屬性元數據所在的目標類和當前需要注入屬性的bean的類型不一致時,需要重寫獲取
        AnnotatedInjectionMetadata metadata = this.injectionMetadataCache.get(cacheKey);
        if (InjectionMetadata.needsRefresh(metadata, clazz)) {
            synchronized (this.injectionMetadataCache) {
                metadata = this.injectionMetadataCache.get(cacheKey); //雙層判斷鎖定
                if (InjectionMetadata.needsRefresh(metadata, clazz)) {
                    if (metadata != null) { //原來的目標類不一致,先clear下參數屬性,但排除需要的參數pvs
                        metadata.clear(pvs);
                    }
                    try {
                        metadata = buildAnnotatedMetadata(clazz); //通過需要注入的類的字節碼clazz,得到需要被注入的屬性的元信息。
                        this.injectionMetadataCache.put(cacheKey, metadata); //放入緩存。
                    } catch (NoClassDefFoundError err) {
                        throw new IllegalStateException("Failed to introspect object class [" + clazz.getName() +
                                "] for annotation metadata: could not find class that it depends on", err);
                    }
                }
            }
        }
        return metadata;
    }

該注釋已經非常清楚了,其實就是通過buildAnnotatedMetadata()方法構建注入的元數據信息,然后放入緩存injectionMetadataCache中。而buildAnnotatedMetadata()方法的如下:

private AnnotatedInjectionMetadata buildAnnotatedMetadata(final Class<?> beanClass) {
        /**
         *
         * 1、查找 需要注入的成員的元信息
         */
        Collection<AnnotatedFieldElement> fieldElements = findFieldAnnotationMetadata(beanClass);
        /**
         *
         * 2、查找 需要注入的方法的元信息
         */
        Collection<AnnotatedMethodElement> methodElements = findAnnotatedMethodMetadata(beanClass);

        /**
         *
         * 組合返回元信息
         */
        return new AnnotatedInjectionMetadata(beanClass, fieldElements, methodElements);
    }

其中findFieldAnnotationMetadata()和findAnnotatedMethodMetadata()方法其實就是分別在clazz上的成員和方法上找是否有被@DubboReference打標的屬性。

3、真正的屬性注入是怎么實現的

Dubbo框架為了實現屬性的注入,分別定義了2個注入類,一個AnnotatedFieldElement和一個AnnotatedMethodElement。很顯然,一個是整對成員,一個整對方法。這個2個類都繼承了
Spring的InjectionMetadata.InjectedElement,然后實現inject方法。接着我們來看下
這2個類的inject 是如何實現:

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);

        }

    }
private class AnnotatedMethodElement extends InjectionMetadata.InjectedElement {

        private final Method method;

        private final AnnotationAttributes attributes;

        private volatile Object object;

        protected AnnotatedMethodElement(Method method, PropertyDescriptor pd, AnnotationAttributes attributes) {
            super(method, pd);
            this.method = method;
            this.attributes = attributes;
        }

        @Override
        protected void inject(Object bean, String beanName, PropertyValues pvs) throws Throwable {

            Class<?> injectedType = pd.getPropertyType();

            Object injectedObject = getInjectedObject(attributes, bean, beanName, injectedType, this);

            ReflectionUtils.makeAccessible(method);

            method.invoke(bean, injectedObject);

        }

    }

其實就是通過反射,把需要的屬性對象注入進來。成員屬性就通過field.set()。而方法就通過method.invoke().那么注入的injectedObject對象是如何獲取的呢。從上面的inject()方法知道,通過調用getInjectedObject()方法來實現,而該方法其實只是從緩存injectedObjectsCache中獲取注入的對象,如果不存在,就調用doGetInjectedBean().接着放入緩存中。
doGetInjectedBean()方法的作用見注釋:

 /**
     *
     *
     * 該方法是個模板方法,用來得到一個需要注入類型的的對象。
     *
     * @param attributes ReferenceBean注解屬性
     * @param bean  需要被注入的對象,一般是Spring Bean
     * @param beanName  需要被注入的對象名
     * @param injectedType  注入對象的類型
     * @param injectedElement  注入對象描述元信息
     * @return
     * @throws Exception
     */

    @Override
    protected Object doGetInjectedBean(AnnotationAttributes attributes, Object bean, String beanName, Class<?> injectedType,
                                       InjectionMetadata.InjectedElement injectedElement) throws Exception {
        /**
         * The name of bean that annotated Dubbo's {@link Service @Service} in local Spring {@link ApplicationContext}
         */
        /**
         *
         * 得到需要被注入的對象的BeanName,生成規則默認是,查看ServiceBeanNameBuilder.build()
         * ServiceBean:${interfaceName}:${version}:${group}
         */
        String referencedBeanName = buildReferencedBeanName(attributes, injectedType);

        /**
         * The name of bean that is declared by {@link Reference @Reference} annotation injection
         */
        /**
         * 得到引用對象@ReferenceBean的BeanName ,
         * 如果有Id 就是Id,沒有通過generateReferenceBeanName()產生,產生的類名如下:
         * @Reference(${attributes})${interface}
         */
        String referenceBeanName = getReferenceBeanName(attributes, injectedType);

        /**
         * 構建一個ReferenceBean 對象,如果不存在的話。
         *
         */
        ReferenceBean referenceBean = buildReferenceBeanIfAbsent(referenceBeanName, attributes, injectedType);

        /**
         * 判斷是否為本地ServiceBean,一般都是遠程引用dubbo服務。
         */
        boolean localServiceBean = isLocalServiceBean(referencedBeanName, referenceBean, attributes);

        /**
         * 然后注冊referenceBean
         */
        registerReferenceBean(referencedBeanName, referenceBean, attributes, localServiceBean, injectedType);

        /**
         * 把referenceBean 放入緩存中。
         */
        cacheInjectedReferenceBean(referenceBean, injectedElement);

        /**
         *
         * 通過referenceBean 創建動態代理創建一個injectedType類型的對象。核心,創建一個代理對象,用來代表需要引用遠程的Service服務
         */
        return getOrCreateProxy(referencedBeanName, referenceBean, localServiceBean, injectedType);
    }

上面內部方法調用都比較簡單,邏輯也非常清楚,最終是通過getOrCreateProxy()方法來創建一個遠程的代理對象,然后通過InjectedElement.inject注入該代理對象。
接着我們最好看下getOrCreateProxy()方法。

private Object getOrCreateProxy(String referencedBeanName, ReferenceBean referenceBean, boolean localServiceBean,
                                    Class<?> serviceInterfaceType) {
        /**
         *
         * 如果是本地就有服務Bean的話。
         */
        if (localServiceBean) { // If the local @Service Bean exists, build a proxy of Service
            //通過Proxy.newProxyInstance創建一個新的代理對象,在內部通過applicationContext獲取本地Service即可。
            return newProxyInstance(getClassLoader(), new Class[]{serviceInterfaceType},
                    newReferencedBeanInvocationHandler(referencedBeanName));
        } else {
            //如果是遠程Service,判斷被引用的服務referencedBeanName是否已經存在在applicationContext上,是的話,直接暴露服務,
            // 基本上不太可能,因為在前面已經判斷了被引用的服務Bean在遠程,所以這里僅僅是為了防止localServiceBean判斷錯誤。
            exportServiceBeanIfNecessary(referencedBeanName); // If the referenced ServiceBean exits, export it immediately
            //接着直接通過get()方法來創建一個代理,這get操作就會引入dubbo服務的訂閱等相關內容。
            return referenceBean.get();
        }
    }

可以看到最終調用了referenceBean.get()方法來創建一個遠程本地代理對象。referenceBean.get()在下一個章節分析。
到這里,我們已經分析了@DubboReference注解的處理過程,然后知道了referenceBean.get()在Spring的postProcessPropertyValues擴展點上被調用。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM