Tomcat 类加载器

开篇

 这是一篇尝试讲解清楚Tomcat的类加载器的文章,估摸着能讲清楚六成左右,待后续再理理思路。

文末有彩蛋,可以直接翻到文章末尾。


Tomcat 类加载器概览

Tomcat类加载器

说明:

    1. BootstrapClassLoader : 系统类加载器,加载%JAVA_HOME%/lib目录下的jar
    1. ExtClassLoader : 扩展类加载器,加载%JAVA_HOME%/ext/lib目录下的jar
    1. AppClassLoader : 普通类加载器,加载CLASSPATH指定目录下的jar
    1. commonLoader : Tomcat 通用类加载器, 加载的资源可被 Tomcat 和 所有的 Web 应用程序共同获取
    1. catalinaLoader : Tomcat 类加载器, 加载的资源只能被 Tomcat 获取(但 所有 WebappClassLoader 不能获取到 catalinaLoader 加载的类)
    1. sharedLoader : Tomcat 各个Context的父加载器, 这个类是所有 WebappClassLoader 的父类, sharedLoader 所加载的类将被所有的 WebappClassLoader 共享获取
    1. 这个版本 (Tomcat 8.x.x) 中, 默认情况下 commonLoader = catalinaLoader = sharedLoader
    1. (PS: 为什么这样设计, 主要这样这样设计 ClassLoader 的层级后, WebAppClassLoader 就能直接访问 tomcat 的公共资源, 若需要tomcat 有些资源不让 WebappClassLoader 加载, 则直接在 ${catalina.base}/conf/catalina.properties 中的 server.loader 配置一下 加载路径就可以了)



Tomcat类关系图

说明:

    1. Common、Catalina、Shared类加载器继承自URLCLassLoader。
    1. WebappClassLoader继承WebappClassLoaderBase继承URLCLassLoader。
  • 3、JVM自带的ExtClassLoader也是URLClassLoader的子类。

  • 4、JVM自带的AppClassLoader也是URLClassLoader的子类。


Tomcat 各类ClassLoader初始化

Common&Catalina&Shared ClassLoader

    1. Bootstrap.init()初始化Tomcat的类加载器。
    1. Bootstrap的initClassLoaders()初始化Common、Catalina、Shared的类加载器。
    1. Common ClassLoader的父加载器是AppClassLoader。
    1. Catalina ClassLoader = Common ClassLoader。
    1. Shared ClassLoader = Common ClassLoader。
public final class Bootstrap {

    public static void main(String args[]) {

        if (daemon == null) {
            // Don't set daemon until init() has completed
            Bootstrap bootstrap = new Bootstrap();
            try {
                // 初始化Bootstrap
                bootstrap.init();
            } catch (Throwable t) {
                handleThrowable(t);
                t.printStackTrace();
                return;
            }
            daemon = bootstrap;
        }
    }


    public void init() throws Exception {
        // 初始化Tomcat的类加载器
        initClassLoaders();
    }


    private void initClassLoaders() {
        try {
            // 创建commonLoader并且未指定父节点,默认为
            commonLoader = createClassLoader("common", null);
            if( commonLoader == null ) {
                // no config file, default to this loader - we might be in a 'single' env.
                commonLoader=this.getClass().getClassLoader();
            }
            // 创建CatalinaLoader并且指定parent为commonLoader
            catalinaLoader = createClassLoader("server", commonLoader);
            // 创建SharedLoader并且指定parent为commonLoader
            sharedLoader = createClassLoader("shared", commonLoader);
        } catch (Throwable t) {
            handleThrowable(t);
            log.error("Class loader creation threw exception", t);
            System.exit(1);
        }
    }
}


ClassLoader创建过程

    1. CatalinaProperties的getProperty方法加载配置conf/Catalina/catalina.properties。
    1. catalina.properties的配置文件内容中:
      common.loader="${catalina.base}/lib","${catalina.base}/lib/.jar","${catalina.home}/lib","${catalina.home}/lib/.jar"。
    1. catalina.properties的配置文件内容中:server.loader=,返回common classLoader。
    1. catalina.properties的配置文件内容中:shared.loader=,返回common classLoader。
public final class Bootstrap {

    private ClassLoader createClassLoader(String name, ClassLoader parent)
        throws Exception {
        
        // common.loader配置jar路径,负责加载${catalina.base}/lib和{catalina.home}/lib
        // server.loader和shared.loader配置路径为空,所以返回parent即common classloader
        String value = CatalinaProperties.getProperty(name + ".loader");
        if ((value == null) || (value.equals("")))
            return parent;

        value = replace(value);

        List<Repository> repositories = new ArrayList<>();

        String[] repositoryPaths = getPaths(value);

        for (String repository : repositoryPaths) {

            if (repository.endsWith("*.jar")) {
                repository = repository.substring
                    (0, repository.length() - "*.jar".length());
                repositories.add(
                        new Repository(repository, RepositoryType.GLOB));
            } else if (repository.endsWith(".jar")) {
                repositories.add(
                        new Repository(repository, RepositoryType.JAR));
            } else {
                repositories.add(
                        new Repository(repository, RepositoryType.DIR));
            }
        }

        return ClassLoaderFactory.createClassLoader(repositories, parent);
    }
}


catalina.properties配置解析

    1. 解析tomcat目录下的conf/Catalina/catalina.properties配置文件。
    1. server.loader和shared.loader的值为空
    1. common.loader="${catalina.base}/lib","${catalina.base}/lib/.jar","${catalina.home}/lib","${catalina.home}/lib/.jar"
public class CatalinaProperties {

    private static Properties properties = null;

    static {
        loadProperties();
    }

    public static String getProperty(String name) {
        return properties.getProperty(name);
    }


    private static void loadProperties() {
        InputStream is = null;
        if (is == null) {
            try {

                // conf/Catalina/catalina.properties
                // common.loader="${catalina.base}/lib","${catalina.base}/lib/*.jar","${catalina.home}/lib","${catalina.home}/lib/*.jar"
                // server.loader=
                // shared.loader=
                File home = new File(Bootstrap.getCatalinaBase());
                File conf = new File(home, "conf");
                File propsFile = new File(conf, "catalina.properties");
                is = new FileInputStream(propsFile);
            } catch (Throwable t) {
            }
        }

        if (is != null) {
            try {
                properties = new Properties();
                properties.load(is);
            } catch (Throwable t) {

            } finally {
                try {
                    is.close();
                } catch (IOException ioe) {
                }
            }
        }

        Enumeration<?> enumeration = properties.propertyNames();
        while (enumeration.hasMoreElements()) {
            String name = (String) enumeration.nextElement();
            String value = properties.getProperty(name);
            if (value != null) {
                System.setProperty(name, value);
            }
        }
    }
}


ClassLoaderFactory的ClassLoader工厂

    1. 加载repositories对应的jar目录
    1. new URLClassLoader(array)创建没传parent的ClassLoader,这种情况下父加载器是AppClassLoader。
    1. new URLClassLoader(array, parent)创建以parent作为父加载器的ClassLoader。
public final class ClassLoaderFactory {

    public static ClassLoader createClassLoader(List<Repository> repositories,
                                                final ClassLoader parent)
        throws Exception {

        Set<URL> set = new LinkedHashSet<>();

        if (repositories != null) {
            for (Repository repository : repositories)  {
                if (repository.getType() == RepositoryType.URL) {
                    URL url = buildClassLoaderUrl(repository.getLocation());
                    set.add(url);
                } else if (repository.getType() == RepositoryType.DIR) {
                    File directory = new File(repository.getLocation());
                    URL url = buildClassLoaderUrl(directory);
                    set.add(url);
                } else if (repository.getType() == RepositoryType.JAR) {
                    File file=new File(repository.getLocation());
                    file = file.getCanonicalFile();
                    URL url = buildClassLoaderUrl(file);
                    set.add(url);
                } else if (repository.getType() == RepositoryType.GLOB) {
                    File directory=new File(repository.getLocation());
                    directory = directory.getCanonicalFile();
                    String filenames[] = directory.list();
                    if (filenames == null) {
                        continue;
                    }
                    for (int j = 0; j < filenames.length; j++) {
                        String filename = filenames[j].toLowerCase(Locale.ENGLISH);
                        File file = new File(directory, filenames[j]);
                        file = file.getCanonicalFile();
                        URL url = buildClassLoaderUrl(file);
                        set.add(url);
                    }
                }
            }
        }

        final URL[] array = set.toArray(new URL[set.size()]);
        return AccessController.doPrivileged(
                new PrivilegedAction<URLClassLoader>() {
                    @Override
                    public URLClassLoader run() {
                        if (parent == null)
                            return new URLClassLoader(array);
                        else
                            return new URLClassLoader(array, parent);
                    }
                });
    }
}


URLClassLoader的实现

URLClassLoader

说明:

    1. URLClassLoder是JVM当中的实现方式。
    1. URLClassLoder继承SecureClassLoader。
    1. SecureClassLoader继承ClassLoader。
public class URLClassLoader extends SecureClassLoader implements Closeable {

    public URLClassLoader(URL[] urls) {
        super();
        // this is to make the stack depth consistent with 1.1
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkCreateClassLoader();
        }
        this.acc = AccessController.getContext();
        ucp = new URLClassPath(urls, acc);
    }

    public URLClassLoader(URL[] urls, ClassLoader parent) {
        super(parent);
        // this is to make the stack depth consistent with 1.1
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkCreateClassLoader();
        }
        this.acc = AccessController.getContext();
        ucp = new URLClassPath(urls, acc);
    }
}


public class SecureClassLoader extends ClassLoader {

    protected SecureClassLoader() {
        super();
        // this is to make the stack depth consistent with 1.1
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkCreateClassLoader();
        }
        initialized = true;
    }
}


抽象类ClassLoader

    1. ClassLoader()方法使用getSystemClassLoader作为parent。
    1. getSystemClassLoader()返回sun.misc.Launcher.getLauncher().getClassLoader()。
    1. sun.misc.Launcher.getLauncher().getClassLoader()是。AppClassLoader对象
public abstract class ClassLoader {

    private final ClassLoader parent;
    private static ClassLoader scl;

    protected ClassLoader() {
        this(checkCreateClassLoader(), getSystemClassLoader());
    }

    private ClassLoader(Void unused, ClassLoader parent) {
        this.parent = parent;

        if (ParallelLoaders.isRegistered(this.getClass())) {
            parallelLockMap = new ConcurrentHashMap<>();
            package2certs = new ConcurrentHashMap<>();
            domains =
                Collections.synchronizedSet(new HashSet<ProtectionDomain>());
            assertionLock = new Object();
        } else {
            parallelLockMap = null;
            package2certs = new Hashtable<>();
            domains = new HashSet<>();
            assertionLock = this;
        }
    }


    public static ClassLoader getSystemClassLoader() {
        initSystemClassLoader();
        if (scl == null) {
            return null;
        }
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkClassLoaderPermission(scl, Reflection.getCallerClass());
        }
        return scl;
    }

    private static synchronized void initSystemClassLoader() {
        if (!sclSet) {
            sun.misc.Launcher l = sun.misc.Launcher.getLauncher();

            if (l != null) {
                Throwable oops = null;
                scl = l.getClassLoader();
                try {
                    scl = AccessController.doPrivileged(
                        new SystemClassLoaderAction(scl));
                } catch (PrivilegedActionException pae) {
                }
               
            }
            sclSet = true;
        }
    }
}


Launcher

    1. Launcher.AppClassLoader.getAppClassLoader(var1)返回AppClassLoader对象。
public class Launcher {

    private static Launcher launcher = new Launcher();
    private ClassLoader loader;

    public static Launcher getLauncher() {
        return launcher;
    }

    public Launcher() {
        Launcher.ExtClassLoader var1;
        try {
            var1 = Launcher.ExtClassLoader.getExtClassLoader();
        } catch (IOException var10) {
        }

        try {
            this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
        } catch (IOException var9) {
        }
    }
}


Webapp Classloader

整个Class loader的传递过程如下,证明Webapp classLoader的parent节点。
StandardEngine通过xml解析获得了Catalina的class loader。
StandardHost通过xml解析获得了StandardEngine的class loader。
StandardContext通过getParentClassLoader获得了StandardHost的class loader。

设置Bootstrap的common、catalina、shared等Loader

Catalina的parent设置

    1. Bootstrap通过反射调用Catalina的setParentClassLoader设置Catalina的parentClassLoader为sharedLoader。
public final class Bootstrap {
    ClassLoader commonLoader = null;
    ClassLoader catalinaLoader = null;
    ClassLoader sharedLoader = null;

    private void initClassLoaders() {
        try {
            commonLoader = createClassLoader("common", null);
            if( commonLoader == null ) {
                // no config file, default to this loader - we might be in a 'single' env.
                commonLoader=this.getClass().getClassLoader();
            }
            catalinaLoader = createClassLoader("server", commonLoader);
            sharedLoader = createClassLoader("shared", commonLoader);
        } catch (Throwable t) {
            handleThrowable(t);
            log.error("Class loader creation threw exception", t);
            System.exit(1);
        }
    }

    public void init() throws Exception {

        initClassLoaders();

        Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
        Object startupInstance = startupClass.getConstructor().newInstance();

        // Set the shared extensions class loader
        String methodName = "setParentClassLoader";
        Class<?> paramTypes[] = new Class[1];
        paramTypes[0] = Class.forName("java.lang.ClassLoader");
        Object paramValues[] = new Object[1];
        paramValues[0] = sharedLoader;
        Method method =
            startupInstance.getClass().getMethod(methodName, paramTypes);
        method.invoke(startupInstance, paramValues);

        catalinaDaemon = startupInstance;
    }
}


StandardEingine的parent设置

    1. StandardEingine的parentClassLoder设置为Catalina当中的parentClassLoader。
    1. Catalina当中的parentClassLoader是sharedLoader。
    1. StandardEingine通过如下方式注入parentClassLoader: digester.addRule("Server/Service/Engine,
      new SetParentClassLoaderRule(parentClassLoader));
public class Catalina {

    protected ClassLoader parentClassLoader =
        Catalina.class.getClassLoader();

    public void setParentClassLoader(ClassLoader parentClassLoader) {
        this.parentClassLoader = parentClassLoader;
    }

    public ClassLoader getParentClassLoader() {
        if (parentClassLoader != null) {
            return (parentClassLoader);
        }
        return ClassLoader.getSystemClassLoader();
    }

    // Engine的解析规则
    protected Digester createStartDigester() {
        digester.addRule("Server/Service/Engine",
                         new SetParentClassLoaderRule(parentClassLoader));
}


StandardHost的parent设置

    1. StandardHost通过StandardEngine的getParentClassLoader获取ClassLoader。
    1. StandardHost的setParentClassLoader注入StandardEngine的parentClassLoader。
    1. StandardHost的parentClassLoader为sharedLoader。
public class HostRuleSet extends RuleSetBase {
    public void addRuleInstances(Digester digester) {

        digester.addObjectCreate(prefix + "Host",
                                 "org.apache.catalina.core.StandardHost",
                                 "className");
        digester.addSetProperties(prefix + "Host");
        // Host的XML的解析规则
        digester.addRule(prefix + "Host",
                         new CopyParentClassLoaderRule());
    }
}


public class CopyParentClassLoaderRule extends Rule {

    public CopyParentClassLoaderRule() {
    }

    @Override
    public void begin(String namespace, String name, Attributes attributes)
        throws Exception {

        if (digester.getLogger().isDebugEnabled())
            digester.getLogger().debug("Copying parent class loader");
        Container child = (Container) digester.peek(0);
        Object parent = digester.peek(1);
        Method method =
            parent.getClass().getMethod("getParentClassLoader", new Class[0]);
        ClassLoader classLoader =
            (ClassLoader) method.invoke(parent, new Object[0]);
        child.setParentClassLoader(classLoader);

    }
}


StandardContext的parent设置

    1. StandardContext通过parent.getParentClassLoader()返回parentClassLoader。
    1. StandardContext的parent是StandardHost对象。
    1. StandardHost.getParentClassLoader()返回StandardHost的sharedLoader。
public class StandardContext extends ContainerBase
        implements Context, NotificationEmitter {

    public ClassLoader getParentClassLoader() {
        if (parentClassLoader != null)
            return (parentClassLoader);
        if (getPrivileged()) {
            return this.getClass().getClassLoader();
        } else if (parent != null) {
            // parent是StandardHost
            return (parent.getParentClassLoader());
        }
        return (ClassLoader.getSystemClassLoader());
    }
}


类加载过程双亲委派

WebappClassLoader

    1. WebappClassLoader继承自WebappClassLoaderBase。
    1. 核心实现在于WebappClassLoaderBase类。
public class WebappClassLoader extends WebappClassLoaderBase {

    public WebappClassLoader() {
        super();
    }


    public WebappClassLoader(ClassLoader parent) {
        super(parent);
    }

   @Override
    public WebappClassLoader copyWithoutTransformers() {

        WebappClassLoader result = new WebappClassLoader(getParent());

        super.copyStateWithoutTransformers(result);

        try {
            result.start();
        } catch (LifecycleException e) {
            throw new IllegalStateException(e);
        }

        return result;
    }

    @Override
    protected Object getClassLoadingLock(String className) {
        return this;
    }
}


WebappClassLoaderBase

    1. 调用 findLocaledClass0 从 resourceEntries 中判断 class 是否已经加载 OK。
    1. 调用 findLoadedClass(内部调用一个 native 方法) 直接查看对应的 WebappClassLoader 是否已经加载过。
    1. 调用 binaryNameToPath 判断是否 当前 class 是属于 J2SE 范围中的, 若是的则直接通过 ExtClassLoader, BootstrapClassLoader 进行加载 (这里是双亲委派)。
    1. 在设置 JVM 权限校验的情况下, 调用 securityManager 来进行权限的校验(当前类是否有权限加载这个类, 默认的权限配置文件是 ${catalina.base}/conf/catalina.policy)。
    1. 判断是否设置了双亲委派机制 或 当前 WebappClassLoader 是否能加载这个 class (通过 filter(name) 来决定), 将最终的值赋值给 delegateLoad。
    1. 根据上一步中的 delegateLoad 来决定是否用 WebappClassloader.parent(也就是 sharedClassLoader) 来进行加载, 若加载成功, 则直接返回。
    1. 上一步若未加载成功, 则调用 WebappClassloader.findClass(name) 来进行加载。
    1. 若上一还是没有加载成功, 则通过 parent 调用 Class.forName 来进行加载。
    1. 若还没加载成功的话, 那就直接抛异常。
加载过程
public abstract class WebappClassLoaderBase extends URLClassLoader
        implements Lifecycle, InstrumentableClassLoader, WebappProperties, PermissionCheck {

    public Class<?> loadClass(String name) throws ClassNotFoundException {
        return (loadClass(name, false));
    }

    public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {

        synchronized (getClassLoadingLock(name)) {

            Class<?> clazz = null;

            // Log access to stopped class loader
            checkStateForClassLoading(name);

            //首先调用findLoaderClass0() 方法检查WebappClassLoader中是否加载过此类
            // WebappClassLoader 加载过的类都存放在 resourceEntries 缓存中。
            clazz = findLoadedClass0(name);
            if (clazz != null) {
                if (log.isDebugEnabled())
                    log.debug("  Returning class from cache");
                if (resolve)
                    resolveClass(clazz);
                return (clazz);
            }

            // 调用 findLoadedClass(内部调用一个 native 方法) 
            // 直接查看对应的 WebappClassLoader 是否已经加载过
            clazz = findLoadedClass(name);
            if (clazz != null) {
                if (log.isDebugEnabled())
                    log.debug("  Returning class from cache");
                if (resolve)
                    resolveClass(clazz);
                return (clazz);
            }

            // 调用 binaryNameToPath 判断是否 当前 class 是属于 J2SE 范围中的, 
            // 若是的则直接通过 ExtClassLoader, BootstrapClassLoader 进行加载     
            // (这里是双亲委派)

            String resourceName = binaryNameToPath(name, false);

            ClassLoader javaseLoader = getJavaseClassLoader();
            boolean tryLoadingFromJavaseLoader;
            try {
                tryLoadingFromJavaseLoader = (javaseLoader.getResource(resourceName) != null);
            } catch (Throwable t) {
                tryLoadingFromJavaseLoader = true;
            }

            if (tryLoadingFromJavaseLoader) {
                try {
                    clazz = javaseLoader.loadClass(name);
                    if (clazz != null) {
                        if (resolve)
                            resolveClass(clazz);
                        return (clazz);
                    }
                } catch (ClassNotFoundException e) {
                    // Ignore
                }
            }

            // 判断是否需要委托给父类加载器进行加载,
            // delegate属性默认为false,那么delegatedLoad的值就取决于filter的返回值了
            // filter中是优先加载tomcat的lib下的class文件
            // filter方法中根据包名来判断是否需要进行委托加载,
            // 默认情况下会返回false.因此delegatedLoad为false
            boolean delegateLoad = delegate || filter(name, true);

            // 因为delegatedLoad为false,那么此时不会委托父加载器去加载,
            // 这里其实是没有遵循parent-first的加载机制。
            if (delegateLoad) {
                try {
                    clazz = Class.forName(name, false, parent);
                    if (clazz != null) {
                        if (log.isDebugEnabled())
                            log.debug("  Loading class from parent");
                        if (resolve)
                            resolveClass(clazz);
                        return (clazz);
                    }
                } catch (ClassNotFoundException e) {
                    // Ignore
                }
            }

            // 调用findClass方法在webapp级别进行加载
            try {
                clazz = findClass(name);
                if (clazz != null) {
                    if (log.isDebugEnabled())
                        log.debug("  Loading class from local repository");
                    if (resolve)
                        resolveClass(clazz);
                    return (clazz);
                }
            } catch (ClassNotFoundException e) {
                // Ignore
            }

            // 如果还是没有加载到类,并且不采用委托机制的话,则通过父类加载器去加载
            if (!delegateLoad) {
                try {
                    clazz = Class.forName(name, false, parent);
                    if (clazz != null) {
                        if (log.isDebugEnabled())
                            log.debug("  Loading class from parent");
                        if (resolve)
                            resolveClass(clazz);
                        return (clazz);
                    }
                } catch (ClassNotFoundException e) {
                    // Ignore
                }
            }
        }

        throw new ClassNotFoundException(name);
    }


    protected Class<?> findLoadedClass0(String name) {

        String path = binaryNameToPath(name, true);

        ResourceEntry entry = resourceEntries.get(path);
        if (entry != null) {
            return entry.loadedClass;
        }
        return null;
    }


    protected final Class<?> findLoadedClass(String name) {
        if (!checkName(name))
            return null;
        return findLoadedClass0(name);
    }

    private native final Class<?> findLoadedClass0(String name);

}


参考文章

违反ClassLoader双亲委派机制三部曲第二部——Tomcat类加载机制
Tomcat 源码分析 WebappClassLoader 分析 (基于8.0.5)


招聘信息

【招贤纳士】

欢迎热爱技术、热爱生活的你和我成为同事,和贝贝共同成长。

贝贝集团诚招算法、大数据、BI、Java、PHP、android、iOS、测试、运维、DBA等人才,有意可投递zhi.wang@beibei.com

贝贝集团创建于2011年,旗下拥有贝贝网、贝店、贝贷等平台,致力于成为全球领先的家庭消费平台。

贝贝创始团队来自阿里巴巴,先后获得IDG资本、高榕资本、今日资本、新天域资本、北极光等数亿美金的风险投资。

公司地址:杭州市江干区普盛巷9号东谷创业园(上下班有多趟班车)

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容