類加載機(jī)制系列1——深入理解Java中的類加載器

0 類加載器介紹

Java類加載器是Java運(yùn)行時(shí)環(huán)境(Java Runtime Environment)的一部分,它負(fù)責(zé)動(dòng)態(tài)加載Java類到Java虛擬機(jī)的內(nèi)存空間中。類通常是按需加載,即第一次使用該類時(shí)才加載。由于有了類加載器,Java運(yùn)行時(shí)系統(tǒng)不需要知道文件與文件系統(tǒng)。

Java中的類加載器是java.lang.ClassLoader,它是一個(gè)抽象類。給定一個(gè)類名,ClassLoader就負(fù)責(zé)把這個(gè)類從特定的文件系統(tǒng)中加載到虛擬機(jī)中。

Class類有一個(gè)方法getClassLoader(),每一個(gè)類的Class對(duì)象都可以調(diào)用這個(gè)方法來獲取把這個(gè)類加載到虛擬機(jī)中的ClassLoader。

對(duì)于數(shù)組來說,它們不是由ClassLoader來創(chuàng)建,而是由Java運(yùn)行時(shí)創(chuàng)建,數(shù)組的ClassLoader就是加載該數(shù)組元素類的ClassLoader。如果元素類型是基本類型,那么數(shù)組就沒有ClassLoader。

ClassLoader采用的是代理模式來加載類,每一個(gè)ClassLoader實(shí)例都有一個(gè)父ClassLoader(并不是繼承關(guān)系),當(dāng)一個(gè)類加載器需要加載一個(gè)類的時(shí)候,它會(huì)首先傳遞這個(gè)類的信息到parent 類加載器,請(qǐng)求parent來加載,然后依次傳遞,直到該類被成功加載或者失敗。如果失敗了,那么就由最開始的那個(gè)類加載器來進(jìn)行加載。在Java虛擬機(jī)中有一個(gè)內(nèi)置的類加載器是bootstrap class loader,它是沒有parent的,但是可以作為所有ClassLoader實(shí)例的parent。這種加載方式也叫作雙親委派機(jī)制或者父委托機(jī)制。

通常來講,類加載器都是加載本地的Class文件,但是它也可以加載其它來源的文件,比如從網(wǎng)絡(luò)下載下來的。可以通過繼承java.lang.ClassLoader類的方式實(shí)現(xiàn)自己的類加載器,以滿足一些特殊的需求而不需要完全了解Java虛擬機(jī)的類加載的細(xì)節(jié)。ClassLoader的一個(gè)方法defineClass可以把一個(gè)字節(jié)數(shù)組轉(zhuǎn)為Class實(shí)例。然后可以根據(jù)Class.newInstance()方法來創(chuàng)建一個(gè)對(duì)象。被ClassLoader創(chuàng)建的類的方法或者構(gòu)造方法可能還會(huì)引用其它的類,為了確定引用的類,虛擬機(jī)會(huì)調(diào)用最開始加載引用類的ClassLoader的loadClass方法。

protected final Class<?> defineClass(String name, byte[] b, int off, int len)

例如,想要自定義一個(gè)NetworkClassLoader,來加載從網(wǎng)絡(luò)傳來的Class類:

ClassLoader loader = new NetworkClassLoader(host, port);
Object main = loader.loadClass("Main", true).newInstance();
. . .

NetworkClassLoader必須重寫findClass方法,,然后定義一個(gè)方法來返回Class類的字節(jié)數(shù)組。當(dāng)下載完畢,需要調(diào)用defineClass方法,示例如下:

class NetworkClassLoader extends ClassLoader {
    String host;
    int port;

    public Class findClass(String name) {
        byte[] b = loadClassData(name);
        return defineClass(name, b, 0, b.length);
    }

    private byte[] loadClassData(String name) {
        // load the class data from the connection
          . . .
    }
}

1 JVM中的ClassLoader

JVM中有3個(gè)默認(rèn)的類加載器:

  • 引導(dǎo)(Bootstrap)類加載器。用C/C++寫的,在Java代碼中無法獲取到。主要是加載存儲(chǔ)在<JAVA_HOME>/jre/lib目錄下的核心Java庫,對(duì)應(yīng)的加載路徑是sun.boot.class.path
  • 擴(kuò)展(Extensions)類加載器.用來加載<JAVA_HOME>/jre/lib/e。t目錄下或者對(duì)應(yīng)的加載路徑java.ext.dirs中指明的Java擴(kuò)展庫。Java 虛擬機(jī)的實(shí)現(xiàn)會(huì)提供一個(gè)擴(kuò)展庫目錄。該類加載器在此目錄里面查找并加載 Java 類。該類由sun.misc.Launcher$ExtClassLoader實(shí)現(xiàn)。
  • Apps類加載器(也稱系統(tǒng)類加載器)。根據(jù) Java應(yīng)用程序的類路徑(java.class.path或CLASSPATH環(huán)境變量)來加載 Java 類。一般來說,Java 應(yīng)用的類都是由它來完成加載的。可以通過 ClassLoader.getSystemClassLoader()來獲取它。該類由sun.misc.Launcher$AppClassLoader實(shí)現(xiàn),它的parent類加載器是ExtClassLoader。

下面通過一個(gè)示例來看一下:

package classLoader;

public class ClassLoaderTest {
    public static void main(String[] args) {
        
        ClassLoaderTest clt=new ClassLoaderTest();
        ClassLoader cl=clt.getClass().getClassLoader();
        System.out.println(cl);
        System.out.println(cl.getParent());
        System.out.println(cl.getParent().getParent());
        System.out.println(ClassLoader.getSystemClassLoader());
        System.out.println(System.getProperty("sun.boot.class.path"));
        System.out.println(System.getProperty("java.ext.dirs"));
        System.out.println(System.getProperty("java.class.path"));
    }
}

打印結(jié)果:

sun.misc.Launcher$AppClassLoader@2a139a55
sun.misc.Launcher$ExtClassLoader@7852e922
null
sun.misc.Launcher$AppClassLoader@2a139a55
/Library/Java/JavaVirtualMachines/jdk1.8.0_121.jdk/Contents/Home/jre/lib/resources.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_121.jdk/Contents/Home/jre/lib/rt.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_121.jdk/Contents/Home/jre/lib/sunrsasign.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_121.jdk/Contents/Home/jre/lib/jsse.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_121.jdk/Contents/Home/jre/lib/jce.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_121.jdk/Contents/Home/jre/lib/charsets.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_121.jdk/Contents/Home/jre/lib/jfr.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_121.jdk/Contents/Home/jre/classes

/Users/jason/Library/Java/Extensions:/Library/Java/JavaVirtualMachines/jdk1.8.0_121.jdk/Contents/Home/jre/lib/ext:
/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java
/works/EclipseWorkSpace/classLoader/bin

java.lang.ClassLoader繼承關(guān)系如下:

Java中ClassLoader繼承關(guān)系.png

其中AppClassLoader和ExtClassLoader都是在Laucher中的內(nèi)部類。而這個(gè)Laucher是JVM的入口。

注意: 并不是說子加載器繼承自父加載器

2 ClassLoader源碼分析

前面已經(jīng)講了,類的加載使用的是雙親委派機(jī)制。那我們啟動(dòng)一個(gè)Java應(yīng)用程序,它的類加載順序是從AppClassLoader委托ExtClassLoader,如果ExtClassLoader也找不到就會(huì)去委托Bootstrap類加載器加載。如果父加載器沒有找到的話,再從子加載器中加載,加載到的類會(huì)被緩存起來,如果最終都沒有找到這個(gè)類,就會(huì)報(bào)一個(gè)異常ClassNotFoundException

我們先看一下ClassLoader的構(gòu)造方法,它有3個(gè)構(gòu)造方法,但是其中有一個(gè)私有的:

//最終調(diào)用的還是這個(gè)私有的方法
private ClassLoader(Void unused, ClassLoader parent) {}

//有參構(gòu)造,傳遞parent類加載器
protected ClassLoader(ClassLoader parent) {
   this(checkCreateClassLoader(), parent);
}

// 無參構(gòu)造,默認(rèn)采用getSystemClassLoader()方法獲取的ClassLoader作為parent類加載器
protected ClassLoader() {
    this(checkCreateClassLoader(), getSystemClassLoader());
}

下面我們來看getSystemClassLoader()這個(gè)方法:

public static ClassLoader getSystemClassLoader() {
    // 初始化系統(tǒng)類加載器
    initSystemClassLoader();
    if (scl == null) {
        return null;
    }
    // 做一些安全方面的校驗(yàn)
    SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
        checkClassLoaderPermission(scl, Reflection.getCallerClass());
    }
    return scl;
}

initSystemClassLoader()方法如下:

private static synchronized void initSystemClassLoader() {
    if (!sclSet) {
        if (scl != null)
            throw new IllegalStateException("recursive invocation");
        //獲取Launcher對(duì)象
        sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
        if (l != null) {
            Throwable oops = null;
            //調(diào)用Launcher對(duì)象的getClassLoader()方法,這個(gè)獲取的就是AppClassLoader,詳細(xì)內(nèi)容可以看下面對(duì)Launcher的分析
            scl = l.getClassLoader();
            try {
                scl = AccessController.doPrivileged(
                    new SystemClassLoaderAction(scl));
            } catch (PrivilegedActionException pae) {
                oops = pae.getCause();
                if (oops instanceof InvocationTargetException) {
                    oops = oops.getCause();
                }
            }
            if (oops != null) {
                if (oops instanceof Error) {
                    throw (Error) oops;
                } else {
                    // wrap the exception
                    throw new Error(oops);
                }
            }
        }
        sclSet = true;
    }
}

下面我們就ClassLoader加載一個(gè)類的過程來進(jìn)行一下分析。ClassLoader加載一個(gè)類,調(diào)用的方法是loadClass()方法:

//通常外界是調(diào)用ClassLoader的這個(gè)loadClass方法
public Class<?> loadClass(String name) throws ClassNotFoundException {
    return loadClass(name, false);
}

//ClassLoader的默認(rèn)加載方式,如果需要自定義ClassLoader最好不要重寫這個(gè)方法。
protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
{
    // 同步代碼塊
    synchronized (getClassLoadingLock(name)) {
        // findLoadedClass是一個(gè)native方法,如果已經(jīng)加載過的類是會(huì)被緩存起來的,直接從緩存獲取即可
        Class<?> c = findLoadedClass(name);
        if (c == null) {
        // c為null,說明沒有緩存,就需要初次加載
            long t0 = System.nanoTime();
            try {
                if (parent != null) {
                    // 如果parent不為null就委托parent去加載
                    c = parent.loadClass(name, false);
                } else {
                    // 如果parent為null就委托bootstrap class loader去加載
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // 如果非空的父加載器找不到類會(huì)拋出異常,在這里try-catch住了
            }
            
            if (c == null) {
                // 如果父加載器和bootstrap加載器都沒有找到,就會(huì)調(diào)用ClassLoader實(shí)例自身的findClass()方法。
                // 其方法體是拋出一個(gè)ClassNotFoundException異常,
                // 所以繼承ClassLoader的子類加載器需要重寫這個(gè)findClass()方法
                long t1 = System.nanoTime();
                c = findClass(name);
                ...
            }
        }
        if (resolve) {
            // 使Classloader鏈接指定的類,如果這個(gè)類已經(jīng)被鏈接過了,那么這個(gè)方法只做一個(gè)簡單的返回。
            // 否則,這個(gè)類將被按照 Java?規(guī)范中的Execution描述進(jìn)行鏈接
            resolveClass(c);
        }
        return c;
    }
}


需要注意的是第一個(gè)參數(shù),name表示的是二進(jìn)制名稱(Binary name),例如:

"java.lang.String"
"javax.swing.JSpinner$DefaultEditor"
"java.security.KeyStore$Builder$FileBuilder$1"
"java.net.URLClassLoader$3$1"

需要指出:類加載過程是同步的

簡單總結(jié)一下類加載器的工作過程:

  1. 如果當(dāng)前加載的類已經(jīng)加載過,直接從緩存獲取。
  2. 之前沒有加載過,如果該ClassLoader對(duì)象的parent不為null就委托父加載器加載,父加載器會(huì)重新開始走第1步。如果parent為null,那么就采用根加載器bootstrap class loader進(jìn)行加載。
  3. 如果之前還是沒有成功加載類,那么就會(huì)調(diào)用當(dāng)前ClassLoader的findClass()方法去加載。

類加載器采用雙親委派機(jī)制的好處:

  1. 加載的類會(huì)被緩存起來,下次加載就快了。
  2. 安全,比如我們自定義一個(gè)與系統(tǒng)String包名類型一致的類,然后想要把這個(gè)String類加載進(jìn)來干點(diǎn)壞事的話實(shí)際上是做不到的,由于父委托機(jī)制,真正的String類會(huì)被bootstrap class loader 加載(String類是存放在bootstrap class loader 負(fù)責(zé)加載的區(qū)域),就不會(huì)再調(diào)用我們這個(gè)假的String類。實(shí)際上,如果你自定義了一個(gè)類加載器并且重寫了loadClass的邏輯,最終還是不能加載假的String類,因?yàn)镃lassLoader有一個(gè)preDefineClass方法,該方法會(huì)檢測類的包名,如果是'java'開頭就會(huì)拋出一個(gè)SecurityException異常。

那么AppClassLoader和ExtClassLoader是什么時(shí)候初始化的呢?下面我們?cè)偃タ匆幌翷auncher的部分源碼:

# sun.misc.Launcher
//構(gòu)造方法
public Launcher() {
    ClassLoader extcl;
    try {
        // 創(chuàng)建ExtClassLoader對(duì)象
        extcl = ExtClassLoader.getExtClassLoader();
    } catch (IOException e) {
        throw new InternalError(
            "Could not create extension class loader", e);
    }
    //創(chuàng)建AppClassLoader對(duì)象loader,這個(gè)loader就是上面講到的,Launcher的getClassLoader()方法返回的對(duì)象。 
    //AppClassLoader.getAppClassLoader()方法的參數(shù)為extcl,實(shí)際上就是把ExtClassLoader對(duì)象當(dāng)作其父加載器
    try {
        loader = AppClassLoader.getAppClassLoader(extcl);
    } catch (IOException e) {
        throw new InternalError(
            "Could not create application class loader", e);
    }
    // 設(shè)置上下文的類加載器,也就是AppClassLoader對(duì)象。
    Thread.currentThread().setContextClassLoader(loader);
    // Finally, install a security manager if requested
    String s = System.getProperty("java.security.manager");
    if (s != null) {
        SecurityManager sm = null;
        if ("".equals(s) || "default".equals(s)) {
            sm = new java.lang.SecurityManager();
        } else {
            try {
                sm = (SecurityManager)loader.loadClass(s).newInstance();
            } catch (IllegalAccessException e) {
            } catch (InstantiationException e) {
            } catch (ClassNotFoundException e) {
            } catch (ClassCastException e) {
            }
        }
        if (sm != null) {
            System.setSecurityManager(sm);
        } else {
            throw new InternalError(
                "Could not create SecurityManager: " + s);
        }
    }
}

關(guān)于ExtClassLoader和AppClassLoader的源碼我們這里就不做多余的介紹了,感興趣的可以去看一下Launcher這個(gè)類,ExtClassLoader和AppClassLoader都是其靜態(tài)內(nèi)部類。

3 自定義類加載器

我們完全可以通過自定義類加載器來加載我們想要加載的類,這個(gè)類可能來源于網(wǎng)絡(luò),也可能來源于文件系統(tǒng)。
從前面的分析我們知道,加載一個(gè)類的過程調(diào)用的是ClassLoader的loadClass()方法。自定義類加載器通常不要重寫loadClass()方法的邏輯。在這個(gè)方法內(nèi)部,如果所有的父加載器都沒有成功加載,就會(huì)調(diào)用ClassLoader對(duì)象自身的findClass()方法,自定義類加載器可以實(shí)現(xiàn)這個(gè)findClass()方法即可。

還有一個(gè)關(guān)鍵的方法就是調(diào)用ClassLoader對(duì)象的defineClass()方法,這樣就可以創(chuàng)建一個(gè)Class對(duì)象了。

public class CustomClassLoader extends ClassLoader {

    private String dirPath;

    public CustomClassLoader(String dirPath) {
        this.dirPath = dirPath;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        Class clazz = null;
        //注意這個(gè)name一定要是二進(jìn)制名稱,如'java.lang.String'
        //根據(jù)類的二進(jìn)制名稱,獲得該class文件的字節(jié)碼數(shù)組
        byte[] classData = getClassDataBytes(name);
        if (classData == null) {
            throw new ClassNotFoundException();
        }
        //調(diào)用define()方法將class的字節(jié)碼數(shù)組轉(zhuǎn)換成Class類的實(shí)例
        clazz = defineClass(name, classData, 0, classData.length);
        return clazz;
    }

    private byte[] getClassDataBytes(String name) {
        FileInputStream is = null;
        try {
            String path = classNameToPath(name);
            is = new FileInputStream(path);
            byte[] buff = new byte[1024];
            int len;
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            while ((len = is.read(buff)) != -1) {
                baos.write(buff, 0, len);
            }
            return baos.toByteArray();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (is != null) {
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return null;
    }

    private String classNameToPath(String name) {
        return dirPath + "/" + name.replace(".", "/") + ".class";
    }
}

自定義的類加載器已經(jīng)寫好了,下面我們來演示一下如何加載一個(gè)類,首先我們編寫一個(gè)java類:

package com.sososeen09;

class Test {
    public Test() {
    }

    public void print() {
        System.out.println("this is Test Class");
    }
}

根據(jù)javac命令把java文件編譯為對(duì)應(yīng)的class文件。這個(gè)Test.class文件的二進(jìn)制名稱就是com.sososeen09.Test

public class ClassLoaderTest {

    public static void main(String[] args) {
        String srcPath = "/test/bin";
        CustomClassLoader customClassLoader = new CustomClassLoader(srcPath);
        String classname = "com.sososeen09.Test";
        try {
            Class clazz = customClassLoader.loadClass(classname);
            System.out.println("loaded class: " + clazz);
            System.out.println("class loader: " + clazz.getClassLoader());
            System.out.println("class loader parent: " + clazz.getClassLoader().getParent());
            Constructor constructor = clazz.getConstructor();
            constructor.setAccessible(true);
            Object o = constructor.newInstance();
            Method print = clazz.getDeclaredMethod("print");
            print.setAccessible(true);
            print.invoke(o);
        } catch (Exception e) {
            e.printStackTrace();
        } 

運(yùn)行一下可以查看打印結(jié)果:

loaded class: class com.sososeen09.Test
class loader: com.sososeen09.javamodule.classloaders.CustomClassLoader@14ae5a5
class loader parent: sun.misc.Launcher$AppClassLoader@18b4aac2
this is Test Class

可以看到我們自定義的類加載器已經(jīng)成功的把一個(gè)文件系統(tǒng)中的class加載了。

需要注意:我們是把二進(jìn)制文件前面的包名轉(zhuǎn)為路徑了,所以我們傳遞的srcPath是"/test/bin",那么實(shí)際上class文件存放路徑應(yīng)該是"/test/bin/com/sososeen09/"

4 擴(kuò)展知識(shí)點(diǎn)

在Java中,類的加載時(shí)按需加載,也就是需要的時(shí)候才會(huì)把class文件加載到內(nèi)存中。可以分為隱式加載和顯示加載。

  • 隱式加載:由當(dāng)new一個(gè)Java對(duì)象,或者調(diào)用類的靜態(tài)方法或者使用靜態(tài)成員變量的時(shí)候,會(huì)加載當(dāng)前的Class。
  • 顯示加載,顯示的調(diào)用Class.forName()方法,或者調(diào)用ClassLoader的loadClass()方法。

參考文章

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

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