zoukankan      html  css  js  c++  java
  • Dubbo解析之服务暴露流程(上)

    前面已经讲到Dubbo的初始化流程,Dubbo的初始化是随着Spring容器Bean的实例化而进行的,今天重点看这样一个节点,它在配置文件中是这样的:
    <dubbo:service interface="com.viewscenes.netsupervisor.service.InfoUserService" ref="service" />
    它会完成Dubbo服务暴露的逻辑,先看下大概流程。
     
    0 

    一、开始

    上述配置文件中的节点信息对应的处理类是ServiceBean。先看下它的结构
    public class ServiceBean<T> extends ServiceConfig<T> implements 
                InitializingBean, DisposableBean, ApplicationContextAware,
                        ApplicationListener<ContextRefreshedEvent>, BeanNameAware {}

    可以看到,这个类实现了Spring的不同接口,这就意味着Spring在不同的时机就会调用到相应方法。

    1、设置上下文

    在Spring完成实例化和IOC之后,会调用到invokeAwareInterfaces方法,来判断Bean是否实现了Aware接口,然后调用对应的方法。
    public void setApplicationContext(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
        SpringExtensionFactory.addApplicationContext(applicationContext);
        if (applicationContext != null) {
            SPRING_CONTEXT = applicationContext;
            try {
                Method method = applicationContext.getClass().getMethod(
                        "addApplicationListener", new Class<?>[]{ApplicationListener.class});
                method.invoke(applicationContext, new Object[]{this});
                supportedApplicationListener = true;
            } catch (Throwable t) {
                //省略无关代码....
            }
        }
    }

    2、初始化方法

    然后我们还看到它实现了InitializingBean接口,那么初始化方法afterPropertiesSet也是跑不掉呀。在这里面,就是拿到Dubbo中的应用信息、注册信息、协议信息等,设置到变量中。最后有个判断方法值得我们注意isDelay 当方法返回 true 时,表示无需延迟导出。返回 false 时,表示需要延迟导出。
    public void afterPropertiesSet() throws Exception {
        if (getProvider() == null) {
            //......
        }
        if (getApplication() == null
                && (getProvider() == null || getProvider().getApplication() == null)) {
            //......
        }
        if (getModule() == null
                && (getProvider() == null || getProvider().getModule() == null)) {
            //......
        }
        if ((getRegistries() == null || getRegistries().isEmpty())) {
            //......
        }
        if ((getProtocols() == null || getProtocols().isEmpty())
                && (getProvider() == null || getProvider().getProtocols() == null || 
                getProvider().getProtocols().isEmpty())) {
            //......
        }
        if (getPath() == null || getPath().length() == 0) {
            if (beanName != null && beanName.length() > 0
                    && getInterface() != null && getInterface().length() > 0
                    && beanName.startsWith(getInterface())) {
                setPath(beanName);
            }
        }
        if (!isDelay()) {
            export();
        }
    }

    3、开始暴露

    这个方法是在Spring 上下文刷新事件后被调用到,它是服务暴露的入口方法。
    public void onApplicationEvent(ContextRefreshedEvent event) {
        //是否有延迟暴露 && 是否已暴露 && 是不是已被取消暴露
        if (isDelay() && !isExported() && !isUnexported()) {
            if (logger.isInfoEnabled()) {
                logger.info("The service ready on spring started. service: " + getInterface());
            }
            //暴露服务
            export();
        }
    } 

    二、准备工作

    Dubbo在暴露服务之前,要检查各种配置 ,设置参数信息,还要补充一些缺省的配置项,然后封装URL对象信息 。在这里,我们必须重视URL对象,Dubbo 使用 URL 作为配置载体,所有的拓展点都是通过 URL 获取配置。

    1、检查配置

    export()方法在服务暴露之前,有两个配置项要检查。是否暴露服务以及是否延迟暴露服务。
    public synchronized void export() {
        // 获取 export 和 delay 配置
        if (provider != null) {
            if (export == null) {
                export = provider.getExport();
            }
            if (delay == null) {
                delay = provider.getDelay();
            }
        }
        // 如果 export 为 false,则不暴露服务
        if (export != null && !export) {
            return;
        }
        // delay > 0,延时暴露服务
        if (delay != null && delay > 0) {
            delayExportExecutor.schedule(new Runnable() {
                @Override
                public void run() {
                    doExport();
                }
            }, delay, TimeUnit.MILLISECONDS);
        }else {
            doExport();
        }
    }
    很显然,如果我们不想暴露服务,这样可以来配置: 
    <dubbo:provider export="false"/>

    同样的,如果我们想延迟暴露服务,就可以这样来配置它:

    <dubbo:provider delay="100"/>
    在Dubbo决定要暴露服务之后,还要做一些事情。

    检查服务接口合法性

    • 检查Conifg核心配置类等是否为空,为空就从其他配置中获取相应实例
    • 区分泛化服务和普通服务
    • 检查各种对象是否为空,创建或抛出异常等
    protected synchronized void doExport() {
        if (unexported) {
            throw new IllegalStateException("Already unexported!");
        }
        if (exported) {
            return;
        }
        exported = true;
        if (interfaceName == null || interfaceName.length() == 0) {
            throw new IllegalStateException("
                <dubbo:service interface=\"\" /> interface not allow null!");
        }
        checkDefault();
        if (provider != null) {
            //检查配置对象是否为空并赋值
        }
        if (module != null) {
            //检查配置对象是否为空并赋值
        }
        if (application != null) {
            //检查配置对象是否为空并赋值
        }
        //区分泛化类和普通类
        if (ref instanceof GenericService) {
            interfaceClass = GenericService.class;
            if (StringUtils.isEmpty(generic)) {
                generic = Boolean.TRUE.toString();
            }
        } else {
            //返回接口Class对象并检查
            try {
                interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()
                        .getContextClassLoader());
            } catch (ClassNotFoundException e) {
                throw new IllegalStateException(e.getMessage(), e);
            }
            checkInterfaceAndMethods(interfaceClass, methods);
            checkRef();
            generic = Boolean.FALSE.toString();
        }
        //检查各种对象是否为空,创建或抛出异常
        checkApplication();
        checkRegistry();
        checkProtocol();
        appendProperties(this);
        checkStubAndMock(interfaceClass);
        if (path == null || path.length() == 0) {
            path = interfaceName;
        }
        //服务暴露
        doExportUrls();
        ProviderModel providerModel = new ProviderModel(getUniqueServiceName(), this, ref);
        ApplicationModel.initProviderModel(getUniqueServiceName(), providerModel);
    } 

    2、多协议多注册中心

    2.1、配置

    Dubbo 允许我们使用不同的协议暴露服务,也允许我们向多个注册中心注册服务。 比如我们想同时使用dubbo、rmi两种协议来暴露服务:
    <dubbo:protocol name="dubbo" port="20880"/>  
    <dubbo:protocol name="rmi" port="1099" /> 
    又或者我们需要把服务注册到zookeeper、redis:
    <dubbo:registry address="zookeeper://192.168.139.129:2181"/>
    <dubbo:registry address="redis://192.168.139.129:6379"/> 
    我们这样配置后,在zookeeper、redis里面都会保存基于两种协议的服务信息。
    首先在redis中我们执行命令:
    hgetall /dubbo/com.viewscenes.netsupervisor.service.InfoUserService/providers
    得到以下结果:
    "rmi://192.168.100.74:1099/com.viewscenes.netsupervisor.service.InfoUserService?anyhost=true&application=dubbo_producer1&dubbo=2.6.2&generic=false&interface=com.viewscenes.netsupervisor.service.InfoUserService&methods=getUserById,getAllUser,insertInfoUser,deleteUserById&pid=7064&side=provider&timestamp=1545804399128"
    "1545805385379"
    "dubbo://192.168.100.74:20880/com.viewscenes.netsupervisor.service.InfoUserService?anyhost=true&application=dubbo_producer1&dubbo=2.6.2&generic=false&interface=com.viewscenes.netsupervisor.service.InfoUserService&methods=getUserById,getAllUser,insertInfoUser,deleteUserById&pid=7064&side=provider&timestamp=1545804391176"
    "1545805385379"

    同时,我们在zookeeper中执行命令:

    ls /dubbo/com.viewscenes.netsupervisor.service.InfoUserService/providers

    得到以下结果:

    [dubbo%3A%2F%2F192.168.100.74%3A20880%2Fcom.viewscenes.netsupervisor.service.InfoUserService%3Fanyhost%3Dtrue%26application%3
    Ddubbo_producer1%26dubbo%3D2.6.2%26generic%3Dfalse%26interface%3Dcom.viewscenes.netsupervisor.service.InfoUserService%26methods%3D
    getUserById%2CgetAllUser%2CinsertInfoUser%2CdeleteUserById%26pid%3D7064%26side%3Dprovider%26timestamp%3D1545804391176, 
    
    rmi%3A%2F%2F192.168.100.74%3A1099%2Fcom.viewscenes.netsupervisor.service.InfoUserService%3Fanyhost%3Dtrue%26application%3
    Ddubbo_producer1%26dubbo%3D2.6.2%26generic%3Dfalse%26interface%3Dcom.viewscenes.netsupervisor.service.InfoUserService%26methods%3D
    getUserById%2CgetAllUser%2CinsertInfoUser%2CdeleteUserById%26pid%3D7064%26side%3Dprovider%26timestamp%3D1545804399128] 

    2.2、多注册中心

    首先是多个注册中心的加载,loadRegistries方法返回一个List registryURLs。基于上面的配置文件,这里的registries就是一个长度为2的List,最终将它们解析为List registryURLs。
    protected List<URL> loadRegistries(boolean provider) {
        //检查配置
        checkRegistry();
        List<URL> registryList = new ArrayList<URL>();
        if (registries != null && !registries.isEmpty()) {
            for (RegistryConfig config : registries) {
                String address = config.getAddress();
                if (address == null || address.length() == 0) {
                    // 若 address 为空,则将其设为 0.0.0.0
                    address = Constants.ANYHOST_VALUE;
                }
                // 从系统属性中加载注册中心地址
                String sysaddress = System.getProperty("dubbo.registry.address");
                if (sysaddress != null && sysaddress.length() > 0) {
                    address = sysaddress;
                }
                //检查address合法性以及从封装map配置信息
                if (address != null && address.length() > 0
                        && !RegistryConfig.NO_AVAILABLE.equalsIgnoreCase(address)) {
                    Map<String, String> map = new HashMap<String, String>();
                    appendParameters(map, application);
                    appendParameters(map, config);
                    map.put("path", RegistryService.class.getName());
                    map.put("dubbo", Version.getVersion());
                    map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));
                    if (ConfigUtils.getPid() > 0) {
                        map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));
                    }
                    if (!map.containsKey("protocol")) {
                        if (ExtensionLoader.getExtensionLoader(RegistryFactory.class).
                                                                    hasExtension("remote")) {
                            map.put("protocol", "remote");
                        } else {
                            map.put("protocol", "dubbo");
                        }
                    }
                    //将配置信息封装成URL对象
                    List<URL> urls = UrlUtils.parseURLs(address, map);
                    for (URL url : urls) {
                        url = url.addParameter(Constants.REGISTRY_KEY, url.getProtocol());
                        url = url.setProtocol(Constants.REGISTRY_PROTOCOL);
                        if ((provider && url.getParameter(Constants.REGISTER_KEY, true))
                                || (!provider && url.getParameter(Constants.SUBSCRIBE_KEY, true))) {
                            registryList.add(url);
                        }
                    }
                }
            }
        }
        return registryList;
    } 

    2.3 多协议支持

    多协议的支持,也是一样,遍历数组循环暴露服务。其中,doExportUrlsFor1Protocol方法为服务暴露的具体流程。
    private void doExportUrls() {
        //加载注册中心
        List<URL> registryURLs = loadRegistries(true);
        //遍历协议,为每个协议暴露一个服务
        for (ProtocolConfig protocolConfig : protocols) {
            doExportUrlsFor1Protocol(protocolConfig, registryURLs);
        }
    }

    三、服务暴露

    经过各种配置检查后,来到doExportUrlsFor1Protocol方法,它是基于单个协议来暴露服务。方法的前半部分是根据配置信息组装URL对象。前面我们说过,对于Dubbo来说,URL对象信息至关重要,它是Dubbo配置的载体。 关于它的组装过程,无非就是各种设置属性,我们不再细看,我们看看封装好的URL对象信息是什么样的。
    dubbo://192.168.100.74:20880/com.viewscenes.netsupervisor.service.InfoUserService?
    anyhost=true&application=dubbo_producer1&bind.ip=192.168.100.74&bind.port=20880&dubbo=2.6.2&generic=false&
    interface=com.viewscenes.netsupervisor.service.InfoUserService&methods=getUserById,getAllUser,insertInfoUser,deleteUserById&pid=8596&side=provider&timestamp=1545807234666
    
    rmi://192.168.100.74:1099/com.viewscenes.netsupervisor.service.InfoUserService?
    anyhost=true&application=dubbo_producer1&bind.ip=192.168.100.74&bind.port=1099&dubbo=2.6.2&generic=false&
    interface=com.viewscenes.netsupervisor.service.InfoUserService&methods=getUserById,getAllUser,insertInfoUser,deleteUserById&pid=8596&side=provider&timestamp=1545807267085 
    有服务暴露之前,我们还可以对暴露方式有所选择。
    • 本地暴露
    • 远程暴露
    • 不暴露
    它们的配置对应如下:
    <dubbo:service scope="local" />
    <dubbo:service scope="remote" />
    <dubbo:service scope="none" /> 
    默认情况下,Dubbo两种方式都会暴露。即本地+远程。
    private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
        
        //省略设置URL对象信息过程...
        String scope = url.getParameter(Constants.SCOPE_KEY);
        // 如果 scope = none,则不暴露服务
        if (!Constants.SCOPE_NONE.toString().equalsIgnoreCase(scope)) {
    
            // scope != remote,服务暴露到本地
            if (!Constants.SCOPE_REMOTE.toString().equalsIgnoreCase(scope)) {
                exportLocal(url);
            }
            // scope != local,服务暴露到远程
            if (!Constants.SCOPE_LOCAL.toString().equalsIgnoreCase(scope)) {
                if (registryURLs != null && !registryURLs.isEmpty()) {
                    //循环注册中心进行暴露服务
                    for (URL registryURL : registryURLs) {
                        url = url.addParameterIfAbsent(Constants.DYNAMIC_KEY, registryURL.getParameter(Constants.DYNAMIC_KEY));
                        URL monitorUrl = loadMonitor(registryURL);
                        if (monitorUrl != null) {
                            url = url.addParameterAndEncoded(Constants.MONITOR_KEY, monitorUrl.toFullString());
                        }
                        if (logger.isInfoEnabled()) {
                            logger.info("Register dubbo service " + interfaceClass.getName() + " url " + url + " to registry " + registryURL);
                        }
                        
                        //为服务类ref生成invoker
                        Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, 
                                registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
                        // DelegateProviderMetaDataInvoker 用于持有 Invoker 和 ServiceConfig
                        DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
                        //调用对应的协议,暴露服务
                        Exporter<?> exporter = protocol.export(wrapperInvoker);
                        exporters.add(exporter);
                    }
                } else {
                    Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url);
                    DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
    
                    Exporter<?> exporter = protocol.export(wrapperInvoker);
                    exporters.add(exporter);
                }
            }
        }
        this.urls.add(url);
    } 

    3.1、创建Invoker

    在开始之前,我们必须了解另外一个东西:Invoker 。在 Dubbo 中,Invoker 是一个非常重要的模型。在服务提供端,以及服务引用端均会出现 Invoker。 Dubbo官网对它是这样说明的:
    Invoker 是实体域,它是 Dubbo 的核心模型,其它模型都向它靠扰,或转换成它,它代表一个可执行体,可向它发起 invoke 调用,它有可能是一个本地的实现,也可能是一个远程的实现,也可能一个集群实现。
     
    在Dubbo中,Invoker通过以下代码创建:
    ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).
                      getAdaptiveExtension();
    Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url);
     
    首先通过扩展点加载器创建ProxyFactory接口的自适应扩展类,在Dubbo中,默认是JavassistProxyFactory,所以当调用proxyFactory.getInvoker就会调用到JavassistProxyFactory
    public class JavassistProxyFactory extends AbstractProxyFactory {
    
        public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
            // 为目标类创建 Wrapper
            final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().
                        indexOf('$') < 0 ? proxy.getClass() : type);
            // 创建匿名 Invoker 类对象,并实现 doInvoke 方法。
            return new AbstractProxyInvoker<T>(proxy, type, url) {
                @Override
                protected Object doInvoke(T proxy, String methodName,
                                          Class<?>[] parameterTypes,
                                          Object[] arguments) throws Throwable {
                    // 调用 Wrapper 的 invokeMethod 方法,invokeMethod 最终会调用目标方法
                    return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
                }
            };
        }
    }
    如上方法,它主要完成了两件事:为目标类创建 Wrapper和实现doInvoke方法。这样一来,当调用AbstractProxyInvoker.doInvoke()方法时,实际则调用的是,wrapper的invokeMethod()方法。
    那么,wrapper又是怎么来的呢?它通过ClassGenerator创建而来。 ClassGenerator是 Dubbo 自己封装的,该类的核心是 toClass() 的重载方法 toClass(ClassLoader, ProtectionDomain),该方法通过 javassist 构建 Class。创建过程涉及的代码较长,我们不再细看,主要看看这个wrapper类生成后的样子。
    package com.alibaba.dubbo.common.bytecode;
    
    import com.viewscenes.netsupervisor.entity.InfoUser;
    import com.viewscenes.netsupervisor.service.impl.InfoUserServiceImpl;
    import java.lang.reflect.InvocationTargetException;
    import java.util.Map;
    
    public class Wrapper1 extends Wrapper implements ClassGenerator.DC {
        public static String[] pns;
        public static Map pts;
        public static String[] mns;
        public static String[] dmns;
        public static Class[] mts0;
        public static Class[] mts1;
        public static Class[] mts2;
        public static Class[] mts3;
        
        public Class getPropertyType(String paramString) {
            return ((Class) pts.get(paramString));
        }
        public String[] getPropertyNames() {
            return pns;
        }
        public Object invokeMethod(Object paramObject, String paramString, Class[] paramArrayOfClass,
                Object[] paramArrayOfObject) throws InvocationTargetException {
            InfoUserServiceImpl localInfoUserServiceImpl;
            try {
                localInfoUserServiceImpl = (InfoUserServiceImpl) paramObject;
            } catch (Throwable localThrowable1) {
                throw new IllegalArgumentException(localThrowable1);
            }
            try {
                if (("getAllUser".equals(paramString)) && (paramArrayOfClass.length == 0))
                    return localInfoUserServiceImpl.getAllUser();
                if (("getUserById".equals(paramString)) && (paramArrayOfClass.length == 1))
                    return localInfoUserServiceImpl.getUserById((String) paramArrayOfObject[0]);
                if (("insertInfoUser".equals(paramString)) && (paramArrayOfClass.length == 1)) {
                    localInfoUserServiceImpl.insertInfoUser((InfoUser) paramArrayOfObject[0]);
                    return null;
                }
                if (("deleteUserById".equals(paramString)) && (paramArrayOfClass.length == 1)) {
                    localInfoUserServiceImpl.deleteUserById((String) paramArrayOfObject[0]);
                    return null;
                }
            } catch (Throwable localThrowable2) {
                throw new InvocationTargetException(localThrowable2);
            }
            throw new NoSuchMethodException("Not found method \"" + paramString
                    + "\" in class com.viewscenes.netsupervisor.service.impl.InfoUserServiceImpl.");
        }
    
        public Object getPropertyValue(Object paramObject, String paramString) {
            InfoUserServiceImpl localInfoUserServiceImpl;
            try {
                localInfoUserServiceImpl = (InfoUserServiceImpl) paramObject;
            } catch (Throwable localThrowable) {
                throw new IllegalArgumentException(localThrowable);
            }
            if (paramString.equals("allUser"))
                return localInfoUserServiceImpl.getAllUser();
            throw new NoSuchPropertyException("Not found property \"" + paramString
                    + "\" filed or setter method in class com.viewscenes.netsupervisor.service.impl.InfoUserServiceImpl.");
        }
    
        public void setPropertyValue(Object paramObject1, String paramString, Object paramObject2) {
            try {
                InfoUserServiceImpl localInfoUserServiceImpl = (InfoUserServiceImpl) paramObject1;
            } catch (Throwable localThrowable) {
                throw new IllegalArgumentException(localThrowable);
            }
            throw new NoSuchPropertyException("Not found property \"" + paramString
                    + "\" filed or setter method in class com.viewscenes.netsupervisor.service.impl.InfoUserServiceImpl.");
        }
    
        public String[] getMethodNames() {
            return mns;
        }
    
        public String[] getDeclaredMethodNames() {
            return dmns;
        }
    
        public boolean hasProperty(String paramString) {
            return pts.containsKey(paramString);
        }
    }
    我们重点可以关注invokeMethod方法,当调用到它的时候,它根据参数直接调用的是目标类(ref)的对应方法。 综上所述,proxyFactory.getInvoker(ref, (Class) interfaceClass, url);这句代码的返回值就是JavassistProxyFactory的实例,如下图示:
    0 

    3.2、本地暴露

    上面我们已经看到,如果scope != remote 就调用本地暴露。
    if (!Constants.SCOPE_REMOTE.toString().equalsIgnoreCase(scope)) {
        exportLocal(url);
    } 
    本地暴露方法比较简单,首先根据 URL 协议头决定是否导出服务。若需导出,则创建一个新的 URL 并将协议头、主机名以及端口设置成新的值。然后创建 Invoker,并调用 InjvmProtocol 的 export 方法导出服务。
    private void exportLocal(URL url) {
        //判断协议头
        if (!Constants.LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {
            //设置本地暴露协议URL
            URL local = URL.valueOf(url.toFullString())
                    .setProtocol(Constants.LOCAL_PROTOCOL)
                    .setHost(LOCALHOST)
                    .setPort(0);
            ServiceClassHolder.getInstance().pushServiceClass(getServiceClass(ref));
            
            //创建Invoker对象
            //根据协议头,这里的 protocol 会在运行时调用 InjvmProtocol 的 export 方法
            Exporter<?> exporter = protocol.export(
                    proxyFactory.getInvoker(ref, (Class) interfaceClass, local));
            exporters.add(exporter);
        }
    } 
    如上代码,当调用protocol.export的时候,Dubbo SPI 自适应的特性的好处就出来了,可以自动根据 URL 参数,获得对应的拓展实现。比如这里是本地暴露,那么它就会调用到InjvmProtocol。InjvmProtocol的 export 方法仅创建了一个 InjvmExporter,无其他逻辑。至此,本地暴露完毕。
    public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
        return new InjvmExporter<T>(invoker, invoker.getUrl().getServiceKey(), exporterMap);
    }

    3.3、远程暴露

    远程暴露也一样,如果scope != local 则调用方法。值得注意的是,它的方法是在注册中心列表循环里面调用的。
    if (!Constants.SCOPE_LOCAL.toString().equalsIgnoreCase(scope)) {
        if (registryURLs != null && !registryURLs.isEmpty()) {
            //循环注册中心
            for (URL registryURL : registryURLs) {
                //省略无关代码...         
                //先获取invoker
                Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, 
                        registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
                //将invoker和this对象封装成DelegateProviderMetaDataInvoker
                DelegateProviderMetaDataInvoker wrapperInvoker = new 
                                                    DelegateProviderMetaDataInvoker(invoker, this);
                //服务暴露
                Exporter<?> exporter = protocol.export(wrapperInvoker);
                exporters.add(exporter);
            }
        }
    }
    先注意这句代码:
    registryURL.addParameterAndEncoded("export",url.toFullString())
    它把当前的url信息,添加到registryURL对象中去,key为export。 然后当获取到invoker后,此时的url协议头会变成registry 此时,下面再调用protocol.export 就会调用到RegistryProtocol.export()。下面我们把目光移动到 RegistryProtocol 的 export 方法上。
    public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
                
        //暴露服务
        final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker);
        // 获取注册中心 URL,以 zookeeper 注册中心为例,得到的示例 URL 如下:
        //zookeeper://192.168.139.131:2181/com.alibaba.dubbo.registry.RegistryService...
        URL registryUrl = getRegistryUrl(originInvoker);
    
        //根据 URL 加载 Registry 实现类,比如 ZookeeperRegistry
        final Registry registry = getRegistry(originInvoker);
        
        // 获取已注册的服务提供者 URL,比如:
        //dubbo://192.168.100.74:20880/com.viewscenes.netsupervisor.service.InfoUserService...
        final URL registedProviderUrl = getRegistedProviderUrl(originInvoker);
    
        boolean register = registedProviderUrl.getParameter("register", true);
        ProviderConsumerRegTable.registerProvider(originInvoker, registryUrl, registedProviderUrl);
        if (register) {
            // 向注册中心注册服务
            register(registryUrl, registedProviderUrl);
            ProviderConsumerRegTable.getProviderWrapper(originInvoker).setReg(true);
        }
        // 获取订阅 URL,比如:
        //provider://192.168.100.74:20880/com.viewscenes.netsupervisor.service.InfoUserService...
        final URL overrideSubscribeUrl = getSubscribedOverrideUrl(registedProviderUrl); 
        // 创建监听器
        OverrideListener overrideSubscribeListener = 
                                    new OverrideListener(overrideSubscribeUrl, originInvoker);
        overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);
        // 向注册中心进行订阅 override 数据
        registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
        // 创建并返回 DestroyableExporter
        return new 
        DestroyableExporter<T>(exporter, originInvoker, overrideSubscribeUrl, registedProviderUrl);
    } 
    如上代码,主要完成了两件事: 调用doLocalExport进行服务暴露、向注册中心注册服务。
  • 相关阅读:
    最大子数组问题(分治策略实现)
    Solving the Detached Many-to-Many Problem with the Entity Framework
    Working With Entity Framework Detached Objects
    Attaching detached POCO to EF DbContext
    如何获取qq空间最近访问人列表
    Health Monitoring in ASP.NET 2.0
    problem with displaying the markers on Google maps
    WebMatrix Database.Open… Close() and Dispose()
    Accessing and Updating Data in ASP.NET: Retrieving XML Data with XmlDataSource Control
    Create web setup project that has crystal reports and sql script run manually on client system
  • 原文地址:https://www.cnblogs.com/johnvwan/p/15650190.html
Copyright © 2011-2022 走看看