Spring事件驅(qū)動(dòng)模型

事件驅(qū)動(dòng)模型簡(jiǎn)介

事件驅(qū)動(dòng)模型也就是我們常說(shuō)的觀察者,或者發(fā)布-訂閱模型;理解它的幾個(gè)關(guān)鍵點(diǎn):

  • 1.首先是一種對(duì)象間的一對(duì)多的關(guān)系;最簡(jiǎn)單的如交通信號(hào)燈,信號(hào)燈是目標(biāo)(一方),行人注視著信號(hào)燈(多方);
  • 2.當(dāng)目標(biāo)發(fā)送改變(發(fā)布),觀察者(訂閱者)就可以接收到改變;
  • 3.觀察者如何處理(如行人如何走,是快走/慢走/不走,目標(biāo)不會(huì)管的),目標(biāo)無(wú)需干涉;所以就松散耦合了它們之間的關(guān)系。

接下來(lái)先看一個(gè)用戶(hù)注冊(cè)的例子:

用戶(hù)注冊(cè)

用戶(hù)注冊(cè)成功后,需要做這么多事:

1、加積分
2、發(fā)確認(rèn)郵件
3、如果是游戲帳戶(hù),可能贈(zèng)送游戲大禮包
4、索引用戶(hù)數(shù)據(jù)


問(wèn)題:

  1. UserService和其他Service耦合嚴(yán)重,增刪功能比較麻煩;

  2. 有些功能可能需要調(diào)用第三方系統(tǒng),如增加積分/索引用戶(hù),速度可能比較慢,此時(shí)需要異步支持;這個(gè)如果使用Spring,可以輕松解決,后邊再介紹;

從如上例子可以看出,應(yīng)該使用一個(gè)觀察者來(lái)解耦這些Service之間的依賴(lài)關(guān)系,如圖:

注冊(cè)功能設(shè)計(jì)結(jié)構(gòu)

增加了一個(gè)Listener來(lái)解耦UserService和其他服務(wù),即注冊(cè)成功后,只需要通知相關(guān)的監(jiān)聽(tīng)器,不需要關(guān)系它們?nèi)绾翁幚?。增刪功能非常容易。

這就是一個(gè)典型的事件處理模型/觀察者,解耦目標(biāo)對(duì)象和它的依賴(lài)對(duì)象,目標(biāo)只需要通知它的依賴(lài)對(duì)象,具體怎么處理,依賴(lài)對(duì)象自己決定。比如是異步還是同步,延遲還是非延遲等。

上邊其實(shí)也使用了DIP(依賴(lài)倒置原則),依賴(lài)于抽象,而不是具體。

還是就是使用了IoC思想,即以前主動(dòng)去創(chuàng)建它依賴(lài)的Service,現(xiàn)在只是被動(dòng)等待別人注冊(cè)進(jìn)來(lái)。

其他的例子還有如GUI中的按鈕和動(dòng)作的關(guān)系,按鈕和動(dòng)作本身都是一種抽象,每個(gè)不同的按鈕的動(dòng)作可能不一樣;如“文件-->新建”打開(kāi)新建窗口;點(diǎn)擊“關(guān)閉”按鈕關(guān)閉窗口等等。

主要目的是:松散耦合對(duì)象間的一對(duì)多的依賴(lài)關(guān)系,如按鈕和動(dòng)作的關(guān)系;

如何實(shí)現(xiàn)呢?面向接口編程(即面向抽象編程),而非面向?qū)崿F(xiàn)。即按鈕和動(dòng)作可以定義為接口,這樣它倆的依賴(lài)是最小的(如在Java中,沒(méi)有比接口更抽象的了)。

有朋友會(huì)問(wèn),我剛開(kāi)始學(xué)的時(shí)候也是這樣:抽象類(lèi)不也行嗎?記住一個(gè)原則:接口目的是抽象,抽象類(lèi)目的是復(fù)用;所以如果接觸過(guò)servlet/struts2/spring等框架,大家都應(yīng)該知道:
Servlet<-----GenericServlet<-----HttpServlet<------我們自己的
Action<------ActionSupport<------我們自己的
DaoInterface<------××DaoSupport<-----我們自己的
從上邊大家應(yīng)該能體會(huì)出接口、抽象類(lèi)的主要目的了。現(xiàn)在想想其實(shí)很簡(jiǎn)單。

在Java中接口還一個(gè)非常重要的好處:接口是可以多實(shí)現(xiàn)的,類(lèi)/抽象類(lèi)只能單繼承,所以使用接口可以非常容易擴(kuò)展新功能(還可以實(shí)現(xiàn)所謂的mixin),類(lèi)/抽象類(lèi)辦不到。

Java GUI事件驅(qū)動(dòng)模型/觀察者

扯遠(yuǎn)了,再來(lái)看看Java GUI世界里的事件驅(qū)動(dòng)模型吧:

如果寫(xiě)過(guò)AWT/Swing程序,應(yīng)該知道其所有組件都繼承自java.awt.Component抽象類(lèi),其內(nèi)部提供了addXXXListener(XXXListener l) 注冊(cè)監(jiān)聽(tīng)器的方法,即Component與實(shí)際動(dòng)作之間依賴(lài)于XXXListener抽象。

比如獲取焦點(diǎn)事件,很多組件都可以有這個(gè)事件,是我們知道組件獲取到焦點(diǎn)后需要一個(gè)處理,雖然每個(gè)組件如何處理是特定的(具體的),但我們可以抽象一個(gè)FocusListener,讓所有具體實(shí)現(xiàn)它然后提供具體動(dòng)作,這樣組件只需依賴(lài)于FocusListener抽象,而不是具體。

還有如java.awt.Button,提供了一個(gè)addActionListener(ActionListener l),用于注冊(cè)點(diǎn)擊后觸發(fā)的ActionListener實(shí)現(xiàn)。

組件是一個(gè)抽象類(lèi),其好處主要是復(fù)用,比如復(fù)用這些監(jiān)聽(tīng)器的觸發(fā)及管理等。

JavaBean規(guī)范的事件驅(qū)動(dòng)模型/觀察者

JavaBean規(guī)范提供了JavaBean的PropertyEditorSupport及PropertyChangeListener支持。

PropertyEditorSupport就是目標(biāo),而PropertyChangeListener就是監(jiān)聽(tīng)器,大家可以google搜索下,具體網(wǎng)上有很多例子。

Java提供的事件驅(qū)動(dòng)模型/觀察者抽象

JDK內(nèi)部直接提供了觀察者模式的抽象:
目標(biāo):java.util.Observable,提供了目標(biāo)需要的關(guān)鍵抽象:addObserver/deleteObserver/notifyObservers()等,具體請(qǐng)參考javadoc。
觀察者:java.util.Observer,提供了觀察者需要的主要抽象:update(Observable o, Object arg),此處還提供了一種推模型(目標(biāo)主動(dòng)把數(shù)據(jù)通過(guò)arg推到觀察者)/拉模型(目標(biāo)需要根據(jù)o自己去拉數(shù)據(jù),arg為null)。

因?yàn)榫W(wǎng)上介紹的非常多了,請(qǐng)google搜索了解如何使用這個(gè)抽象及推/拉模型的優(yōu)缺點(diǎn)。

接下來(lái)是我們的重點(diǎn):spring提供的事件驅(qū)動(dòng)模型。

Spring提供的事件驅(qū)動(dòng)模型/觀察者抽象

首先看一下Spring提供的事件驅(qū)動(dòng)模型體系圖:

Spring事件驅(qū)動(dòng)模型

事件

具體代表者是:ApplicationEvent:

1、其繼承自JDK的EventObject,JDK要求所有事件將繼承它,并通過(guò)source得到事件源,比如我們的AWT事件體系也是繼承自它;

2、系統(tǒng)默認(rèn)提供了如下ApplicationEvent事件實(shí)現(xiàn):

事件體系

只有一個(gè)ApplicationContextEvent,表示ApplicationContext容器事件,且其又有如下實(shí)現(xiàn):

  • ContextStartedEvent:ApplicationContext啟動(dòng)后觸發(fā)的事件;(目前版本沒(méi)有任何作用)
  • ContextStoppedEvent:ApplicationContext停止后觸發(fā)的事件;(目前版本沒(méi)有任何作用)
  • ContextRefreshedEvent:ApplicationContext初始化或刷新完成后觸發(fā)的事件;(容器初始化完成后調(diào)用)
  • ContextClosedEvent:ApplicationContext關(guān)閉后觸發(fā)的事件;(如web容器關(guān)閉時(shí)自動(dòng)會(huì)觸發(fā)spring容器的關(guān)閉,如果是普通java應(yīng)用,需要調(diào)用ctx.registerShutdownHook();注冊(cè)虛擬機(jī)關(guān)閉時(shí)的鉤子才行)

注:org.springframework.context.support.AbstractApplicationContext抽象類(lèi)實(shí)現(xiàn)了LifeCycle的start和stop回調(diào)并發(fā)布ContextStartedEvent和ContextStoppedEvent事件;但是無(wú)任何實(shí)現(xiàn)調(diào)用它,所以目前無(wú)任何作用。

目標(biāo)(發(fā)布事件者)

具體代表者是:ApplicationEventPublisher及ApplicationEventMulticaster,系統(tǒng)默認(rèn)提供了如下實(shí)現(xiàn):


事件發(fā)布體系

1、ApplicationContext接口繼承了ApplicationEventPublisher,并在AbstractApplicationContext實(shí)現(xiàn)了具體代碼,實(shí)際執(zhí)行是委托給ApplicationEventMulticaster(可以認(rèn)為是多播):

public void publishEvent(ApplicationEvent event) {
    Assert.notNull(event, "Event must not be null");
    if (logger.isTraceEnabled()) {
        logger.trace("Publishing event in " + getDisplayName() + ": " + event);
    }
    getApplicationEventMulticaster().multicastEvent(event);
    if (this.parent != null) {
        this.parent.publishEvent(event);
    }
}

我們常用的ApplicationContext都繼承自AbstractApplicationContext,如ClassPathXmlApplicationContext、XmlWebApplicationContext等。所以自動(dòng)擁有這個(gè)功能。

2、ApplicationContext自動(dòng)到本地容器里找一個(gè)名字為ApplicationEventMulticaster的實(shí)現(xiàn),如果沒(méi)有自己new一個(gè)SimpleApplicationEventMulticaster。其中SimpleApplicationEventMulticaster發(fā)布事件的代碼如下:

public void multicastEvent(final ApplicationEvent event) {
    for (final ApplicationListener listener : getApplicationListeners(event)) {
        Executor executor = getTaskExecutor();
        if (executor != null) {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    listener.onApplicationEvent(event);
                }
            });
        }
        else {
            listener.onApplicationEvent(event);
        }
    }
}

大家可以看到如果給它一個(gè)executor(java.util.concurrent.Executor),它就可以異步支持發(fā)布事件了。否則就是同步發(fā)送。

所以我們發(fā)送事件只需要通過(guò)ApplicationContext.publishEvent即可,沒(méi)必要再創(chuàng)建自己的實(shí)現(xiàn)了。除非有必要。

監(jiān)聽(tīng)器

具體代表者是:ApplicationListener
1、其繼承自JDK的EventListener,JDK要求所有監(jiān)聽(tīng)器將繼承它,比如我們的AWT事件體系也是繼承自它;
2、ApplicationListener接口:

public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
    void onApplicationEvent(E event);
}

其只提供了onApplicationEvent方法,我們需要在該方法實(shí)現(xiàn)內(nèi)部判斷事件類(lèi)型來(lái)處理,也沒(méi)有提供按順序觸發(fā)監(jiān)聽(tīng)器的語(yǔ)義,所以Spring提供了另一個(gè)接口,SmartApplicationListener:

public interface SmartApplicationListener extends ApplicationListener<ApplicationEvent>, Ordered {

    /**
     * 如果實(shí)現(xiàn)支持該事件類(lèi)型 那么返回true  
     */
    boolean supportsEventType(Class<? extends ApplicationEvent> eventType);
    /**
     * 如果實(shí)現(xiàn)支持“目標(biāo)”類(lèi)型,那么返回true 
     */
    boolean supportsSourceType(Class<?> sourceType);
    /**
     * 順序,即監(jiān)聽(tīng)器執(zhí)行的順序,值越小優(yōu)先級(jí)越高
     */
    int getOrder(); 
}

該接口可方便實(shí)現(xiàn)去判斷支持的事件類(lèi)型、目標(biāo)類(lèi)型,及執(zhí)行順序。

Spring事件機(jī)制的簡(jiǎn)單例子

本例子模擬一個(gè)給多個(gè)人發(fā)送內(nèi)容(類(lèi)似于報(bào)紙新聞)的例子。

1、定義事件

Java代碼

public class ContentEvent extends ApplicationEvent {  
    public ContentEvent(final String content) {  
        super(content);  
    }  
}

非常簡(jiǎn)單,如果用戶(hù)發(fā)送內(nèi)容,只需要通過(guò)構(gòu)造器傳入內(nèi)容,然后通過(guò)getSource即可獲取。

2、定義無(wú)序監(jiān)聽(tīng)器

之所以說(shuō)無(wú)序,類(lèi)似于AOP機(jī)制,順序是無(wú)法確定的。

@Component  
public class LisiListener implements ApplicationListener<ApplicationEvent> {  
        @Override  
        public void onApplicationEvent(final ApplicationEvent event) {  
            if(event instanceof ContentEvent) {  
            System.out.println("李四收到了新的內(nèi)容:" + event.getSource());  
        }  
    }  
} 

1、使用@Compoent注冊(cè)Bean即可;
2、在實(shí)現(xiàn)中需要判斷event類(lèi)型是ContentEvent才可以處理;

更簡(jiǎn)單的辦法是通過(guò)泛型指定類(lèi)型,如下所示

@Component  
public class ZhangsanListener implements ApplicationListener<ContentEvent> {  
    @Override  
    public void onApplicationEvent(final ContentEvent event) {  
        System.out.println("張三收到了新的內(nèi)容:" + event.getSource());  
    }  
}

3、定義有序監(jiān)聽(tīng)器

實(shí)現(xiàn)SmartApplicationListener接口即可。

WangwuListener.java

@Component  
public class WangwuListener implements SmartApplicationListener {  

    @Override  
    public boolean supportsEventType(final Class<? extends ApplicationEvent> eventType) {  
        return eventType == ContentEvent.class;  
    }  
    @Override  
    public boolean supportsSourceType(final Class<?> sourceType) {  
        return sourceType == String.class;  
    }  
    @Override  
    public void onApplicationEvent(final ApplicationEvent event) {  
        System.out.println("王五在孫六之前收到新的內(nèi)容:" + event.getSource());  
    }  
    @Override  
    public int getOrder() {  
        return 1;  
    }  
} 

SunliuListener.java

@Component  
public class SunliuListener implements SmartApplicationListener {  

    @Override  
    public boolean supportsEventType(final Class<? extends ApplicationEvent> eventType) {  
        return eventType == ContentEvent.class;  
    }  

    @Override  
    public boolean supportsSourceType(final Class<?> sourceType) {  
         return sourceType == String.class;  
    }  

    @Override  
    public void onApplicationEvent(final ApplicationEvent event) {  
        System.out.println("孫六在王五之后收到新的內(nèi)容:" + event.getSource());  
    }  

    @Override  
    public int getOrder() {  
        return 2;  
    }  
}  

1.supportsEventType:用于指定支持的事件類(lèi)型,只有支持的才調(diào)用onApplicationEvent;
2.supportsSourceType:支持的目標(biāo)類(lèi)型,只有支持的才調(diào)用onApplicationEvent;
3.getOrder:即順序,越小優(yōu)先級(jí)越高

4、測(cè)試

4.1、配置文件
<context:component-scan base-package="com.xxx"/> 

就一句話,自動(dòng)掃描注解Bean。

4.2、測(cè)試類(lèi)
@RunWith(SpringJUnit4ClassRunner.class)  
@ContextConfiguration(locations={"classpath:applicationContext.xml"})  
public class HelloIT {  

    @Autowired  
    private ApplicationContext applicationContext;  
    @Test  
    public void testPublishEvent() {  
        applicationContext.publishEvent(new ContentEvent("test......"));  
    }  
}  

接著會(huì)輸出:

王五在孫六之前收到新的內(nèi)容:test......
孫六在王五之后收到新的內(nèi)容:test......
李四收到了新的內(nèi)容:test......
張三收到了新的內(nèi)容:test......

一個(gè)簡(jiǎn)單的測(cè)試?yán)泳脱菔就戤叄椅覀兪褂胹pring的事件機(jī)制去寫(xiě)相關(guān)代碼會(huì)非常簡(jiǎn)單。

Spring 對(duì)Event的注解支持

上述的幾個(gè)接口已經(jīng)非常清爽了,如果習(xí)慣使用注解,Spring也提供了,不再需要顯示實(shí)現(xiàn)

注解式的事件發(fā)布者

@Service
public class UserService {
    public void register(String name) {
        System.out.println("用戶(hù):" + name + " 已注冊(cè)!");
        applicationEventPublisher.publishEvent(new UserRegisterEvent(name));
    }
    @Autowired
    private ApplicationEventPublisher applicationEventPublisher;
}

Spring4.2之后,ApplicationEventPublisher自動(dòng)被注入到容器中,采用Autowired即可獲取。

注解式的事件訂閱者

@Service
public class EmailService {
    @EventListener
    public void listenUserRegisterEvent(UserRegisterEvent userRegisterEvent) {
        System.out.println("郵件服務(wù)接到通知,給 " + userRegisterEvent.getSource() + " 發(fā)送郵件...");
    }
}

@EventListener注解完成了ApplicationListener<E extends ApplicationEvent>接口的使命。

Spring事件機(jī)制實(shí)現(xiàn)之前提到的注冊(cè)流程

用戶(hù)注冊(cè)

這里講解一下Spring對(duì)異步事件機(jī)制的支持,實(shí)現(xiàn)方式有兩種:

1、全局異步

即只要是觸發(fā)事件都是以異步執(zhí)行,具體配置(spring-config-register.xml)如下:

<task:executor id="executor" pool-size="10" />  
<!--名字必須是applicationEventMulticaster和messageSource是一樣的,默認(rèn)找這個(gè)名字的對(duì)象 
名字必須是applicationEventMulticaster,因?yàn)锳bstractApplicationContext默認(rèn)找個(gè)
如果找不到就new一個(gè),但不是異步調(diào)用而是同步調(diào)用 -->  
<bean id="applicationEventMulticaster"     class="org.springframework.context.event.SimpleApplicationEventMulticaster">  
<!-- 注入任務(wù)執(zhí)行器 這樣就實(shí)現(xiàn)了異步調(diào)用(缺點(diǎn)是全局的,要么全部異步,要么全部同步(刪除這個(gè)屬性即是同步))  -->  
    <property name="taskExecutor" ref="executor"/>  
</bean> 

通過(guò)注入taskExecutor來(lái)完成異步調(diào)用。具體實(shí)現(xiàn)可參考之前的代碼介紹。這種方式的缺點(diǎn)很明顯:要么大家都是異步,要么大家都不是。所以不推薦使用這種方式。

2、更靈活的異步支持

spring3提供了@Aync注解來(lái)完成異步調(diào)用。此時(shí)我們可以使用這個(gè)新特性來(lái)完成異步調(diào)用。不僅支持異步調(diào)用,還支持簡(jiǎn)單的任務(wù)調(diào)度,比如我的項(xiàng)目就去掉Quartz依賴(lài),直接使用spring3這個(gè)新特性,具體可參考spring-config.xml。

2.1、開(kāi)啟異步調(diào)用支持
<!-- 開(kāi)啟@AspectJ AOP代理 -->  
<aop:aspectj-autoproxy proxy-target-class="true"/>  
<!-- 任務(wù)調(diào)度器 -->  
<task:scheduler id="scheduler" pool-size="10"/>  
<!-- 任務(wù)執(zhí)行器 -->  
<task:executor id="executor" pool-size="10"/>  
<!--開(kāi)啟注解調(diào)度支持 @Async @Scheduled-->  
<task:annotation-driven executor="executor" scheduler="scheduler" proxy-target-class="true"/>  
2.2、配置監(jiān)聽(tīng)器讓其支持異步調(diào)用
@Component  
public class EmailRegisterListener implements ApplicationListener<RegisterEvent> {  
    @Async  
    @Override  
    public void onApplicationEvent(final RegisterEvent event) {  
        System.out.println("注冊(cè)成功,發(fā)送確認(rèn)郵件給:" + ((User)event.getSource()).getUsername());  
    }  
}  

使用@Async注解即可,非常簡(jiǎn)單。

這樣不僅可以支持通過(guò)調(diào)用,也支持異步調(diào)用,非常的靈活,實(shí)際應(yīng)用推薦大家使用這種方式。

通過(guò)如上,大體了解了Spring的事件機(jī)制,可以使用該機(jī)制非常簡(jiǎn)單的完成如注冊(cè)流程,而且對(duì)于比較耗時(shí)的調(diào)用,可以直接使用Spring自身的異步支持來(lái)優(yōu)化。

代碼地址:https://gitee.com/algernoon/event.git

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,702評(píng)論 6 534
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,615評(píng)論 3 419
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事?!?“怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 176,606評(píng)論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 63,044評(píng)論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,826評(píng)論 6 410
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 55,227評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,307評(píng)論 3 442
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,447評(píng)論 0 289
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,992評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,807評(píng)論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,001評(píng)論 1 370
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,550評(píng)論 5 361
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,243評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 34,667評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 35,930評(píng)論 1 287
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,709評(píng)論 3 393
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,996評(píng)論 2 374

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

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,785評(píng)論 18 139
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,899評(píng)論 6 342
  • 正則表達(dá)式是一種用來(lái)匹配字符串的強(qiáng)有力的武器。它的設(shè)計(jì)思想是用一種描述性的語(yǔ)言來(lái)給字符串定義一個(gè)規(guī)則,凡是符合規(guī)則...
    XYZ7閱讀 3,027評(píng)論 0 0
  • 第一次知道簡(jiǎn)書(shū),是在課上老師打開(kāi)了簡(jiǎn)書(shū)的網(wǎng)站,當(dāng)時(shí)就被簡(jiǎn)書(shū)的設(shè)計(jì)風(fēng)格給迷住了。不同一些其他網(wǎng)站的雜而亂,而...
    dearestlala閱讀 212評(píng)論 0 0
  • 一、完成的事 1.看《黑鏡》 第三季完結(jié),絕對(duì)神劇,漲知識(shí)了 2.看《白夜行》 耶耶耶,完結(jié),知道真相的我心情有些...
    媚兒大人閱讀 71評(píng)論 0 0