05--SpringBoot啟動之事件監(jiān)聽機制

之前的文章分析了SpringBoot如何實例化SpringApplication對象,接下來分析run方法中的事件監(jiān)聽機制,在此之前先了解下Java的事件監(jiān)聽機制

1. Java的事件監(jiān)聽機制

Java自定義事件實現(xiàn):

  • 自定義事件繼承-->java.util.EventObject
  • 自定義事件監(jiān)聽器實現(xiàn)-->java.util.EventListener接口
    例:監(jiān)聽方法耗時

1.1 繼承EventObject定義事件類型

package sample.simple.event;

import java.util.EventObject;

public class MethodMonitorEvent extends EventObject {

    public long timestamp;

    /**
     * Constructs a prototypical Event.
     *
     * @param source The object on which the Event initially occurred.
     * @throws IllegalArgumentException if source is null.
     */
    public MethodMonitorEvent(Object source) {
        super(source);
    }
}

1.2 實現(xiàn)EventListener接口定義監(jiān)聽器

package sample.simple.event;

import java.util.EventListener;

public class MethodMonitorEventListener implements EventListener {

    public void onMethodBegin(MethodMonitorEvent event) {
        // 記錄方法開始執(zhí)行時的時間
        System.out.println("==記錄方法耗時開始");
        event.timestamp = System.currentTimeMillis();
    }

    public void onMethodEnd(MethodMonitorEvent event) {
        // 計算方法耗時
        long duration = System.currentTimeMillis() - event.timestamp;
        System.out.println("==記錄方法耗時結(jié)束");
        System.out.println("==耗時:" + duration);
    }
}

1.3 發(fā)布事件并測試

package sample.simple.event;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

public class MethodMonitorEventPublisher {
    private List<MethodMonitorEventListener> listeners = new ArrayList<MethodMonitorEventListener>();


    public void methodMonitor() throws InterruptedException {
        MethodMonitorEvent eventObject = new MethodMonitorEvent(this);
        publishEvent("begin", eventObject);
        // 模擬方法執(zhí)行:休眠5秒鐘
        TimeUnit.SECONDS.sleep(5);
        publishEvent("end", eventObject);

    }

    private void publishEvent(String status, MethodMonitorEvent event) {
        List<MethodMonitorEventListener> copyListeners = new ArrayList<MethodMonitorEventListener>(listeners);
        for (MethodMonitorEventListener listener : copyListeners) {
            if ("begin".equals(status)) {
                listener.onMethodBegin(event);
            } else {
                listener.onMethodEnd(event);
            }
        }
    }

    public void addEventListener(MethodMonitorEventListener listener) {
        listeners.add(listener);
    }


    public static void main(String[] args) throws InterruptedException {
        MethodMonitorEventPublisher publisher = new MethodMonitorEventPublisher();
        publisher.addEventListener(new MethodMonitorEventListener());
        publisher.methodMonitor();
    }

}

運行main函數(shù)

image.png

此例,了解了Java的事件監(jiān)聽機制,并簡單的實現(xiàn)了一個觀察者模式,有助于接下來對SpringBoot事件監(jiān)聽機制的了解

2. SpringBoot中的事件監(jiān)聽機制

2.1 SpringBoot中的事件發(fā)布者

接著看run方法

//根據(jù)args獲取所有SpringApplicationRunListeners監(jiān)聽器
SpringApplicationRunListeners listeners = getRunListeners(args);

//這段代碼大家應該已經(jīng)熟悉了,獲取SpringApplicationRunListeners擴展
private SpringApplicationRunListeners getRunListeners(String[] args) {
    Class<?>[] types = new Class<?>[]{SpringApplication.class, String[].class};
    return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
}

以上代碼通過Class<?>[] types = new Class<?>[]{SpringApplication.class, String[].class};類型加載對應的監(jiān)聽器,并創(chuàng)建SpringApplicationRunListener實例,看下'SpringApplicationRunListener'的構(gòu)造方法

// 接受一個Log和Collection對象并賦給類成員變量
private final Log log;
private final List<SpringApplicationRunListener> listeners;

SpringApplicationRunListeners(Log log, Collection<? extends SpringApplicationRunListener> listeners) {
    this.log = log;
    this.listeners = new ArrayList<>(listeners);
}

通過分析,SpringApplicationRunListeners類的主要作用就是存儲監(jiān)聽器對象集合并發(fā)布各種監(jiān)聽事件,SpringApplicationRunListeners其本質(zhì)上就是一個事件對象存儲和發(fā)布者,它在SpringBoot應用啟動的不同時間點委托給ApplicationEventMulticaster(下面有介紹)發(fā)布不同應用事件類型(ApplicationEvent)
SpringApplicationRunListeners會發(fā)布哪些事件呢,看源碼

//首次啟動run方法時立即調(diào)用阳惹。
public void starting() {
    for (SpringApplicationRunListener listener : this.listeners) {
        listener.starting();
    }
}

// 一旦準備好環(huán)境谍失,但在ApplicationContext創(chuàng)建環(huán)境之前調(diào)用 。
public void environmentPrepared(ConfigurableEnvironment environment) {
    for (SpringApplicationRunListener listener : this.listeners) {
        listener.environmentPrepared(environment);
    }
}

// ApplicationContext在創(chuàng)建和準備之后調(diào)用莹汤,但在加載源之前調(diào)用快鱼。
public void contextPrepared(ConfigurableApplicationContext context) {
    for (SpringApplicationRunListener listener : this.listeners) {
        listener.contextPrepared(context);
    }
}

// 在應用程序上下文加載之后但在刷新之前調(diào)用。
public void contextLoaded(ConfigurableApplicationContext context) {
    for (SpringApplicationRunListener listener : this.listeners) {
        listener.contextLoaded(context);
    }
}

// 上下文已被刷新纲岭,并且應用程序已啟動抹竹,且CommandLineRunners和ApplicationRunners未被調(diào)用。
public void started(ConfigurableApplicationContext context) {
    for (SpringApplicationRunListener listener : this.listeners) {
        listener.started(context);
    }
}

// 在run方法完成之前立即調(diào)用止潮,應用上下文已經(jīng)被刷新,并且CommandLineRunners和ApplicationRunners已經(jīng)被調(diào)用
public void running(ConfigurableApplicationContext context) {
    for (SpringApplicationRunListener listener : this.listeners) {
        listener.running(context);
    }
}

// 在運行應用程序時發(fā)生故障時調(diào)用窃判。
public void failed(ConfigurableApplicationContext context, Throwable exception) {
    for (SpringApplicationRunListener listener : this.listeners) {
        callFailedListener(listener, context, exception);
    }
}
2.2 SpringBoot中的事件類型

查看org.springframework.boot.context.event包下類共定義了以下事件類型

  • ApplicationEnvironmentPreparedEvent.java
  • ApplicationFailedEvent.java
  • ApplicationPreparedEvent.java
  • ApplicationReadyEvent.java
  • ApplicationStartedEvent.java
  • ApplicationStartingEvent.java
  • SpringApplicationEvent.java
    上面類的定義和作用比較簡答,可查看類注釋
    SpringApplicationEvent類是SpringBoot事件類的抽象基類,查看其類圖關(guān)系,可以發(fā)現(xiàn),該類也是通過繼承java.util.EventObject實現(xiàn)的
image.png

注意:在該包下還有EventPublishingRunListener接口,用來發(fā)布各種事件,下面我們會詳細分析

此處要注意SpringApplicationRunListenersSpringApplicationRunListener的關(guān)系
  • SpringApplicationRunListeners中包含了private final List<SpringApplicationRunListener> listeners集合
  • 真正負責事件發(fā)布的是SpringApplicationRunListener
  • SpringApplicationRunListener中又維護了SimpleApplicationEventMulticaster對象,并通過該對象將事件廣播給各個監(jiān)聽器
2.3 SpringBoot中的事件監(jiān)聽器

打開spring.factories文件查看

# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.ConfigFileApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.context.logging.LoggingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener
2.4 SpringBoot中的事件注冊

SpringBoot的事件,監(jiān)聽器,以及其發(fā)布者,事件廣播器已經(jīng)有所介紹,并且SpringBoot委托ApplicationEventMulticaster進行事件廣播,那么,事件是何時注冊到廣播器的呢,通過下面的代碼逐步調(diào)用,實例化EventPublishingRunListener對象時會將事件注冊到廣播器

//入口是SpringApplication的run方法
SpringApplicationRunListeners listeners = getRunListeners(args);
 -getRunListeners(String[] args)
  -return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
    -getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args)
    -T instance = (T) BeanUtils.instantiateClass(constructor, args);

打開EventPublishingRunListener源碼

public EventPublishingRunListener(SpringApplication application, String[] args) {
    this.application = application;
    this.args = args;
    //創(chuàng)建SimpleApplicationEventMulticaster對象
    //SimpleApplicationEventMulticaster-->AbstractApplicationEventMulticaster-->ApplicationEventMulticaster
    this.initialMulticaster = new SimpleApplicationEventMulticaster();
    //從application對象中獲取所有已經(jīng)加載的Listener對象,循環(huán)并注冊至initialMulticaster對象
    for (ApplicationListener<?> listener : application.getListeners()) {
        this.initialMulticaster.addApplicationListener(listener);
    }
}
2.5 SpringBoot中的事件發(fā)布與監(jiān)聽

EventPublishingRunListener作為事件的發(fā)布者已經(jīng)在前面初始化,當程序執(zhí)行到listeners.starting();時,調(diào)用了
SpringApplicationRunListenersstarting()方法

//首次啟動run方法時立即調(diào)用。
public void starting() {
    for (SpringApplicationRunListener listener : this.listeners) {
        listener.starting();
    }
}

this.listeners集合中包含了EventPublishingRunListener實例,那么這里將要調(diào)用其starting()方法

private final SimpleApplicationEventMulticaster initialMulticaster;

@Override
// 廣播ApplicationStartingEvent事件
public void starting() {
    this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));
}

SimpleApplicationEventMulticaster繼承了AbstractApplicationEventMulticaster,其作用如下

  • 將所有事件多播到所有已注冊的偵聽器喇闸,將其留給偵聽器以忽略他們不感??興趣的事件袄琳。偵聽器通常instanceof 會對傳入的事件對象執(zhí)行相應的檢查。

  • 默認情況下燃乍,在調(diào)用線程中調(diào)用所有偵聽器唆樊。這允許惡意偵聽器阻塞整個應用程序的危險,但增加了最小的開銷刻蟹。指定備用任務(wù)執(zhí)行程序以使偵聽器在不同的線程中執(zhí)行逗旁,例如從線程池中執(zhí)行。

繼續(xù)跟蹤源碼

@Override
public void multicastEvent(ApplicationEvent event) {
    multicastEvent(event, resolveDefaultEventType(event));
}

@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
    ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
    //getApplicationListeners(event, type)-->通過給定的事件類型,返回監(jiān)聽器集合
    //此處獲取到的監(jiān)聽器有LoggingApplicationListener,
    //DelegatingApplicationListener,
    //LiquibaseServiceLocatorApplicationListener等監(jiān)聽器,
    //以LoggingApplicationListener為例繼續(xù)debug跟蹤,至于符合獲取對應的監(jiān)聽器,放在下面分析
    for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
        //如果上下文中有線程池則使用線程池調(diào)用
        Executor executor = getTaskExecutor();
        if (executor != null) {
            executor.execute(() -> invokeListener(listener, event));
        }
        else {
            invokeListener(listener, event);
        }
    }
}

//根據(jù)事件執(zhí)行對應的監(jiān)聽器
protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
    ErrorHandler errorHandler = getErrorHandler();
    if (errorHandler != null) {
        try {
            doInvokeListener(listener, event);
        }
        catch (Throwable err) {
            errorHandler.handleError(err);
        }
    }
    else {
        doInvokeListener(listener, event);
    }
}

//執(zhí)行監(jiān)聽器
//-->本例分析的監(jiān)聽器為LoggingApplicationListener
//-->事件為ApplicationStartedEvent
//大家debug時要注意看自己的事件和監(jiān)聽器是什么,代碼跟蹤會進到不同的監(jiān)聽器中
private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
    try {
        listener.onApplicationEvent(event);
    }
    catch (ClassCastException ex) {
        String msg = ex.getMessage();
        if (msg == null || matchesClassCastMessage(msg, event.getClass().getName())) {
            // Possibly a lambda-defined listener which we could not resolve the generic event type for
            // -> let's suppress the exception and just log a debug message.
            Log logger = LogFactory.getLog(getClass());
            if (logger.isDebugEnabled()) {
                logger.debug("Non-matching event type for listener: " + listener, ex);
            }
        }
        else {
            throw ex;
        }
    }
}

@Override
//-->LoggingApplicationListener類
//判斷事件類型決定調(diào)用對應的事件
public void onApplicationEvent(ApplicationEvent event) {
    if (event instanceof ApplicationStartingEvent) {
        //-->被調(diào)用
        onApplicationStartingEvent((ApplicationStartingEvent) event);
    } else if (event instanceof ApplicationEnvironmentPreparedEvent) {
        onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
    } else if (event instanceof ApplicationPreparedEvent) {
        onApplicationPreparedEvent((ApplicationPreparedEvent) event);
    } else if (event instanceof ContextClosedEvent && ((ContextClosedEvent) event).getApplicationContext().getParent() == null) {
        onContextClosedEvent();
    } else if (event instanceof ApplicationFailedEvent) {
        onApplicationFailedEvent();
    }
}

//實例化loggingSystem并將將日志記錄系統(tǒng)重置為限制輸出座咆。
private void onApplicationStartingEvent(ApplicationStartingEvent event) {
    this.loggingSystem = LoggingSystem.get(event.getSpringApplication().getClassLoader());
    this.loggingSystem.beforeInitialize();
}

至此,已經(jīng)分析了SpringBoot中的事件類型,監(jiān)聽器類型,事件發(fā)布與監(jiān)聽的過程

繼續(xù)上文未完成分析

2.6 SpringBoot根據(jù)事件類型獲取對應的監(jiān)聽器集合

上面代碼中有getApplicationListeners(event, type)一句話,SpringBoot是如何根據(jù)不同的事件來獲取不同的監(jiān)聽器呢,看源碼

//返回與給定事件類型匹配的ApplicationListeners集合痢艺。不匹配的Listeners會盡早被排除在外。
protected Collection<ApplicationListener<?>> getApplicationListeners(ApplicationEvent event, ResolvableType eventType) {
    //獲取事件源,并封裝至ListenerCacheKey對象
    Object source = event.getSource();
    Class<?> sourceType = (source != null ? source.getClass() : null);
    ListenerCacheKey cacheKey = new ListenerCacheKey(eventType, sourceType);

    // Quick check for existing entry on ConcurrentHashMap...
    //嘗試從緩存中獲取事件
    ListenerRetriever retriever = this.retrieverCache.get(cacheKey);
    if (retriever != null) {
        return retriever.getApplicationListeners();
    }
    //檢查給定類在給定上下文中是否是緩存安全的介陶,即它是由給定的ClassLoader還是由其父類加載堤舒。
    if (this.beanClassLoader == null || (ClassUtils.isCacheSafe(event.getClass(), this.beanClassLoader) &&
                    (sourceType == null || ClassUtils.isCacheSafe(sourceType, this.beanClassLoader)))) {
        // 加鎖
        synchronized (this.retrievalMutex) {
            ////搶到鎖之后再做一次判斷,因為有可能在前面BLOCK的時候哺呜,另一個搶到鎖的線程已經(jīng)設(shè)置好了緩存
            retriever = this.retrieverCache.get(cacheKey);
            if (retriever != null) {
                return retriever.getApplicationListeners();
            }
            //前面都沒有能從緩存中獲取到,則創(chuàng)建ListenerRetriever對象,
            retriever = new ListenerRetriever(true);
            //根據(jù)事件源檢索對應的監(jiān)聽器,詳解見下面
            Collection<ApplicationListener<?>> listeners = retrieveApplicationListeners(eventType, sourceType, retriever);
            //加入到retrieverCache緩存中
            this.retrieverCache.put(cacheKey, retriever);
            //返回監(jiān)聽
            return listeners;
        }
    }
    else {
        // No ListenerRetriever caching -> no synchronization necessary
        return retrieveApplicationListeners(eventType, sourceType, null);
    }
}

//根據(jù)事件源檢索對應的監(jiān)聽器,以下例分析
//eventType-->org.springframework.boot.context.event.ApplicationStartingEvent
//sourceType-->class org.springframework.boot.SpringApplication
//retriever-->Helper類舌缤,它封裝一組特定的目標監(jiān)聽器,允許有效地檢索預先過濾的監(jiān)聽器某残。
private Collection<ApplicationListener<?>> retrieveApplicationListeners(
        ResolvableType eventType, @Nullable Class<?> sourceType, @Nullable ListenerRetriever retriever) {

    List<ApplicationListener<?>> allListeners = new ArrayList<>();
    Set<ApplicationListener<?>> listeners;
    Set<String> listenerBeans;
    synchronized (this.retrievalMutex) {
        listeners = new LinkedHashSet<>(this.defaultRetriever.applicationListeners);
        listenerBeans = new LinkedHashSet<>(this.defaultRetriever.applicationListenerBeans);
    }
    //循環(huán)上下文初始化加載的所有監(jiān)聽器
    for (ApplicationListener<?> listener : listeners) {
        //判斷監(jiān)聽器是否支持給定的事件,下面詳解
        if (supportsEvent(listener, eventType, sourceType)) {
            if (retriever != null) {
                retriever.applicationListeners.add(listener);
            }
            allListeners.add(listener);
        }
    }
    //此段代碼不知道干啥的...
    if (!listenerBeans.isEmpty()) {
        BeanFactory beanFactory = getBeanFactory();
        for (String listenerBeanName : listenerBeans) {
            try {
                Class<?> listenerType = beanFactory.getType(listenerBeanName);
                if (listenerType == null || supportsEvent(listenerType, eventType)) {
                    ApplicationListener<?> listener = beanFactory.getBean(listenerBeanName, ApplicationListener.class);
                    if (!allListeners.contains(listener) && supportsEvent(listener, eventType, sourceType)) {
                        if (retriever != null) {
                            retriever.applicationListenerBeans.add(listenerBeanName);
                        }
                        allListeners.add(listener);
                    }
                }
            }
            catch (NoSuchBeanDefinitionException ex) {
                // Singleton listener instance (without backing bean definition) disappeared -
                // probably in the middle of the destruction phase
            }
        }
    }
    //排序并返回
    AnnotationAwareOrderComparator.sort(allListeners);
    return allListeners;
}

//判斷監(jiān)聽器是否支持給定的事件
protected boolean supportsEvent(ApplicationListener<?> listener, ResolvableType eventType, @Nullable Class<?> sourceType) {
    //如果監(jiān)聽器是GenericApplicationListener實例,則直接返回
    //否則創(chuàng)建GenericApplicationListenerAdapter實例
    //GenericApplicationListenerAdapter-->事件適配器
    GenericApplicationListener smartListener = (listener instanceof GenericApplicationListener ?
            (GenericApplicationListener) listener : new GenericApplicationListenerAdapter(listener));
    return (smartListener.supportsEventType(eventType) && smartListener.supportsSourceType(sourceType));
}

至此,我們已經(jīng)分析了SpringBoot根據(jù)事件類型獲取對應的監(jiān)聽器集合,細節(jié)還很多,還有很多沒分析到的地方,篇幅限制,不能把所有的代碼都粘貼出來,大家諒解!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末国撵,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子玻墅,更是在濱河造成了極大的恐慌介牙,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,591評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件澳厢,死亡現(xiàn)場離奇詭異环础,居然都是意外死亡囚似,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評論 3 392
  • 文/潘曉璐 我一進店門线得,熙熙樓的掌柜王于貴愁眉苦臉地迎上來饶唤,“玉大人,你說我怎么就攤上這事贯钩∧伎瘢” “怎么了?”我有些...
    開封第一講書人閱讀 162,823評論 0 353
  • 文/不壞的土叔 我叫張陵角雷,是天一觀的道長祸穷。 經(jīng)常有香客問我,道長谓罗,這世上最難降的妖魔是什么粱哼? 我笑而不...
    開封第一講書人閱讀 58,204評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮檩咱,結(jié)果婚禮上揭措,老公的妹妹穿的比我還像新娘。我一直安慰自己刻蚯,他們只是感情好绊含,可當我...
    茶點故事閱讀 67,228評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著炊汹,像睡著了一般躬充。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上讨便,一...
    開封第一講書人閱讀 51,190評論 1 299
  • 那天充甚,我揣著相機與錄音,去河邊找鬼霸褒。 笑死伴找,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的废菱。 我是一名探鬼主播技矮,決...
    沈念sama閱讀 40,078評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼殊轴!你這毒婦竟也來了衰倦?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,923評論 0 274
  • 序言:老撾萬榮一對情侶失蹤旁理,失蹤者是張志新(化名)和其女友劉穎樊零,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體孽文,經(jīng)...
    沈念sama閱讀 45,334評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡驻襟,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,550評論 2 333
  • 正文 我和宋清朗相戀三年十性,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片塑悼。...
    茶點故事閱讀 39,727評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖楷掉,靈堂內(nèi)的尸體忽然破棺而出厢蒜,到底是詐尸還是另有隱情,我是刑警寧澤烹植,帶...
    沈念sama閱讀 35,428評論 5 343
  • 正文 年R本政府宣布斑鸦,位于F島的核電站,受9級特大地震影響草雕,放射性物質(zhì)發(fā)生泄漏巷屿。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,022評論 3 326
  • 文/蒙蒙 一墩虹、第九天 我趴在偏房一處隱蔽的房頂上張望嘱巾。 院中可真熱鬧,春花似錦诫钓、人聲如沸旬昭。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽问拘。三九已至,卻和暖如春惧所,著一層夾襖步出監(jiān)牢的瞬間骤坐,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評論 1 269
  • 我被黑心中介騙來泰國打工下愈, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留纽绍,地道東北人。 一個月前我還...
    沈念sama閱讀 47,734評論 2 368
  • 正文 我出身青樓驰唬,卻偏偏與公主長得像顶岸,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子叫编,可洞房花燭夜當晚...
    茶點故事閱讀 44,619評論 2 354

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

  • /Library/Java/JavaVirtualMachines/jdk-9.jdk/Contents/Home...
    光劍書架上的書閱讀 3,878評論 2 8
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理辖佣,服務(wù)發(fā)現(xiàn),斷路器搓逾,智...
    卡卡羅2017閱讀 134,652評論 18 139
  • 阿菱:好大的雨呀卷谈。 阿球:來也沖沖,去也匆匆霞篡。多姿多彩世蔗,滑蓋繽紛端逼。 阿菱:別站著說話不腰疼,還不都是為了生活污淋?哎顶滩,...
    云水坡頭閱讀 289評論 0 0
  • 兇殺案 下雨的早晨 (細節(jié)刻畫,小清新寸爆,注重時間礁鲁,結(jié)尾突兀。) 失控的雙重人格患者 郁郁的尋找親人欄目主持人 所謂...
    美女一號閱讀 173評論 0 1
  • 忙碌的一天赁豆。 床上瞬間回顧過去十年仅醇,再次感到立場的重要性。中午魔种、晚上餐桌析二,不知不覺又吃多了,如果說喝酒是氛圍的事情...
    天之心語閱讀 141評論 0 1