Dubbo 系列之服务订阅(一)

一码到底 2020年08月18日 23次浏览

介绍

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