ARouter解析六:攔截器

今天我們接著來拆下ARouter的攔截器溪食,這個(gè)是ARouter路由框架的第六篇分享了。在Android系統(tǒng)自帶的startActivity的方法調(diào)用后迅办,我們是沒有辦法在跳轉(zhuǎn)的中間過程插入一些其它處理的力惯,ARouter中的攔截器就實(shí)現(xiàn)了這種功能,可以在跳轉(zhuǎn)過程中添加自定義的功能南捂,比如添加攜帶參數(shù),判斷是否需要登錄等旧找,是針對AOP切面編程思想的實(shí)現(xiàn)溺健。

今天攔截器的分享會涉及到多線程,APT等技術(shù),有一些反復(fù)的內(nèi)容在前面的系列文章說過了钮蛛,比如ARouter單例初始化過程鞭缭,PostCard等內(nèi)容,這里就不再贅述了魏颓。今天我們從下面幾個(gè)方面來解析攔截器:

1.攔截器的注冊

2.攔截器的初始化

3.攔截器的攔截過程源碼分析

開始之前我們先看下官方Demo中攔截器的實(shí)現(xiàn)效果:

攔截器.png

點(diǎn)擊攔截岭辣,會彈出攔截提示框點(diǎn)擊加點(diǎn)料就會攔截,在攔截器中添加參數(shù):

攔截.png
攔截添加跳轉(zhuǎn)參數(shù).png

好了甸饱,開始我們的攔截器之旅吧~~~

1.攔截器注冊

攔截器使用和Activity@Route差不多沦童,只不過攔截器是使用另外一個(gè)注解@Interceptor仑濒。有兩個(gè)變量,priority是攔截器的優(yōu)先級偷遗,值越小優(yōu)先級越高墩瞳,會優(yōu)先攔截。name是攔截器的名稱氏豌,開發(fā)也不怎么用喉酌。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.CLASS)
public @interface Interceptor {
    /**
     * The priority of interceptor, ARouter will be excute them follow the priority.
     */
    int priority();

    /**
     * The name of interceptor, may be used to generate javadoc.
     */
    String name() default "Default";
}

攔截器都默認(rèn)實(shí)現(xiàn)IInterceptor接口:

public interface IInterceptor extends IProvider {

    /**
     * The operation of this interceptor.
     *
     * @param postcard meta
     * @param callback cb
     */
    void process(Postcard postcard, InterceptorCallback callback);
}

在攔截器實(shí)現(xiàn)類使用注解@Interceptor會自動(dòng)注冊,就是在編譯期間會自動(dòng)生成映射關(guān)系類泵喘,使用到的就是APT技術(shù)泪电,不了解的小伙伴可參考Android模塊開發(fā)之APT技術(shù).
這里因?yàn)槭亲詣?dòng)注冊的,所以可以將不同功能的攔截器放在不同功能的模塊中纪铺,只有模塊被打包到整個(gè)項(xiàng)目中相速,因?yàn)樽詣?dòng)注冊機(jī)制所以攔截器就會生效,如果不將這些攔截器放到模塊并打包到項(xiàng)目中鲜锚,那就不會生效和蚪,這樣就不用去做很多注冊與反注冊的工作,這也是ARouter適用于模塊開發(fā)的原因之一烹棉。
看看自動(dòng)生成的映射關(guān)系類是怎樣,官方Demo提供了兩個(gè)攔截器,Test1Interceptor攔截器在app模塊中怯疤,TestInterceptor90攔截器在test-module-1中浆洗。下面文件路徑ARouter/app/build/generated/source/apt/debug/com/alibaba/android/arouter/routes/ARouter$$Interceptors$$app.java

public class ARouter$$Interceptors$$app implements IInterceptorGroup {
  @Override
  public void loadInto(Map<Integer, Class<? extends IInterceptor>> interceptors) {
    interceptors.put(7, Test1Interceptor.class);
  }
}

下面文件路徑:ARouter/test-module-1/build/generated/source/apt/release/com/alibaba/android/arouter/routes/ARouter$$Interceptors$$testmodule1.java

public class ARouter$$Interceptors$$testmodule1 implements IInterceptorGroup {
  @Override
  public void loadInto(Map<Integer, Class<? extends IInterceptor>> interceptors) {
    interceptors.put(90, TestInterceptor90.class);
  }
}

生成的映射關(guān)系類就實(shí)現(xiàn)一個(gè)功能,將攔截器實(shí)現(xiàn)類加載緩存集峦。接下來我們看看攔截器在什么時(shí)候加載進(jìn)緩存伏社,也就是初始化。

2.攔截器初始化

因?yàn)閿r截器是在每次跳轉(zhuǎn)中間都需要判斷的塔淤,所以在ARouter初始化的時(shí)候就會加載進(jìn)來摘昌,初始化過程肯定需要和編譯期間生成的映射關(guān)系類打交道,所以邏輯自然就放在LogisticsCenter中高蜂。我們看看初始化的源碼,和攔截器無關(guān)的邏輯我做了刪減聪黎,為了看起來方便點(diǎn)。

public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {

        try {
            // These class was generate by arouter-compiler.
            List<String> classFileNames = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);

            for (String className : classFileNames) {
                if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) {
                    // Load interceptorMeta
                    ((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex);
                } 
            }

            if (Warehouse.groupsIndex.size() == 0) {
                logger.error(TAG, "No mapping files were found, check your configuration please!");
            }

            if (ARouter.debuggable()) {
                logger.debug(TAG, String.format(Locale.getDefault(), "LogisticsCenter has already been loaded, GroupIndex[%d], InterceptorIndex[%d], ProviderIndex[%d]", Warehouse.groupsIndex.size(), Warehouse.interceptorsIndex.size(), Warehouse.providersIndex.size()));
            }
        } catch (Exception e) {
            throw new HandlerException(TAG + "ARouter init logistics center exception! [" + e.getMessage() + "]");
        }
}

可以看出來备恤,攔截器映射關(guān)系會加載到倉庫的mapWarehouse.interceptorsIndex 中稿饰,要分清楚這個(gè)時(shí)候只是代碼緩存中知道了有框架中有哪些攔截器,但是還沒初始化露泊。
我在這里下了個(gè)斷點(diǎn)喉镰,看看有幾個(gè)攔截器。
可以看出來有兩個(gè)映射文件惭笑,這個(gè)和前面注冊的分析是一樣的侣姆。

攔截器個(gè)數(shù).png

再看看加載到倉庫中的是不是兩個(gè)生真,可以看出來確實(shí)是兩個(gè),好厲害:)

倉庫攔截器.png

到這里只是找到了攔截器捺宗,還沒有進(jìn)行初始化柱蟀,我們接著看源碼,在_ARouter.init就是主要工作就是調(diào)用LogisticsCenter進(jìn)行初始化,這里就是上面的分析工程,然后會調(diào)用_ARouter.afterInit()偿凭。

public static void init(Application application) {
        if (!hasInit) {
            logger = _ARouter.logger;
            _ARouter.logger.info(Consts.TAG, "ARouter init start.");
            hasInit = _ARouter.init(application);

            if (hasInit) {
                _ARouter.afterInit();
            }

            _ARouter.logger.info(Consts.TAG, "ARouter init over.");
        }
}

我們接著跟到_ARouter.afterInit(),獲取interceptorService产弹。

static void afterInit() {
        // Trigger interceptor init, use byName.
        interceptorService = (InterceptorService) ARouter.getInstance().build("/arouter/service/interceptor").navigation();
    }

在獲取interceptorService過程中會在LogisticsCenter中實(shí)例化interceptorService實(shí)現(xiàn)類InterceptorServiceImpl后調(diào)用init(Context)方法。

switch (routeMeta.getType()) {
                case PROVIDER:  
                    Class<? extends IProvider> providerMeta = (Class<? extends IProvider>) routeMeta.getDestination();
                    IProvider instance = Warehouse.providers.get(providerMeta);
                    if (null == instance) { // There's no instance of this provider
                        IProvider provider;
                        try {
                            provider = providerMeta.getConstructor().newInstance();
                            provider.init(mContext);
                            Warehouse.providers.put(providerMeta, provider);
                            instance = provider;
                        } catch (Exception e) {
                            throw new HandlerException("Init provider failed! " + e.getMessage());
                        }
                    }
                    postcard.setProvider(instance);
                    postcard.greenChannel();    // Provider should skip all of interceptors
                    break;
                case FRAGMENT:
                    postcard.greenChannel();    // Fragment needn't interceptors
                default:
                    break;
}

我們接著跟到init方法中,可以看出弯囊,就是從倉庫Warehouse.interceptorsIndex中取出攔截器然后進(jìn)行實(shí)例化痰哨,接著將實(shí)例對象添加到Warehouse.interceptors 中。

@Override
public void init(final Context context) {
     LogisticsCenter.executor.execute(new Runnable() {
            @Override
            public void run() {
                if (MapUtils.isNotEmpty(Warehouse.interceptorsIndex)) {
                    for (Map.Entry<Integer, Class<? extends IInterceptor>> entry : Warehouse.interceptorsIndex.entrySet()) {
                        Class<? extends IInterceptor> interceptorClass = entry.getValue();
                        try {
                            IInterceptor iInterceptor = interceptorClass.getConstructor().newInstance();
                            iInterceptor.init(context);
                            Warehouse.interceptors.add(iInterceptor);
                        } catch (Exception ex) {
                            throw new HandlerException(TAG + "ARouter init interceptor error! name = [" + interceptorClass.getName() + "], reason = [" + ex.getMessage() + "]");
                        }
                    }

                    interceptorHasInit = true;

                    logger.info(TAG, "ARouter interceptors init over.");

                    synchronized (interceptorInitLock) {
                        interceptorInitLock.notifyAll();
                    }
                }
            }
        });
}

到這里攔截器的初始化過程就比較清楚了匾嘱,有一點(diǎn)需要知道斤斧,就是攔截器怎么根據(jù)優(yōu)先級進(jìn)行攔截?原理就在倉庫的兩個(gè)緩存map中霎烙,一個(gè)就是存儲攔截器類的Warehouse.interceptorsIndex,另外一個(gè)就是存儲攔截器實(shí)例的Warehouse.interceptors.我們看下這兩個(gè)類型:

// Cache interceptor
static Map<Integer, Class<? extends IInterceptor>> interceptorsIndex = new UniqueKeyTreeMap<>("More than one interceptors use same priority [%s]");
static List<IInterceptor> interceptors = new ArrayList<>();

其中interceptorsIndex的類型是TreeMap,原理是紅黑樹撬讽,可以實(shí)現(xiàn)按順序存儲,所以我們攔截器就可以按照優(yōu)先級存儲在這個(gè)緩存中悬垃。實(shí)例化的時(shí)候也是按照優(yōu)先級取出游昼,然后實(shí)例化存儲到ArrayList中,優(yōu)先級值越小就在隊(duì)列中越靠前尝蠕,這就是攔截器優(yōu)先級的原理烘豌。細(xì)節(jié)處見功夫啊看彼!

接下來就看看攔截器是怎么攔截的~~~

3.攔截器的攔截過程源碼分析

攔截是在跳轉(zhuǎn)之前攔截廊佩,所以我們到_ARouter中的navigation去看,如果需要攔截就通過interceptorService進(jìn)行攔截,不需要攔截就直接跳轉(zhuǎn)靖榕。

if (!postcard.isGreenChannel()) {   // It must be run in async thread, maybe interceptor cost too mush time made ANR.
      interceptorService.doInterceptions(postcard, new InterceptorCallback() {
                /**
                 * Continue process
                 *
                 * @param postcard route meta
                 */
                @Override
                public void onContinue(Postcard postcard) {
                    _navigation(context, postcard, requestCode, callback);
                }

                /**
                 * Interrupt process, pipeline will be destory when this method called.
                 *
                 * @param exception Reson of interrupt.
                 */
                @Override
                public void onInterrupt(Throwable exception) {
                    if (null != callback) {
                        callback.onInterrupt(postcard);
                    }

                    logger.info(Consts.TAG, "Navigation failed, termination by interceptor : " + exception.getMessage());
                }
            });
        } else {
            return _navigation(context, postcard, requestCode, callback);
}

interceptorServiceInterceptorServiceImpl的實(shí)例對象.

@Route(path = "/arouter/service/interceptor")
public class InterceptorServiceImpl implements InterceptorService

我們接著看攔截方法:

1.首先看下Warehouse.interceptors是否有攔截器标锄,如果沒有就直接調(diào)用回調(diào)接口跳轉(zhuǎn);有攔截器就進(jìn)行后面步驟茁计。

public void doInterceptions(final Postcard postcard, final InterceptorCallback callback) {
        //1.是否有攔截器
        if (null != Warehouse.interceptors && Warehouse.interceptors.size() > 0) {

            //2.檢查攔截器初始化情況
            checkInterceptorsInitStatus();

            if (!interceptorHasInit) {//未初始化就拋異常
                callback.onInterrupt(new HandlerException("Interceptors initialization takes too much time."));
                return;
            }

            //3.攔截
            LogisticsCenter.executor.execute(new Runnable() {
                @Override
                public void run() {
                    CancelableCountDownLatch interceptorCounter = new CancelableCountDownLatch(Warehouse.interceptors.size());
                    try {
                        _excute(0, interceptorCounter, postcard);
                        interceptorCounter.await(postcard.getTimeout(), TimeUnit.SECONDS);
                        if (interceptorCounter.getCount() > 0) {    // Cancel the navigation this time, if it hasn't return anythings.
                            callback.onInterrupt(new HandlerException("The interceptor processing timed out."));
                        } else if (null != postcard.getTag()) {    // Maybe some exception in the tag.
                            callback.onInterrupt(new HandlerException(postcard.getTag().toString()));
                        } else {
                            callback.onContinue(postcard);
                        }
                    } catch (Exception e) {
                        callback.onInterrupt(e);
                    }
                }
            });
        } else {
            callback.onContinue(postcard);
        }
    }

2.首先檢查攔截器的初始化情況checkInterceptorsInitStatus料皇,這是個(gè)同步方法,攔截器未初始化就會等待一定時(shí)間星压,超時(shí)就拋出錯(cuò)誤瓶蝴。在前面init 方法是在線程池中異步執(zhí)行的,中如果初始化成功就會釋放鎖interceptorInitLock租幕。為什么要異步舷手?因?yàn)閿r截器可能數(shù)目比較多或者初始化比較耗時(shí)。

synchronized (interceptorInitLock) {
        interceptorInitLock.notifyAll();
}

接著看看checkInterceptorsInitStatus方法劲绪,當(dāng)攔截器還沒初始化完成男窟,這里就在鎖上同步等待時(shí)間10 * 1000盆赤,超時(shí)還未獲取到鎖就會報(bào)錯(cuò)。

private static void checkInterceptorsInitStatus() {
        synchronized (interceptorInitLock) {
            while (!interceptorHasInit) {
                try {
                    interceptorInitLock.wait(10 * 1000);
                } catch (InterruptedException e) {
                    throw new HandlerException(TAG + "Interceptor init cost too much time error! reason = [" + e.getMessage() + "]");
                }
            }
        }
    }

攔截器都初始化成功后歉眷,來到第三步攔截實(shí)現(xiàn)牺六,攔截也是在線程池中異步執(zhí)行,同上面init的原因一樣汗捡。

3.攔截過程就在_excute方法中實(shí)現(xiàn),這個(gè)我們在第四步中再分析淑际。攔截器不可能無限時(shí)長攔截吧,那么怎么實(shí)現(xiàn)扇住?就是通過Java提供的異步工具CountDownLatch.在等待interceptorCounter.await(postcard.getTimeout(), TimeUnit.SECONDS);后進(jìn)行判斷interceptorCounter.getCount是否等于0或者postcard.getTag是否是空春缕,如果滿足就是攔截出現(xiàn)問題;否則就繼續(xù)放行艘蹋。

接著看看攔截的真正實(shí)現(xiàn)邏輯:

3.攔截真正過程在_execute中, 邏輯比較簡單锄贼,就是依次取出攔截器實(shí)例,然后調(diào)用process方法女阀,傳入回調(diào)接口InterceptorCallback宅荤,當(dāng)前攔截器處理完畢回調(diào)onContinue,在這里面遞減counter.countDown浸策,然后取出下一個(gè)攔截器循環(huán)上面過程冯键。

private static void _excute(final int index, final CancelableCountDownLatch counter, final Postcard postcard) {
        if (index < Warehouse.interceptors.size()) {
            IInterceptor iInterceptor = Warehouse.interceptors.get(index);
            iInterceptor.process(postcard, new InterceptorCallback() {
                @Override
                public void onContinue(Postcard postcard) {
                    // Last interceptor excute over with no exception.
                    counter.countDown();
                    _excute(index + 1, counter, postcard);  // When counter is down, it will be execute continue ,but index bigger than interceptors size, then U know.
                }
                @Override
                public void onInterrupt(Throwable exception) {
                    // Last interceptor excute over with fatal exception.
                    postcard.setTag(null == exception ? new HandlerException("No message.") : exception.getMessage());    // save the exception message for backup.
                    counter.cancel();
                }
            });
        }
}

攔截器的實(shí)現(xiàn)流程就是以上。接著我們挑一個(gè)攔截器Test1Interceptor的實(shí)現(xiàn)看看庸汗。這個(gè)就是上面Demo演示的彈框攔截器惫确,如果選擇選擇“加點(diǎn)料”,就會添加參數(shù)夫晌,然后調(diào)用回調(diào)接口的callback.onContinue,繼續(xù)到下一個(gè)攔截器昧诱。

@Override
public void process(final Postcard postcard, final InterceptorCallback callback) {
        if ("/test/activity4".equals(postcard.getPath())) {
            final AlertDialog.Builder ab = new AlertDialog.Builder(MainActivity.getThis());
            ab.setCancelable(false);
            ab.setTitle("溫馨提醒");
            ab.setMessage("想要跳轉(zhuǎn)到Test4Activity么晓淀?(觸發(fā)了\"/inter/test1\"攔截器,攔截了本次跳轉(zhuǎn))");
            ab.setNegativeButton("繼續(xù)", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    callback.onContinue(postcard);
                }
            });
            ab.setNeutralButton("算了", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    callback.onInterrupt(null);
                }
            });
            ab.setPositiveButton("加點(diǎn)料", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    postcard.withString("extra", "我是在攔截器中附加的參數(shù)");
                    callback.onContinue(postcard);
                }
            });

            MainLooper.runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    ab.create().show();
                }
            });
        } else {
            callback.onContinue(postcard);
        }
}

4.總結(jié)

今天的攔截器內(nèi)容會比較多一點(diǎn)盏档,攔截器的注冊和其它的activity或者service是差不多的凶掰,主要是注解不同。攔截器的初始化過程是在線程池中進(jìn)行蜈亩,為了是攔截器可能耗時(shí)的問題懦窘。攔截器的攔截過程其實(shí)就是在線程池中從倉庫里依次取出攔截器實(shí)例進(jìn)行攔截,稍微難一點(diǎn)的地方就是在多線程的同步問題上稚配,用的還是Java的多線程技術(shù)畅涂。

有需要的小伙伴可以看下前面的系列分享:
ARouter解析一:基本使用及頁面注冊源碼解析
ARouter解析二:頁面跳轉(zhuǎn)源碼分析
ARouter解析三:URL跳轉(zhuǎn)本地頁面源碼分析
ARouter解析四:發(fā)現(xiàn)服務(wù)和Fragment
ARouter解析五:IoC與依賴注入

今天的攔截器的內(nèi)容就是這樣,希望對大家有點(diǎn)幫助道川,謝謝午衰!

歡迎關(guān)注公眾號:JueCode

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末立宜,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子臊岸,更是在濱河造成了極大的恐慌橙数,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,451評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件帅戒,死亡現(xiàn)場離奇詭異灯帮,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)逻住,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,172評論 3 394
  • 文/潘曉璐 我一進(jìn)店門钟哥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事怀伦〔J” “怎么了?”我有些...
    開封第一講書人閱讀 164,782評論 0 354
  • 文/不壞的土叔 我叫張陵银受,是天一觀的道長。 經(jīng)常有香客問我鸦采,道長宾巍,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,709評論 1 294
  • 正文 為了忘掉前任渔伯,我火速辦了婚禮顶霞,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘锣吼。我一直安慰自己选浑,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,733評論 6 392
  • 文/花漫 我一把揭開白布玄叠。 她就那樣靜靜地躺著古徒,像睡著了一般。 火紅的嫁衣襯著肌膚如雪读恃。 梳的紋絲不亂的頭發(fā)上隧膘,一...
    開封第一講書人閱讀 51,578評論 1 305
  • 那天,我揣著相機(jī)與錄音寺惫,去河邊找鬼疹吃。 笑死,一個(gè)胖子當(dāng)著我的面吹牛西雀,可吹牛的內(nèi)容都是我干的萨驶。 我是一名探鬼主播,決...
    沈念sama閱讀 40,320評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼艇肴,長吁一口氣:“原來是場噩夢啊……” “哼篡撵!你這毒婦竟也來了判莉?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,241評論 0 276
  • 序言:老撾萬榮一對情侶失蹤育谬,失蹤者是張志新(化名)和其女友劉穎券盅,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體膛檀,經(jīng)...
    沈念sama閱讀 45,686評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡锰镀,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,878評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了咖刃。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片泳炉。...
    茶點(diǎn)故事閱讀 39,992評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖嚎杨,靈堂內(nèi)的尸體忽然破棺而出花鹅,到底是詐尸還是另有隱情,我是刑警寧澤枫浙,帶...
    沈念sama閱讀 35,715評論 5 346
  • 正文 年R本政府宣布刨肃,位于F島的核電站,受9級特大地震影響箩帚,放射性物質(zhì)發(fā)生泄漏真友。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,336評論 3 330
  • 文/蒙蒙 一紧帕、第九天 我趴在偏房一處隱蔽的房頂上張望盔然。 院中可真熱鬧,春花似錦是嗜、人聲如沸愈案。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,912評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽站绪。三九已至,卻和暖如春涩嚣,著一層夾襖步出監(jiān)牢的瞬間崇众,已是汗流浹背掂僵。 一陣腳步聲響...
    開封第一講書人閱讀 33,040評論 1 270
  • 我被黑心中介騙來泰國打工航厚, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人锰蓬。 一個(gè)月前我還...
    沈念sama閱讀 48,173評論 3 370
  • 正文 我出身青樓幔睬,卻偏偏與公主長得像,于是被迫代替她去往敵國和親芹扭。 傳聞我的和親對象是個(gè)殘疾皇子麻顶,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,947評論 2 355

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

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理赦抖,服務(wù)發(fā)現(xiàn),斷路器辅肾,智...
    卡卡羅2017閱讀 134,657評論 18 139
  • 今天老師在群里發(fā)了照片队萤,是獲得的獎(jiǎng)勵(lì)貼多的孩子的合影,沒有女兒矫钓。當(dāng)我拿著手機(jī)給女兒看時(shí)要尔,她滿不在乎的說,我知道啊新娜,...
    仲思涵媽媽閱讀 181評論 0 0
  • 我不知道有多少人在意別人目光中的自己赵辕。在幾年以前,我也是一個(gè)在意別人看法的人概龄,害怕自己有一點(diǎn)點(diǎn)超出平常的舉動(dòng)就會引...
    阿淼閱讀 358評論 0 0
  • 姓名:劉小瓊 公司:寧波大發(fā)化纖有限公司 寧波盛和塾《六項(xiàng)精進(jìn)》第235期學(xué)員 【日精進(jìn)打卡第83天】 知~學(xué)習(xí) ...
    劉小瓊123閱讀 170評論 0 0
  • 五月份的天空是蔚藍(lán)的私杜,也是燥熱的蚕键,在這樣令人又驚又喜的天氣里,C姑娘的心思被掩藏的狠狠地寂寞空虛冷歪今。不知道其中緣由...
    阿俊xi閱讀 386評論 0 1