Dubbo整合Spring原理之@Reference和@DubboReference生效机制(3)

    技术2022-07-12  64

    Dubbo的2.7.7版本中已经标注了Reference和Service注解为@Deprecated弃用了。改用DubboReference和DubboService这两个注解了

    我们可以看到注解的方式启动可以使用EnableDubbo注解,这个注解又’继承’了EnableDubboConfig和DubboComponentScan 两个注解。 在EnableDubboConfig注解中的起作用的类是Import引入的DubboConfigConfigurationRegistrar类。

    注解的继承, 是Spring的一种技巧。在注解上添加注解就实现了类似的一个注解继承另外一个注解的能力

    DubboConfigConfigurationRegistrar类。

    可以看到方法的最后一行, registerCommonBeans 方法. 进入其中可以看到

    registerCommonBeans方法

    方法内部会放入下面五个类到容器中,如果容器中不存在这些类才会放入:

    注入ReferenceAnnotationBeanPostProcessor类的bean到容器中,注入DubboConfigAliasPostProcessor类的bean到容器中注入DubboLifecycleComponentApplicationListener类的bean到容器中注入DubboBootstrapApplicationListener类的bean到容器中注入DubboConfigDefaultPropertyValueBeanPostProcessor到容器中

    其中ReferenceAnnotationBeanPostProcessor类就是与DubboReference注解息息相关的类

    ReferenceAnnotationBeanPostProcessor

    可以看到构造方法传入了两个注解的信息到父类中, 实际上这两个注解就是待会ReferenceAnnotationBeanPostProcessor需要处理的注解.

    这个类继承自AbstractAnnotationBeanPostProcessor类,继续往上追查查看源码 发现它实现InstantiationAwareBeanPostProcessor接口,这个InstantiationAwareBeanPostProcessor也是一个十分重要的类,这个类是在bean实例化的前后进行一些操作的, dubbo就是使用这个InstantiationAwareBeanPostProcessor接口的com.alibaba.spring.beans.factory.annotation.AbstractAnnotationBeanPostProcessor#postProcessPropertyValues方法在bean实例化后注入对象到字段的。 实际上Spring的Autowire也是使用的这个方式实现的功能。

    InstantiationAwareBeanPostProcessor 的知识可以参考Spring的Autowire注解来功能实现类AutowiredAnnotationBeanPostProcessor类来理解。

    要理解Spring的bean的生命周期,区分实例化和初始化的区别。BeanPostProcessor接口是只有初始化前后的调用, InstantiationAwareBeanPostProcessor 接口增加了三个方法,作用分别是"实例化前调用",“实例化后调用” 和 “注入字段值”. 而Dubbo的Reference和Spring的Autowire注解就是使用了第三方法来实现注入字段值的。

    在实例化后会调用InstantiationAwareBeanPostProcessor接口的postProcessPropertyValues方法,并最终会调用到ReferenceAnnotationBeanPostProcessor#doGetInjectedBean方法。 具体的调用栈如下图所示:

    图中最下面的红框标识正在创建bean第二个红框可以看到正好调用了AbstractAnnotationBeanPostProcessor类的postProcessPropertyValues方法, ReferenceAnnotationBeanPostProcessor类继承自这个类,并且这个类实现了InstantiationAwareBeanPostProcessor接口。第二个红框就进入了dubbo的ReferenceAnnotationBeanPostProcessor中的逻辑。

    我们看下postProcessPropertyValues的实现逻辑:

    从图中可以看到实现步骤如下:

    找到所有符合要求的字段注入字段对象,所以实际上依赖注入就是在这里实现的, Autowire的这里的实现逻辑也是大同小异的。

    如何找到所有符合要求的字段

    我们看下实现逻辑中的找到符合要求的字段的实现 findInjectionMetadata方法的源码如下:

    private InjectionMetadata findInjectionMetadata(String beanName, Class <? > clazz, PropertyValues pvs) { // Fall back to class name as cache key, for backwards compatibility with custom callers. String cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName()); // Quick check on the concurrent map first, with minimal locking. AbstractAnnotationBeanPostProcessor.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方法中查看

    private AbstractAnnotationBeanPostProcessor.AnnotatedInjectionMetadata buildAnnotatedMetadata(final Class<?> beanClass) { //通过下面的方法名就可以知道是找到符合注入要求的字段 Collection<AbstractAnnotationBeanPostProcessor.AnnotatedFieldElement> fieldElements = findFieldAnnotationMetadata(beanClass); //通过下面的方法名就可以知道是找到符合注入要求的方法 Collection<AbstractAnnotationBeanPostProcessor.AnnotatedMethodElement> methodElements = findAnnotatedMethodMetadata(beanClass); return new AbstractAnnotationBeanPostProcessor.AnnotatedInjectionMetadata(beanClass, fieldElements, methodElements); }

    继续深入到findFieldAnnotationMetadata方法中:

    图中第一个红框就是反射工具类方法,标识遍历传入类的所有字段第二个红框实际就是ReferenceAnnotationBeanPostProcessor构造方法中传入的两个注解DubboReference和Reference.

    这里如何筛选要注入字段就很清楚了, 就是获取到Bean中有指定注解的字段。而指定的注解就是在构造放入中传入的

    如何注入字段值

    注入字段值的方法调用堆栈图就在上面。 从堆栈图中知道,最后会调用ReferenceAnnotationBeanPostProcessor#doGetInjectedBean方法。 这个方法的作用就是:获取要注入字段的值。 我们看下dubbo的实现:

    buildReferencedBeanName 这个方法就是生成Service服务的标识字符串, 这个字符串的示例: ServiceBean:org.apache.dubbo.demo.DemoService。 组成部分分别是: “ServiceBean:” +接口的全限定名称+ group:version ,因为示例中group和version没有在Reference注解中设置,所以是空的。getReferenceBeanName获取标识调用者的字符串, 示例: @Reference org.apache.dubbo.demo.DemoService 实际组成是:@Reference +(key1=value2,key2=value2, …) + 接口全限定名称buildReferenceBeanIfAbsent方法就是构造ReferenceBean的,ReferenceBean是继承自ReferenceConfig的isLocalServiceBean 检查服务消费者和提供者是否是在同一个应用中registerReferenceBean 注册ReferenceBean对象到容器中。 这个方法的内部逻辑是有点复杂的。 如果消费者和提供者在同一个程序中,会直接把ReferenceBean的ref字段注入服务提供者。如果不在同一个程序中则会直接拿ReferenceBean放入容器中cacheInjectedReferenceBean 放入ReferenceBean到缓存中getOrCreateProxy 生成代理类赋值给ReferenceBean的object中。并返回

    doGetInjectedBean方法的7步流程如上, 实际上最重要的流程就是第7步

    如何生成代理类的

    如果服务提供者和消费者在同一个程序中, 就会使用JDK的动态代理生成代理类,代理类ReferencedBeanInvocationHandler的源码如下: 可以中init方法看到实际是从Spring中获取到ServieBean的实例作为被代理的对象的。如果服务提供者在远方。 exportServiceBeanIfNecessary 其实是多余的方法,这里还是会从容器中获取服务提供者并尝试暴露服务提供者服务, 我们要关注的是referenceBean.get()这一行. referenceBean继承了FactoryBean接口,所以这里调用的是FactoryBean的get方法。

    referenceBean的get方法

    主要逻辑都在init方法中, init方法才是Reference注解实现的真正核心部分, 代码量有点多,下面全部贴出

    public synchronized void init() { if (initialized) { return; } if (bootstrap == null) { bootstrap = DubboBootstrap.getInstance(); bootstrap.init(); } checkAndUpdateSubConfigs(); checkStubAndLocal(interfaceClass); ConfigValidationUtils.checkMock(interfaceClass, this); Map<String, String> map = new HashMap<String, String>(); map.put(SIDE_KEY, CONSUMER_SIDE); ReferenceConfigBase.appendRuntimeParameters(map); if (!ProtocolUtils.isGeneric(generic)) { String revision = Version.getVersion(interfaceClass, version); if (revision != null && revision.length() > 0) { map.put(REVISION_KEY, revision); } String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames(); if (methods.length == 0) { logger.warn("No method found in service interface " + interfaceClass.getName()); map.put(METHODS_KEY, ANY_VALUE); } else { map.put(METHODS_KEY, StringUtils.join(new HashSet<String>(Arrays.asList(methods)), COMMA_SEPARATOR)); } } map.put(INTERFACE_KEY, interfaceName); AbstractConfig.appendParameters(map, getMetrics()); AbstractConfig.appendParameters(map, getApplication()); AbstractConfig.appendParameters(map, getModule()); // remove 'default.' prefix for configs from ConsumerConfig // appendParameters(map, consumer, Constants.DEFAULT_KEY); AbstractConfig.appendParameters(map, consumer); AbstractConfig.appendParameters(map, this); MetadataReportConfig metadataReportConfig = getMetadataReportConfig(); if (metadataReportConfig != null && metadataReportConfig.isValid()) { map.putIfAbsent(METADATA_KEY, REMOTE_METADATA_STORAGE_TYPE); } Map<String, AsyncMethodInfo> attributes = null; if (CollectionUtils.isNotEmpty(getMethods())) { attributes = new HashMap<>(); for (MethodConfig methodConfig : getMethods()) { AbstractConfig.appendParameters(map, methodConfig, methodConfig.getName()); String retryKey = methodConfig.getName() + ".retry"; if (map.containsKey(retryKey)) { String retryValue = map.remove(retryKey); if ("false".equals(retryValue)) { map.put(methodConfig.getName() + ".retries", "0"); } } AsyncMethodInfo asyncMethodInfo = AbstractConfig.convertMethodConfig2AsyncInfo(methodConfig); if (asyncMethodInfo != null) { // consumerModel.getMethodModel(methodConfig.getName()).addAttribute(ASYNC_KEY, asyncMethodInfo); attributes.put(methodConfig.getName(), asyncMethodInfo); } } } String hostToRegistry = ConfigUtils.getSystemProperty(DUBBO_IP_TO_REGISTRY); if (StringUtils.isEmpty(hostToRegistry)) { hostToRegistry = NetUtils.getLocalHost(); } else if (isInvalidLocalHost(hostToRegistry)) { throw new IllegalArgumentException("Specified invalid registry ip from property:" + DUBBO_IP_TO_REGISTRY + ", value:" + hostToRegistry); } map.put(REGISTER_IP_KEY, hostToRegistry); serviceMetadata.getAttachments().putAll(map); ref = createProxy(map); serviceMetadata.setTarget(ref); serviceMetadata.addAttribute(PROXY_CLASS_REF, ref); ConsumerModel consumerModel = repository.lookupReferredService(serviceMetadata.getServiceKey()); consumerModel.setProxyObject(ref); consumerModel.init(attributes); initialized = true; // dispatch a ReferenceConfigInitializedEvent since 2.7.4 dispatch(new ReferenceConfigInitializedEvent(this, invoker)); }

    真正生成代理类的是createProxy方法, 具体如何生成代理对象的是使用的Dubbo的SPI机制导入的协议和实现类来生成的, 具体的详情会在后面的dubbo的SPI机制中详细讲解。 这里就不再说明。

    总结

    dubbo 使用ReferenceAnnotationBeanPostProcessor类在每个bean示例化之前获取到bean中Reference注解的字段,并且为这个字段生成代理类ReferenceBean. 具体的dubbo远程调用逻辑实际上是在这个代理类中完成的。

    Processed: 0.013, SQL: 9