dubbo服务暴露之本地服务暴露


前言

dubbo服务发布共6个步骤:
    1)暴露本地服务
    2)暴露远程服务
    3)启动netty服务
    4)打开连接zk
    5)注册到zk
    6)监听zk
本文学习的是暴露本地服务的过程

暴露本地服务:暴露在同一个JVM里面,不用通过调用ZK来进行远程通信。
暴露远程服务:暴露给远程客户端的IP和端口,通过网络来实现通信。


过程分析

ServiceBean

spring解析dubbo提供的xml标签的时候,是通过spring.handlers配置中的DubboNamespaceHandler完成的。如下源码所示,<dubbo:service/>是通过ServiceBean解析完成的,所以暴露服务也是在ServiceBean类中进行的。
DubboNamespaceHandler的源码如下:

public class DubboNamespaceHandler extends NamespaceHandlerSupport {

    static {
        Version.checkDuplicate(DubboNamespaceHandler.class);
    }

    @Override
    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("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());
    }

}

xml配置文件

在阅读源码前,我们先看下我们的xml配置文件。暴露服务时进行的:参数检查,组装 URL,都是围绕着该xml文件进行的

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
       xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
       http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">

    <dubbo:application name="demo-provider" />

    <!-- multicast代表组播; 除了组播,注册中心还可以配置dubbo、redis、zookeeper -->
    <dubbo:registry address="zookeeper://127.0.0.1:2181"/>
	<!-- <dubbo:registry address="multicast://224.5.6.7:1234"/> -->

    <!-- use dubbo protocol to export service on port 20880 -->
    <dubbo:protocol name="dubbo" port="20880"/>

    <bean id="demoService" class="com.alibaba.dubbo.demo.provider.DemoServiceImpl"/>

    <dubbo:service interface="com.alibaba.dubbo.demo.DemoService" ref="demoService"/>

</beans>

入口方法onApplicationEvent()

服务暴露的入口方法是 ServiceBean类的 onApplicationEvent()ServiceBean实现了 ApplicationListener接口的onApplicationEvent方法,该方法会在收到 Spring 上下文刷新事件后执行。
代码逻辑为:
    1)通过isDelay()方法检查是否需要延迟暴露,在当前版本中isDelay()返回true表示无需延迟暴露,否则需要延迟暴露;如果需要延迟暴露,那么系统会调用afterPropertiesSet()方法进行延迟暴露。配置形式为:<dubbo:service interface="com.xxx.xxxService" ref="xxxService" delay="3000"/><dubbo:provider delay="20000"/>
    2)服务是否已经暴露,如果已经暴露,就不需要在此导出
    3)是否已经取消暴露服务
    4)服务暴露
源码如下:

public void onApplicationEvent(ContextRefreshedEvent event) {
    	// 是否有延迟导出 && 是否已导出 && 是不是已被取消导出
        if (isDelay() && !isExported() && !isUnexported()) {
            if (logger.isInfoEnabled()) {
                logger.info("The service ready on spring started. service: " + getInterface());
            }
            // 暴露服务
            export();
        }
    }

export()

代码逻辑为:
    1)如果xml配置中配置了<dubbo:provider/>,则获取provider的export (是否暴露)和delay (延迟时间)属性
    2)如果禁止服务暴露, 则直接返回
    3)如果配置延迟时间,就延迟暴露服务
    4)如果不满足1,2,则直接进行服务暴露

public synchronized void export() {
        if (provider != null) {
        	// 获取 export 和 delay 配置
            if (export == null) {
                export = provider.getExport();
            }
            if (delay == null) {
                delay = provider.getDelay();
            }
        }
        // 如果 export 为 false,则不导出服务
        // <dubbo:provider 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();
        }
    }

doExport()

代码逻辑为:
    1)检测 <dubbo:service interface="com.alibaba.dubbo.demo.DemoService" ref="demoService"> 标签的 interface 属性合法性,不合法则抛出异常
    2)检测 ProviderConfig、ApplicationConfig 等核心配置类对象是否为空,若为空,则尝试从其他配置类对象中获取相应的实例。
    3)检测并处理泛化服务和普通服务类
    4)检测本地存根配置,并进行相应的处理
    5)对 ApplicationConfig、RegistryConfig 等配置类进行检测,为空则尝试创建,若无法创建则抛出异常
    6)执行 doExportUrls()方法,进行服务暴露
注:虽然源码较长,但大部分都是验证对象、设置对象值的方法,真正执行服务暴露的方法为doExportUrls()方法

protected synchronized void doExport() {
		// 如果serverbean的unexported属性为真,则抛出异常
        if (unexported) {
            throw new IllegalStateException("Already unexported!");
        }
        // 如果serverbean已经暴露过了,就直接返回
        if (exported) {
            return;
        }
        // 修改serverbean暴露状态
        exported = true;
        // 检测 interfaceName 是否合法(interfaceName = com.alibaba.dubbo.demo.DemoService)
        if (interfaceName == null || interfaceName.length() == 0) {	
            throw new IllegalStateException("<dubbo:service interface=\"\" /> interface not allow null!");
        }
        // 检测 provider 是否为空,为空则新建一个,并通过系统变量为其初始化
        checkDefault();
        // 下面几个 if 语句用于检测 provider、application 等核心配置类对象是否为空,若为空,则尝试从其他配置类对象中获取相应的实例。
        if (provider != null) {
            if (application == null) {	
                application = provider.getApplication();
            }
            if (module == null) {
                module = provider.getModule();
            }
            if (registries == null) {
           	 	// registries = [<dubbo:registry address="zookeeper://127.0.0.1:2181" id="com.alibaba.dubbo.config.RegistryConfig" />]
                registries = provider.getRegistries();
            }
            if (monitor == null) {
                monitor = provider.getMonitor();
            }
            if (protocols == null) {	
            // protocols = [<dubbo:protocol name="dubbo" port="20880" id="dubbo" />]
                protocols = provider.getProtocols();
            }
        }
        if (module != null) {
            if (registries == null) {
                registries = module.getRegistries();
            }
            if (monitor == null) {
                monitor = module.getMonitor();
            }
        }
        if (application != null) {
            if (registries == null) {
                registries = application.getRegistries();
            }
            if (monitor == null) {
                monitor = application.getMonitor();
            }
        }
        // 检测 ref 是否为泛化服务类型--ref在本例中为 com.alibaba.dubbo.demo.provider.DemoServiceImpl@2cb2fc20
        if (ref instanceof GenericService) {
            interfaceClass = GenericService.class;
            if (StringUtils.isEmpty(generic)) {
                generic = Boolean.TRUE.toString();
            }
        } else {
            try {
                interfaceClass = Class.forName(interfaceName, true, Thread.currentThread().getContextClassLoader());
            } catch (ClassNotFoundException e) {
                throw new IllegalStateException(e.getMessage(), e);
            }
            // 检查methods方法是否都存在于interface中,否则抛出异常
            checkInterfaceAndMethods(interfaceClass, methods);
            // 检查ref是否是interfaceClass类型的实现
            checkRef();
            generic = Boolean.FALSE.toString();
        }
        // local:配置本地存根
        if (local != null) {
            if ("true".equals(local)) {
                local = interfaceName + "Local";
            }
            Class<?> localClass;
            try {
            	// 获取本地存根类
                localClass = ClassHelper.forNameWithThreadContextClassLoader(local);
            } catch (ClassNotFoundException e) {
                throw new IllegalStateException(e.getMessage(), e);
            }
            // 检测本地存根类是否实现了接口类,若没有则表示本地存根类类型不合法,抛出异常
            if (!interfaceClass.isAssignableFrom(localClass)) {
                throw new IllegalStateException("The local implementation class " + localClass.getName() + " not implement interface " + interfaceName);
            }
        }
        // local功能一致,用于配置本地存根
        if (stub != null) {
            if ("true".equals(stub)) {
                stub = interfaceName + "Stub";
            }
            Class<?> stubClass;
            try {
                stubClass = ClassHelper.forNameWithThreadContextClassLoader(stub);
            } catch (ClassNotFoundException e) {
                throw new IllegalStateException(e.getMessage(), e);
            }
            if (!interfaceClass.isAssignableFrom(stubClass)) {
                throw new IllegalStateException("The stub implementation class " + stubClass.getName() + " not implement interface " + interfaceName);
            }
        }
        // 检测各种对象是否为空,为空则新建,通过dubbo.properties文件中的配置为对象设置默认值
        checkApplication();
        checkRegistry();
        checkProtocol();
        appendProperties(this);
        // 如果配置了local、stub、mock,则检查其是否合法
        checkStubAndMock(interfaceClass);
        if (path == null || path.length() == 0) {
            path = interfaceName;
        }
        // 导出服务
        doExportUrls();
     	// ProviderModel 表示服务提供者模型,此对象中存储了与服务提供者相关的信息。
    	// 比如服务的配置信息,服务实例等。每个被导出的服务对应一个 ProviderModel。
     	// ApplicationModel 持有所有的 ProviderModel。
        ProviderModel providerModel = new ProviderModel(getUniqueServiceName(), this, ref);
        ApplicationModel.initProviderModel(getUniqueServiceName(), providerModel);
    }

doExportUrls()

Dubbo 允许我们使用不同的协议导出服务,也允许我们向多个注册中心注册服务。Dubbo 在 doExportUrls 方法中对多协议,多注册中心进行了支持。
代码逻辑:
    1)通过 loadRegistries 加载注册中心链接
    2)遍历 ProtocolConfig 集合导出每个服务,并在导出服务的过程中,将服务注册到注册中心
相关代码如下:

private void doExportUrls() {
    	// 加载注册中心链接
        List<URL> registryURLs = loadRegistries(true);
        // 下面的for循环,代表了一个服务可以有多个通信协议,如:TCP、HTTP等
        for (ProtocolConfig protocolConfig : protocols) {// <dubbo:protocol name="dubbo" port="20880" id="dubbo" />
        	// 将registryURLs的url暴露出去[暴露本地服务][暴露远程服务]
            doExportUrlsFor1Protocol(protocolConfig, registryURLs);
        }
    }

loadRegistries()

代码逻辑:
    1)检测是否存在注册中心配置类,不存在则抛出异常
    2)构建参数映射集合,也就是 map
    3)解析map,构建注册中心链接列表
    4)遍历链接列表,并根据条件决定是否将其添加到 registryList 中

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();	// address = zookeeper://127.0.0.1:2181
                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 是否合法
                if (address.length() > 0 && !RegistryConfig.NO_AVAILABLE.equalsIgnoreCase(address)) {
                    Map<String, String> map = new HashMap<String, String>();
                    // 添加 ApplicationConfig 中的字段信息到 map 中
                    appendParameters(map, application);/
                    // 添加 RegistryConfig 字段信息到 map 中
                    appendParameters(map, config);
                    
                    // 添加 path、pid,protocol 等信息到 map 中
                    map.put("path", RegistryService.class.getName());
                    map.put("dubbo", Version.getVersion());
                    map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));
                    // 添加pid信息到map中
                    if (ConfigUtils.getPid() > 0) {
                        map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));
                    }
                    if (!map.containsKey("protocol")) {
                    // 获取RegistryFactory的实现类(SPI配置中只有两个ZookeeperRegistryFactory、MulticastRegistryFactory),他们的key都不是remote
                        if (ExtensionLoader.getExtensionLoader(RegistryFactory.class).hasExtension("remote")) {
                            map.put("protocol", "remote");
                        } else {
                            map.put("protocol", "dubbo");
                        }
                    }
                    // 解析map得到 URL 列表,address 可能包含多个注册中心 ip,因此解析得到的是一个 URL 列表
                    List<URL> urls = UrlUtils.parseURLs(address, map);
                    for (URL url : urls) {												  
                        url = url.addParameter("registry", url.getProtocol());
                     	// 将 URL 协议头设置为 registry
                        url = url.setProtocol("registry");
                     	// 通过判断条件,决定是否添加 url 到 registryList 中
                     		// provider 是方法参数,值为true
                        if ((provider && url.getParameter("registry", true))
                                || (!provider && url.getParameter("subscribe", true))) {
                            registryList.add(url);
                        }
                    }
                }
            }
        }
        return registryList;
    }

doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List registryURLs)

代码逻辑:
    1)将一些信息,比如版本、时间戳、方法名以及各种配置对象的字段信息放入到 map
    2)构建好 map 后,紧接着是获取上下文路径、主机名以及端口号等信息
    3)最后将 map 和主机名等数据传给 URL 构造方法创建 URL 对象,通过 URL 可让 Dubbo 的各种配置在各个模块之间传递
    4)暴露本地服务
注:源码中大量的代码都是为了组装URL信息,而真正的本地服务暴露方法为exportLocal(URL)

 private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
 

/* ------从这个注释开始,到下面的这个注释结束-----这些代码都是为了组装URL的逻辑,并不重要-----*/

        String name = protocolConfig.getName();
     	// 如果协议名为空,或空串,则将协议名变量设置为 dubbo
        if (name == null || name.length() == 0) {
            name = "dubbo";
        }

        Map<String, String> map = new HashMap<String, String>();
     	// 添加 side、版本、时间戳以及进程号等信息到 map 中
        map.put(Constants.SIDE_KEY, Constants.PROVIDER_SIDE);
        map.put(Constants.DUBBO_VERSION_KEY, Version.getVersion());
        map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));// {side=provider, dubbo=2.0.0, pid=23724, timestamp=1598602654234}
        if (ConfigUtils.getPid() > 0) {
            map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));
        }
     // 通过反射将对象的字段信息添加到 map 中
        appendParameters(map, application);
        appendParameters(map, module);
        appendParameters(map, provider, Constants.DEFAULT_KEY);
        appendParameters(map, protocolConfig);
        appendParameters(map, this);
     	// methods 为 MethodConfig 集合,MethodConfig 中存储了 <dubbo:method> 标签的配置信息
        if (methods != null && !methods.isEmpty()) {
        	// 这段代码用于添加 Callback 配置到 map 中
            for (MethodConfig method : methods) {
            	// 添加 MethodConfig 对象的字段信息到 map 中,键 = 方法名.属性名。
                // 比如存储 <dubbo:method name="sayHello" retries="2"> 对应的 MethodConfig,键 = sayHello.retries,map = {"sayHello.retries": 2}
                appendParameters(map, method, method.getName());
                String retryKey = method.getName() + ".retry";
                if (map.containsKey(retryKey)) {
                    String retryValue = map.remove(retryKey);
                 	// 检测 MethodConfig retry 是否为 false,若是,则设置重试次数为0
                    if ("false".equals(retryValue)) {
                        map.put(method.getName() + ".retries", "0");
                    }
                }
             	// 获取 ArgumentConfig 列表
                List<ArgumentConfig> arguments = method.getArguments();
                if (arguments != null && !arguments.isEmpty()) {
                    for (ArgumentConfig argument : arguments) {
                        // 检测 type 属性是否为空,或者空串
                        if (argument.getType() != null && argument.getType().length() > 0) {
                            Method[] methods = interfaceClass.getMethods();
                            // visit all methods
                            if (methods != null && methods.length > 0) {
                                for (int i = 0; i < methods.length; i++) {
                                    String methodName = methods[i].getName();
                                    // // 比对方法名,查找目标方法
                                    if (methodName.equals(method.getName())) {
                                        Class<?>[] argtypes = methods[i].getParameterTypes();
                                        // one callback in the method
                                        if (argument.getIndex() != -1) {
                                        	// 检测 ArgumentConfig 中的 type 属性与方法参数列表中的参数名称是否一致,不一致则抛出异常
                                            if (argtypes[argument.getIndex()].getName().equals(argument.getType())) {
                                            	// 添加 ArgumentConfig 字段信息到 map 中,
                                                // 键前缀 = 方法名.index,比如: map = {"sayHello.3": true}
                                                appendParameters(map, argument, method.getName() + "." + argument.getIndex());
                                            } else {
                                                throw new IllegalArgumentException("argument config error : the index attribute and type attribute not match :index :" + argument.getIndex() + ", type:" + argument.getType());
                                            }
                                        } else {
                                            for (int j = 0; j < argtypes.length; j++) {
                                                Class<?> argclazz = argtypes[j];
                                                // 从参数类型列表中查找类型名称为 argument.type 的参数
                                                if (argclazz.getName().equals(argument.getType())) {
                                                    appendParameters(map, argument, method.getName() + "." + j);
                                                    if (argument.getIndex() != -1 && argument.getIndex() != j) {
                                                        throw new IllegalArgumentException("argument config error : the index attribute and type attribute not match :index :" + argument.getIndex() + ", type:" + argument.getType());
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        } else if (argument.getIndex() != -1) {
                        	// 用户未配置 type 属性,但配置了 index 属性,且 index != -1
                        	// 添加 ArgumentConfig 字段信息到 map 中
                            appendParameters(map, argument, method.getName() + "." + argument.getIndex());
                        } else {
                            throw new IllegalArgumentException("argument config must set index or type attribute.eg: <dubbo:argument index='0' .../> or <dubbo:argument type=xxx .../>");
                        }

                    }
                }
            } // end of methods for
        }

     // 检测 generic 是否为 "true",如果是则根据检测结果向 map 中添加不同的信息(generic="true"代表ref是泛化服务类型  )
        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);
            }
         // 为接口生成包裹类 Wrapper,Wrapper 中包含了接口的详细信息,比如接口方法名数组,字段信息等
            String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();
         // 添加方法名到 map 中,如果包含多个方法名,则用逗号隔开,比如 method = init,destroy
            if (methods.length == 0) {
                logger.warn("NO method found in service interface " + interfaceClass.getName());
                map.put(Constants.METHODS_KEY, Constants.ANY_VALUE);
            } else {
            	// 将逗号作为分隔符连接方法名,并将连接后的字符串放入 map 中
                map.put(Constants.METHODS_KEY, StringUtils.join(new HashSet<String>(Arrays.asList(methods)), ","));
            }
        }
     // 添加 token 到 map 中
        if (!ConfigUtils.isEmpty(token)) {
            if (ConfigUtils.isDefault(token)) {
            	// 随机生成 token
                map.put(Constants.TOKEN_KEY, UUID.randomUUID().toString());
            } else {
                map.put(Constants.TOKEN_KEY, token);
            }
        }
     // 判断协议名是否为 injvm
        if (Constants.LOCAL_PROTOCOL.equals(protocolConfig.getName())) {
            protocolConfig.setRegister(false);
            map.put("notify", "false");
        }
        // 获取上下文路径
        String contextPath = protocolConfig.getContextpath();
        if ((contextPath == null || contextPath.length() == 0) && provider != null) {
            contextPath = provider.getContextpath();
        }
     // 获取 host 和 port
        String host = this.findConfigedHosts(protocolConfig, registryURLs, map);
        Integer port = this.findConfigedPorts(protocolConfig, name, map);
     // 组装 URL
        URL url = new URL(name, host, port, (contextPath == null || contextPath.length() == 0 ? "" : contextPath + "/") + path, map);




/* --------------上面的代码都是组装URL, 下面才是暴露服务的逻辑-----------------------*/



		// spi加载的ConfiguratorFactory实现类是
        	// override=com.alibaba.dubbo.rpc.cluster.configurator.override.OverrideConfiguratorFactory
        	// absent=com.alibaba.dubbo.rpc.cluster.configurator.absent.AbsentConfiguratorFactory
        // url.getProtocol()在本例中值为dubbo,所以不会进入判断
        if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
                .hasExtension(url.getProtocol())) {
        	
        	// 加载 ConfiguratorFactory,并生成 Configurator 实例,然后通过实例配置 url	
            url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
                    .getExtension(url.getProtocol()).getConfigurator(url).configure(url);
        }

        String scope = url.getParameter("scope");
        // scope 为none表示不暴露
        if (!Constants.SCOPE_NONE.toString().equalsIgnoreCase(scope)) {

        	// 配置不是remote的情况下做本地暴露(配置remote则只暴露远程服务)
            if (!Constants.SCOPE_REMOTE.toString().equalsIgnoreCase(scope)) {
                exportLocal(url);	//暴露本地服务
            }
            
            // 配置不是local的情况下做远程暴露(配置local则只暴露本地服务)
            if (!Constants.SCOPE_LOCAL.toString().equalsIgnoreCase(scope)) {
            	// ...这里的内容是暴露远程服务,不是本节的内容,代码省略...
            }
        }
        this.urls.add(url);
    }

exportLocal(URL)

exportLocal方法是暴露本地服务的方法。
代码逻辑:
    1)根据 URL 协议头判断是否已经暴露,如果已暴露,则无需再次暴露
    2)如果需要暴露,则创建一个新的 URL 并将协议头、主机名以及端口设置成新的值
    3)创建 Invoker,并调用 InjvmProtocolexport 方法导出服务

private void exportLocal(URL url) {
    	// 如果 URL 的协议头等于 injvm,说明已经导出到本地了,无需再次导出
        if (!Constants.LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {
            URL local = URL.valueOf(url.toFullString())
                    .setProtocol(Constants.LOCAL_PROTOCOL)	// 设置协议头为 injvm
                    .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);
            logger.info("Export dubbo service " + interfaceClass.getName() + " to local registry");
        }
    }

其中protocol 和proxyFactory 的生成方式我们在 《dubbo spi的实现》中讲过,
代码如下:

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

他们通过dubbo自带的编译器编译成了Protocol$AdaptiveProxyFactory$Adaptive这两个类,读者可以自行编译查询他们的代码。最终他们调用的接口实现类分别是:InjvmProtocolJavassistProxyFactory

JavassistProxyFactory

名词解释:

Wrapper:包装一个类或接口,类似于spring的BeanWrapper,可以通过Wrapper对实例对象进行赋值、取值以及对方法的调用
Invoker:它是一个可执行对象,能够根据方法的名称、参数得到相应的结果

上面exportLocal(URL)的方法中执行了proxyFactory.getInvoker(ref, (Class) interfaceClass, local)生成了一个Invoker对象,而实际上执行的是JavassistProxyFactorygetInvoker()方法:将服务对象(如:DemoServiceImpl)包装成一个invoker对象

代码如下:

@Override
    public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
        // TODO Wrapper cannot handle this scenario correctly: the classname contains '$'
    	// 为目标类创建 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);
            }
        };
    }

InjvmProtocol

创建一个InjvmExporter对象

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

到此导出服务到本地就分析完了

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值