dubbo服务订阅_@Reference注解

2,627 阅读8分钟

参考资料:Dubbo系列之(四)服务订阅(1)

介绍

dubbo的服务订阅有两种方式,第一种是通过xml文件的标签<dubbo:reference />,第二种是通过注解@Reference。两者在使用上没有什么区别,标签上的属性都可以在注解上找到对应的配置。在源码实现上,两者存在一定的区别和共同点。

共同点:两者最终都是调用com.alibaba.dubbo.config.ReferenceConfig#get方法来产生代理对象和订阅服务

区别:

  • 标签<dubbo:reference />的实现方式是通过spring自定义标签实现的。dubbo使用ReferenceBean解析标签内容,ReferenceBean实现了FactoryBean接口,因此可通过getObject方法创建具体的实例对象,该方法里面就是调用父类ReferenceConfig#get方法。

    • public class DubboNamespaceHandler extends NamespaceHandlerSupport {
          @Override
          public void init() {
          	// 省略部分代码...
              registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));
              registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false));
          }
      }
      
    • image-20201221145847852
    • image-20201221145924255
  • @Reference注解是通过后置处理器实现的,与@Autowired、@Qualifier等注解类似,都是往Spring Bean对象注入指定属性。dubbo框架自己编写类似AutowiredAnnotationBeanPostProcessor的后置处理器,叫做ReferenceAnnotationBeanPostProcessor,通过这个后置处理器来解析被@Reference注解表标注的变量和方法。

    • com.alibaba.dubbo.config.annotation.Reference

    • @Documented
      @Retention(RetentionPolicy.RUNTIME)
      @Target({ElementType.FIELD, ElementType.METHOD, ElementType.ANNOTATION_TYPE})
      public @interface Reference {   
          Class<?> interfaceClass() default void.class;
          String interfaceName() default "";
          String url() default "";
          boolean check() default true;
          int retries() default 2;
          int timeout() default 0;
          // 省略部分信息
      }
      

总结

主线:postProcessPropertyValues -> findInjectionMetadata -> inject

步骤:

  1. 利用spring提供的扩展点,InstantiationAwareBeanPostProcessorpostProcessPropertyValues方法,该方法在spring bean对象属性赋值时触发;
  2. dubbo编写ReferenceAnnotationBeanPostProcessor后置处理器,实现InstantiationAwareBeanPostProcessor的postProcessPropertyValues方法;
  3. 先获取目标类上被**@Reference**标注的成员变量和方法的元信息,把元信息集合封装到InjectionMetadata对象;
  4. 遍历被标注的成员变量和成员方法,两者都是通过反射完成属性值的注入。如果是成员变量,调用field.set(),如果是成员方法,调用method.invoke()
  5. 属性值的获取过程主要分为三步:
    1. 创建ReferenceBean对象,并放入缓存
    2. 创建ReferenceBeanInvocationHandler对象,接着执行handler#init方法,里面是调用ReferenceBean#get方法,进入真正的服务订阅
    3. 最后,通过jdk动态代理,返回一个代理对象

ReferenceAnnotationBeanPostProcessor解析

spring相关:属性依赖注入的扩展点

参考资料:

在解析远程服务引入前,我们先了解一个特殊的后置处理器,InstantiationAwareBeanPostProcessor,源码如下:

public interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor {

	/**
	 * 在bean实例化前执行的回调方法,先于postProcessBeforeInitialization执行
	 */
	Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException;

	/**
	 * 在bean实例化后,属性显式填充和自动注入前回调
	 */
	boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException;

	/**
	 * 关键!
	 * 给属性赋值
	 */
	PropertyValues postProcessPropertyValues(
			PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException;

}
单词含义
Instantiation表示实例化,对象还未生成
Initialization表示初始化,对象已经生成

dubbo就是对上面的postProcessPropertyValues方法进行扩展,给对象注入依赖时,创建代理对象,订阅远程服务。

先来看下ReferenceAnnotationBeanPostProcessor的继承关系,然后重点关注postProcessPropertyValues方法。

image-20201221153451683

由于ReferenceAnnotationBeanPostProcessor没有实现postProcessPropertyValues方法,所以我们看到的是其父类AnnotationInjectedBeanPostProcessor

AnnotationInjectedBeanPostProcessor#postProcessPropertyValues

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

    // 查找当前Bean对象需要注入的成员的元信息,包括成员变量和方法
    // 即被@Reference标注的成员,把这些元信息集合封转成一个InjectionMetadata对象
    InjectionMetadata metadata = findInjectionMetadata(beanName, bean.getClass(), pvs);
    try {
        // 注入属性值
        metadata.inject(bean, beanName, pvs);
    } catch (BeanCreationException ex) {
        throw ex;
    } catch (Throwable ex) {
        throw new BeanCreationException("xxx");
    }
    return pvs;
}

该方法先通过findInjectionMetadata()得到被注解标注的成员变量和方法的元信息,把这些信息集合封装成InjectionMetadata对象,最后调用inject方法,内部使用for循环将属性值逐个注入。

org.springframework.beans.factory.annotation.InjectionMetadata#inject

public void inject(Object target, String beanName, PropertyValues pvs) throws Throwable {
    Collection<InjectedElement> elementsToIterate =
            (this.checkedElements != null ? this.checkedElements : this.injectedElements);
    
    if (!elementsToIterate.isEmpty()) {
        boolean debug = logger.isDebugEnabled();
        for (InjectedElement element : elementsToIterate) {
            if (debug) {
                logger.debug("Processing injected element of bean '" + beanName + "': " + element);
            }
            // 调用具体实现类的inject方法,接下来会分析这块
            element.inject(target, beanName, pvs);
        }
    }
}

spring相关:如何找到@Reference标注的成员

通过上面小节,我们知道当某个Spring Bean对象需要引入dubbo服务时,是通过@Reference注入服务提供者的实例对象,原理是通过postProcessPropertyValues方法注入。接下来,我们需要了解怎么找到@Reference标注的成员,包括成员变量和成员方法。

AnnotationInjectedBeanPostProcessor#findInjectionMetadata

/**
  * 找到需要注入的成员元信息,封装成InjectionMetadata
  *
  * @param beanName 当前需要被注入的对象名
  * @param clazz    当前需要被注入的类对象
  * @param pvs      当前需要被注入对象的参数
  * @return 返回需要注入的成员元信息
  */
public InjectionMetadata findInjectionMetadata(String beanName, Class<?> clazz, PropertyValues pvs) {
    // 获取缓存key,默认是beanName,否则是claaName
    String cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName());
    
    // 先从缓存获取,获取不到,或者注入属性的元数据所在的目标类与当前被注入的类不一致,需要重新获取
    AnnotationInjectedBeanPostProcessor.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) {
                    metadata.clear(pvs);
                }
                try {
                    // 找到需要注入的属性信息,并放入缓存
                    metadata = buildAnnotatedMetadata(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方法获取。

AnnotationInjectedBeanPostProcessor#buildAnnotatedMetadata

private AnnotationInjectedBeanPostProcessor.AnnotatedInjectionMetadata buildAnnotatedMetadata(final Class<?> beanClass) {
        // 查找被标注的成员变量的元信息
        Collection<AnnotationInjectedBeanPostProcessor.AnnotatedFieldElement> fieldElements = 
            																		findFieldAnnotationMetadata(beanClass);
        // 查找被标注的成员方法的元信息
        Collection<AnnotationInjectedBeanPostProcessor.AnnotatedMethodElement> methodElements = 
            																		findAnnotatedMethodMetadata(beanClass);
       
        return new AnnotationInjectedBeanPostProcessor.AnnotatedInjectionMetadata(beanClass, fieldElements, methodElements);
}

AnnotationInjectedBeanPostProcessor#findFieldAnnotationMetadata

/**
  * Finds {@link InjectionMetadata.InjectedElement} Metadata from annotated {@link A} fields
  *
  * @param beanClass The {@link Class} of Bean
  * @return non-null {@link List}
  */
private List<AnnotationInjectedBeanPostProcessor.AnnotatedFieldElement> findFieldAnnotationMetadata(final Class<?> beanClass) {

    final List<AnnotationInjectedBeanPostProcessor.AnnotatedFieldElement> elements = 
        						new LinkedList<AnnotationInjectedBeanPostProcessor.AnnotatedFieldElement>();

    ReflectionUtils.doWithFields(beanClass, new ReflectionUtils.FieldCallback() {
        @Override
        public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
            // 遍历每个成员变量,并通过getAnnotationType()指定注解类型(@Reference),最后得到结果后存入集合
            A annotation = getAnnotation(field, getAnnotationType());
            if (annotation != null) {
                if (Modifier.isStatic(field.getModifiers())) {
                    if (logger.isWarnEnabled()) {
                        logger.warn("@" + getAnnotationType().getName() + " is not supported on static fields: " + field);
                    }
                    return;
                }
                elements.add(new AnnotationInjectedBeanPostProcessor.AnnotatedFieldElement(field, annotation));
            }
        }
    });
    return elements;
}

AnnotationInjectedBeanPostProcessor#findAnnotatedMethodMetadata

/**
  * Finds {@link InjectionMetadata.InjectedElement} Metadata from annotated {@link A @A} methods
  *
  * @param beanClass The {@link Class} of Bean
  * @return non-null {@link List}
  */
private List<AnnotationInjectedBeanPostProcessor.AnnotatedMethodElement> findAnnotatedMethodMetadata(final Class<?> beanClass) {

    final List<AnnotationInjectedBeanPostProcessor.AnnotatedMethodElement> elements = 
        									new LinkedList<AnnotationInjectedBeanPostProcessor.AnnotatedMethodElement>();

    ReflectionUtils.doWithMethods(beanClass, new ReflectionUtils.MethodCallback() {
        @Override
        public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
            Method bridgedMethod = findBridgedMethod(method);
            if (!isVisibilityBridgeMethodPair(method, bridgedMethod)) {
                return;
            }
            // 遍历每个方法,获取被@Reference标注的方法
            A annotation = findAnnotation(bridgedMethod, getAnnotationType());
            if (annotation != null && method.equals(ClassUtils.getMostSpecificMethod(method, beanClass))) {
                // 省略部分异常检查
                PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, beanClass);
                elements.add(new AnnotationInjectedBeanPostProcessor.AnnotatedMethodElement(method, pd, annotation));
            }
        }
    });
    return elements;
}

总的来说,dubbo都是通过AnnotationUtils#findAnnotation方法获取到被注解@Reference标注成员变量和成员方法。

如何实现依赖注入

获取到需要被@Reference标注的成员元信息后,接着就是属性值的注入。dubbo提供了两个注入类,分别是AnnotatedFieldElement和AnnotatedMethodElement,显然,一个用于处理成员变量,一个是用于处理成员方法。

AnnotatedFieldElement#inject

@Override
protected void inject(Object bean, String beanName, PropertyValues pvs) throws Throwable {
	// 获取属性的类对象
    Class<?> injectedType = field.getType();
	// 获取注入的实例对象
    injectedBean = getInjectedObject(annotation, bean, beanName, injectedType, this);
	// 反射,允许访问
    ReflectionUtils.makeAccessible(field);
	// 属性赋值
    field.set(bean, injectedBean);
}

AnnotatedMethodElement#inject

@Override
protected void inject(Object bean, String beanName, PropertyValues pvs) throws Throwable {
	// 获取参数的类对象
    Class<?> injectedType = pd.getPropertyType();

    injectedBean = getInjectedObject(annotation, bean, beanName, injectedType, this);

    ReflectionUtils.makeAccessible(method);
	// 调用方法
    method.invoke(bean, injectedBean);
}

获取属性值

上面两个方法都需要通过getInjectedObject()获取注入的实例对象,该方法先从缓存获取结果,如果没有,则调用doGetInjectBean()创建对象

ReferenceAnnotationBeanPostProcessor#doGetInjectedBean

/**
  * 该方法是一个模板方法,用来得到一个指定注入类型的对象
  *
  * @param reference
  * @param bean            Current bean that will be injected
  * @param beanName        Current bean name that will be injected
  * @param injectedType    the type of injected-object
  * @param injectedElement {@link InjectionMetadata.InjectedElement}
  * @return
  * @throws Exception
  */
@Override
protected Object doGetInjectedBean(Reference reference, Object bean, String beanName, Class<?> injectedType,
                                   InjectionMetadata.InjectedElement injectedElement) throws Exception {
    // 构建ReferenceBean名称
    String referencedBeanName = buildReferencedBeanName(reference, injectedType);
    // 构建ReferenceBean对象
    ReferenceBean referenceBean = buildReferenceBeanIfAbsent(referencedBeanName, reference, injectedType, getClassLoader());
    // 放入缓存
    cacheInjectedReferenceBean(referenceBean, injectedElement);
    // 创建代理对象
    Object proxy = buildProxy(referencedBeanName, referenceBean, injectedType);

    return proxy;
}

doGetInjectedBean方法先是构建ReferenceBean对象,然后通过jdk动态代理返回一个代理对象。

在构建代理对象前,需要先创建一个ReferenceBeanInvocationHandler,接着handler会调用ReferenceBean#get方法,这个方法是服务订阅的核心方法,下篇文章会讲解。得到InvocationHandler对象后,就通过jdk动态代理返回代理对象。

源码如下:

// ReferenceAnnotationBeanPostProcessor#buildProxy
private Object buildProxy(String referencedBeanName, ReferenceBean referenceBean, Class<?> injectedType) {
    InvocationHandler handler = buildInvocationHandler(referencedBeanName, referenceBean);
    Object proxy = Proxy.newProxyInstance(getClassLoader(), new Class[]{injectedType}, handler);
    return proxy;
}

// ReferenceAnnotationBeanPostProcessor#buildInvocationHandler
private InvocationHandler buildInvocationHandler(String referencedBeanName, ReferenceBean referenceBean) {
	// 先从缓存获取
    ReferenceBeanInvocationHandler handler = localReferenceBeanInvocationHandlerCache.get(referencedBeanName);
    if (handler == null) {
        handler = new ReferenceBeanInvocationHandler(referenceBean);
    }
    if (applicationContext.containsBean(referencedBeanName)) { // Is local @Service Bean or not ?
        // ReferenceBeanInvocationHandler's initialization has to wait for current local @Service Bean has been exported.
        // 本地服务先缓存,等服务暴露再初始化
        localReferenceBeanInvocationHandlerCache.put(referencedBeanName, handler);
    } else {
        // Remote Reference Bean should initialize immediately
        // 远程服务需要马上初始化,调用ReferenceBeanInvocationHandler的init方法
        handler.init();
    }
    return handler;
}

ReferenceBeanInvocationHandler源码如下

private static class ReferenceBeanInvocationHandler implements InvocationHandler {

    private final ReferenceBean referenceBean;

    private Object bean;

    private ReferenceBeanInvocationHandler(ReferenceBean referenceBean) {
        this.referenceBean = referenceBean;
    }
	
    // 代理对象执行目标方法时,会被invoke拦截
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result = null;
        try {
            if (bean == null) { // If the bean is not initialized, invoke init()
                // issue: https://github.com/apache/incubator-dubbo/issues/3429
                init();
            }
            result = method.invoke(bean, args);
        } catch (InvocationTargetException e) {
            // re-throws the actual Exception.
            throw e.getTargetException();
        }
        return result;
    }

    private void init() {
        // 调用ReferenceBean的get方法,进入真正的服务订阅过程
        this.bean = referenceBean.get();
    }
}