【Dubbo】2.服务发布和引用

服务发布

SpringContainer启动之后就开始进入Spring的生命周期管理了,依赖Spring提供的各种扩展机制,比如spring会自动查找dubbo.jar!/META-INF/spring.handlers文件的内容,实现命名空间自定义扩展

DubboNamespaceHandler

Dubbo的配置文件中有很多xml节点比如:

如果我们要自己写标签同时整合到Spring,首先要查看官方文档找到Xml扩展接口,Dubbo就是通过DubboNamespaceHandler继承Spring提供的扩展接口NamespaceHandlerSupport,允许Dubbo自定义Xml标签向Spring容器注册BeanDefinition,而Xml文件经过DubboBeanDefinitionParser.parse()之后又具备了XMLJava代码转换的能力,具体的配置参数如下所示,后面有专题章节对配置以及转换过程做详细介绍,这里只需要了解下面的xml等同于手写Dubbo Api代码即可。

Consumer配置




Provider配置






DubboNamespaceHandler只有一个init()方法,从中观察到,标签注册的分别是ServiceBeanReferenceBean对象,而其他的元素注册的都是Config对象,表明被作者赋予高于普通配置的行为和地位(皆继承AbstractConfig

ServiceBean和ReferenceBean是Dubbo与Spring容器整合的核心方法

每一个elementName都是xml中的标签,有其相应属性,具有可配置的能力

package org.apache.dubbo.config.spring.schema;
class DubboNamespaceHandler extends NamespaceHandlerSupport {
    @Override
    public void init() {
        // 代码太长放不下,为了格式漂亮定义下变量
        // register=registerBeanDefinitionParser
        // BeanDefParser=DubboBeanDefinitionParser
        register("application", new BeanDefParser(ApplicationConfig.class, true));
        register("module", new BeanDefParser(ModuleConfig.class, true));
        register("registry", new BeanDefParser(RegistryConfig.class, true));
        register("config-center", new BeanDefParser(ConfigCenterBean.class, true));
        register("metadata-report", new BeanDefParser(MetadataReportConfig.class, true));
        register("monitor", new BeanDefParser(MonitorConfig.class, true));
        register("metrics", new BeanDefParser(MetricsConfig.class, true));
        register("provider", new BeanDefParser(ProviderConfig.class, true));
        register("consumer", new BeanDefParser(ConsumerConfig.class, true));
        register("protocol", new BeanDefParser(ProtocolConfig.class, true));
        // 名字是以Bean结尾的区别于上面的Config
        register("service", new BeanDefParser(ServiceBean.class, true));
        register("reference", new BeanDefParser(ReferenceBean.class, false));
        register("annotation", new BeanDefParser());
    }
}

ServiceBean

Dubbo要求标签由Provider端定义,下面开始介绍Provider的定义入口ServiceBean。其泛型由interface="org.apache.dubbo.demo.DemoService"字段指定。

package org.apache.dubbo.config.spring;
public class ServiceBean extends ServiceConfig
implements InitializingBean, DisposableBean, BeanNameAware,
      ApplicationContextAware, ApplicationListener,
      ApplicationEventPublisherAware 

ServiceBean实现了好多接口,看下各自的作用

接口 方法 作用
InitializingBean afterPropertiesSet() bean启动扩展
DisposableBean destroy() bean销毁扩展
ApplicationContextAware setApplicationContext(ApplicationContext) 注入spring环境
ApplicationEventPublisherAware setApplicationEventPublisher(Publisher) 注入事件发布
BeanNameAware setBeanName(String name) 注入当前bean的名字
ServiceConfig export(),doExportUrls() 生成Protocol
  1. ServiceBean实现了Spring的InitializingBean扩展,容器启动时会自动执行afterPropertiesSet()

  2. ServiceBean实现了Spring的DisposableBean扩展,Bean销毁时会自动执行destroy()

  3. ServiceBean实现了Spring的ApplicationContextAware扩展,应用启动完毕会自动注入ApplicationContext上下文,方便Dubbo从全局获取Bean对象

  4. ServiceBean实现了Spring的ApplicationEventPublisherAware扩展,具有发布事件的能力

  5. ServiceBean实现了Spring的BeanNameAware扩展,知道自己的BeanName

  6. ServiceBean继承了ServiceConfig,主要目的是根据Config生成dubbo协议

    dubbo://host/com.foo.FooService?route=route&cluster=&version=1.0.0

Spring容器启动自动调用afterPropertiesSet(),时序上先于onApplicationEvent()

ServiceBean.afterPropertiesSet():

afterPropertiesSet():代码很长分两部分说明

1.标签优先级设定:根据BeanDefinition标签设定优先级

2.判断当前Spring版本是否支持ApplicationListener(),否则延迟导出直到Spring容器启动完毕

// 标签优先级设定伪代码
if exist() {find(); setProviders(ProviderBean)}
if exist() { setApplication(ApplicationBean)}
if exist() {super.setRegistries(ModuleBean)}
if exist() {super.setProtocol(ProtocolBean)}
...
// 以上设置完毕判断spring是否支持事件监听,低版本兼容
if !supportedApplicationListener {export();}    

Spring容器启动完毕后监听ContextRefreshedEvent执行onApplicationEvent()

ServiceBean.onApplicationEvent():

onApplicationEvent():根据服务export发布状态,判断是否export()

    public void onApplicationEvent(ContextRefreshedEvent event) {
        if (!isExported() && !isUnexported()) {
            if (logger.isInfoEnabled()) {
                logger.info("The service ready on spring started. service: " + getInterface());
            }
            export();
        }
    }

ServiceBean.export():

export方法做了两件事

  1. 调用父类的export(),父类为ServiceConfig, ServiceBean#export()->ServiceConfig#export()
  2. 发布ExportEvent
    public void export() {
        // Step1
        super.export();
        // Step2
        publishExportEvent();
    }

super.export():

Step1:super.export() => ServiceConfig#export()

  1. 配置检查和更新事先由afterPropertiesSet()配置完毕
  2. 判断是否需要导出,不导出就是本地方法
  3. 若延迟导出则委托delayExecutor执行导出
  4. 否则调用 doExport()
package org.apache.dubbo.config;
super class ServiceConfig
public synchronized void export() {
        // 配置检查和更新 事先由afterPropertiesSet配置完毕
        checkAndUpdateSubConfigs();
        if (!shouldExport()) { // 标签属性export boolean
            return;
        }
        if (shouldDelay()) {// 标签属性delay boolean
            // 延时export
            delayExecutor.schedule(this::doExport, getDelay(), MILLISECONDS);
        } else {
            doExport();
        }
    }

super.doExportUrls():

doExport()在简单开关验证及设置path=interfaceName之后执行doExportUrls()

doExportUrls():

  1. 获取注册中心URL地址,由上面标签定义,此处已知配置为multicast

  2. 获取协议列表,一般只配置一个protocol,由标签定义

  3. 开始循环协议列表

    获取pathKeycom.kaiyuan.qyd.topic.dubbo.basic.DemoService

    创建ProviderModel实体对象并加入缓存,interfaceClass是接口类,ref是接口实现类

    执行核心逻辑doExportUrlsFor1Protocol()

package org.apache.dubbo.config;
super class ServiceConfig
private void doExportUrls() {
        // registry://host/org.xxx.registry.RegistryService?application=demo-provider®istry=multicast
        List registryURLs = loadRegistries(true);
        // 
        for (ProtocolConfig protocolConfig : protocols) {
            // pathKey = com.kaiyuan.qyd.topic.dubbo.basic.DemoService
            String pathKey = URL.buildKey(getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), group, version);
            // interfaceClass是接口类,ref是接口实现类
            ProviderModel providerModel = new ProviderModel(pathKey, ref, interfaceClass);
            // 放入缓存PROVIDED_SERVICES
            ApplicationModel.initProviderModel(pathKey, providerModel);
            // 核心代码
            doExportUrlsFor1Protocol(protocolConfig, registryURLs);
        }
    }

AbstractInterfaceConfig.loadRegistries():

这个方法来自抽象父类,主要逻辑为:

1.参数验证,代码中省略

2.根据BeanDefinition配置,获取parameters放到map

3.根据map中的参数生成注册中心URL地址,翻译如下:

registry://host/org.xxx.RegistryService?application=demo-provider®istry=multicast

   protected List loadRegistries(boolean provider) {
      List registryList = new ArrayList();
      // 省略参数验证
            if (!RegistryConfig.NO_AVAILABLE.equalsIgnoreCase(address)) {
             Map map = new HashMap();
         // 把中的parameter放到map中,比如name
             appendParameters(map, application);
         // 把中的parameter反射放到map中,比如host
             appendParameters(map, config);
             map.put(PATH_KEY, RegistryService.class.getName());
         // 追加一些运行时参数比如时间戳,版本号,pid
             appendRuntimeParameters(map);
             List urls = UrlUtils.parseURLs(address, map);
             for (URL url : urls) {
                 url = URLBuilder.from(url)
                         .addParameter(REGISTRY_KEY, url.getProtocol())
                         .setProtocol(REGISTRY_PROTOCOL)
                         .build();
             // registry://host/org.xxx.RegistryService?application=demo-provider®istry=multicast
                 if ((provider && url.getParameter(REGISTER_KEY, true))
                         || (!provider && url.getParameter(SUBSCRIBE_KEY, true))) {
                     registryList.add(url);
                 }
             }
            }
      return registryList;
    }

appendParameters()

看下参数追加的代码逻辑

1.获取当前BeanDefinition所配置的所有Getter方法,本例是applicationgetApplication()

2.反射获取getApplication()的注解修饰类,判断是否被@Parameter(excluded = true)修饰跳出设置,否则追加到parameters

    protected static void appendParameters(Map parameters, Object config, String prefix) {
        Method[] methods = config.getClass().getMethods();
        for (Method method : methods) {
            try {
                String name = method.getName();
                // 获取ApplicationConfig下所有的Getter方法:getApplication()
                if (MethodUtils.isGetter(method)) {//getName()
                    Parameter parameter = method.getAnnotation(Parameter.class);
                    if (method.getReturnType() == Object.class || parameter != null && parameter.excluded()) {
                        continue;
                    }
                } 
                // put parameters key:prefix.xxx ....
            } catch (Exception e) {
                throw new IllegalStateException(e.getMessage(), e);
            }
        }
    }

super.doExportUrlsFor1Protocol():

服务发布核心代码,比较长,不适合阅读,所以拆分成四块分析

1.根据BeanDefinition配置组装URL

    private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List registryURLs) {
        String name = protocolConfig.getName();
        if (StringUtils.isEmpty(name)) {
            name = DUBBO;
        }
        Map map = new HashMap();
        // 标记为Provider端
        map.put(SIDE_KEY, PROVIDER_SIDE);
        // 追加一些运行时参数比如时间戳,版本号,pid
        appendRuntimeParameters(map);
        // 把BeanDefinition中的parameter反射放到map中
        appendParameters(map, metrics);
        appendParameters(map, application);
        appendParameters(map, module);
        appendParameters(map, provider);
        appendParameters(map, protocolConfig);
        appendParameters(map, this);
        // 子标签配置
    }

此时的map是这样的:

{
    "side": "provider", // 标记为生产者
    "application": "demo-provider",
    "release": "2.7.2", // 从jar包截取的版本号
    "deprecated": "false",
    "dubbo": "2.0.2", // Dubbo RPC protocol version
    "pid": "48180",
    "dynamic": "true",
    "interface": "com.kaiyuan.qyd.topic.dubbo.basic.DemoService",
    "generic": "false", // 是否是泛化调用
    "timestamp": "1563865286121", // 时间戳
    "register": "true",
    "bean.name": "com.kaiyuan.qyd.topic.dubbo.basic.DemoService"
}

2.子标签配置,配置粒度可以到级别,跟踪这段代码需要补充配置标签:


    
        
    

细粒度的配置主要用来为泛化调用,区分参数类型,参数下标,正常业务很少对它配置

private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List registryURLs) {
      // 判断是否配置
            if (CollectionUtils.isNotEmpty(methods)) {
            for (MethodConfig method : methods) {
                // 追加timeout参数到map中,key:sayHello.timeout value:2000
                appendParameters(map, method, method.getName());
                // 判断是否设置sayHello.retry或重写retries
                String retryKey = method.getName() + ".retry";
                if (map.containsKey(retryKey)) {
                    String retryValue = map.remove(retryKey);
                    if ("false".equals(retryValue)) {
                        map.put(method.getName() + ".retries", "0");
                    }
                }
                List arguments = method.getArguments();
                // 是否配置了,列表判断是因为参数可能多参
                if (CollectionUtils.isNotEmpty(arguments)) {
                    // 省略的这部分代码主要是配置和上面的逻辑相同
                }
            } // end of methods for
        }
}

配置完 map变成这样:

{
    ... // 省略未变化参数
    "sayHello.timeout": "2000", // 方法的超时时间
    "sayHello.0.callback": "true" // 标记下标为0的参数是callback函数(举例而已)
}

3.判断是否是泛化调用,并追加一些参数到map中,生成URL对象

这里有个特殊的Wrapperjavassist处理后自动生成getMethodNames(),留在Invoke章节详细介绍

private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List registryURLs) {
      // 是否泛化调用,本例是用户业务接口,不是泛化调用接口
            if (ProtocolUtils.isGeneric(generic)) {
            map.put(GENERIC_KEY, generic);
            map.put(METHODS_KEY, ANY_VALUE);
        } else {
            String revision = Version.getVersion(interfaceClass, version);
            if (revision != null && revision.length() > 0) {
                map.put(REVISION_KEY, revision);
            }
                // 包装类,javassist处理之后自动生成getMethodNames(),Invoker章节详细介绍
            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(Arrays.asList(methods)), ","));
            }
        }
        // 是否配置token
        if (!ConfigUtils.isEmpty(token)) {
            if (ConfigUtils.isDefault(token)) {
                map.put(TOKEN_KEY, UUID.randomUUID().toString());
            } else {
                map.put(TOKEN_KEY, token);
            }
        }
            // 获取本地ip
        String host = this.findConfigedHosts(protocolConfig, registryURLs, map);
        // port:20880
        Integer port = this.findConfigedPorts(protocolConfig, name, map);
            // 由map对象转换生成url
        URL url = new URL(name, host, port, getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), map);
        // export service ...
}

urlmap对象转换而成:

dubbo://172.16.69.49:20880/com.kaiyuan.qyd.topic.dubbo.basic.DemoService?anyhost=true&application=demo-provider&bean.name=com.kaiyuan.qyd.topic.dubbo.basic.DemoService&bind.ip=172.16.69.49&bind.port=20880&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=com.kaiyuan.qyd.topic.dubbo.basic.DemoService&methods=sayHello&pid=46800®ister=true&release=2.7.2&sayHello.0.callback=true&sayHello.timeout=2000&side=provider×tamp=1563870489213

4.服务发布,涉及RPC模块的内容,将在第三章Invoker详细介绍,这里需要了解Dubbo的领域模型

  • 实体域:Invoker,是RPC调用的核心

  • 会话域:Invocation,一次RPC调用的上下文

  • 服务域:Protocol,实体域会话域生命周期管理,发布或引用入口

package org.apache.dubbo.rpc;
@SPI("dubbo")
// 服务域
public interface Protocol { 
    int getDefaultPort();
    @Adaptive
     Exporter export(Invoker invoker) throws RpcException;
    @Adaptive
     Invoker refer(Class type, URL url) throws RpcException;
}
// 实体域
public interface Invoker extends Node {
    Class getInterface();
    Result invoke(Invocation invocation) throws RpcException;
}
// 会话域
public interface Invocation {
    String getMethodName();
    Class[] getParameterTypes();
    Object[] getArguments();
    Map getAttachments();
    void setAttachment(String key, String value);
    void setAttachmentIfAbsent(String key, String value);
    String getAttachment(String key);
    String getAttachment(String key, String defaultValue);
    Invoker getInvoker();
}

服务发布逻辑:

1.判断协议是否被重新定义

2.本地服务发布:url重写,创建Invoker动态代理类,缓存Exporter等待export()

3.远程服务发布:和本地调用一样先生成Exporter后执行export(),开启远程端口,注册dubbo协议到注册中心等待调用

package org.apache.dubbo.config;

public class ServiceConfig extends AbstractServiceConfig {
        private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();

    private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List registryURLs) {
            // 判断dubbo协议是否被用户自定义,此处使用默认dubbo协议
            if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
                    .hasExtension(url.getProtocol())) {
                url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
                        .getExtension(url.getProtocol()).getConfigurator(url).configure(url);
            }
            // url中没有配置scope,此处为null
            String scope = url.getParameter(SCOPE_KEY);
            // don't export when none is configured
            if (!SCOPE_NONE.equalsIgnoreCase(scope)) {
                if (!SCOPE_REMOTE.equalsIgnoreCase(scope)) {
                    // 先从本地export
                    exportLocal(url);
                }
                // 如果配置了注册中心则进行远程export 
                // 1.createExporter(ref,interface,register)
                // 2.register("dubbo://xxx")
                // 3.openServer(url);
            }
    }
  
    private void exportLocal(URL url) {
        // url host port重写变成injvm://127.0.0.1,其他参数未变
        URL local = URLBuilder.from(url)
                .setProtocol(LOCAL_PROTOCOL)
                .setHost(LOCALHOST_VALUE)
                .setPort(0)
                .build();
        // 包装ref引用为Invoker接口对象,执行protocol.export()
        Exporter exporter = protocol.export(
                proxyFactory.getInvoker(ref, (Class) interfaceClass, local));
        // 加入缓存
        exporters.add(exporter);
    }
}

小结:方法export()概括的说做了两件事

  1. 配置URL,生成Protocol服务域。dubbo协议是register协议的一个export参数同步到注册中心

    registry://host/xxx.RegistryService?export=dubbo%3A%2F%2F172.16.69.49%3A20880%2F/xxx

  2. 生成远程代理类Invoker,执行Protocol.export(Invoker),注册服务,开启端口监听,等待Protocol.refer()


ServiceBean.export()方法Step2执行publishExportEvent(),发布一个事件,下面看下具体逻辑

Step2:publishExportEvent()

  1. 创建ServiceBeanExportedEvent事件,此对象结构比较简单,只有一个构造参数source
  2. source对象代表发生事件的主体,继承自Spring的ApplicationEvent,source=ServiceBean(this)
  3. 调用spring注入applicationEventPublisher发布事件等待监听器响应
public void export() {
   // Step1
   super.export();
   // Step2
   publishExportEvent();
}
  
private void publishExportEvent() {
   // 创建事件实体对象,source对象为ServiceBean
   ServiceBeanExportedEvent exportEvent = new ServiceBeanExportedEvent(this);
   // ApplicationEventPublisherAware方法注入的Publisher
   applicationEventPublisher.publishEvent(exportEvent);
}

ReferenceAnnotationBeanPostProcessor

ServiceBeanExportedEvent事件只有一个监听Listener即ReferenceAnnotationBeanPostProcessor,该类结构如下:

package org.apache.dubbo.config.spring.beans.factory.annotation;
public class ReferenceAnnotationBeanPostProcessor
    extends AnnotationInjectedBeanPostProcessor
    implements ApplicationContextAware, ApplicationListener
接口 方法 作用
ApplicationContextAware setApplicationContext() Spring上下文
ApplicationListener onApplicationEvent(E event) 监听逻辑
AnnotationInjectedBeanPostProcessor doGetInjectedBean(...) 注解依赖注入
  1. ApplicationListener,监听ServiceBeanExportedEvent,执行onApplicationEvent()
  2. AnnotationInjectedBeanPostProcessor,dubbo的依赖注入工具与spring注入对接

先看ApplicationListener唯一方法onApplicationEvent(),事件的执行者

onApplicationEvent()

  1. 判断instanceof ServiceBeanExportedEvent
  2. 执行onServiceBeanExportEvent(event)
    public void onApplicationEvent(ApplicationEvent event) {
        if (event instanceof ServiceBeanExportedEvent) {
            // 判断类型执行相应操作 内部直接调用了initReferenceBeanInvocationHandler方法
            onServiceBeanExportEvent((ServiceBeanExportedEvent) event);
        } else if (event instanceof ContextRefreshedEvent) {
            onContextRefreshedEvent((ContextRefreshedEvent) event);
        }
  }

onServiceBeanExportEvent()内部直接调用了initReferenceBeanInvocationHandler()

initReferenceBeanInvocationHandler()

  1. 获取cache key
  2. 从localReferenceBeanInvocationHandlerCache中移除了key对象
  3. 执行了init()
 private void initReferenceBeanInvocationHandler(ServiceBean serviceBean) {
        String beanName = serviceBean.getBeanName();
        // Remove ServiceBean when it's exported
        // 为了调整格式修改了变量名称lrbhc=localReferenceBeanHandlerCache
        ReferenceBeanInvocationHandler handler = lrbhc.remove(beanName);
        // Initialize
        if (handler != null) {
            handler.init();
        }
 }

问题:为什么要用缓存?

因为都是重量级对象,不能频繁创建,缓存和双重锁保证单例
dubbo依托spring生命周期做缓存证明了dubbo使用api启动的方式要考虑程序健壮性,需要自己做内存管理

问题: 为什么要从localReferenceBeanHandlerCache中移除对象?

带着这个问题继续分析,cache可以被remove说明cache里面有值,如果我们知道缓存存放的是什么值,以及存放的逻辑,那么也就知道问题所在了。

按照这个思路定位到方法doGetInjectedBean(),是来自父类AnnotationInjectedBeanPostProcessor的抽象方法,该方法有个关键的参数

 Object doGetInjectedBean(Reference reference, Object bean, String beanName,Class injectedType,InjectedElement injectedElement)

Reference reference: 这是我们@Reference 的注解类,通过它可以注入SpringBean,通常这么写

package com.kaiyuan.qyd.business.tx;
@Reference FooService fooService;

Dubbo依托Spring包扫描机制把@Service/@Reference注解修饰的对象注册/注入到Spring容器中

@Reference使用范围:可以用在FieldMethod上,说明是用来依赖注入

package org.apache.dubbo.config.annotation;
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.ANNOTATION_TYPE})
public @interface Reference {}

@Service的使用范围:只能用在Class上为了注册到spring容器

package org.apache.dubbo.config.annotation;
@Target({ElementType.TYPE})
public @interface Service {}

DubboComponentScanRegistrar:

上面说到Dubbo依托Spring包扫描机制把@Service/@Reference注解修饰的对象注册/注入到Spring容器中,下面简单介绍下是怎么实现的

父类ImportBeanDefinitionRegistrar是Spring接口,用来实现包扫描注册功能

  1. @DubboComponentScan包扫描获取路径
  2. 注册@Service注解修饰的对象到spring,会触发BeanDefinition拦截器自定义注入逻辑
  3. 注入@Reference注解修饰的对象到spring,最终会触发依赖注入doGetInjectedBean逻辑
package org.apache.dubbo.config.spring.context.annotation;
// 本节只做介绍后面会有spring和dubbo整合的源码分析
public class DubboComponentScanRegistrar implements ImportBeanDefinitionRegistrar {
@Override
void registerBeanDefinitions(AnnotationMetadata meta, BeanDefinitionRegistry registry) {
       // @DubboComponentScan包扫描
     Set packagesToScan = getPackagesToScan(meta);
     // 注册@Service注解修饰的对象到spring,使用了BeanDefinitionRegistry注册工厂
     // 核心逻辑在ServiceAnnotationBeanPostProcessor.registerServiceBean方法内部
     registerServiceAnnotationBeanPostProcessor(packagesToScan, registry);
     // 注入@Reference注解修饰的对象到spring,使用了BeanDefinitionRegistry注册工厂
     registerReferenceAnnotationBeanPostProcessor(registry);
 }
}

Spring所使用的注册工厂如下,registerBeanDefinition()执行注册逻辑时会触发Dubbo实现的BeanDefinitionRegistryPostProcessor子类ServiceAnnotationBeanPostProcessor实现自定义注册

package org.springframework.beans.factory.support;
public interface BeanDefinitionRegistry extends AliasRegistry {
    // spring注册方法
    void registerBeanDefinition(String beanName, BeanDefinition beanDefinition);

doGetInjectedBean
上面说到使用@Reference依赖注入最终会执行本方法,下面开始分析方法逻辑

doGetInjectedBean

  1. 获取referencedBeanName
  2. 包装业务接口成ReferenceBean
  3. 缓存ReferenceBean到BeanCache
  4. 创建代理类
 @Override
 Object doGetInjectedBean(Reference reference, Object bean, String beanName,Class injectedType,InjectedElement injectedElement){
     // 构建beanname
     String referencedBeanName = buildReferencedBeanName(reference, injectedType);
     // 包装业务接口成ReferenceBean
     ReferenceBean referenceBean = buildReferenceBeanIfAbsent(referencedBeanName,reference, injectedType, getClassLoader());
     cacheInjectedReferenceBean(referenceBean, injectedElement);
     // 创建代理类 
     return buildProxy(referencedBeanName, referenceBean, injectedType);
 }

动态代理在这里的作用是动态生成代理类,对原始方法进行控制,对使用者透明,本章只阐述下直观作用

未代理之前:
调用方->业务接口->业务接口实现类
动态代理后:
调用方->业务接口->动态代理类->业务接口实现类

动态代理类InvocationHandler.invoke()方法示例:

begin()
 try 
  action1
        srcMethod.invoke();// 原始方法调用
    action2
 catch e {}
 finally {}
end()

真实的代理比这个要复杂,本代理类其实就是远程调用的Invoker对象(包含五元组,协议,序列化等等),比如consumer端执行远程调用的同时也要生成代理类,provider端接收亦然,在后面章节Invoker和Proxy会详细介绍

buildProxy:

介绍完动态代理,回过头来看下代码逻辑

buildProxy目的是针对业务接口生成代理方法,对调用方无感知

Object buildProxy(String referencedBeanName, ReferenceBean referenceBean, Class injectedType) {
                // 生成动态代理handler,对业务接口进行控制代理
        InvocationHandler handler = buildInvocationHandler(referencedBeanName, referenceBean);
        // dubbo注解管理的bean对象都是动态代理类,会执行handler.invoke()
        return Proxy.newProxyInstance(getClassLoader(), new Class[]{injectedType}, handler);
}

问题的线索就在buildInvocationHandler()内部

缓存存放的值ReferenceBeanInvocationHandler,该对象是上面的动态代理类
缓存存放逻辑@Reference注入的是不是本地@Service注册的Spring对象

InvocationHandler buildInvocationHandler(String referencedBeanName, ReferenceBean referenceBean) {
        ReferenceBeanInvocationHandler handler = lcrih.get(referencedBeanName);
        if (handler == null) {
            handler = new ReferenceBeanInvocationHandler(referenceBean);
        }
        // 从这能看到缓存添加的是本地spring管理的bean对象,用referencedBeanName做区分
        if (applicationContext.containsBean(referencedBeanName)) { 
            // lcrih=localReferenceBeanInvocationHandlerCache
            lcrih.put(referencedBeanName, handler);
        } else {
            handler.init();
        }
        return handler;
}

ReferenceBeanInvocationHandler

ReferenceBeanInvocationHandler继承了InvocationHandler动态代理类,代理对象是业务定义的接口:


方法逻辑很简单,只是从缓存中获取管理的ReferenceBean调用执行,同时解决了一个单元测试的bug:

package org.apache.dubbo.config.spring.beans.factory.annotation;
public class ReferenceAnnotationBeanPostProcessor {}
// static内部类
private static class ReferenceBeanInvocationHandler implements InvocationHandler {
        private final ReferenceBean referenceBean;
        private Object bean;
        private ReferenceBeanInvocationHandler(ReferenceBean referenceBean) {
            this.referenceBean = referenceBean;
        }

        @Override // 动态代理方法执行入口
        public Object invoke(Object proxy, Method method, Object[] args) {
            Object result;
            try {
                if (bean == null) { 
                    // issue: https://github.com/apache/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() {
            this.bean = referenceBean.get();
        }
    }
}

小结

  1. ServiceBean执行export方法时发布ServiceBeanExportedEvent事件
  2. ReferenceAnnotationBeanPostProcessor监听到该事件,从本地localReferenceBeanInvocationHandlerCache缓存中移除bean对象
  3. 缓存中的数据是依托DubboComponentScan包扫描机制根据@Service注解注入到spring容器中,同时对@Reference进行依赖注入
  4. 如果ReferenceBean所代理的bean其实是本地spring容器管理的,也就是本地调用,则存放到localReferenceBeanInvocationHandlerCache
  5. 因为有延迟导出的存在,所以采用事件通知机制及时更新本地缓存,动态代理生成InvocationHandler对象
  6. localReferenceBeanInvocationHandlerCache移除的只是本地代理对象,真正的远程代理类都在referenceBeanCache里面保存着

服务引用

服务发布完成后,生成RPC代理,同步Invoker信息到注册中心,等待Consumer端引用,配置如下:




ReferenceBean

Dubbo要求标签由Consumer端定义,下面开始介绍Consumer的定义入口ReferenceBean

public class ReferenceBean extends ReferenceConfig 
    implements FactoryBean, ApplicationContextAware, 
                         InitializingBean, DisposableBean {}
接口 方法 作用
FactoryBean getObject() 获取spring对象
ApplicationContextAware setApplicationContext(ApplicationContext) 注入spring环境
InitializingBean afterPropertiesSet() bean启动扩展
DisposableBean destroy() bean销毁扩展

这个类实现的接口与ServiceBean类似,相同的地方无需赘述,区别在于FactoryBean.getObject()

Spring容器启动自动调用afterPropertiesSet(),进行一系列的BeanDefinition设置之后执行FactoryBean.getObject()

package org.apache.dubbo.config.spring;

public class ReferenceBean extends ReferenceConfig
implements FactoryBean, ApplicationContextAware,
                     InitializingBean, DisposableBean {}

public void afterPropertiesSet() throws Exception {
    // 执行一系列BeanDefinition配置
    // setConsumer()
    // setApplication()
    // setRegistries()
    if (shouldInit()) {// 是否延迟加载
        getObject();
    }
}

public Object getObject() {
    // 父类ReferenceConfig中的方法
    return get();
}

ReferenceConfig.get():

同步方法get()和非空判断保证了单例,内部逻辑先是做了内部的验证和默认配置重写,之后执行ReferenceConfig.init()

package org.apache.dubbo.config;
public class ReferenceConfig {
  
  public synchronized T get() {
      // 内部验证比如注册中心,还有一些默认参数的重写
      checkAndUpdateSubConfigs();

      if (destroyed) {
          throw new IllegalStateException("The invoker has already destroyed!");
      }
      if (ref == null) {
          // 单例初始化
          init();
      }
      return ref;
  }
}

ReferenceConfig.init():

核心是从注册中心获取元数据创建代理类,细节会在Invoker里面详细介绍

1.例行参数检查

2.map对象参数设置

2.创建远程代理类

private void init() {
    if (initialized) {
        return;
    }
    // 例行检查
    checkStubAndLocal(interfaceClass);
    checkMock(interfaceClass);
    Map map = new HashMap();
    map.put(SIDE_KEY, CONSUMER_SIDE);
    appendRuntimeParameters(map);
    // 省略map参数准备...
    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:");
    }
    map.put(REGISTER_IP_KEY, hostToRegistry);
        // 从注册中心获取元数据创建远程调用代理类
    ref = createProxy(map);
    String serviceKey = URL.buildKey(interfaceName, group, version);
    // 创建ConsumerModel并放到ApplicationModel缓存里
    ApplicationModel.initConsumerModel(serviceKey, buildConsumerModel(serviceKey, attributes));
    initialized = true;
}

小结:

ServiceConfigReferenceConfig最终都指向了Invoker,也说明了实体域Invoker的重要性,在这里对两个重要方法做个标记,也是后面第三章的引子。

1.服务发布创建远程代理类:ServiceConfig.doExportUrlsFor1Protocol()

2.服务导出引用远程代理类:ReferenceConfig.init()

你可能感兴趣的:(【Dubbo】2.服务发布和引用)