dubbo源碼學習(1)--服務發布

一、demo構建

1. demo項目結構如圖所示:
demo項目結構.jpg

包含api,consumer,provider三個module
api如下所示,提供provider需要暴露的接口

package com.alibaba.dubbo.demo;

import java.util.List;

/**
 * Created by Xkane on 2018/9/13.
 */
public interface DemoService {
    List<String> getPermissions(Long id);
}
2. provider 發布服務配置文件
項目provider配置文件.jpg
3. consumer訂閱服務配置文件
demo-consumer的配置文件.jpg

二、dubbo框架結構

dubbo框架設計總共分了10層:
1. 服務接口層(Service):
    該層是與實際業務邏輯相關,就如上面demo配置的 
    <!--使用 dubbo 協議實現定義好的 api.PermissionService 接口-->
    <dubbo:service interface="com.alibaba.dubbo.demo.DemoService" ref="demoService" protocol="dubbo" />
    這個service就是業務方自己定義的接口與其實現。
2.配置層(Config):
    該層是將業務方的service信息,配置文件的信息進行收集配置,主要是以ServiceConfig和ReferenceConfig為中心,
ServiceConfig是配置提供方provider提供的配置,當Spring啟動的時候會響應的啟動provider服務發布和注冊的過程,
主要是加入一個ServiceBean繼承ServiceConfig在spring注冊,同理ReferenceConfig是consumer方的配置,
當消費方consumer啟動時,會啟動consumer的發布服務訂閱服務的過程,當然也是使用一個ReferenceBean繼承
ReferenceConfig注冊在spring上
3. 服務代理層(Proxy):
   對服務接口進行透明代理,生成服務的客戶端和服務端,使服務的遠程調用就像在本地調用一樣,默認使用JavassistProxyFactory,
返回一個Invoker,Invoker則是個可執行核心實體,Invoker的invoke方法通過反射執行service方法。
4. 服務注冊層(Registry):
    封裝服務地址的注冊和發現,以服務URL為中心,基于zk。
5. 集群層(Cluster): 
    封裝多個提供者的路由及負載均衡,并橋接注冊中心,以Invoker為中心,擴展接口為Cluster、Directory、Router和LoadBalance。
    將多個服務提供方組合為一個服務提供方,實現對服務消費方來透明,只需要與一個服務提供方進行交互。
6. 監控層(Monitor):
    RPC調用次數和調用時間監控,以Statistics為中心,擴展接口為MonitorFactory、Monitor和MonitorService。
7. 遠程調用層(Protocol):
    封裝RPC調用,provider通過export方法進行暴露服務/consumer通過refer方法調用服務。而Protocol依賴的是Invoker。通過上面說的Proxy獲得的Invoker,包裝成Exporter。
8.信息交換層(Exchange):
    該層封裝了請求響應模型,將同步轉為異步,信息交換層依賴Exporter,最終將通過網絡傳輸層接收調用請求RequestFuture和ResponseFuture。
9.網絡傳輸層(Transport):
    抽象mina和netty為統一接口,以Message為中心,擴展接口為Channel、Transporter、Client、Server和Codec。
10.數據序列化層:
    該層無需多言,將數據序列化反序列化。

三、dubbo發布過程

發布過程的一些動作

暴露本地服務
暴露遠程服務
啟動netty
連接zookeeper
到zookeeper注冊
監聽zookeeper

流程圖:


RPC發布流程圖(引用).png

dubbo發布服務涉及到的相關類。


類圖.png

上圖展示了部分服務發布過程中需要使用到的類和接口,其中:

spring適配涉及到的類DubboNamespaceHandler、DubboBeanDefinitionParser、ServiceBean;
配置信息存儲ServicdConfig、RegistryConfig、MonitorConfig、ProtocolConfig、ProviderConfig等;
應用協議Protocol、DubboProtocol、HessianProtocol、ThriftProtocol、RmiProtocol、AbstractProxyProtocol、AbstractProtocol等;
Server相關Exchanger、HeaderExchanger、ExchangeServer、HeaderExchangeServer、Transporters、Transporter、NettyTransporter、NettyServer等;

  1. 通過demo配置文件查看
    <!--定義了提供方應用信息,用于計算依賴關系;在 dubbo-admin 或 dubbo-monitor 會顯示這個名字,方便辨識-->
    <dubbo:application name="demotest-provider" owner="programmer" organization="dubbox"/>

    <!--使用 zookeeper 注冊中心暴露服務,注意要先開啟 zookeeper-->
    <dubbo:registry address="zookeeper://localhost:2181"/>

    <!-- 用dubbo協議在20880端口暴露服務 -->
    <dubbo:protocol name="dubbo" port="20880" />

    <!--使用 dubbo 協議實現定義好的 api.PermissionService 接口-->
    <dubbo:service interface="com.alibaba.dubbo.demo.DemoService" ref="demoService" protocol="dubbo" />

    <!--具體實現該接口的 bean-->
    <bean id="demoService" class="com.alibaba.dubbo.demo.impl.DemoServiceImpl"/>

是通過dubbo的schema service進行注入的,找到DubboNameSpaceHandler,Dubbo命名空間處理器,找到<dubbo:service>標簽解析行:
此時,進入DubboNameSpaceHandler類,初始化配置文件,spring容器通過DubboBeanDefinitionParser類的parse方法來解xml文件中的標簽,生成ServiceConfig等配置對象;

public class DubboNamespaceHandler extends NamespaceHandlerSupport {

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

    public void init() {
        //<dubbo:application>標簽解析行
        registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.class, true));
        registerBeanDefinitionParser("module", new DubboBeanDefinitionParser(ModuleConfig.class, true));
         //<dubbo:registry>標簽解析行,本例中使用 zookeeper 注冊中心暴露服務
        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));
        //<dubbo:protocol>標簽解析行,例如,demo中用dubbo協議在20880端口暴露服務 
        registerBeanDefinitionParser("protocol", new DubboBeanDefinitionParser(ProtocolConfig.class, true));
        //<dubbo:service>標簽解析行
        registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));
        registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false));
        registerBeanDefinitionParser("annotation", new DubboBeanDefinitionParser(AnnotationBean.class, true));
    }
}

從上述代碼中可以看到 registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));會把<dubbo:service> 標簽會被解析成 ServiceBeanServiceBean 實現了 InitializingBean,在類加載完成之后會調用afterPropertiesSet() 方法。在afterPropertiesSet() 方法中,依次解析以下標簽信息:

<dubbo:provider>
<dubbo:application>
<dubbo:module>
<dubbo:registry>
<dubbo:monitor>
<dubbo:protocol>

在 Spring 容器初始化的時候會調用 onApplicationEvent方法,即spring的事件機制(event),監聽Spring 容器初始化ServiceBean重寫了 onApplicationEvent方法,實現了服務暴露的功能。

延遲方法.png

dubbo暴露服務有兩種情況,一種是設置了延遲暴露(比如delay=”5000”),另外一種是沒有設置延遲暴露或者延遲設置為-1(delay=”-1”):

1、設置了延遲暴露: dubbo在Spring實例化bean(initializeBean)的時候會對實現了InitializingBean的類進行回調,回調方法是afterPropertySet(),如果設置了延遲暴露,dubbo在這個方法中進行服務的發布。

2、沒有設置延遲或者延遲為-1: dubbo會在Spring實例化完bean之后,在刷新容器最后一步發布ContextRefreshEvent事件的時候,通知實現了ApplicationListener的類進行回調onApplicationEvent,dubbo會在這個方法中發布服務。

onApplicationEvent.png

ServiceConfig.export()的流程圖如下:

ServiceConfig.export()的流程圖.png

<1> export()方法
export()進行服務發布,是發布核心。
由上面代碼可知,如果設置了 delay 參數,Dubbo 的處理方式是啟動一個守護線程在 sleep 指定時間后再doExport()
export.png

從export代碼中可以看到,export先判斷是否需要延遲暴露,如果設置了延遲delay!=null,則通過一個后臺線程Thread.sleep(delay)延遲調用doExport()方法;反之如果不存愛delay,直接調用doExport()方法。
<2> doExport()

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 (application == null) {
                application = provider.getApplication();
            }
            if (module == null) {
                module = provider.getModule();
            }
            if (registries == null) {
                registries = provider.getRegistries();
            }
            if (monitor == null) {
                monitor = provider.getMonitor();
            }
            if (protocols == null) {
                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();
            }
        }
        if (ref instanceof GenericService) {
            interfaceClass = GenericService.class;
            generic = true;
        } else {
            try {
                interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()
                        .getContextClassLoader());
            } catch (ClassNotFoundException e) {
                throw new IllegalStateException(e.getMessage(), e);
            }
            checkInterfaceAndMethods(interfaceClass, methods);
            checkRef();
            generic = false;
        }
        if(local !=null){
            if(local=="true"){
                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 implemention class " + localClass.getName() + " not implement interface " + interfaceName);
            }
        }
        if(stub !=null){
            if(stub=="true"){
                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 implemention class " + stubClass.getName() + " not implement interface " + interfaceName);
            }
        }
        checkApplication();
        checkRegistry();
        checkProtocol();
        appendProperties(this);
        checkStubAndMock(interfaceClass);
        if (path == null || path.length() == 0) {
            path = interfaceName;
        }
        doExportUrls();
    }

?? doExport方法先執行一系列的檢查方法,然后調用doExportUrls方法。檢查方法會檢測dubbo的配置是否在Spring配置文件中聲明,沒有的話讀取properties文件初始化。

doExportUrls.png

??doExportUrls方法先調用loadRegistries加載注冊信息,組裝注冊中心url信息,如源碼中config.properties中讀取鏈接信息,組裝Provider注冊鏈接串; 然后遍歷調用doExportUrlsFor1Protocol方法。 里面有個for循環,代表一個服務可以有多個通信協議,例如tcp、http、dubbo等協議,dubbo支持多種協議,默認使用的是dubbo協議。
??debug圖中可以看到,<dubbo:protocol>name="dubbo"為dubbo協議。
可以在配置文件中進行配置協議:
??<dubbo:protocol name="dubbo" port="20880" />

其他幾種協議對比:

1 dubbo協議
   連接個數:單連接 
   連接方式:長連接 
   傳輸協議:TCP 
   傳輸方式:NIO異步傳輸 
   序列化:Hessian二進制序列化 
   適用范圍:傳入傳出參數數據包較小(建議小于100K),消費者比提供者個數多,單一消費者無法壓滿提供者,盡量不要用dubbo協議傳輸大文件或超大字符串。 
   適用場景:常規遠程服務方法調用
   注:
   1)、dubbo默認采用dubbo協議,dubbo協議采用單一長連接和NIO異步通訊,適合于小數據量大 并發的服務調用,以及服務消費者機器數遠大于服務提供者機器數的情況 
   2)、他不適合傳送大數據量的服務,比如傳文件,傳視頻等,除非請求量很低。
   3)、為防止被大量連接撐掛,可在服務提供方限制大接收連接數,以實現服務提供方自我保護 
         <dubbo:protocol name="dubbo" accepts="1000" />
2 rmi協議
   Java標準的遠程調用協議。 
   連接個數:多連接 
   連接方式:短連接 
   傳輸協議:TCP 
   傳輸方式:同步傳輸 
   序列化:Java標準二進制序列化 
   適用范圍:傳入傳出參數數據包大小混合,消費者與提供者個數差不多,可傳文件。 
   適用場景:常規遠程服務方法調用,與原生RMI服務互操作
   1)、RMI協議采用JDK標準的java.rmi.*實現,采用阻塞式短連接和JDK標準序列化方式 

3 hessian協議
   基于Hessian的遠程調用協議。 
   連接個數:多連接 
   連接方式:短連接 
   傳輸協議:HTTP 
   傳輸方式:同步傳輸 
   序列化:  表單序列化 
   適用范圍:傳入傳出參數數據包大小混合,提供者比消費者個數多,可用瀏覽器查看,可用表單或URL傳入參數,暫不支持傳文件。 
   適用場景:需同時給應用程序和瀏覽器JS使用的服務。
   約束 
      1)、參數及返回值需實現Serializable接口 
      2)、參數及返回值不能自定義實現List, Map, Number, Date, Calendar等接口,只能用JDK自帶的實現,因為hessian會做特殊處理,自定義實現類中的屬性值都會丟失。

4 http協議
   基于http表單的遠程調用協議。參見:[HTTP協議使用說明] 
   連接個數:多連接 
   連接方式:短連接 
   傳輸協議:HTTP 
   傳輸方式:同步傳輸 
   序列化:表單序列化 
   適用范圍:傳入傳出參數數據包大小混合,提供者比消費者個數多,可用瀏覽器查看,可用表單或URL傳入參數,暫不支持傳文件。 
   適用場景:需同時給應用程序和瀏覽器JS使用的服務。

5 webservice協議
   基于WebService的遠程調用協議。 
   連接個數:多連接 
   連接方式:短連接 
   傳輸協議:HTTP 
   傳輸方式:同步傳輸 
   序列化:SOAP文本序列化 
   適用場景:系統集成,跨語言調用

6 thrift協議
  當前 dubbo 支持的 thrift 協議是對 thrift 原生協議的擴展,在原生協議的基礎上添加了一些額外的頭信息,比如service name,magic number等。使用dubbo thrift協議同樣需要使用thrift的idl compiler編譯生成相應的java代碼,后續版本中會在這方面做一些增強 。
  注意:
     Thrift不支持null值,不能在協議中傳null

7 memcached協議
  可以通過腳本或監控中心手工填寫表單注冊memcached服務的地址。

8 redis協議
  可以通過腳本或監控中心手工填寫表單注冊redis服務的地址。

??繼續debug代碼,進入方法doExportUrlsFor1Protocol:
doExportUrlsFor1Protocol()先將Bean屬性轉換成URL對象,然后根據不同協議將服務已URL形式發布。如果scope配置為none則不暴露,如果服務未配置成remote,則本地暴露exportLocal,如果未配置成local,則遠程暴露。
??方法第一部分:

private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
       //獲取是哪種協議,如果未配置,默認為dubbo
        String name = protocolConfig.getName();
        if (name == null || name.length() == 0) {
            name = "dubbo";
        }
       //獲取host
        String host = protocolConfig.getHost();  //從配置中獲取
        if (provider != null && (host == null || host.length() == 0)) {
            host = provider.getHost();
        }
        boolean anyhost = false;
        if (NetUtils.isInvalidLocalHost(host)) {  //為了確保獲得的主機有效,還有相應的驗證:
            anyhost = true;
            try {
                host = InetAddress.getLocalHost().getHostAddress();//返回本機的ip地址
            } catch (UnknownHostException e) {
                logger.warn(e.getMessage(), e);
            }
            if (NetUtils.isInvalidLocalHost(host)) {
                if (registryURLs != null && registryURLs.size() > 0) {
                    for (URL registryURL : registryURLs) {
                        try {
                            Socket socket = new Socket();
                            try {
                                 //通過獲取注冊中心地址和端口號,創建socket連接地址
                                SocketAddress addr = new InetSocketAddress(registryURL.getHost(), registryURL.getPort());
                                socket.connect(addr, 1000);
                                host = socket.getLocalAddress().getHostAddress();
                                break;
                            } finally {
                                try {
                                    socket.close();
                                } catch (Throwable e) {}
                            }
                        } catch (Exception e) {
                            logger.warn(e.getMessage(), e);
                        }
                    }
                }
                if (NetUtils.isInvalidLocalHost(host)) {
                    host = NetUtils.getLocalHost();//遍歷本地網卡,返回第一個合理的IP
                }
            }
        }
       //獲取各個協義需要暴露的端口, 按照這樣的優先級去獲取  :   
  //Protocol的實現類的默認端口 ——> Protocol的配置端口 ——> 隨機端口
        Integer port = protocolConfig.getPort();
        if (provider != null && (port == null || port == 0)) {
            port = provider.getPort();
        }
        final int defaultPort = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(name).getDefaultPort();
        if (port == null || port == 0) {
            port = defaultPort;
        }
        if (port == null || port <= 0) {
            port = getRandomPort(name);
            if (port == null || port < 0) {
                port = NetUtils.getAvailablePort(defaultPort);
                putRandomPort(name, port);
            }
            logger.warn("Use random available port(" + port + ") for protocol " + name);
        }

??第二部分:收集各類參數,放入map中,在為服務暴露做參數收集準備工作

        // paramsMap,存放所有配置參數,下面生成url用。
        Map<String, String> map = new HashMap<String, String>();
        if (anyhost) {
            map.put(Constants.ANYHOST_KEY, "true");
        }
        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()));
        if (ConfigUtils.getPid() > 0) {
            map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));
        }
        appendParameters(map, application);
        appendParameters(map, module);
        appendParameters(map, provider, Constants.DEFAULT_KEY);
        appendParameters(map, protocolConfig);
        appendParameters(map, this);
        // method子標簽配置規則解析,retry次數,參數等。沒有使用過,不做解釋
        if (methods != null && methods.size() > 0) {//服務接口的方法
            for (MethodConfig method : methods) {
                appendParameters(map, method, method.getName());
                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<ArgumentConfig> arguments = method.getArguments();
                if (arguments != null && arguments.size() > 0) {
                    for (ArgumentConfig argument : arguments) {
                        //類型自動轉換.
                        if(argument.getType() != null && argument.getType().length() >0){
                            Method[] methods = interfaceClass.getMethods();
                            //遍歷所有方法
                            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();
                                        //一個方法中單個callback
                                        if (argument.getIndex() != -1 ){
                                            if (argtypes[argument.getIndex()].getName().equals(argument.getType())){
                                                appendParameters(map, argument, method.getName() + "." + argument.getIndex());
                                            }else {
                                                throw new IllegalArgumentException("argument config error : the index attribute and type attirbute not match :index :"+argument.getIndex() + ", type:" + argument.getType());
                                            }
                                        } else {
                                            //一個方法中多個callback
                                            for (int j = 0 ;j<argtypes.length ;j++) {
                                                Class<?> argclazz = argtypes[j];
                                                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 attirbute not match :index :"+argument.getIndex() + ", type:" + argument.getType());
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }else if(argument.getIndex() != -1){
                            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
        }

        if (generic) {
            map.put("generic", String.valueOf(true));
            map.put("methods", Constants.ANY_VALUE);
        } else {
            String revision = Version.getVersion(interfaceClass, version);
            if (revision != null && revision.length() > 0) {
                map.put("revision", revision);
            }

            String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();
            if(methods.length == 0) {
                logger.warn("NO method found in service interface " + interfaceClass.getName());
                map.put("methods", Constants.ANY_VALUE);
            }
            else {
                map.put("methods", StringUtils.join(new HashSet<String>(Arrays.asList(methods)), ","));
            }
        }
        if (! ConfigUtils.isEmpty(token)) {
            if (ConfigUtils.isDefault(token)) {
                map.put("token", UUID.randomUUID().toString());
            } else {
                map.put("token", token);
            }
        }
        if ("injvm".equals(protocolConfig.getName())) {
            protocolConfig.setRegister(false);
            map.put("notify", "false");
        }

??第三部分:
??這段主要是拼接URL,Dubbo框架是以URL為總線的模式,即運行過程中所有的狀態數據信息都可以通過URL來獲取,比如當前系統采用什么序列化,采用什么通信,采用什么負載均衡等信息,都是通過URL的參數來呈現的,所以在框架運行過程中,運行到某個階段需要相應的數據,都可以通過對應的Key從URL的參數列表中獲取。
本例url: dubbo://xxx.xxx.xxx.xxx:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demotest-provider&dubbo=2.5.3&interface=com.alibaba.dubbo.demo.DemoService&methods=getPermissions&organization=dubbox&owner=programmer&pid=35270&side=provider&timestamp=1539154732792

        // 導出服務
        String contextPath = protocolConfig.getContextpath();//獲取協議的上下文路徑
        if ((contextPath == null || contextPath.length() == 0) && provider != null) {
            contextPath = provider.getContextpath();
        }
       // 根據參數創建url對象
        URL url = new URL(name, host, port, (contextPath == null || contextPath.length() == 0 ? "" : contextPath + "/") + path, map);
       // 如果url使用的協議存在擴展,調用對應的擴展來修改原url。目前擴展有override,absent
        if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
                .hasExtension(url.getProtocol())) {
            url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
                    .getExtension(url.getProtocol()).getConfigurator(url).configure(url);
        }

       //根據scope的配置決定是作本地暴露還是遠程暴露,
       //做服務暴露從結果上看就是產生了一個特定服務的 Exporter 類,
       //并將其存儲在對應的ServiceBean實例的 exporters屬性中。
        String scope = url.getParameter(Constants.SCOPE_KEY);
        //配置為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) ){
                if (logger.isInfoEnabled()) {
                    logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
                }
                //只有遠程暴露才需要用到注冊中心url
                if (registryURLs != null && registryURLs.size() > 0
                        && url.getParameter("register", true)) {
                    for (URL registryURL : registryURLs) {
                        url = url.addParameterIfAbsent("dynamic", registryURL.getParameter("dynamic"));
                        //獲取監控中心的URL
                        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);
                        }
                        Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));

                        Exporter<?> exporter = protocol.export(invoker);
                        exporters.add(exporter);
                    }
                } else {
                    Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url);

                    Exporter<?> exporter = protocol.export(invoker);
                    exporters.add(exporter);
                }
            }
        }
        this.urls.add(url);

本地暴露
此處demo,為本地暴露,此時,調用exportLocal(url);生成exporter

 private void exportLocal(URL url) {
        if (!Constants.LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {
            URL local = URL.valueOf(url.toFullString())
                    .setProtocol(Constants.LOCAL_PROTOCOL)
                    .setHost(NetUtils.LOCALHOST)
                    .setPort(0);
            //這里的protocol是Protocol$Adpative的實例(spi機制)
            //proxyFactory是ProxyFactory$Adpative實例(spi機制)
            Exporter<?> exporter = protocol.export(
                    proxyFactory.getInvoker(ref, (Class) interfaceClass, local));
            exporters.add(exporter);
            logger.info("Export dubbo service " + interfaceClass.getName() +" to local registry");
        }
    }

上述exportLocal方法參數url,為:
dubbo://xxx.xxx.xxx.xxx:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demotest-provider&dubbo=2.5.3&interface=com.alibaba.dubbo.demo.DemoService&methods=getPermissions&organization=dubbox&owner=programmer&pid=78965&side=provider&timestamp=1539163125437
上述方法中URL local 重新構造url,本地暴露的url是以injvm開頭的,host修改為本地,端口更改為0,這與遠程發布時不同的,url更換為一下內容:
injvm://127.0.0.1/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demotest-provider&dubbo=2.5.3&interface=com.alibaba.dubbo.demo.DemoService&methods=getPermissions&organization=dubbox&owner=programmer&pid=78965&side=provider&timestamp=1539163125437
這里的proxyFactory.getInvoker使用的是JavassistProxyFactory.getInvoker方法,
對服務接口進行透明代理,生成服務的客戶端和服務端,使服務的遠程調用就像在本地調用一樣,默認使用JavassistProxyFactory,返回一個InvokerInvoker則是個可執行核心實體,Invokerinvoke方法通過反射執行service方法。

public class JavassistProxyFactory extends AbstractProxyFactory {

    @SuppressWarnings("unchecked")
    public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {
        return (T) Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));
    }
    //proxy 是服務實現類,type是服務接口
    public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
        ////利用Wrapper類通過服務接口生成對應的代理類。
        //根據傳入的 proxy對象的類信息創建對它的包裝對象Wrapper, Wrapper類不能正確處理帶$的類名
        final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type);
        return new AbstractProxyInvoker<T>(proxy, type, url) {
           //實現抽象類AbstractProxyInvoker抽象方法doInvoke,
          //并調用(proxy, type, url)構造函數實例化匿名類
            @Override
            protected Object doInvoke(T proxy, String methodName, 
                                      Class<?>[] parameterTypes, 
                                      Object[] arguments) throws Throwable {

                return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
            }
        };
    }

}

最后返回invoker后,使用protocol.export進行發布:
Protocol的實現是RegistryProtocol類,實現了export方法

類及方法的概念作用:
1、proxyFactory:就是為了獲取一個接口的代理類,例如獲取一個遠程接口的代理。
  它有2個方法,代表2個作用
    a、getInvoker:針對server端,將服務對象,例如DemoServiceImpl包裝成一個Wrapper對象。
    b、getProxy:針對client端,創建接口的代理對象,例如DemoService的接口。
2、makeWrapper:它類似spring的BeanWrapper,它就是包裝了一個接口或一個類,可以通過Wrapper對實例對象進行賦值取值以及指定方法的調用。
3、Invoker:它是一個可執行的對象,能夠根據方法名稱、參數得到相應的執行結果。它里面有一個很重要的方法Result invoke(Invocation invocation)Invocation是包含了需要執行的方法和參數的重要信息,目前它只有2個實現類RpcInvocation、 MockInvocation
    它有3種類型的Invoker
      1、本地執行類的Invoker
      2、遠程通信類的Invoker
      3、多個遠程通信執行類的Invoker聚合成集群版的Invoker
4、Protocol:
    1)export暴露遠程服務(用于服務端),就是將proxyFactory.getInvoker創建的代理類invoker對象,通過協議暴露給外部。
    2)refer:引用遠程服務(用于客戶端)
5、Exporter:維護invoker的生命周期
6、exchanger:信息交換層,封裝請求響應模式同步轉異步
7、transporter:網絡傳輸層,用來抽象Netty(dubbo默認)或者Mina的統一接口

暴露本地服務與暴露遠程服務的區別

a、暴露本地服務:指暴露在同一個JVM里面,不用通過zk來進行遠程通信,例如在同一個服務里面(tomcat),自己調用自己的接口,就無需進行網絡IP連接通信。
b、暴露遠程服務:指暴露給遠程客戶端的IP和端口號,通過網絡來實現通信。

遠程暴露

doExportUrlsFor1Protocol方法中if (! Constants.SCOPE_LOCAL.toString().equalsIgnoreCase(scope) )條件,如果配置不是local則暴露為遠程服務.(配置為local,則表示只暴露遠程服務)
public static final String SCOPE_LOCAL = "local";

未完待續

歡迎關注公眾號


image.png
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,501評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,673評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,610評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,939評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,668評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,004評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,001評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,173評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,705評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,426評論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,656評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,139評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,833評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,247評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,580評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,371評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,621評論 2 380

推薦閱讀更多精彩內容