Java反序列化本地不存在的類

前言

java序列化與反序列化應(yīng)該是大家都比較熟悉的東西了。今天處理的是一種比較特殊的情況:在本地缺乏相應(yīng)類的情況下结胀,反序列化一個第三方的類赞咙。可能有點繞嘴糟港,其實就是在A環(huán)境下保存了一個類A的序列化之后的信息攀操,然后把這個信息在環(huán)境B下反序列化出來(環(huán)境B中是沒有類A的)。正常的業(yè)務(wù)中很少出現(xiàn)這種用途秸抚,但是在逆向工程或者一些特殊場景下(比如我上篇文章..)還是可能會用到的速和。整體思路很簡單歹垫,就是四個字——”無中生有“。也就是根據(jù)序列化的信息颠放,本地生成一個符合要求的Class县钥。主要用到了動態(tài)字節(jié)碼技術(shù)。

反序列化流程

要想解決這個問題慈迈,還是需要了解一下反序列化的流程的若贮。先貼一個典型的反序列化代碼:

FileInputStream fileInputStream = new FileInputStream(new File("JavaBean.txt"));
ObjectInputStream ois = new ObjectInputStream(fileInputStream);
Object obj = ois.readObject();

咱們先看一下ObjectInputStream的構(gòu)造方法:

 public ObjectInputStream(InputStream in) throws IOException {
        verifySubclass();
        bin = new BlockDataInputStream(in);
        handles = new HandleTable(10);
        vlist = new ValidationList();
        enableOverride = false;
        readStreamHeader();
        bin.setBlockDataMode(true);
    }

這里邊除了初始化之外還做了兩個校驗工作。

verifySubclass:校驗類是否是ObjectInputStream子類痒留,如果是的話需要校驗子類是否具有SUBCLASS_IMPLEMENTATION_PERMISSION權(quán)限谴麦。咱們最后的處理方式肯定是繼承ObjectInputStream類然后重寫關(guān)鍵方法的,這個校驗需要注意下伸头。

readStreamHeader:這個校驗是對序列化之后的文件的頭文件進(jìn)行校驗匾效,校驗序列化的版本號及Magic標(biāo)記。

介紹完了構(gòu)造方法恤磷,現(xiàn)在可以看反序列化方法了面哼,也就是readObject這個方法。readObject里邊邏輯很簡單扫步,做了個簡單校驗魔策,然后調(diào)用了readObject0。直接來看readObject0

 private Object readObject0(boolean unshared) throws IOException {
        ....
        depth++;
        try {
            switch (tc) {
               ....

                case TC_OBJECT:
                    return checkResolve(readOrdinaryObject(unshared));

               ....
                default:
                    throw new StreamCorruptedException(
                        String.format("invalid type code: %02X", tc));
            }
        } finally {
            depth--;
            bin.setBlockDataMode(oldMode);
        }
    }

繼續(xù)看readOrdinaryObject

private Object readOrdinaryObject(boolean unshared)
        throws IOException
    {
            //重復(fù)檢查標(biāo)記是否為Object
        if (bin.readByte() != TC_OBJECT) {
            throw new InternalError();
        }
        
                //下面重點講解
        ObjectStreamClass desc = readClassDesc(false);
        desc.checkDeserialize();

                //獲取描述對應(yīng)的類
        Class<?> cl = desc.forClass();
        //排除String河胎、Class闯袒、ObjectStreamClass這三個類,序列化時就做了特殊處理
        if (cl == String.class || cl == Class.class
                || cl == ObjectStreamClass.class) {
            throw new InvalidClassException("invalid class descriptor");
        }

        Object obj;
        try {
            //根據(jù)描述數(shù)據(jù)中的構(gòu)造函數(shù)游岳,利用反射創(chuàng)建對象政敢,構(gòu)造函數(shù)的規(guī)則在序列化時已經(jīng)說明
            obj = desc.isInstantiable() ? desc.newInstance() : null;
        } catch (Exception ex) {
            throw (IOException) new InvalidClassException(
                desc.forClass().getName(),
                "unable to create instance").initCause(ex);
        }
        
        ...
        //兩種接口的不同實現(xiàn)
        if (desc.isExternalizable()) {
            readExternalData((Externalizable) obj, desc);
        } else {
            //實現(xiàn)Serializable接口的調(diào)用
            readSerialData(obj, desc);
        }
        ...
                //判斷是否存在readResolve()方法
        if (obj != null &&
            handles.lookupException(passHandle) == null &&
            desc.hasReadResolveMethod())
        {
            //執(zhí)行并返回替換的對象
            Object rep = desc.invokeReadResolve(obj);
            if (unshared && rep.getClass().isArray()) {
                rep = cloneArray(rep);
            }
            if (rep != obj) {
                handles.setObject(passHandle, obj = rep);
            }
        }

        return obj;
    }

重點看readClassDesc

 private ObjectStreamClass readClassDesc(boolean unshared)
        throws IOException
    {
        byte tc = bin.peekByte();
        switch (tc) {
            case TC_NULL:
                return (ObjectStreamClass) readNull();

            case TC_REFERENCE:
                return (ObjectStreamClass) readHandle(unshared);

            case TC_PROXYCLASSDESC:
                return readProxyDesc(unshared);

            case TC_CLASSDESC:
                return readNonProxyDesc(unshared);

            default:
                throw new StreamCorruptedException(
                    String.format("invalid type code: %02X", tc));
        }
    }

對應(yīng)序列化時,null,reference,proxy和nonProxy的情況胚迫,這里主要分析非代理對象的反序列化喷户。

private ObjectStreamClass readNonProxyDesc(boolean unshared)
        throws IOException
    {
            //檢查寫入的是否為非代理對象
        if (bin.readByte() != TC_CLASSDESC) {
            throw new InternalError();
        }

        ObjectStreamClass desc = new ObjectStreamClass();
        ...
        ObjectStreamClass readDesc = null;
        try {
                //獲取序列化時保存的描述類元信息,按照序列化時的順序讀取 
            readDesc = readClassDescriptor();
        ...

        Class cl = null;
        ...
        final boolean checksRequired = isCustomSubclass();
        try {
            //初始化加載描述類代表的序列化類
            if ((cl = resolveClass(readDesc)) == null) 
            ...
        skipCustomData();
        //初始化描述類元信息,注意這里又遞歸調(diào)用readClassDesc访锻。我們序列化的時候是先寫入子類的類元信息褪尝,再寫入父類的;反序列化時朗若,也需要先讀入子類再父類恼五,因此readClassDesc返回的是父類的類元描述信息昌罩,但是具體的初始化類元信息順序還是先初始化父類再子類哭懈。
        desc.initNonProxy(readDesc, cl, resolveEx, readClassDesc(false));
        ...
        return desc;
    }

readClassDescriptor 方法完成了對ObjectStreamClass對象的填充,重點看一下茎用,內(nèi)部引用了ObjectStreamClass的readNonProxy方法:

void readNonProxy(ObjectInputStream in)
        throws IOException, ClassNotFoundException
    {
       //讀取類名
        name = in.readUTF();
    //讀取類型序列號
        suid = Long.valueOf(in.readLong());
        isProxy = false;
        //讀取對象序列化的方式遣总,writeObject方法睬罗,Serializable,Externalizable旭斥,還是SC_BLOCK_DATA
        byte flags = in.readByte();
        hasWriteObjectData =
            ((flags & ObjectStreamConstants.SC_WRITE_METHOD) != 0);
        hasBlockExternalData =
            ((flags & ObjectStreamConstants.SC_BLOCK_DATA) != 0);
        externalizable =
            ((flags & ObjectStreamConstants.SC_EXTERNALIZABLE) != 0);
        boolean sflag =
            ((flags & ObjectStreamConstants.SC_SERIALIZABLE) != 0);
        if (externalizable && sflag) {
            throw new InvalidClassException(
                name, "serializable and externalizable flags conflict");
        }
        serializable = externalizable || sflag;
        isEnum = ((flags & ObjectStreamConstants.SC_ENUM) != 0);
        if (isEnum && suid.longValue() != 0L) {
            throw new InvalidClassException(name,
                "enum descriptor has non-zero serialVersionUID: " + suid);
        }
        //讀取對象屬性個數(shù)
        int numFields = in.readShort();
        if (isEnum && numFields != 0) {
            throw new InvalidClassException(name,
                "enum descriptor has non-zero field count: " + numFields);
        }
        fields = (numFields > 0) ?
            new ObjectStreamField[numFields] : NO_FIELDS;
                //讀取對象屬性類型容达,及name
        for (int i = 0; i < numFields; i++) {
                    //讀取對象屬性,類型編碼
            char tcode = (char) in.readByte();
                    //讀取對象屬性名
            String fname = in.readUTF();
            String signature = ((tcode == 'L') || (tcode == '[')) ?
                in.readTypeString() : new String(new char[] { tcode });
            try {
                fields[i] = new ObjectStreamField(fname, signature, false);
            } catch (RuntimeException e) {
                throw (IOException) new InvalidClassException(name,
                    "invalid descriptor for field " + fname).initCause(e);
            }
        }
        //根據(jù)屬性類型垂券,初始化屬性讀取的緩存位置
        computeFieldOffsets();
    }

這一段代碼就是讀取序列化數(shù)據(jù)指定位置的字節(jié)花盐,每個位置的字節(jié)都有特定的含義,然后對ObjectStreamClass進(jìn)行填充菇爪。返回繼續(xù)看readNonProxyDesc方法算芯。當(dāng)ObjectStreamClass填充完之后做了一個校驗

if ((cl = resolveClass(readDesc)) == null) 

重點看這個校驗方法

 protected Class<?> resolveClass(ObjectStreamClass desc)
        throws IOException, ClassNotFoundException
    {
        String name = desc.getName();
        try {
            return Class.forName(name, false, latestUserDefinedLoader());
        } catch (ClassNotFoundException ex) {
            Class<?> cl = primClasses.get(name);
            if (cl != null) {
                return cl;
            } else {
                throw ex;
            }
        }
    }

其實就是根據(jù)ObjectStreamClass里邊存儲的解析出來的Class路徑,從本地加載該路徑凳宙。到這就能看出來了熙揍,假如我們本地不存在這個Class的話就會報出ClassNotFoundException的異常。也就是說氏涩,我們需要處理的方法就是resolveClass届囚。不過都看到這了,咱們先把反序列化的流程看完是尖,后續(xù)再考慮怎么處理ClassNotFoundException的異常意系。

回到readNonProxyDesc方法,校驗完成之后又調(diào)用了

desc.initNonProxy(readDesc, cl, resolveEx, readClassDesc(false));

利用剛才解析出來的readDesc將desc初始化完畢饺汹。initNonProxy里邊基本就是將readDesc 字段的值復(fù)制到

desc中昔字,只不過是多了一層校驗。到這就徹底完成了ObjectStreamClass的解析讀取首繁,咱們回到readOrdinaryObject繼續(xù)看作郭。

ObjectStreamClass解析完成之后,調(diào)用ObjectStreamClass.newInstance()生成了一個空的目標(biāo)對象弦疮。ObjectStreamClass.newInstance()底層調(diào)用了是剛才加載出來的Class的Constructor的newInstance夹攒。到這反序列化的容器對象已經(jīng)生成,只不過里邊的字段還在等待填充胁塞。
填充的代碼是這兩句

 if (desc.isExternalizable()) {
     readExternalData((Externalizable) obj, desc);
 } else {
     readSerialData(obj, desc);
 }

我們這只考慮實現(xiàn)了Serializable接口的也就是readSerialData(obj, desc);

private void readSerialData(Object obj, ObjectStreamClass desc)
        throws IOException
    {
        // 獲取要序列化的類咏尝,包括實現(xiàn)了 Serializable 接口的父類
        ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout();
        for (int i = 0; i < slots.length; i++) {
            ObjectStreamClass slotDesc = slots[i].desc;
           ....
           //執(zhí)行數(shù)據(jù)填充
           defaultReadFields(obj, slotDesc);
           ....
        }
    }

 private void defaultReadFields(Object obj, ObjectStreamClass desc)
        throws IOException
    {
        ....
        int objHandle = passHandle;
        //獲取類的需要填充數(shù)據(jù)的字段數(shù)組
        ObjectStreamField[] fields = desc.getFields(false);
        //生成對應(yīng)數(shù)量的值的數(shù)組
        Object[] objVals = new Object[desc.getNumObjFields()];
        int numPrimFields = fields.length - objVals.length;
        //遍歷字段數(shù)組
        for (int i = 0; i < objVals.length; i++) {
            ObjectStreamField f = fields[numPrimFields + i];
            // 關(guān)鍵點在這,對于字段的值其實遞歸調(diào)用了readObject0方法啸罢,所以咱們只需要針對readObject0做處理编检,不用擔(dān)心嵌套Serializable的情況
            objVals[i] = readObject0(f.isUnshared());
            if (f.getField() != null) {
                handles.markDependency(objHandle, passHandle);
            }
        }
        if (obj != null) {
            //利用反射完成了值的填充
            desc.setObjFieldValues(obj, objVals);
        }
        passHandle = objHandle;
    }

好了其實到這java反序列化的源碼咱們就看的差不多了∪挪牛總結(jié)一下:
java反序列化的整體流程并不復(fù)雜允懂,首先通過分析序列化信息,讀取目標(biāo)類的類名衩匣、字段蕾总、serialVersionUID等關(guān)鍵信息粥航,讀取之后判斷讀取到的類本地是否存在,也就是在這當(dāng)這個類本地不存在時虛擬機(jī)會拋出一個ClassNotFoundException生百,這也就是咱們需要處理的關(guān)鍵點递雀。校驗完成之后,會利用反射生成一個空的目標(biāo)對象,然后通過遞歸調(diào)用readObject0方法,給這個對象的字段賦值妻坝,完成了一次反序列化流程。

瞞天過海杠输,無中生有

了解了反序列化流程之后,后續(xù)需要做的就十分清楚了秕衙。我們就是要繼承ObjectInputStream蠢甲,然后重寫resolveClass方法,在這個方法中通過Java的動態(tài)字節(jié)碼技術(shù)据忘,根據(jù)方法中傳入的ObjectStreamClass對象(包含目標(biāo)類的類信息)動態(tài)生成一個目標(biāo)類鹦牛。

public class MyObjectInputStream extends ObjectInputStream{
    public MyObjectInputStream(InputStream in) throws IOException {
        super(in);
    }

    protected Class<?> resolveClass(ObjectStreamClass desc)
            throws IOException, ClassNotFoundException {
        String name = desc.getName();
        try {
            return Class.forName(name, false, sun.misc.VM.latestUserDefinedLoader());
        } catch (ClassNotFoundException ex) {
            try {
                return createClass(desc);
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }
    }
}

createClass 方法根據(jù)傳入的對象,動態(tài)生成了目標(biāo)類的字節(jié)碼信息勇吊,并加載進(jìn)了內(nèi)存曼追。
Java的動態(tài)字節(jié)碼技術(shù),有很多方式實現(xiàn)汉规,比如我之前寫到的關(guān)于ASM技術(shù)的文章ASM——運行時/編譯時動態(tài)修改class源碼礼殊。不過ASM的api偏底層,需要對JVM虛擬機(jī)了解的深一些针史。在這里我采用了javassist框架晶伦,api封裝的更面向?qū)ο笠恍褂闷饋黹T檻較低啄枕。

    private Class createClass(ObjectStreamClass desc) throws Exception {
        //獲取類的全路徑
        String name = desc.getName();
        //獲取類的字段數(shù)組
        ObjectStreamField[] fields = desc.getFields();
        ClassPool pool = ClassPool.getDefault();
        //根據(jù)類的全路徑生成一個空類的字節(jié)碼信息
        CtClass cc = pool.makeClass(name);
        CtClass interf = pool.getCtClass(Serializable.class.getName());
        CtClass[] classes = new CtClass[]{interf};
        //設(shè)置這個類實現(xiàn)Serializable接口
        cc.setInterfaces(classes);
        //遍歷字段數(shù)組婚陪,添加到剛生成的類的字節(jié)碼信息中
        for (ObjectStreamField field : fields) {
            String name1 = field.getName();
            String name2 = field.getType().getName();
            CtField param = new CtField(pool.get(name2), name1, cc);
            //統(tǒng)一設(shè)置public修飾
            param.setModifiers(Modifier.PUBLIC);
            //添加到字節(jié)碼信息中
            cc.addField(param);
        }
        //必須添加一個構(gòu)造參數(shù)
        CtConstructor cons = new CtConstructor(new CtClass[]{}, cc);
        //設(shè)置構(gòu)造函數(shù)的方法體
        cons.setBody("{}");
        //將構(gòu)造函數(shù)添加到字節(jié)碼信息中
        cc.addConstructor(cons);
        //生成serialVersionUID相關(guān)字段,并添加到字節(jié)碼信息中
        CtField param = new CtField(pool.get("long"), "serialVersionUID", cc);
        param.setModifiers(Modifier.PUBLIC | Modifier.STATIC | Modifier.FINAL);
        cc.addField(param, CtField.Initializer.constant(desc.getSerialVersionUID()));
        //返回剛剛生成的Class
        return cc.toClass();
    }

這里有幾點需要注意的:

  1. 生成的類除了字段數(shù)組里邊的字段频祝,還需要生成serialVersionUID字段泌参。
  2. 記得讓類實現(xiàn)Serializable接口。
  3. 自動生成的目標(biāo)類對比真正的目標(biāo)類會缺失除了構(gòu)造函數(shù)之外的所有方法常空,并且所有字段都為變成public修飾沽一,不過這個并不影響效果。

然后重新進(jìn)行反序列化操作:

FileInputStream fileInputStream = new FileInputStream(new File("JavaBean.txt"));
ObjectInputStream ois = new MyObjectInputStream(fileInputStream);
Object obj = ois.readObject();
System.out.println(new Gson().toJson(obj ));

這個時候即使你本地沒有JavaBean類漓糙,也能成功的將序列化的信息反序列化出來铣缠,并且利用Gson打印出來。
到這java反序列化一個本地不存在的class基本上就算是做完了。我本地只跑過有限的幾個測試用例攘残,可能不能覆蓋所有的情況拙友,但是大體上的思路應(yīng)該是沒問題的为狸。

Android Parcelable 反序列化

上邊說的是純java環(huán)境下的反序列化歼郭,在Android環(huán)境下還有一種序列化情況:實現(xiàn)Parcelable接口的類。在這簡單說一下Parcelable的反序列化過程辐棒。Parcelable的序列化與反序列化其實對比java的Serializable方式的序列化與反序列化病曾,你會發(fā)現(xiàn)Parcelable的序列化和反序列化的操作全部都是由自己實現(xiàn)的,而Serializable的序列化和反序列化則完全是由jdk實現(xiàn)的漾根,jdk這樣設(shè)計有個比較大的好處就是代碼侵入性低泰涂,Serializable接口是個空接口,只需要聲明實現(xiàn)一下辐怕,但是并不需要過多的改動代碼逼蒙。下面看個Parcelable的例子

public class Album implements Parcelable {

    /**
     * 負(fù)責(zé)反序列化
     */
    private static final Creator<Album> CREATOR = new Creator<Album>() {
        /**
         * 從序列化對象中,獲取原始的對象
         * @param source
         * @return
         */
        @Override
        public Album createFromParcel(Parcel source) {
            return new Album(source);
        }

        /**
         * 創(chuàng)建指定長度的原始對象數(shù)組
         * @param size
         * @return
         */
        @Override
        public Album[] newArray(int size) {
            return new Album[0];
        }
    };



    private final String mId;
    private final String mCoverPath;
    private final String mDisplayName;
    private final long mCount;


    Album(String id, String coverPath, String displayName, long count) {
        mId = id;
        mCoverPath = coverPath;
        mDisplayName = displayName;
        mCount = count;
    }

    Album(Parcel source) {
        mId = source.readString();
        mCoverPath = source.readString();
        mDisplayName = source.readString();
        mCount = source.readLong();
    }

    /**
     * 描述
     * 返回的是內(nèi)容的描述信息
     * 只針對一些特殊的需要描述信息的對象,需要返回1,其他情況返回0就可以
     *
     * @return
     */
    @Override
    public int describeContents() {
        return 0;
    }

    /**
     * 序列化
     *
     * @param dest
     * @param flags
     */
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(mId);
        dest.writeString(mCoverPath);
        dest.writeString(mDisplayName);
        dest.writeLong(mCount);
    }

可以看到Parcelable 的實現(xiàn)方法還是稍微有點復(fù)雜的寄疏。里邊執(zhí)行序列化操作的方法是writeToParcel方法是牢,將對象的字段信息用固定的順序?qū)懭肓薖arcel 中。反序列化操作則是用參數(shù)為Parcel的構(gòu)造方法完成的陕截。這個構(gòu)造函數(shù)的方法體其實就是對應(yīng)著writeToParcel中的寫入順序驳棱,將信息再次從Parcel中讀出并賦值到類的字段中。writeToParcel和參數(shù)為Parcel的構(gòu)造方法中的讀寫順序必須是一一對應(yīng)农曲,絕對不能出錯的社搅。
結(jié)合我之前的文章Android爬取第三方app推送消息,可以知道Parcel中對于Parcelable數(shù)據(jù)存儲時其實只存儲了在writeToParcel方法中寫入的信息乳规。也就是并沒有像Serializable序列化過程中一并存儲下來的類相關(guān)信息形葬。也就導(dǎo)致了我們無法用處理Serializable反序列化的思路來處理Parcelable反序列化。實際上Parcelable反序列化的核心就是字段讀取的順序暮的,但凡一個字段讀取順序沒對上都可能會導(dǎo)致后續(xù)字段讀取的嚴(yán)重錯荷并。所以至少目前為止,是沒有很好的方法能夠批量解決Parcelable反序列化問題的青扔。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末源织,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子微猖,更是在濱河造成了極大的恐慌谈息,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件凛剥,死亡現(xiàn)場離奇詭異侠仇,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進(jìn)店門逻炊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來互亮,“玉大人,你說我怎么就攤上這事余素”荩” “怎么了?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵桨吊,是天一觀的道長威根。 經(jīng)常有香客問我,道長视乐,這世上最難降的妖魔是什么洛搀? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮佑淀,結(jié)果婚禮上留美,老公的妹妹穿的比我還像新娘。我一直安慰自己伸刃,他們只是感情好谎砾,可當(dāng)我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著奕枝,像睡著了一般棺榔。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上隘道,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天症歇,我揣著相機(jī)與錄音,去河邊找鬼谭梗。 笑死忘晤,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的激捏。 我是一名探鬼主播设塔,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼远舅!你這毒婦竟也來了闰蛔?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤图柏,失蹤者是張志新(化名)和其女友劉穎序六,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蚤吹,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡例诀,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年随抠,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片繁涂。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡拱她,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出扔罪,到底是詐尸還是另有隱情秉沼,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布步势,位于F島的核電站氧猬,受9級特大地震影響背犯,放射性物質(zhì)發(fā)生泄漏坏瘩。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一漠魏、第九天 我趴在偏房一處隱蔽的房頂上張望倔矾。 院中可真熱鬧,春花似錦柱锹、人聲如沸哪自。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽壤巷。三九已至,卻和暖如春瞧毙,著一層夾襖步出監(jiān)牢的瞬間胧华,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工宙彪, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留矩动,地道東北人。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓释漆,卻偏偏與公主長得像悲没,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子男图,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,762評論 2 345