2. Dubbo源码解析之服务暴露过程

Dubbo 服务暴露过程中,主要有Dubbo的xml标签解析器、装配ServiceBean、服务暴露、订阅与通知、元数据注册等步骤,其中服务暴露又分为本地暴露和远程暴露,远程暴露中主要有将invoker转换为exporter、启动netty、注册zookeeper等,下面进行详细解析。

2.1 Dubbo自定义xml解析器

Dubbo定义了DubboNamespaceHandler、DubboBeanDefinitionParser 来处理xml的BeanDefinition,并将BeanDefinition转换成相应的对象。

2.1.1 DubboNamespaceHandler 添加过程

当Spring Boot启动时,会通过 @ImportResource("classpath:spring/applicationContext.xml") 来加载xml配置文件,dubbo的xml配置文件就是通过 applicationContext.xml 文件引入的,然后通过Spring 的loadBeanDefinitions(beanFactory) 来获取Xml的空间命名处理器,具体代码如下:

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
            throws BeanDefinitionStoreException {
    try {
        Document doc = doLoadDocument(inputSource, resource);
        return registerBeanDefinitions(doc, resource);
    }
    ...
}
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
    BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
    int countBefore = getRegistry().getBeanDefinitionCount();
    documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
    return getRegistry().getBeanDefinitionCount() - countBefore;
}
public XmlReaderContext createReaderContext(Resource resource) {
   return new XmlReaderContext(resource, this.problemReporter, this.eventListener,
         this.sourceExtractor, this, getNamespaceHandlerResolver());
}

代码中,将xml转换成为到Document,创建 XmlReaderContext,通过 getNamespaceHandlerResolver -> createDefaultNamespaceHandlerResolver -> getHandlerMappings,获取命名空间处理器的。具体的代码如下:

private Map getHandlerMappings() {
    if (this.handlerMappings == null) {
        synchronized (this) {
            if (this.handlerMappings == null) {
                try {
                    Properties mappings = PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);

                    Map handlerMappings = new ConcurrentHashMap(mappings.size());
                    CollectionUtils.mergePropertiesIntoMap(mappings, handlerMappings);
                    this.handlerMappings = handlerMappings;
                }
                ...
            }
        }
    }
    return this.handlerMappings;
}

注:Double-Check的方式避免非线程安全问题。这里的getHanndlerMappings() 在debug时,是通过IDEA自动启动的线程执行的,因为IDEA在Debug时,会自动重启线程执行类的 toString() 方法,而这里的 toString() 方法如下:

public String toString() {
   return "NamespaceHandlerResolver using mappings " + getHandlerMappings();
}

不在Debug状态时,是在对元素解析时,进行获取的,parseCustomElement -> resolve,代码如下:

public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
    String namespaceUri = getNamespaceURI(ele);
    NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
    ...
    return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}
public NamespaceHandler resolve(String namespaceUri) {
   Map handlerMappings = getHandlerMappings();
   Object handlerOrClassName = handlerMappings.get(namespaceUri);
   ...
       NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
       namespaceHandler.init();
       handlerMappings.put(namespaceUri, namespaceHandler);
    
      ...
   }
}

2.1.2 DubboBeanDefinitionParser 添加过程

从上面代码中可以看出,resolve -> init ,对命名空间处理器进行了初始化,初始化时注册了标签的解析器,代码如下:

public void init() {
    registerBeanDefinitionParser("application", 
            new DubboBeanDefinitionParser(ApplicationConfig.class, true));
    registerBeanDefinitionParser("module", 
            new DubboBeanDefinitionParser(ModuleConfig.class, true));
    registerBeanDefinitionParser("registry", 
            new DubboBeanDefinitionParser(RegistryConfig.class, true));
    registerBeanDefinitionParser("config-center", 
            new DubboBeanDefinitionParser(ConfigCenterBean.class, true));
    registerBeanDefinitionParser("metadata-report", 
            new DubboBeanDefinitionParser(MetadataReportConfig.class, true));
    registerBeanDefinitionParser("monitor", 
            new DubboBeanDefinitionParser(MonitorConfig.class, true));
    registerBeanDefinitionParser("provider", 
            new DubboBeanDefinitionParser(ProviderConfig.class, true));
    registerBeanDefinitionParser("consumer", 
            new DubboBeanDefinitionParser(ConsumerConfig.class, true));
    registerBeanDefinitionParser("protocol", 
            new DubboBeanDefinitionParser(ProtocolConfig.class, true));
    registerBeanDefinitionParser("service", 
            new DubboBeanDefinitionParser(ServiceBean.class, true));
    registerBeanDefinitionParser("reference", 
            new DubboBeanDefinitionParser(ReferenceBean.class, false));
    registerBeanDefinitionParser("annotation", new AnnotationBeanDefinitionParser());
}

2.1.3 DubboBeanDefinitionParser 解析过程

添加完 DubboBeanDefinitionParser 解析器后,就是对标签的解析过程:

public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
   ...
   return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}

2.2 装配 ServiceBean

完成了对xml标签的解析后,Spring会通过finishBeanFactoryInitialization(beanFactory); 对ServiceBean进行初始化,具体调用步骤如下:

AbstractApplicationContext#finishBeanFactoryInitialization
    |- AbstractBeanFactory#createBean
        |- AbstractAutowireCapableBeanFactory#doCreateBean
            |- AbstractAutowireCapableBeanFactory#createBeanInstance
                |- AbstractAutowireCapableBeanFactory#instantiateBean
                    |- SimpleInstantiationStrategy#instantiate
                        |- BeanUtils.instantiateClass(constructorToUse);

从上面调用步骤可以看出,最后使用JDK的方式初始化ServiceBean的。

2.2.1 初始化静态成员

看看ServiceBean的静态成员变量都有哪些?

private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();

private static final ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();

根据初始化的顺序,会先对静态成员变量初始化,然后才调用构造函数。根据SPI的原理,初始化这两个类时的都加载了哪些类?

META-INF/dubbo/internal/org.apache.dubbo.rpc.ProxyFactory:

stub=org.apache.dubbo.rpc.proxy.wrapper.StubProxyFactoryWrapper
jdk=org.apache.dubbo.rpc.proxy.jdk.JdkProxyFactory
javassist=org.apache.dubbo.rpc.proxy.javassist.JavassistProxyFactory
META-INF/dubbo/internal/org.apache.dubbo.rpc.Protocol:

filter=org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper
listener=org.apache.dubbo.rpc.protocol.ProtocolListenerWrapper
mock=org.apache.dubbo.rpc.support.MockProtocol
dubbo=org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol
injvm=org.apache.dubbo.rpc.protocol.injvm.InjvmProtocol
rmi=org.apache.dubbo.rpc.protocol.rmi.RmiProtocol
hessian=org.apache.dubbo.rpc.protocol.hessian.HessianProtocol
http=org.apache.dubbo.rpc.protocol.http.HttpProtocol

org.apache.dubbo.rpc.protocol.webservice.WebServiceProtocol
thrift=org.apache.dubbo.rpc.protocol.thrift.ThriftProtocol
memcached=org.apache.dubbo.rpc.protocol.memcached.MemcachedProtocol
redis=org.apache.dubbo.rpc.protocol.redis.RedisProtocol
rest=org.apache.dubbo.rpc.protocol.rest.RestProtocol
registry=org.apache.dubbo.registry.integration.RegistryProtocol
qos=org.apache.dubbo.qos.protocol.QosProtocolWrapper

没有@Adaptive注解,所以会动态创建类,动态类的字节码如下:

package org.apache.dubbo.rpc;
import org.apache.dubbo.common.extension.ExtensionLoader;

public class Protocol$Adaptive implements org.apache.dubbo.rpc.Protocol {
    public void destroy() {
        throw new UnsupportedOperationException("...");
    }

    public int getDefaultPort() {
        throw new UnsupportedOperationException("...");
    }

    public org.apache.dubbo.rpc.Exporter export(org.apache.dubbo.rpc.Invoker arg0) throws org.apache.dubbo.rpc.RpcException {
        ...
        org.apache.dubbo.common.URL url = arg0.getUrl();
        String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
        ...
        org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
        return extension.export(arg0);
    }

    public org.apache.dubbo.rpc.Invoker refer(java.lang.Class arg0, org.apache.dubbo.common.URL arg1) throws org.apache.dubbo.rpc.RpcException {
        if (arg1 == null) throw new IllegalArgumentException("url == null");
        org.apache.dubbo.common.URL url = arg1;
        String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
        ...
        org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
        return extension.refer(arg0, arg1);
    }
}
package org.apache.dubbo.rpc;
import org.apache.dubbo.common.extension.ExtensionLoader;
public class ProxyFactory$Adaptive implements org.apache.dubbo.rpc.ProxyFactory {
    
    public java.lang.Object getProxy(org.apache.dubbo.rpc.Invoker arg0) throws org.apache.dubbo.rpc.RpcException {
        ...
        org.apache.dubbo.common.URL url = arg0.getUrl();
        String extName = url.getParameter("proxy", "javassist");
        
        org.apache.dubbo.rpc.ProxyFactory extension = (org.apache.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension(extName);
        return extension.getProxy(arg0);
    }
    
    public java.lang.Object getProxy(org.apache.dubbo.rpc.Invoker arg0, boolean arg1) throws org.apache.dubbo.rpc.RpcException {
        ...
        org.apache.dubbo.common.URL url = arg0.getUrl();
        String extName = url.getParameter("proxy", "javassist");
        
        org.apache.dubbo.rpc.ProxyFactory extension = (org.apache.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension(extName);
        return extension.getProxy(arg0, arg1);
    }
    
    public org.apache.dubbo.rpc.Invoker getInvoker(java.lang.Object arg0, java.lang.Class arg1, org.apache.dubbo.common.URL arg2) throws org.apache.dubbo.rpc.RpcException {
        
        org.apache.dubbo.common.URL url = arg2;
        String extName = url.getParameter("proxy", "javassist");
        
        org.apache.dubbo.rpc.ProxyFactory extension = (org.apache.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension(extName);
        return extension.getInvoker(arg0, arg1, arg2);
    }
}

2.2.2 构造函数初始化

public ServiceBean() {
    super();
    this.service = null;
}

可以看到没有做其他操作。

2.2.3 ApplicationContext 与 afterPropertiesSet 初始化

构造函数初始化后,会调用初始化函数,具体调用入口如下:

AbstractAutowireCapableBeanFactory#initializeBean:

protected Object initializeBean(final String beanName, final Object bean, RootBeanDefinition mbd) {
        
    Object wrappedBean = bean;
    if (mbd == null || !mbd.isSynthetic()) {
        wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
    }

    try {
        invokeInitMethods(beanName, wrappedBean, mbd);
    }

    if (mbd == null || !mbd.isSynthetic()) {
        wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
    }
    return wrappedBean;
}

从上面可以看到,调用顺序为:

applyBeanPostProcessorsBeforeInitialization
    |- invokeInitMethods
        |- ((InitializingBean) bean).afterPropertiesSet();
            |- invokeCustomInitMethod(beanName, bean, mbd);
                |- applyBeanPostProcessorsAfterInitialization

其中,applyBeanPostProcessorsBeforeInitialization 中又做了哪些初始化呢?

private void invokeAwareInterfaces(Object bean) {
    if (bean instanceof Aware) {
        if (bean instanceof EnvironmentAware) {
            bean.setEnvironment(this.applicationContext.getEnvironment());
        }
        if (bean instanceof EmbeddedValueResolverAware) {
            bean.setEmbeddedValueResolver(this.embeddedValueResolver);
        }
        if (bean instanceof ResourceLoaderAware) {
            ((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext);
        }
        if (bean instanceof ApplicationEventPublisherAware) {
            bean.setApplicationEventPublisher(this.applicationContext);
        }
        if (bean instanceof MessageSourceAware) {
            ((MessageSourceAware) bean).setMessageSource(this.applicationContext);
        }
        if (bean instanceof ApplicationContextAware) {
            bean.setApplicationContext(this.applicationContext);
        }
    }
}

发现调用了 setApplicationContext ,ServiceBean的这个函数又做了什么呢?

public void setApplicationContext(ApplicationContext applicationContext) {
    this.applicationContext = applicationContext;
    SpringExtensionFactory.addApplicationContext(applicationContext);
    supportedApplicationListener = addApplicationListener(applicationContext, this);
}

做了两件事,其一,将applicationContext设置到SpringExtensionFactory中,用于后续从SpringExtensionFactory中获取Bean,其二,调用方法addApplicationListener,之后将当前类(因为当前类监听了ContextRefreshedEvent事件)加入spring的监听器列表。

然后调用 ServiceBean 的 afterPropertiesSet, 初始化变量,判断延迟的事件是否大于0,如果是,执行export(),进行服务暴露,如果不是,结束(这种情况下服务暴露,会发生在发布上下文刷新事件的时候)

2.2.4 Spring上下文刷新时事件监听

public void onApplicationEvent(ContextRefreshedEvent event) {
    if (!isExported() && !isUnexported()) {
        ...
        export();
    }
}

当服务没有暴露,并且没有反注册时,进行服务暴露。

2.3 服务暴露

服务暴露分为本地暴露和远程暴露,整个暴露的调用顺序如下:

ServiceBean#onApplicationEvent
    |- ServiceConfig#export
        |- ServiceConfig#checkAndUpdateSubConfigs
        |- ServiceConfig#doExport
            |- ServiceConfig#doExportUrls
                |- AbstractInterfaceConfig#loadRegistries
                |- ServiceConfig#doExportUrlsFor1Protocol
                    |- ServiceConfig#exportLocal
                            |- proxyFactory.getInvoker
                            |- protocol#export
                    |- proxyFactory.getInvoker
                    |- protocol#export

在本地暴露和远程暴露之前,会checkAndUpdateSubConfigs 检查和更新一下ServiceBean的配置,然后loadRegistries 获取要暴露的服务注册信息,即拼接一下URL,只有在远程暴露时才用到。另外,还做一些初始化工作,ProviderModelApplicationModel,如下:

private void doExportUrls() {
    List registryURLs = loadRegistries(true);
    for (ProtocolConfig protocolConfig : protocols) {
        String pathKey = URL.buildKey(getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), group, version);
        ProviderModel providerModel = new ProviderModel(pathKey, ref, interfaceClass);
        ApplicationModel.initProviderModel(pathKey, providerModel);
        doExportUrlsFor1Protocol(protocolConfig, registryURLs);
    }
}

最终的暴露对象都存放在ServiceConfig中,如下:

private final List> exporters = new ArrayList>();

另外,在服务暴露中比较重要角色之一协议(Protocol),实现的功能如下:

@SPI("dubbo")
public interface Protocol {

    /**
     * 获取缺省端口,当用户没有配置端口时使用。
     * 
     * @return 缺省端口
     */
    int getDefaultPort();

    /**
     * 暴露远程服务:
* 1. 协议在接收请求时,应记录请求来源方地址信息:RpcContext.getContext(). * setRemoteAddress();
* 2. export()必须是幂等的,也就是暴露同一个URL的Invoker两次,和暴露一次没有区别。
* 3. export()传入的Invoker由框架实现并传入,协议不需要关心。
* * @param 服务的类型 * @param invoker 服务的执行体 * @return exporter 暴露服务的引用,用于取消暴露 * @throws RpcException 当暴露服务出错时抛出,比如端口已占用 */ @Adaptive Exporter export(Invoker invoker) throws RpcException; /** * 引用远程服务:
* 1. 当用户调用refer()所返回的Invoker对象的invoke()方法时,协议需相应执行同URL远端 * export()传入的Invoker对象的invoke()方法。
* 2. refer()返回的Invoker由协议实现,协议通常需要在此Invoker中发送远程请求。
* 3. 当url中有设置check=false时,连接失败不能抛出异常,并内部自动恢复。
* * @param 服务的类型 * @param type 服务的类型 * @param url 远程服务的URL地址 * @return invoker 服务的本地代理 * @throws RpcException 当连接服务提供方失败时抛出 */ @Adaptive Invoker refer(Class type, URL url) throws RpcException; /** * 释放协议:
* 1. 取消该协议所有已经暴露和引用的服务。
* 2. 释放协议所占用的所有资源,比如连接和端口。
* 3. 协议在释放后,依然能暴露和引用新的服务。
*/ void destroy(); }

ServiceBean都是对外提供服务,所以用的都是export,而ReferenceBean是调用服务,所以都是用refer。

2.3.1 拼凑URL

拼接URL分为两部分,一部分为远程暴露用的,一部分是为本地暴露用的。

  • 远程暴露URL拼接

    拼接的URL主要包括 address、application、registry-config、path、protocol等信息

    protected List loadRegistries(boolean provider) {
        // check && override if necessary
        List registryList = new ArrayList();
        if (CollectionUtils.isNotEmpty(registries)) {
            for (RegistryConfig config : registries) {
                String address = config.getAddress();
                if (StringUtils.isEmpty(address)) {
                    address = Constants.ANYHOST_VALUE;
                }
                if (!RegistryConfig.NO_AVAILABLE.equalsIgnoreCase(address)) {
                    Map map = new HashMap();
                    appendParameters(map, application);
                    appendParameters(map, config);
                    map.put(Constants.PATH_KEY, RegistryService.class.getName());
                    appendRuntimeParameters(map);
                    if (!map.containsKey(Constants.PROTOCOL_KEY)) {
                        map.put(Constants.PROTOCOL_KEY, Constants.DUBBO_PROTOCOL);
                    }
                    List urls = UrlUtils.parseURLs(address, map);
    
                    for (URL url : urls) {
                        url = URLBuilder.from(url)
                            .addParameter(Constants.REGISTRY_KEY, url.getProtocol())
                            .setProtocol(Constants.REGISTRY_PROTOCOL)
                            .build();
                        if ((provider && url.getParameter(Constants.REGISTER_KEY, true))
                            || (!provider && url.getParameter(Constants.SUBSCRIBE_KEY, true))) {
                            registryList.add(url);
                        }
                    }
                }
            }
        }
        return registryList;
    }
    

    最终得到的url串如下:

    dubbo://10.216.128.81:20880/com.gmr.dubbo.remote.facade.DemoFacade?anyhost=true&application=dubbo-demo-provider&bean.name=com.gmr.dubbo.remote.facade.DemoFacade&bind.ip=10.216.128.81&bind.port=20880&default.deprecated=false&default.dynamic=true&default.group=test&default.register=true&default.version=1.0&deprecated=false&dubbo=2.0.2&dynamic=true&environment=test&generic=false&interface=com.gmr.dubbo.remote.facade.DemoFacade&logger=slf4j&methods=sayHello&organization=decision&owner=hzguomeiran&pid=25468®ister=true&release=2.7.1&side=provider×tamp=1559268057411
    
  • 本地暴露URL拼接

    代码中可以看到拼接的URL 主要包括side、application、module、provider、protocol、method、revision、token、host、port 等信息。代码如下:

    private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List registryURLs) {
    
        Map map = new HashMap();
        map.put(Constants.SIDE_KEY, Constants.PROVIDER_SIDE);
        appendRuntimeParameters(map);
        appendParameters(map, application);
        appendParameters(map, module);
        appendParameters(map, provider, Constants.DEFAULT_KEY);
        appendParameters(map, protocolConfig);
        appendParameters(map, this);
        if (CollectionUtils.isNotEmpty(methods)) {
            for (MethodConfig method : methods) {
                ...
            } // end of methods for
        }
    
        if (ProtocolUtils.isGeneric(generic)) {
            map.put(Constants.GENERIC_KEY, generic);
            map.put(Constants.METHODS_KEY, Constants.ANY_VALUE);
        } else {
            String revision = Version.getVersion(interfaceClass, version);
            if (revision != null && revision.length() > 0) {
                map.put("revision", revision);
            }
            ...
        }
        if (!ConfigUtils.isEmpty(token)) {
            if (ConfigUtils.isDefault(token)) {
                map.put(Constants.TOKEN_KEY, UUID.randomUUID().toString());
            } else {
                map.put(Constants.TOKEN_KEY, token);
            }
        }
        // export service
        String host = this.findConfigedHosts(protocolConfig, registryURLs, map);
        Integer port = this.findConfigedPorts(protocolConfig, name, map);
        URL url = new URL(name, host, port, getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), map);
    
        if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
            .hasExtension(url.getProtocol())) {
            url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
                .getExtension(url.getProtocol()).getConfigurator(url).configure(url);
        }
    
        String scope = url.getParameter(Constants.SCOPE_KEY);
        // don't export when none is configured
        if (!Constants.SCOPE_NONE.equalsIgnoreCase(scope)) {
    
            // export to local if the config is not remote (export to remote only when config is remote)
            if (!Constants.SCOPE_REMOTE.equalsIgnoreCase(scope)) {
                exportLocal(url);
            }
            ...
        }
        ...
    }
    

    最终得到url串如下:

    injvm://127.0.0.1/com.gmr.dubbo.remote.facade.DemoFacade?anyhost=true&application=dubbo-demo-provider&bean.name=com.gmr.dubbo.remote.facade.DemoFacade&bind.ip=10.216.128.81&bind.port=20880&default.deprecated=false&default.dynamic=true&default.group=test&default.register=true&default.version=1.0&deprecated=false&dubbo=2.0.2&dynamic=true&environment=test&generic=false&interface=com.gmr.dubbo.remote.facade.DemoFacade&logger=slf4j&methods=sayHello&organization=decision&owner=hzguomeiran&pid=25468®ister=true&release=2.7.1&side=provider×tamp=1559268057411
    

2.3.2 本地暴露

private void exportLocal(URL url) {
    if (!Constants.LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {
        URL local = URLBuilder.from(url)
            .setProtocol(Constants.LOCAL_PROTOCOL)
            .setHost(LOCALHOST_VALUE)
            .setPort(0)
            .build();
        Exporter exporter = protocol.export(
            proxyFactory.getInvoker(ref, (Class) interfaceClass, local));
        exporters.add(exporter);
    }
}

从代码中,可以看到先通过代理工厂,ProxyFactory获取Invoker,这里的ProxyFactory是通过动态代理生成的对象ProxyFactory$Adaptive,看看getInvoker具体做了什么?

public org.apache.dubbo.rpc.Invoker getInvoker(java.lang.Object arg0, java.lang.Class arg1, org.apache.dubbo.common.URL arg2) throws org.apache.dubbo.rpc.RpcException {
        
    org.apache.dubbo.common.URL url = arg2;
    String extName = url.getParameter("proxy", "javassist");

    org.apache.dubbo.rpc.ProxyFactory extension = (org.apache.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension(extName);
    return extension.getInvoker(arg0, arg1, arg2);
}

可以看到通过spi获取了javassist的代理工厂,JavassistProxyFactory,getInvoker 方法新建了一个Invoker对象,代码如下:

public  Invoker getInvoker(T proxy, Class type, URL url) {
    // TODO Wrapper cannot handle this scenario correctly: the classname contains '$'
    final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type);
    return new AbstractProxyInvoker(proxy, type, url) {
        @Override
        protected Object doInvoke(T proxy, String methodName,
                                  Class[] parameterTypes,
                                  Object[] arguments) throws Throwable {
            return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
        }
    };
}

下面看看protocol.export 做了哪些工作:

public org.apache.dubbo.rpc.Exporter export(org.apache.dubbo.rpc.Invoker arg0) throws org.apache.dubbo.rpc.RpcException {
    ...
    org.apache.dubbo.common.URL url = arg0.getUrl();
    String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
    ...
        org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
    return extension.export(arg0);
}

[图片上传失败...(image-727c73-1614659196687)]

从图中,可以看到获取的protocol是InjvmProtocol,创建了Exporter对象,具体方法是:

public  Exporter export(Invoker invoker) throws RpcException {
    return new InjvmExporter(invoker, invoker.getUrl().getServiceKey(), exporterMap);
}

最后将InjvmExporter添加到 exporters 中。

2.3.3 远程暴露

远程和本地暴露基本相同,具体操作如下:

Invoker invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, 
        registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
DelegateProviderMetaDataInvoker wrapperInvoker = 
        new DelegateProviderMetaDataInvoker(invoker, this);
Exporter exporter = protocol.export(wrapperInvoker);
  • RegistryProtocol

    用DelegateProviderMetaDataInvoker将invoker进行了封装。除此之外,用到的protocol不同,是RegistryProtocol,产生的暴露器类型不同,如下图:

    [图片上传失败...(image-158efa-1614659196687)]

    具体代码如下:

    public  Exporter export(final Invoker originInvoker) throws RpcException {
        URL registryUrl = getRegistryUrl(originInvoker);
        // url to export locally
        URL providerUrl = getProviderUrl(originInvoker);
    
        final URL overrideSubscribeUrl = getSubscribedOverrideUrl(providerUrl);
        final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker);
        overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);
    
        providerUrl = overrideUrlWithConfig(providerUrl, overrideSubscribeListener);
        //服务暴露
        final ExporterChangeableWrapper exporter = doLocalExport(originInvoker, providerUrl);
        ...
    }
    
    private  ExporterChangeableWrapper doLocalExport(final Invoker originInvoker, URL providerUrl) {
        String key = getCacheKey(originInvoker);
    
        return (ExporterChangeableWrapper) bounds.computeIfAbsent(key, s -> {
            Invoker invokerDelegete = new InvokerDelegate<>(originInvoker, providerUrl);
            return new ExporterChangeableWrapper<>((Exporter) protocol.export(invokerDelegete), originInvoker);
        });
    }
    
  • DubboProtocol

    这里先从缓存中看有没有注册过,如果没有再调用protocol.export(invokerDelegete) 进行服务暴露,这里用的暴露协议是DubboProtocol,DubboProtocol协议在服务暴露时又做了哪些工作呢?

    public  Exporter export(Invoker invoker) throws RpcException {
        URL url = invoker.getUrl();
    
        // export service.
        String key = serviceKey(url);
        DubboExporter exporter = new DubboExporter(invoker, key, exporterMap);
        exporterMap.put(key, exporter);
      // 启动服务
        openServer(url);
        optimizeSerialization(url);
    
        return exporter;
    }
    
    DubboProtocol#openServer
      |- DubboProtocol#createServer
          |- Exchangers.bind(url, requestHandler)
              |- Exchangers#getExchanger
              |- HeaderExchanger#bind
                  |- Transporters#bind
                      |- NettyServer#doOpen
    

    Transporters 根据SPI获取默认Transporter,为NettyTransporter,bind实现的功能是创建一个NettyServer,具体创建过程如下:

    protected void doOpen() throws Throwable {
        bootstrap = new ServerBootstrap();
        bossGroup = 
            new NioEventLoopGroup(1, new DefaultThreadFactory("NettyServerBoss", true));
        workerGroup = 
            new NioEventLoopGroup(getUrl().getPositiveParameter(Constants.IO_THREADS_KEY, Constants.DEFAULT_IO_THREADS), new DefaultThreadFactory("NettyServerWorker", true));
    
        final NettyServerHandler nettyServerHandler = new NettyServerHandler(getUrl(), this);
        channels = nettyServerHandler.getChannels();
    
        bootstrap.group(bossGroup, workerGroup)
            .channel(NioServerSocketChannel.class)
            .childOption(ChannelOption.TCP_NODELAY, Boolean.TRUE)
            .childOption(ChannelOption.SO_REUSEADDR, Boolean.TRUE)
            .childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
            .childHandler(new ChannelInitializer() {
                @Override
                protected void initChannel(NioSocketChannel ch) throws Exception {
                    // FIXME: should we use getTimeout()?
                    int idleTimeout = UrlUtils.getIdleTimeout(getUrl());
                    NettyCodecAdapter adapter = 
                        new NettyCodecAdapter(getCodec(), getUrl(), NettyServer.this);
                    ch.pipeline()
                        .addLast("decoder", adapter.getDecoder())
                        .addLast("encoder", adapter.getEncoder())
                        .addLast("server-idle-handler", 
                                 new IdleStateHandler(0, 0, idleTimeout, MILLISECONDS))
                        .addLast("handler", nettyServerHandler);
                }
            });
        // bind
        ChannelFuture channelFuture = bootstrap.bind(getBindAddress());
        channelFuture.syncUninterruptibly();
        channel = channelFuture.channel();
    
    }
    

    这里启动一个nettyServer,是一个主从多线程模型。

  • 注册到zk

    public  Exporter export(final Invoker originInvoker) throws RpcException {
      ...
        // url to registry
        final Registry registry = getRegistry(originInvoker);
        final URL registeredProviderUrl = 
            getRegisteredProviderUrl(providerUrl, registryUrl);
        ProviderInvokerWrapper providerInvokerWrapper = 
            ProviderConsumerRegTable.registerProvider(originInvoker,
                                     registryUrl, registeredProviderUrl);
        //to judge if we need to delay publish
        boolean register = registeredProviderUrl.getParameter("register", true);
        if (register) {
            register(registryUrl, registeredProviderUrl);
            providerInvokerWrapper.setReg(true);
        }
        ...
    }
    

    调用顺序如下:

    RegistryProtocol#export
      |- RegistryProtocol#register
          |- RegistryService#register
              |- FailbackRegistry#register
                  |- FailbackRegistry#doRegister
                      |- ZookeeperRegistry#doRegister
                          |- AbstractZookeeperClient#create
    

    zkClient.create(toUrlPath(url), url.getParameter(Constants.DYNAMIC_KEY, true)); 这行代码可以看出是创建的节点是否是临时节点,虽然给了默认值是true,但是在AbstractServiceConfig,给了默认值是false,protected Boolean dynamic = false; 所以在dubbo2.7版本中,默认创建不是临时节点。

    public void create(String path, boolean ephemeral) {
        if (!ephemeral) {
            if (checkExists(path)) {
                return;
            }
        }
        int i = path.lastIndexOf('/');
        if (i > 0) {
            create(path.substring(0, i), false);
        }
        if (ephemeral) {
            createEphemeral(path);
        } else {
            createPersistent(path);
        }
    }
    

    这是个递归创建过程,由于临时节点下面不能再创建临时节点,所以只有叶子节点才能创建临时节点。当服务提供者断开连接时,仅仅删除的是叶子节点。

  • 订阅处理

    注册完服务之后,还需要实现服务的订阅功能,具体如下:

    public  Exporter export(final Invoker originInvoker) throws RpcException {
            ...
            final URL overrideSubscribeUrl = getSubscribedOverrideUrl(providerUrl);
            final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker);
            overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);
          ...
            // Deprecated! Subscribe to override rules in 2.6.x or before.
            registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
            exporter.setRegisterUrl(registeredProviderUrl);
            exporter.setSubscribeUrl(overrideSubscribeUrl);
            ...
        }
    

    具体订阅操作如下:

    public void doSubscribe(final URL url, final NotifyListener listener) {
        try {
            ...
            for (String path : toCategoriesPath(url)) {
                ConcurrentMap listeners = 
                                  zkListeners.get(url);
                if (listeners == null) {
                    zkListeners.putIfAbsent(url, new ConcurrentHashMap<>());
                    listeners = zkListeners.get(url);
                }
                ChildListener zkListener = listeners.get(listener);
                if (zkListener == null) {
                    listeners.putIfAbsent(listener,
    (parentPath, currentChilds) -> ZookeeperRegistry.this.notify(url, listener, toUrlsWithEmpty(url, parentPath, currentChilds)));
                    zkListener = listeners.get(listener);
                }
                ...
                zkClient.create(path, false);
                List children = zkClient.addChildListener(path, zkListener);
                ...
            }
            notify(url, listener, urls);
            }
        ...
    }
    

    订阅Zookeeper节点是通过创建ChildListener来实现的具体调用的方法是 addChildListener(),具体调用顺序如下:

    FailbackRegistry#subscribe
      |- ZookeeperRegistry#doSubscribe
          |- AbstractZookeeperClient#addChildListener
              |- CuratorZookeeperClient#addTargetChildListener
                  |- client.getChildren().usingWatcher(listener).forPath(path);
                      |- GetChildrenBuilderImpl#usingWatcher
    

    client.getChildren().usingWatcher(listener).forPath(path) 这行代码是具体的监听订阅执行操作。

    public List addChildListener(String path, final ChildListener listener) {
        ...
        TargetChildListener targetListener = listeners.get(listener);
        if (targetListener == null) {
            listeners.putIfAbsent(listener, createTargetChildListener(path, listener));
            targetListener = listeners.get(listener);
        }
        return addTargetChildListener(path, targetListener);
    }
    

    具体做了以下工作:

    • 根据ChildListener获取TargetChildListener
    • 创建一个真正的用来执行当path节点的子节点发生变化时的逻辑
    • 将刚刚创建出来的子节点监听器订阅path的变化,这样之后,path的子节点发生了变化时,TargetChildListener 才会执行相应的逻辑。而实际上TargetChildListener 又会调用ChildListener 的实现类的childChanged(String parentPath, List currentChilds)方法,而该实现类,正好是ZookeeperRegistry中实现的匿名内部类,在该匿名内部类的childChanged(String parentPath, List currentChilds)方法中,调用了ZookeeperRegistry.notify(URL url, NotifyListener listener, List urls)

    方法createTargetChildListener 会创建一个 CuratorWatcher 来做具体的监听工作。

    static class CuratorWatcherImpl implements CuratorWatcher, TreeCacheListener {
    
            private CuratorFramework client;
            private volatile ChildListener childListener;
            private volatile DataListener dataListener;
    
            public CuratorWatcherImpl(CuratorFramework client, ChildListener listener) {
                this.client = client;
                this.childListener = listener;
            }
            ...
            @Override
            public void process(WatchedEvent event) throws Exception {
                if (childListener != null) {
                    String path = event.getPath() == null ? "" : event.getPath();
                    childListener.childChanged(path,
                       StringUtils.isNotEmpty(path) ?
                              client.getChildren().usingWatcher(this).forPath(path)
                              : Collections.emptyList());
                }
            }
    
  • 通知处理

    通知处理的具体方法是 AbstractRegistry#notify ,主要做了以下工作:

    • 将urls按照category进行分类
    • 进行properties设置和文件保存
    • 调用传入放入listener的notify()方法

    接着会调用 OverrideListener#notify 方法,具体实现如下:

    public synchronized void notify(List urls) {
        logger.debug("original override urls: " + urls);
    
        List matchedUrls = 
               getMatchedUrls(urls, 
                    subscribeUrl.addParameter(CATEGORY_KEY, CONFIGURATORS_CATEGORY));
        ...
        doOverrideIfNecessary();
    }
    
    public synchronized void doOverrideIfNecessary() {
        ...
        //The current, may have been merged many times
        URL currentUrl = exporter.getInvoker().getUrl();
        //Merged with this configuration
        URL newUrl = getConfigedInvokerUrl(configurators, originUrl);
        newUrl = getConfigedInvokerUrl(serviceConfigurationListeners
                        .get(originUrl.getServiceKey())
                        .getConfigurators(), newUrl);
        newUrl = getConfigedInvokerUrl(providerConfigurationListener
                                       .getConfigurators(), newUrl);
        if (!currentUrl.equals(newUrl)) {
            RegistryProtocol.this.reExport(originInvoker, newUrl);
            ...
        }
    }
    

    这里发现,如果url发生变化,会重新暴露服务。总之,当前的provider订阅了/dubbo/.../configurators,当其下的子节点发生变化时,如果其下的子节点的url或者当前的providerUrl发生了变化,需要重新暴露。

2.3.4 元数据注册

当远程暴露、订阅和通知完成后,会进行元数据注册,代码如下:

private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List registryURLs) {
    ...
    MetadataReportService metadataReportService = null;
    if ((metadataReportService = getMetadataReportService()) != null) {
        metadataReportService.publishProvider(url);
    }
    ...
}

位置如下图:

2-3-4-1.png

你可能感兴趣的:(2. Dubbo源码解析之服务暴露过程)