Dubbo中訂閱和通知解析

Dubbo中關(guān)于服務(wù)的訂閱和通知主要發(fā)生在服務(wù)提供方暴露服務(wù)的過程和服務(wù)消費方初始化時候引用服務(wù)的過程中蹲诀。

服務(wù)引用過程中的訂閱和通知

在服務(wù)消費者初始化的過程中,會有一步是進(jìn)行服務(wù)的引用括细,具體的代碼是在RegistryProtocol的refer方法:

public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
    url = url.setProtocol(url.getParameter(Constants.REGISTRY_KEY, Constants.DEFAULT_REGISTRY)).removeParameter(Constants.REGISTRY_KEY);
    //在這一步獲取注冊中心實例的過程中,也會有notify的操作。(這里省略)
    Registry registry = registryFactory.getRegistry(url);
    if (RegistryService.class.equals(type)) {
        return proxyFactory.getInvoker((T) registry, type, url);
    }

    // group="a,b" or group="*"
    Map<String, String> qs = StringUtils.parseQueryString(url.getParameterAndDecoded(Constants.REFER_KEY));
    String group = qs.get(Constants.GROUP_KEY);
    if (group != null && group.length() > 0 ) {
        if ( ( Constants.COMMA_SPLIT_PATTERN.split( group ) ).length > 1
                || "*".equals( group ) ) {
            return doRefer( getMergeableCluster(), registry, type, url );
        }
    }
    return doRefer(cluster, registry, type, url);
}

在refer方法中有一步是獲取注冊中心實例,這一步中也會有一個notify操作领迈,先暫時不解釋。接著就是doRefer方法:

private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
    RegistryDirectory<T> directory = new RegistryDirectory<T>(type, url);
    directory.setRegistry(registry);
    directory.setProtocol(protocol);
    //訂閱的url
    URL subscribeUrl = new URL(Constants.CONSUMER_PROTOCOL, NetUtils.getLocalHost(), 0, type.getName(), directory.getUrl().getParameters());
    if (! Constants.ANY_VALUE.equals(url.getServiceInterface())
            && url.getParameter(Constants.REGISTER_KEY, true)) {
        //服務(wù)消費方向注冊中心注冊自己,供其他層使用狸捅,比如服務(wù)治理
        registry.register(subscribeUrl.addParameters(Constants.CATEGORY_KEY, Constants.CONSUMERS_CATEGORY,
                Constants.CHECK_KEY, String.valueOf(false)));
    }
    //訂閱服務(wù)提供方
    //同時訂閱了三種類型providers衷蜓,routers,configurators尘喝。
    directory.subscribe(subscribeUrl.addParameter(Constants.CATEGORY_KEY, 
            Constants.PROVIDERS_CATEGORY 
            + "," + Constants.CONFIGURATORS_CATEGORY 
            + "," + Constants.ROUTERS_CATEGORY));
    return cluster.join(directory);
}

在doRefer方法中服務(wù)消費者會訂閱服務(wù)磁浇,同時訂閱了三種類型:providers,routers朽褪,configurators置吓。

接續(xù)看directory.subscribe訂閱方法,這里directory是RegistryDirectory:

public void subscribe(URL url) {
    //設(shè)置消費者url
    setConsumerUrl(url);
    //訂閱
    //url為訂閱條件缔赠,不能為空
    //第二個參數(shù)this衍锚,是變更事件監(jiān)聽器,不允許為空嗤堰,RegistryDirectory實現(xiàn)了NotifyListener接口戴质,因此是一個事件監(jiān)聽器
    registry.subscribe(url, this);
}

這里registry是ZookeeperRegistry,在ZookeeperRegistry調(diào)用subscribe處理之前會先經(jīng)過AbstractRegistry的處理踢匣,然后經(jīng)過FailbackRegistry處理告匠,在FailbackRegistry中會調(diào)用ZookeeperRegistry的doSubscribe方法符糊。

首先看下AbstractRegistry中subscribe方法:

public void subscribe(URL url, NotifyListener listener) {
    if (url == null) {
        throw new IllegalArgumentException("subscribe url == null");
    }
    if (listener == null) {
        throw new IllegalArgumentException("subscribe listener == null");
    }
    //從緩存中獲取已經(jīng)訂閱的url的監(jiān)聽器
    Set<NotifyListener> listeners = subscribed.get(url);
    if (listeners == null) {
        subscribed.putIfAbsent(url, new ConcurrentHashSet<NotifyListener>());
        listeners = subscribed.get(url);
    }
    //將當(dāng)前監(jiān)聽器添加到監(jiān)聽器的set中
    listeners.add(listener);
}

然后是FailbackRegistry的subscribe方法:

public void subscribe(URL url, NotifyListener listener) {
    //上面AbstractRegistry的處理
    super.subscribe(url, listener);
    //移除訂閱失敗的
    removeFailedSubscribed(url, listener);
    try {
        // 向服務(wù)器端發(fā)送訂閱請求
        //子類實現(xiàn),我們這里使用的是ZookeeperRegistry
        doSubscribe(url, listener);
    } catch (Exception e) {
        Throwable t = e;

        List<URL> urls = getCacheUrls(url);
        if (urls != null && urls.size() > 0) {
            //訂閱失敗行贪,進(jìn)行通知,重試
            notify(url, listener, urls);
        } else {
            // 如果開啟了啟動時檢測模闲,則直接拋出異常
            boolean check = getUrl().getParameter(Constants.CHECK_KEY, true)
                    && url.getParameter(Constants.CHECK_KEY, true);
            boolean skipFailback = t instanceof SkipFailbackWrapperException;
            if (check || skipFailback) {
                if(skipFailback) {
                    t = t.getCause();
                }
                throw new IllegalStateException("Failed to subscribe " + url + ", cause: " + t.getMessage(), t);
            }
        }

        // 將失敗的訂閱請求記錄到失敗列表建瘫,定時重試
        addFailedSubscribed(url, listener);
    }
}

這里總共進(jìn)行了一下幾件事情:

  • AbstractRegistry的處理
  • 移除訂閱失敗的
  • 由具體的子類向服務(wù)器端發(fā)送訂閱請求
  • 如果訂閱發(fā)生失敗了尸折,嘗試獲取緩存url,然后進(jìn)行失敗通知或者如果開啟了啟動時檢測实夹,則直接拋出異常
  • 將失敗的訂閱請求記錄到失敗列表橄浓,定時重試

主要看下子類向服務(wù)器段發(fā)送訂閱請求的步驟,在ZookeeperRegistry的doSubscribe方法中:

protected void doSubscribe(final URL url, final NotifyListener listener) {
    try {
        if (Constants.ANY_VALUE.equals(url.getServiceInterface())) {//這里暫時沒用到先不解釋
            String root = toRootPath();
            ConcurrentMap<NotifyListener, ChildListener> listeners = zkListeners.get(url);
            if (listeners == null) {
                zkListeners.putIfAbsent(url, new ConcurrentHashMap<NotifyListener, ChildListener>());
                listeners = zkListeners.get(url);
            }
            ChildListener zkListener = listeners.get(listener);
            if (zkListener == null) {
                listeners.putIfAbsent(listener, new ChildListener() {
                    public void childChanged(String parentPath, List<String> currentChilds) {
                        for (String child : currentChilds) {
                            child = URL.decode(child);
                            if (! anyServices.contains(child)) {
                                anyServices.add(child);
                                subscribe(url.setPath(child).addParameters(Constants.INTERFACE_KEY, child, 
                                        Constants.CHECK_KEY, String.valueOf(false)), listener);
                            }
                        }
                    }
                });
                zkListener = listeners.get(listener);
            }
            zkClient.create(root, false);
            List<String> services = zkClient.addChildListener(root, zkListener);
            if (services != null && services.size() > 0) {
                for (String service : services) {
                    service = URL.decode(service);
                    anyServices.add(service);
                    subscribe(url.setPath(service).addParameters(Constants.INTERFACE_KEY, service, 
                            Constants.CHECK_KEY, String.valueOf(false)), listener);
                }
            }
        } else {
            List<URL> urls = new ArrayList<URL>();
            //這里的path分別為providers荸实,routers缴淋,configurators三種
            for (String path : toCategoriesPath(url)) {
                //根據(jù)url獲取對應(yīng)的監(jiān)聽器map
                ConcurrentMap<NotifyListener, ChildListener> listeners = zkListeners.get(url);
                if (listeners == null) {
                    zkListeners.putIfAbsent(url, new ConcurrentHashMap<NotifyListener, ChildListener>());
                    listeners = zkListeners.get(url);
                }
                //根據(jù)我們的listener獲取一個ChildListener實例
                ChildListener zkListener = listeners.get(listener);
                //沒有的話就創(chuàng)建一個ChildListener實例泄朴。
                if (zkListener == null) {
                    listeners.putIfAbsent(listener, new ChildListener() {
                        public void childChanged(String parentPath, List<String> currentChilds) {
                            ZookeeperRegistry.this.notify(url, listener, toUrlsWithEmpty(url, parentPath, currentChilds));
                        }
                    });
                    zkListener = listeners.get(listener);
                }
                //根據(jù)path在Zookeeper中創(chuàng)建節(jié)點露氮,這里就是訂閱服務(wù)
                zkClient.create(path, false);
                //這里zkClient是dubbo的ZookeeperClient,在addChildListener中會轉(zhuǎn)化為ZkClient的Listener
                List<String> children = zkClient.addChildListener(path, zkListener);
                if (children != null) {
                    urls.addAll(toUrlsWithEmpty(url, path, children));
                }
            }
            //訂閱完成之后畔规,進(jìn)行通知
            notify(url, listener, urls);
        }
    } catch (Throwable e) {
        throw new RpcException("Failed to subscribe " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);
    }
}

上面主要是分別對providers,routers详民,configurators三種不同類型的進(jìn)行訂閱陌兑,也就是往zookeeper中注冊節(jié)點,注冊之前先給url添加監(jiān)聽器兔综。最后是訂閱完之后進(jìn)行通知。

notify方法涧窒,這里notify方法實現(xiàn)是在ZookeeperRegistry的父類FailbackRegistry中:

protected void notify(URL url, NotifyListener listener, List<URL> urls) {
    if (url == null) {
        throw new IllegalArgumentException("notify url == null");
    }
    if (listener == null) {
        throw new IllegalArgumentException("notify listener == null");
    }
    try {
        //doNotify方法中沒做處理锭亏,直接調(diào)用父類的notify方法
        doNotify(url, listener, urls);
    } catch (Exception t) {
        // 將失敗的通知請求記錄到失敗列表,定時重試
        Map<NotifyListener, List<URL>> listeners = failedNotified.get(url);
        if (listeners == null) {
            failedNotified.putIfAbsent(url, new ConcurrentHashMap<NotifyListener, List<URL>>());
            listeners = failedNotified.get(url);
        }
        listeners.put(listener, urls);
    }
}

看下AbstractRegistry的notify方法:

protected void notify(URL url, NotifyListener listener, List<URL> urls) {
    Map<String, List<URL>> result = new HashMap<String, List<URL>>();
    //獲取catagory列表慧瘤,providers,routers糖儡,configurators
    for (URL u : urls) {
        if (UrlUtils.isMatch(url, u)) {
            String category = u.getParameter(Constants.CATEGORY_KEY, Constants.DEFAULT_CATEGORY);
            List<URL> categoryList = result.get(category);
            if (categoryList == null) {
                categoryList = new ArrayList<URL>();
                result.put(category, categoryList);
            }
            categoryList.add(u);
        }
    }
    if (result.size() == 0) {
        return;
    }
    //已經(jīng)通知過
    Map<String, List<URL>> categoryNotified = notified.get(url);
    if (categoryNotified == null) {
        notified.putIfAbsent(url, new ConcurrentHashMap<String, List<URL>>());
        categoryNotified = notified.get(url);
    }
    for (Map.Entry<String, List<URL>> entry : result.entrySet()) {
        //providers怔匣,routers,configurators中的一個
        String category = entry.getKey();
        List<URL> categoryList = entry.getValue();
        categoryNotified.put(category, categoryList);
        saveProperties(url);
        //還記得剛開始的時候每瞒,listener參數(shù)么,這里listener是RegistryDirectory
        listener.notify(categoryList);
    }
}

繼續(xù)看RegistryDirectory的notify方法:

public synchronized void notify(List<URL> urls) {
    //三種類型分開
    List<URL> invokerUrls = new ArrayList<URL>();
    List<URL> routerUrls = new ArrayList<URL>();
    List<URL> configuratorUrls = new ArrayList<URL>();
    for (URL url : urls) {
        String protocol = url.getProtocol();
        String category = url.getParameter(Constants.CATEGORY_KEY, Constants.DEFAULT_CATEGORY);
        if (Constants.ROUTERS_CATEGORY.equals(category) 
                || Constants.ROUTE_PROTOCOL.equals(protocol)) {
            routerUrls.add(url);
        } else if (Constants.CONFIGURATORS_CATEGORY.equals(category) 
                || Constants.OVERRIDE_PROTOCOL.equals(protocol)) {
            configuratorUrls.add(url);
        } else if (Constants.PROVIDERS_CATEGORY.equals(category)) {
            invokerUrls.add(url);
        } else {
        }
    }
    // configurators
    //更新緩存的服務(wù)提供方配置規(guī)則
    if (configuratorUrls != null && configuratorUrls.size() >0 ){
        this.configurators = toConfigurators(configuratorUrls);
    }
    // routers
    //更新緩存的路由配置規(guī)則
    if (routerUrls != null && routerUrls.size() >0 ){
        List<Router> routers = toRouters(routerUrls);
        if(routers != null){ // null - do nothing
            setRouters(routers);
        }
    }
    List<Configurator> localConfigurators = this.configurators; // local reference
    // 合并override參數(shù)
    this.overrideDirectoryUrl = directoryUrl;
    if (localConfigurators != null && localConfigurators.size() > 0) {
        for (Configurator configurator : localConfigurators) {
            this.overrideDirectoryUrl = configurator.configure(overrideDirectoryUrl);
        }
    }
    // providers
    //重建invoker實例
    refreshInvoker(invokerUrls);
}

最重要的重建invoker實例呐矾,在服務(wù)引用的文章中已經(jīng)介紹過懦砂,不再重復(fù),還有上面說省略的獲取注冊中心實例的過程中罚随,也會有notify的操作羽资。(這里省略)這里也是進(jìn)行了invoker實例的重建淘菩。

暴露服務(wù)過程中的訂閱和通知

服務(wù)暴露過程中的訂閱在RegistryProtocol的export方法中:

public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
    //export invoker
    final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker);
    //registry provider
    final Registry registry = getRegistry(originInvoker);
    final URL registedProviderUrl = getRegistedProviderUrl(originInvoker);
    registry.register(registedProviderUrl);
    // 訂閱override數(shù)據(jù)
    // FIXME 提供者訂閱時屠升,會影響同一JVM即暴露服務(wù),又引用同一服務(wù)的的場景汇在,因為subscribed以服務(wù)名為緩存的key脏答,導(dǎo)致訂閱信息覆蓋糕殉。
    final URL overrideSubscribeUrl = getSubscribedOverrideUrl(registedProviderUrl);
    //OverrideListener是RegistryProtocol的內(nèi)部類
    final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl);
    overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);
    //訂閱override數(shù)據(jù)
    registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
    //保證每次export都返回一個新的exporter實例
    return new Exporter<T>() {
        public Invoker<T> getInvoker() {
            return exporter.getInvoker();
        }
        public void unexport() {
            try {
                exporter.unexport();
            } catch (Throwable t) {
                logger.warn(t.getMessage(), t);
            }
            try {
                registry.unregister(registedProviderUrl);
            } catch (Throwable t) {
                logger.warn(t.getMessage(), t);
            }
            try {
                overrideListeners.remove(overrideSubscribeUrl);
                registry.unsubscribe(overrideSubscribeUrl, overrideSubscribeListener);
            } catch (Throwable t) {
                logger.warn(t.getMessage(), t);
            }
        }
    };
}

registry.subscribe訂閱override數(shù)據(jù)阿蝶,會首先經(jīng)過AbstractRegistry處理黄绩,然后經(jīng)過FailbackRegistry處理羡洁。處理方法在上面消費者發(fā)布訂閱的講解中都已經(jīng)介紹爽丹。往下的步驟基本相同,不同之處在于AbstractRegistry的notify方法:

protected void notify(URL url, NotifyListener listener, List<URL> urls) {
    Map<String, List<URL>> result = new HashMap<String, List<URL>>();
    //獲取catagory列表习劫,providers,routers袒餐,configurators
    for (URL u : urls) {
        if (UrlUtils.isMatch(url, u)) {
            String category = u.getParameter(Constants.CATEGORY_KEY, Constants.DEFAULT_CATEGORY);
            List<URL> categoryList = result.get(category);
            if (categoryList == null) {
                categoryList = new ArrayList<URL>();
                result.put(category, categoryList);
            }
            categoryList.add(u);
        }
    }
    if (result.size() == 0) {
        return;
    }
    //已經(jīng)通知過
    Map<String, List<URL>> categoryNotified = notified.get(url);
    if (categoryNotified == null) {
        notified.putIfAbsent(url, new ConcurrentHashMap<String, List<URL>>());
        categoryNotified = notified.get(url);
    }
    for (Map.Entry<String, List<URL>> entry : result.entrySet()) {
        //providers谤狡,routers,configurators中的一個
        String category = entry.getKey();
        List<URL> categoryList = entry.getValue();
        categoryNotified.put(category, categoryList);
        saveProperties(url);
        //對于消費者來說這里listener是RegistryDirectory
        //而對于服務(wù)提供者來說這里是OverrideListener墓懂,是RegistryProtocol的內(nèi)部類
        listener.notify(categoryList);
    }
}

接下來看OverrideListener的notify方法:

/*
 *  provider 端可識別的override url只有這兩種.
 *  override://0.0.0.0/serviceName?timeout=10
 *  override://0.0.0.0/?timeout=10
 */
public void notify(List<URL> urls) {
    List<URL> result = null;
    for (URL url : urls) {
        URL overrideUrl = url;
        if (url.getParameter(Constants.CATEGORY_KEY) == null
                && Constants.OVERRIDE_PROTOCOL.equals(url.getProtocol())) {
            // 兼容舊版本
            overrideUrl = url.addParameter(Constants.CATEGORY_KEY, Constants.CONFIGURATORS_CATEGORY);
        }
        if (! UrlUtils.isMatch(subscribeUrl, overrideUrl)) {
            if (result == null) {
                result = new ArrayList<URL>(urls);
            }
            result.remove(url);
            logger.warn("Subsribe category=configurator, but notifed non-configurator urls. may be registry bug. unexcepted url: " + url);
        }
    }
    if (result != null) {
        urls = result;
    }
    this.configurators = RegistryDirectory.toConfigurators(urls);
    List<ExporterChangeableWrapper<?>> exporters = new ArrayList<ExporterChangeableWrapper<?>>(bounds.values());
    for (ExporterChangeableWrapper<?> exporter : exporters){
        Invoker<?> invoker = exporter.getOriginInvoker();
        final Invoker<?> originInvoker ;
        if (invoker instanceof InvokerDelegete){
            originInvoker = ((InvokerDelegete<?>)invoker).getInvoker();
        }else {
            originInvoker = invoker;
        }

        URL originUrl = RegistryProtocol.this.getProviderUrl(originInvoker);
        URL newUrl = getNewInvokerUrl(originUrl, urls);

        if (! originUrl.equals(newUrl)){
            //對修改了url的invoker重新export
            RegistryProtocol.this.doChangeLocalExport(originInvoker, newUrl);
        }
    }
}

這里也是對Invoker重新進(jìn)行了引用捕仔。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末盈罐,一起剝皮案震驚了整個濱河市闪唆,隨后出現(xiàn)的幾起案子盅粪,更是在濱河造成了極大的恐慌悄蕾,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,723評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件奠骄,死亡現(xiàn)場離奇詭異番刊,居然都是意外死亡含鳞,警方通過查閱死者的電腦和手機撵枢,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評論 2 382
  • 文/潘曉璐 我一進(jìn)店門民晒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來锄禽,“玉大人,你說我怎么就攤上這事沃但。” “怎么了垂攘?”我有些...
    開封第一講書人閱讀 152,998評論 0 344
  • 文/不壞的土叔 我叫張陵淤刃,是天一觀的道長晒他。 經(jīng)常有香客問我逸贾,道長,這世上最難降的妖魔是什么铝侵? 我笑而不...
    開封第一講書人閱讀 55,323評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮狐赡,結(jié)果婚禮上肥照,老公的妹妹穿的比我還像新娘奠滑。我一直安慰自己载萌,他們只是感情好览祖,可當(dāng)我...
    茶點故事閱讀 64,355評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般温自。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上悼泌,一...
    開封第一講書人閱讀 49,079評論 1 285
  • 那天,我揣著相機與錄音馆里,去河邊找鬼。 笑死丙者,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的械媒。 我是一名探鬼主播评汰,決...
    沈念sama閱讀 38,389評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼主儡!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起糜值,我...
    開封第一講書人閱讀 37,019評論 0 259
  • 序言:老撾萬榮一對情侶失蹤踪央,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后畅蹂,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,519評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡累贤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,971評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了硼被。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片渗磅。...
    茶點故事閱讀 38,100評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖仔掸,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情起暮,我是刑警寧澤会烙,帶...
    沈念sama閱讀 33,738評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站柏腻,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏残腌。R本人自食惡果不足惜贫导,卻給世界環(huán)境...
    茶點故事閱讀 39,293評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望孩灯。 院中可真熱鬧,春花似錦峰档、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春缆巧,著一層夾襖步出監(jiān)牢的瞬間豌拙,已是汗流浹背陕悬。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評論 1 262
  • 我被黑心中介騙來泰國打工捉超, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留唯绍,地道東北人狂秦。 一個月前我還...
    沈念sama閱讀 45,547評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像侧啼,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子痊乾,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,834評論 2 345

推薦閱讀更多精彩內(nèi)容

  • dubbo暴露服務(wù)有兩種情況哪审,一種是設(shè)置了延遲暴露(比如delay="5000"),另外一種是沒有設(shè)置延遲暴露或者...
    加大裝益達(dá)閱讀 21,252評論 5 36
  • 首先還是Spring碰到dubbo的標(biāo)簽之后湿滓,會使用parseCustomElement解析dubbo標(biāo)簽,使用的...
    加大裝益達(dá)閱讀 6,815評論 0 11
  • 從三月份找實習(xí)到現(xiàn)在扔水,面了一些公司朝氓,掛了不少,但最終還是拿到小米赵哲、百度、阿里枫夺、京東、新浪、CVTE簸喂、樂視家的研發(fā)崗...
    時芥藍(lán)閱讀 42,192評論 11 349
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理燎潮,服務(wù)發(fā)現(xiàn),斷路器确封,智...
    卡卡羅2017閱讀 134,600評論 18 139
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,516評論 25 707