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托管的类的成员变量上,例如:html

@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者结合起来看。spring

ReferenceAnnotationBeanPostProcessor 分析

经过xml配置的方式的解析没有太多的价值,因此咱们直接分析注解的方式。接下来咱们分析Spring扩展点和dubbo框架的相结合内容。缓存

一、Spring相关:一个Bean属性依赖注入的扩展点在哪

在远程服务引用的解析前,咱们须要先了解下Spring的一个特殊的BeanPostProcessor,即
InstantiationAwareBeanPostProcessor,该后置处理器主要有三个做用:
1)、Bean实例化前的一个回调:postProcessBeforeInstantiation。
2)、Bean实例化后属性显式填充和自动注入前的回调:postProcessAfterInstantiation
3)、给Bean对象属性自动注入的机会:postProcessPropertyValues
显而易见,被打上@DubboReference 注解的属性值注入,就是利用了这个后置处理器的第三个做用(给Bean对象属性自动注入的机会postProcessPropertyValues)。就是在这个方法内处理Bean对象属性成员的注入。
有了以上Spring扩展点的基础,咱们来看下AbstractAnnotationBeanPostProcessor的postProcessPropertyValues。
由于ReferenceAnnotationBeanPostProcessor继承了AbstractAnnotationBeanPostProcessor.app

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

二、Spring相关:一个引用了远程服务的Bean,是如何找到打上@DubboReference注解的成员

经过第一个问题,咱们知道当一个Spring Service的对象引用了远程对象(经过注解@DubboReference),是经过postProcessPropertyValues注入的。接着咱们须要找到它是如何须要注入的属性成员。ide

/**
     * 找到相关的须要注入的成员元信息,并封装为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()方法的以下:post

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

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

其中findFieldAnnotationMetadata()和findAnnotatedMethodMetadata()方法其实就是分别在clazz上的成员和方法上找是否有被@DubboReference打标的属性。ui

三、真正的属性注入是怎么实现的

Dubbo框架为了实现属性的注入,分别定义了2个注入类,一个AnnotatedFieldElement和一个AnnotatedMethodElement。很显然,一个是整对成员,一个整对方法。这个2个类都继承了
Spring的InjectionMetadata.InjectedElement,而后实现inject方法。接着咱们来看下
这2个类的inject 是如何实现:this

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扩展点上被调用。