一起學(xué)RPC(零)

最近又重新開始看jupiter的源碼弥搞。這個(gè)開源項(xiàng)目是阿里的一位大神寫的荣堰,比起現(xiàn)在較為流行的dubbo阔挠、motan等生產(chǎn)上的開源軟件來說輕量很多牛隅,也比較容易入門學(xué)習(xí)扰路。本來想看看dubbo的源碼的扛点,無奈第一步都沒賣出去税灌,被extension機(jī)制給難住了厌漂。雖說目前dubbo已經(jīng)成為apache的孵化項(xiàng)目了券盅,對(duì)于研究源碼的渣渣我來說還是有一定的難度的盆驹。于是退而求其次承耿,jupiter就是一個(gè)比較容易入手的選擇呀狼。為什么說這個(gè)jupiter比較容易入門呢锁摔?首先代碼比較少巡验,不是很多际插,對(duì)閱讀來說不會(huì)有很多繞的地方。其次這個(gè)項(xiàng)目有很多熱心的網(wǎng)友也在一起讀显设,可以有很多交流的地方框弛,有一個(gè)專門討論jupiter的交流群,可以很方便的和各路大神交流學(xué)習(xí)捕捂。

因?yàn)閖upiter源碼我沒有完全讀完瑟枫,只能看一點(diǎn)寫一點(diǎn)斗搞。說不定等看完源碼后再重新整理一下行文結(jié)構(gòu)呢,也說不定放棄了呢慷妙,誰知道呢僻焚?

按照常規(guī)思路來說肯定是從一個(gè)demo來入門,但是我不決定這么做膝擂,因?yàn)槿绻麑?duì)rpc熟悉的伙計(jì)一定知道怎么去玩虑啤,不知道怎么去玩的現(xiàn)在可以關(guān)掉瀏覽器打lol或者吃雞去了,因?yàn)槟悴慌浼懿觥]錯(cuò)狞山,就是這么傲嬌。

看了這么多java rpc的框架比如motan叉寂、dubbo和jupiter萍启,都有一個(gè)共同的地方,他們都使用spring作為容器來集成办绝。這樣也是情有可原伊约,我相信java應(yīng)用中沒有不使用spring的吧姚淆。因此都選擇這樣去做大概是因?yàn)檫@樣很容易去集成到自己的項(xiàng)目中孕蝉。當(dāng)然,這類rpc框架并不是一定得和spring集成腌逢。把他們稱為“框架”其實(shí)并不是很準(zhǔn)確降淮。更準(zhǔn)確的應(yīng)該稱為“中間件”。我的理解是因?yàn)樗麄冸m然是集成到自己的項(xiàng)目代碼中搏讶,但是他們卻占用獨(dú)立的端口佳鳖。

spring目前在java開發(fā)中的地位很高,使用spring來管理bean是非常流行的做法媒惕。更重要的是非常方便系吩。對(duì)于中間件來說,通過寥寥幾行xml的描述就能將一個(gè)復(fù)雜的bean實(shí)例化出來妒蔚,而且耦合度很低穿挨,何樂而不為呢?看一個(gè)sonsumer的配置:

    <bean id="globalInterceptor1" class="org.jupiter.example.spring.interceptor.consumer.MyGlobalConsumerInterceptor1" />
    <bean id="globalInterceptor2" class="org.jupiter.example.spring.interceptor.consumer.MyGlobalConsumerInterceptor2" />

    <jupiter:client id="jupiterClient" registryType="default">
        <jupiter:property registryServerAddresses="127.0.0.1:20001" />
        <jupiter:property globalConsumerInterceptors="globalInterceptor1,globalInterceptor2" />
        <!-- 可選配置 -->
        <!--
            String registryServerAddresses                          // 注冊(cè)中心地址 [host1:port1,host2:port2....]
            String providerServerAddresses                          // IP直連到providers [host1:port1,host2:port2....]
            ConsumerInterceptor[] globalConsumerInterceptors;       // 全局?jǐn)r截器
        -->

        <!-- 網(wǎng)絡(luò)層配置選項(xiàng) -->
        <jupiter:netOptions>
            <jupiter:childOption SO_RCVBUF="8192" />
            <jupiter:childOption SO_SNDBUF="8192" />
            <jupiter:childOption ALLOW_HALF_CLOSURE="false" />
        </jupiter:netOptions>
    </jupiter:client>

    <bean id="interceptor1" class="org.jupiter.example.spring.interceptor.consumer.MyConsumerInterceptor1" />
    <bean id="interceptor2" class="org.jupiter.example.spring.interceptor.consumer.MyConsumerInterceptor2" />

    <!-- consumer -->
    <jupiter:consumer id="serviceTest" client="jupiterClient" interfaceClass="org.jupiter.example.ServiceTest">
        <!-- 以下都選項(xiàng)可不填 -->
        <!-- 服務(wù)版本號(hào), 通常在接口不兼容時(shí)版本號(hào)才需要升級(jí) -->
        <jupiter:property version="1.0.0.daily" />
        <!-- 序列化/反序列化類型: (proto_stuff, hessian, kryo, java)可選, 默認(rèn)proto_stuff -->
        <jupiter:property serializerType="proto_stuff" />
        <!-- 軟負(fù)載均衡類型[random, round_robin] -->
        <jupiter:property loadBalancerType="round_robin" />
        <!-- 派發(fā)方式: (round, broadcast)可選, 默認(rèn)round(單播) -->
        <jupiter:property dispatchType="round" />
        <!-- 調(diào)用方式: (sync, async)可選, 默認(rèn)sync(同步調(diào)用) -->
        <jupiter:property invokeType="sync" />
        <!-- 集群容錯(cuò)策略: (fail_fast, fail_over, fail_safe)可選, 默認(rèn)fail_fast(快速失敗) -->
        <jupiter:property clusterStrategy="fail_over" />
        <!-- 在fail_over策略下的失敗重試次數(shù) -->
        <jupiter:property failoverRetries="2" />
        <!-- 超時(shí)時(shí)間設(shè)置 -->
        <jupiter:property timeoutMillis="3000" />
        <jupiter:methodSpecials>
            <!-- 方法的單獨(dú)配置 -->
            <jupiter:methodSpecial methodName="sayHello" timeoutMillis="5000" clusterStrategy="fail_fast" />
        </jupiter:methodSpecials>
        <jupiter:property consumerInterceptors="interceptor1,interceptor2" />
        <!-- 可選配置 -->
        <!--
            SerializerType serializerType                   // 序列化/反序列化方式
            LoadBalancerType loadBalancerType               // 軟負(fù)載均衡類型[random, round_robin]
            long waitForAvailableTimeoutMillis = -1         // 如果大于0, 表示阻塞等待直到連接可用并且該值為等待時(shí)間
            InvokeType invokeType                           // 調(diào)用方式 [同步, 異步]
            DispatchType dispatchType                       // 派發(fā)方式 [單播, 廣播]
            long timeoutMillis                              // 調(diào)用超時(shí)時(shí)間設(shè)置
            List<MethodSpecialConfig> methodSpecialConfigs; // 指定方法的單獨(dú)配置, 方法參數(shù)類型不做區(qū)別對(duì)待
            ConsumerInterceptor[] consumerInterceptors      // 消費(fèi)者端攔截器
            String providerAddresses                        // provider地址列表, 逗號(hào)分隔(IP直連)
            ClusterInvoker.Strategy clusterStrategy;        // 集群容錯(cuò)策略
            int failoverRetries                             // fail_over的重試次數(shù)
        -->
    </jupiter:consumer>

對(duì)于一個(gè)相對(duì)比較成熟的rpc中間件來說肴盏,核心的bean配置是比較復(fù)雜的科盛。你看看其中的參數(shù)就知道。通過spring的這種xml描述文件起碼能夠稍微容易地理解到一個(gè)bean需要哪些參數(shù)菜皂,哪些可以不要贞绵,同時(shí)根據(jù)xsd的約束能夠讓開發(fā)者更清楚的知道自己的配置有什么問題。如果不給api文檔的情況下干巴巴的給你一個(gè)類恍飘,讓你去實(shí)例化這個(gè)復(fù)雜的class榨崩,我相信很多人都會(huì)抓狂谴垫。在這個(gè)配置文件中很容易的看出要有2個(gè)節(jié)點(diǎn):client和consumer。子節(jié)點(diǎn)的內(nèi)容就是參數(shù)蜡饵。consumer會(huì)去引用client去執(zhí)行一個(gè)請(qǐng)求弹渔。而我們的業(yè)務(wù)中直接去調(diào)用consumer就完事了。如此而已溯祸,簡(jiǎn)單直觀肢专。

這里的spring xml配置使用的是自定義的標(biāo)簽,算是對(duì)spring的拓展焦辅。不僅是jupiter博杖,基本上大多數(shù)rpc中間件都實(shí)現(xiàn)了自己的一套標(biāo)簽,似乎不去自己實(shí)現(xiàn)一套自定義標(biāo)簽都不好意思開源筷登。比如dubbo的自定義標(biāo)簽就是<dubbo:xxx>剃根,motan類似如此。然而實(shí)際上也不是必須得實(shí)現(xiàn)自定義標(biāo)簽前方,使用spring的bean也是可以的狈醉,只不過顯得很臃腫,不是那么直觀罷了惠险。

對(duì)于一個(gè)新手來講苗傅,這些東西顯得格外的高大上。其實(shí)里面沒有什么黑魔法班巩,在spring的reference中對(duì)自定義標(biāo)簽有介紹渣慕。感興趣的去看看這個(gè)官方文檔:spring xml extension.

要實(shí)現(xiàn)一個(gè)自定義的spring xml標(biāo)簽需要做一下幾個(gè)步驟:

  • 定義一個(gè)約束文件,用來規(guī)范xml的內(nèi)容”Щ牛現(xiàn)在都流行使用xsd去編寫約束文件逊桦,dtd已經(jīng)成為老古董了。xsd了解一下.
  • 自定義一個(gè)NamespaceHandler的實(shí)現(xiàn)抑进。實(shí)際上是去實(shí)現(xiàn)這個(gè)接口强经。非常容易,復(fù)制粘貼一把梭寺渗。
  • 寫一個(gè)或者多個(gè)BeanDefinitionParser的實(shí)現(xiàn)匿情。也是去實(shí)現(xiàn)接口,當(dāng)然繼承抽象類也是ok的户秤。這個(gè)是最核心的內(nèi)容码秉。
  • 將上面所定義的全部注冊(cè)到spring中,讓spring知道有這些玩意兒鸡号。也就是在META-INF文件夾下新增兩個(gè)配置文件:spring.handlersspring.schemas

下面就結(jié)合jupiter中自定義的spring標(biāo)簽來談?wù)勊侨绾螌?shí)現(xiàn)的转砖。

首先得定義xsd約束文件,完整的定義在這里.這個(gè)沒什么好說的,枯燥的xml定義罷了。無非就是定義有哪些元素府蔗,哪些元素下有哪些屬性晋控,其中有沒有子元素,屬性類型是什么姓赤,是不是必填的等等赡译。

接下來就是配置一個(gè)handler。這個(gè)handler用來解析自定義的標(biāo)簽不铆。用過spring都知道蝌焚,除了最常見的bean標(biāo)簽還有很多其他的標(biāo)簽,比如<context:component-scan>誓斥、<aop:aspectj-autoproxy proxy-target-class="true" />以及<mvc:annotation-driven/>等只洒。這些標(biāo)簽和bean標(biāo)簽的不同之處在于都有一個(gè)前綴。我們稱這個(gè)叫做命名空間劳坑。然而自定義的當(dāng)然也得加上命名空間毕谴。雖說不能和bean平起平坐,但是和aop距芬、context這樣的標(biāo)簽還是可以一視同仁的涝开。

基于這種思路,那就很容易來自定義自己的標(biāo)簽了框仔。難怪文檔中對(duì)這個(gè)步驟加了一個(gè)說明:

Coding a custom NamespaceHandler implementation (this is an easy step, don’t worry).

的確如此舀武,常人的思路就是照著spring的實(shí)現(xiàn)抄一把。如此簡(jiǎn)單存和!

而比較復(fù)雜的就是對(duì)BeanDefinitionParser的實(shí)現(xiàn)了奕剃。這個(gè)是最核心的步驟衷旅。根據(jù)文檔中的描述捐腿,這個(gè)可以有一個(gè)或者多個(gè)。但是在jupiter中只定義了一個(gè)實(shí)現(xiàn)柿顶。

public class JupiterNamespaceHandler extends NamespaceHandlerSupport {

    @Override
    public void init() {
        registerBeanDefinitionParser("server", new JupiterBeanDefinitionParser(JupiterSpringServer.class));
        registerBeanDefinitionParser("client", new JupiterBeanDefinitionParser(JupiterSpringClient.class));
        registerBeanDefinitionParser("provider", new JupiterBeanDefinitionParser(JupiterSpringProviderBean.class));
        registerBeanDefinitionParser("consumer", new JupiterBeanDefinitionParser(JupiterSpringConsumerBean.class));
    }
}

spring的TaskNamespaceHandler中就使用了多個(gè)paser:

public class TaskNamespaceHandler extends NamespaceHandlerSupport {

    @Override
    public void init() {
        this.registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenBeanDefinitionParser());
        this.registerBeanDefinitionParser("executor", new ExecutorBeanDefinitionParser());
        this.registerBeanDefinitionParser("scheduled-tasks", new ScheduledTasksBeanDefinitionParser());
        this.registerBeanDefinitionParser("scheduler", new SchedulerBeanDefinitionParser());
    }

}

這個(gè)paser用通俗的話來解釋就是將在xml的配置參數(shù)給set到相應(yīng)的實(shí)例中去茄袖。舉個(gè)栗子:

public class SimpleDateFormatBeanDefinitionParser extends AbstractSingleBeanDefinitionParser { 

    protected Class getBeanClass(Element element) {
        return SimpleDateFormat.class; 
    }

    protected void doParse(Element element, BeanDefinitionBuilder bean) {
        // this will never be null since the schema explicitly requires that a value be supplied
        String pattern = element.getAttribute("pattern");
        bean.addConstructorArg(pattern);

        // this however is an optional property
        String lenient = element.getAttribute("lenient");
        if (StringUtils.hasText(lenient)) {
            bean.addPropertyValue("lenient", Boolean.valueOf(lenient));
        }
    }

}

這個(gè)栗子是繼承自AbstractSingleBeanDefinitionParser并沒有去實(shí)現(xiàn)BeanDefinitionParser接口。道理都知道嘁锯,沒有必要去實(shí)現(xiàn)一個(gè)要啥沒啥的接口宪祥,吃現(xiàn)成的就好。重寫父類的getBeanClass方法家乘,將需要納入spring管理的對(duì)象返回掉蝗羊。這里不僅僅可以重寫這個(gè)方法,還有其他例如getBeanClassName也行仁锯。值得注意的是如果采用繼承抽象類的方式耀找,這兩個(gè)方法必須選擇一個(gè)來重寫。這個(gè)也非常容易理解,因?yàn)檫@個(gè)方法返回的class實(shí)例或者類的全路徑名就是用來實(shí)例化的對(duì)象野芒。如果通過實(shí)現(xiàn)接口的方式來定義paser就不需要考慮這個(gè)規(guī)則了蓄愁,只需要?jiǎng)?chuàng)建出BeanDefinition的實(shí)例即可。jupiter中就是采用實(shí)現(xiàn)接口的方式狞悲,因?yàn)槔^承抽象類有一定的局限性撮抓,實(shí)現(xiàn)接口會(huì)有更多的靈活性。

有了要煮飯的鍋摇锋,就差下鍋的米了丹拯。這個(gè)栗子中重寫父類的doParser方法。從代碼的表現(xiàn)上來看實(shí)際上就是將xml配置文件中的屬性獲取到荸恕,然后做一下檢查放到實(shí)例化的對(duì)象中去咽笼。當(dāng)然這里沒有那么直接,這里使用的是BeanDefinitionBuilder來操作的戚炫。這只是最簡(jiǎn)單的實(shí)現(xiàn)剑刑。

復(fù)雜的parser都是自己去實(shí)現(xiàn)接口的。比如jupiter:

public class JupiterBeanDefinitionParser implements BeanDefinitionParser {

    private final Class<?> beanClass;

    public JupiterBeanDefinitionParser(Class<?> beanClass) {
        this.beanClass = beanClass;
    }

    @Override
    public BeanDefinition parse(Element element, ParserContext parserContext) {
        if (beanClass == JupiterSpringServer.class) {
            return parseJupiterServer(element, parserContext);
        } else if (beanClass == JupiterSpringClient.class) {
            return parseJupiterClient(element, parserContext);
        } else if (beanClass == JupiterSpringProviderBean.class) {
            return parseJupiterProvider(element, parserContext);
        } else if (beanClass == JupiterSpringConsumerBean.class) {
            return parseJupiterConsumer(element, parserContext);
        } else {
            throw new BeanDefinitionValidationException("Unknown class to definition: " + beanClass.getName());
        }
    }
}

jupiter的自定義parser中需要納入spring管理的bean class對(duì)象是通過構(gòu)造器傳進(jìn)來的双肤。根據(jù)不同的class來作不同的處理施掏。其中具體的邏輯很枯燥無味,就不再細(xì)細(xì)探討了茅糜。不過我在看源碼的過程中發(fā)現(xiàn)了一個(gè)細(xì)節(jié)的地方七芭,也是值得注意的地方。

JupiterSpringConsumerBean不僅僅和其他(如JupiterSpringServer等)實(shí)現(xiàn)InitializingBean蔑赘,還實(shí)現(xiàn)了一個(gè)叫做FactoryBean的接口狸驳。這說明了一個(gè)問題,這個(gè)bean不是普通的bean缩赛,而是一個(gè)factory bean耙箍。相信很多人都會(huì)疑惑factory bean 和bean factory有什么區(qū)別。要我說兩者都沒有直接的聯(lián)系酥馍,如果在面試的時(shí)候有人問我這個(gè)問題辩昆,我一定直接懟回去:雷鋒和雷峰塔有什么區(qū)別?言歸正傳旨袒,這個(gè)factory bean本質(zhì)上也是bean汁针,但是與其他bean不同的是這個(gè)bean在spring容器中獲取的方式和別的不一樣。通常在spring中獲取一個(gè)bean采用ctx.getBean(xxx.class)方法砚尽。通過這個(gè)方法獲取的factory bean并不是他自己施无,而是它的某個(gè)成員”毓拢可以看看這個(gè)接口的定義:

public interface FactoryBean<T> {
    T getObject() throws Exception;

    Class<?> getObjectType();

    boolean isSingleton();
}

也就是說返回的對(duì)象是getObject()返回值猾骡。那么這個(gè)接口存在的意義是什么呢?我也不復(fù)制粘貼了,覺著這篇文章寫得很不錯(cuò)卓练,淺顯易懂隘蝎。那么如何獲取這個(gè)bean本身呢?干嘛想著獲取它本身襟企,簡(jiǎn)直是無聊嘱么!也有方法,加個(gè)前綴"&"就行了(ctx.getBean("&sb"))顽悼。

最后呢曼振,就是照著spring的官方文檔抄一下配置文件。依葫蘆畫瓢蔚龙,非常簡(jiǎn)單冰评。

完成了以上的幾個(gè)步驟,自定義的spring xml標(biāo)簽就大功告成了木羹。接下來要做的就是去使用自定義的標(biāo)簽甲雅。

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

    <!-- as a top-level bean -->
    <myns:dateformat id="defaultDateFormat" pattern="yyyy-MM-dd HH:mm" lenient="true"/>

    <bean id="jobDetailTemplate" abstract="true">
        <property name="dateFormat">
            <!-- as an inner bean -->
            <myns:dateformat pattern="HH:mm MM-dd-yyyy"/>
        </property>
    </bean>

</beans>

這里是抄的官方文檔的栗子。標(biāo)簽myns:dateformat實(shí)際上定義了一個(gè)SimpleDateFormat的bean實(shí)例坑填。在spring容器加載的時(shí)候這個(gè)實(shí)例就回被初始化抛人。在使用自定義的標(biāo)簽的時(shí)候,需要注意的是得聲明好命名空間和指定location脐瑰,不然會(huì)報(bào)無法找到這個(gè)標(biāo)簽的錯(cuò)誤妖枚。其實(shí)這些東西照著抄就行了,只是不要忘記了或者抄錯(cuò)了苍在。

自定義spring xml標(biāo)簽如此簡(jiǎn)單绝页。無非就是照著文檔抄一把,自己再改吧改吧萬事就大吉了寂恬。對(duì)于其中核心的東西實(shí)際上還是一知半解续誉,比方說BeanDefinition的具體實(shí)現(xiàn)原理等。上層的封裝太抽象了掠剑,留給開發(fā)者的僅僅只是一個(gè)需要實(shí)現(xiàn)的方法屈芜。要想知道為什么要這樣做郊愧,還得去研究spring的源碼朴译。

rpc中的最簡(jiǎn)單的一個(gè)可選模塊就這樣簡(jiǎn)單的實(shí)現(xiàn)了。這是一小步属铁,也是一大步眠寿。接下來會(huì)繼續(xù)探索稍微核心一點(diǎn)的jupiter實(shí)現(xiàn)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末焦蘑,一起剝皮案震驚了整個(gè)濱河市盯拱,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖狡逢,帶你破解...
    沈念sama閱讀 222,183評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件宁舰,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡奢浑,警方通過查閱死者的電腦和手機(jī)蛮艰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來雀彼,“玉大人壤蚜,你說我怎么就攤上這事』惭疲” “怎么了袜刷?”我有些...
    開封第一講書人閱讀 168,766評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)莺丑。 經(jīng)常有香客問我著蟹,道長(zhǎng),這世上最難降的妖魔是什么梢莽? 我笑而不...
    開封第一講書人閱讀 59,854評(píng)論 1 299
  • 正文 為了忘掉前任草则,我火速辦了婚禮,結(jié)果婚禮上蟹漓,老公的妹妹穿的比我還像新娘炕横。我一直安慰自己,他們只是感情好葡粒,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,871評(píng)論 6 398
  • 文/花漫 我一把揭開白布份殿。 她就那樣靜靜地躺著,像睡著了一般嗽交。 火紅的嫁衣襯著肌膚如雪卿嘲。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,457評(píng)論 1 311
  • 那天夫壁,我揣著相機(jī)與錄音拾枣,去河邊找鬼。 笑死盒让,一個(gè)胖子當(dāng)著我的面吹牛梅肤,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播邑茄,決...
    沈念sama閱讀 40,999評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼姨蝴,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了肺缕?” 一聲冷哼從身側(cè)響起左医,我...
    開封第一講書人閱讀 39,914評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤授帕,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后浮梢,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體跛十,經(jīng)...
    沈念sama閱讀 46,465評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,543評(píng)論 3 342
  • 正文 我和宋清朗相戀三年秕硝,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了偶器。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,675評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡缝裤,死狀恐怖屏轰,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情憋飞,我是刑警寧澤霎苗,帶...
    沈念sama閱讀 36,354評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站榛做,受9級(jí)特大地震影響唁盏,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜检眯,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,029評(píng)論 3 335
  • 文/蒙蒙 一厘擂、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧锰瘸,春花似錦刽严、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至管削,卻和暖如春倒脓,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背含思。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評(píng)論 1 274
  • 我被黑心中介騙來泰國(guó)打工崎弃, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人含潘。 一個(gè)月前我還...
    沈念sama閱讀 49,091評(píng)論 3 378
  • 正文 我出身青樓饲做,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親调鬓。 傳聞我的和親對(duì)象是個(gè)殘疾皇子艇炎,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,685評(píng)論 2 360

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

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)腾窝,斷路器缀踪,智...
    卡卡羅2017閱讀 134,707評(píng)論 18 139
  • 1.1 spring IoC容器和beans的簡(jiǎn)介 Spring 框架的最核心基礎(chǔ)的功能是IoC(控制反轉(zhuǎn))容器,...
    simoscode閱讀 6,721評(píng)論 2 22
  • 1.1 Spring IoC容器和bean簡(jiǎn)介 本章介紹了Spring Framework實(shí)現(xiàn)的控制反轉(zhuǎn)(IoC)...
    起名真是難閱讀 2,587評(píng)論 0 8
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,859評(píng)論 6 342
  • 文/徐心漁 好萊塢電影《盜夢(mèng)空間》里的Ariadne虹脯,擁有高超的邏輯推理能力和空間想象力驴娃,專門負(fù)責(zé)制造迷宮一樣的夢(mèng)...
    富姐姐閱讀 504評(píng)論 0 2