【qdox】Java 代碼解析利器 QDox

【qdox】Java 代碼解析利器 QDox

image.png

前言

最近在寫 maven 插件迁筛,涉及到了 java 代碼解析這塊內(nèi)容。需要解析 java 源碼端三,然后對于類中的不同部分進(jìn)行處理舷礼。發(fā)現(xiàn)手寫還是很難的,找了一圈發(fā)現(xiàn)了兩個不錯的工具可以使用郊闯,一個是 javaparser妻献,另一個是 qdox 。個人感覺 javaparser 強(qiáng)大一些团赁,更新與維護(hù)也比較勤育拨,但是相對來說上手難一點(diǎn),從他的使用文檔獨(dú)立成書在買然痊,可見一斑至朗,而 qdox 比較小巧,上手很快剧浸,功能也滿足大部分需求锹引,最終還是選擇了 qdox。

什么是 QDox

官方的介紹是:

QDox - full extractor of Java class/interface/method definitions (including annotations, parameters, param names)

大概意思是一款完整的 java 類唆香、接口嫌变、方法定義的提取器,包括了注釋躬它、參數(shù)及參數(shù)名稱腾啥。其實(shí)核心功能就是我輸入一個 java 類的源碼,他可以把這個 java 類解析成一個對象冯吓,我們通過這個對象可以獲取很方便的獲取解析的類的不同組成倘待,比如我可以獲得這個類有哪些方法,這個方法的參數(shù)是什么组贺,返回值又是什么凸舵,他們的類型又分別是什么?還有這個方法上有哪些注釋失尖、哪些 tag啊奄。也能獲取類中有哪些的 field。掀潮。菇夸。總之把這個類庖丁解牛般解析好仪吧,使得調(diào)用者很方便的獲取到自己感興趣的信息庄新。

為什么使用 QDox

除了上面說的 QDox 上手比較快外,他的運(yùn)行速度及占用空間都十分優(yōu)秀薯鼠。

另外不得不說的是這個項目可以說是一個上古時期的項目了择诈,看了 github 上的提交記錄凡蚜,最早的一條提交記錄是 2002 年的時候,因為這個項目之前使用的是 svn吭从,所以具體時間可能更早。一個開源項目維護(hù)了快 20 年也是一件挺令人欽佩的事恶迈。不過到目前為止涩金,這個項目在 github 上只有 151 個 star,如果這個項目對你有所幫助暇仲,希望大家可以給作者一個 star步做。github 上有太多類似的項目默默無聞的出現(xiàn),又默默無聞的消逝奈附。

扯遠(yuǎn)了全度,雖然項目關(guān)注的人比較少,但是使用它的項目還是比較多的斥滤。maven 的官方 javadoc 插件 maven-javadoc-plugin 就是使用它來解析代碼中的 doc tags 的将鸵。所以可能你沒有直接使用它,但是它其中已經(jīng)在你本地的 maven 倉庫內(nèi)躺著了佑颇。有官方背書顶掉,對于它的使用就比較放心了。

什么情況下適合使用 QDox

這個就比較多了挑胸,通常只要我們需要解析源碼的內(nèi)容就可以使用痒筒,比如我想獲得指定類文件中的全部方法。就可以使用茬贵〔就福可能有些人感到不解了,為什么不通過反射拿到這些內(nèi)容解藻,這樣不是更方便嗎老充?首先,反射的前提是你能拿到這個類的實(shí)例舆逃,或者你項目中就有這個類蚂维。即使這些條件都滿足,但是一個很常見的需求反射沒法滿足路狮,比如說拿到方法的注釋及 tags 等虫啥,這類在編譯時就被抹除了。這種情況就不得不用源碼解析的方法了奄妨。

另外它不只是能解析涂籽,他同時可以生成 java 類文件,所以你可以動態(tài)的生成一些 java 類砸抛。

無論是解析還是生成评雌,在寫插件的時候肯定需要會有這樣的場景树枫,比如我想通過代碼里的 javadoc 這些 tags 生成一個接口文檔給前端,這樣就不用我一個一個手寫了景东。再比如我想通過數(shù)據(jù)表的信息砂轻,自動生成 model 類,service 類斤吐。搔涝。。使用場景的限制主要是個人的想象力和措。

如何使用 QDox

創(chuàng)建 java 項目 builder 對象

        // 創(chuàng)建 java 項目 builder 對象
        JavaProjectBuilder javaProjectBuilder = new JavaProjectBuilder();

添加 java 源文件

        // 添加 java 源文件
        javaProjectBuilder.addSource(new File("/Users/kiwi/study/code/study-example/study-qdox-example/src/main/java/cn/coder4j/study/example/qdox/Demo.java"));

demo 示例是通過文件添加的庄呈,其實(shí)支持很多種類型,比如 URL派阱、Reader 甚至直接添加一個目錄诬留,框架會自己掃描目錄下的所有 java 文件

image.png

獲得解析后的 JavaClass 對象

經(jīng)過上面兩步,準(zhǔn)備工作就已經(jīng)結(jié)束了贫母,可以直接獲得解析后的 JavaClass 對象了文兑,有兩種方式獲取,一種是直接獲得解析后的類集合颁独,為什么是集合呢彩届?因為上面也說了是可以添加目錄的,而且 addSource 可以多次調(diào)用誓酒,添加多個文件樟蠕。另一種是在知道類名稱的情況下直接使用 getClassByName 獲得

image.png
        // 獲得解析后的類
        Collection<JavaClass> classes = javaProjectBuilder.getClasses();
        for (JavaClass javaClass : classes) {
            
        }

JavaClass 接口定義

package com.thoughtworks.qdox.model;

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

import java.util.List;

import com.thoughtworks.qdox.library.ClassLibrary;

/**
 * Equivalent of {@link java.lang.Class}, providing the most important methods.
 * Where the original Class is using an Array, this model is using a List.
 * 
 * @author Robert Scholte
 */
public interface JavaClass extends JavaModel, JavaType, JavaAnnotatedElement, JavaGenericDeclaration
{
    /**
     * The compilation unit, which includes the imports, the public and anonymous classes
     * 
     * @return the {@link JavaSource} of this element
     */
    JavaSource getSource();

    /**
     * (API description of {@link java.lang.Class#isInterface()})
     * <p>
     * Determines if the specified <code>Class</code> object represents an interface type.
     * </p>
     * 
     * @return <code>true</code> if this object represents an interface, otherwise <code>false</code>
     */
    boolean isInterface();

    /**
     * (API description of {@link java.lang.Class#isEnum()})
     * <p>
     * Returns <code>true</code> if and only if this class was declared as an enum in the source code.
     * </p>
     * 
     * @return <code>true</code> if this object represents an enum, otherwise <code>false</code>

     */
    boolean isEnum();

    /**
     * (API description of {@link java.lang.Class#isAnnotation()})
     * <p>Returns true if this <code>Class</code> object represents an annotation type. 
     *    Note that if this method returns true, {@link #isInterface()} would also return true, as all annotation types are also interfaces.
     * </p>
     * 
     * @return <code>true</code> if this object represents an annotation, otherwise <code>false</code>
     * @since 2.0 
     */
    boolean isAnnotation();
    
    JavaClass getDeclaringClass();

    JavaType getSuperClass();

    /**
     * Shorthand for getSuperClass().getJavaClass() with null checking.
     * @return the super class as {@link JavaClass}
     */
    JavaClass getSuperJavaClass();

    List<JavaType> getImplements();

    /**
     * Equivalent of {@link java.lang.Class#getInterfaces()}
     *  Determines the interfaces implemented by the class or interface represented by this object. 
     * 
     * @return a list of interfaces, never <code>null</code>
     * @since 2.0
     */
    List<JavaClass> getInterfaces();

    String getCodeBlock();

    JavaSource getParentSource();

    /**
     * Equivalent of {@link java.lang.Class#getPackage()}
     * @return the package
     */
    JavaPackage getPackage();

    /**
     * If this class has a package, the packagename will be returned.
     * Otherwise an empty String.
     * 
     * @return the name of the package, otherwise an empty String
     */
    String getPackageName();

    /**
     * @since 1.3
     * @return <code>true</code> if this class is an inner class, otherwise <code>false</code>
     */
    boolean isInner();

    /**
     * Equivalent of {@link java.lang.Class#getMethods()}
     * 
     * @return the methods declared or overridden in this class
     */
    List<JavaMethod> getMethods();
    
    /**
     * Equivalent of {@link java.lang.Class#getConstructors()}
     * 
     * @return the list of constructors
     * @since 2.0
     */
    List<JavaConstructor> getConstructors();
    
    
    /**
     * 
     * @param parameterTypes the parameter types of the constructor, can be <code>null</code>
     * @return the matching constructor, otherwise <code>null</code>
     * @since 2.0
     */
    JavaConstructor getConstructor(List<JavaType> parameterTypes);
    
    /**
     * 
     * @param parameterTypes the parameter types of the constructor, can be <code>null</code>
     * @param varArg define is the constructor has varArgs
     * @return the matching constructor, otherwise <code>null</code>
     * @since 2.0
     */
    JavaConstructor getConstructor(List<JavaType> parameterTypes, boolean varArg);
    

    /**
     * Return declared methods and optionally the inherited methods
     *
     * @param superclasses {@code true} if inherited methods should be returned as well 
     * @return all methods
     * @since 1.3
     */
    List<JavaMethod> getMethods( boolean superclasses );

    /**
     * 
     * @param name the name of the method
     * @param parameterTypes the parameter types of the method, can be <code>null</code>.
     * @return the matching method, otherwise <code>null</code>
     */
    JavaMethod getMethodBySignature( String name, List<JavaType> parameterTypes );

    /**
     * This should be the signature for getMethodBySignature.
     * 
     * @param name the name of the method
     * @param parameterTypes the parameter types of the method, can be {@code null}
     * @param varArgs define if the method has varArgs
     * @return the matching method, otherwise  {@code null}
     */
    JavaMethod getMethod( String name, List<JavaType> parameterTypes, boolean varArgs );

    /**
     * 
     * @param name the name of the method
     * @param parameterTypes the parameter types of the method, can be  {@code null}
     * @param superclasses to define if superclasses should be included as well
     * @return the matching method, otherwise  {@code null}
     */
    JavaMethod getMethodBySignature( String name, List<JavaType> parameterTypes, boolean superclasses );

    /**
     * 
     * @param name the name of the method
     * @param parameterTypes the parameter types of the method, can be  {@code null}
     * @param superclasses {@code true} if inherited methods should be matched as well
     * @param varArg define if the method has varArgs
     * @return the matching method, otherwise  {@code null}
     */
    JavaMethod getMethodBySignature( String name, List<JavaType> parameterTypes, boolean superclasses, boolean varArg );

    /**
     * 
     * @param name the name of the method
     * @param parameterTypes the parameter types of the method, can be  {@code null}
     * @param superclasses {@code true} if inherited methods should be matched as well
     * @return the matching methods, otherwise  {@code null}
     */
    List<JavaMethod> getMethodsBySignature( String name, List<JavaType> parameterTypes, boolean superclasses );

    /**
     * 
     * @param name the name of the method
     * @param parameterTypes the parameter types of the method, can be  {@code null}
     * @param superclasses {@code true} if inherited methods should be matched as well
     * @param varArg define if the method has varArgs
     * @return the matching methods, otherwise  {@code null}
     */
    List<JavaMethod> getMethodsBySignature( String name, List<JavaType> parameterTypes, boolean superclasses,
                                                   boolean varArg );

    /**
     * Equivalent of {@link java.lang.Class#getFields()}
     * 
     * @return a list of fiels, never  {@code null}
     */
    List<JavaField> getFields();

    /**
     * Equivalent of {@link java.lang.Class#getField(String)}, where this method can resolve every field
     * 
     * @param name the name of the field
     * @return the field
     */
    JavaField getFieldByName( String name );
    
    /**
     * Based on {@link java.lang.Class#getEnumConstants()}.
     *  
     * 
     * @return a List of enum constants if this class is an <code>enum</code>, otherwise  {@code null}
     */
    List<JavaField> getEnumConstants();

    /**
     * 
     * @param name the name of the enum constant
     * @return the enumConstant matching the {@code name}, otherwise <code>null</code>
     */
    JavaField getEnumConstantByName( String name );

    /**
     * Equivalent of {@link Class#getDeclaredClasses()}
     * 
     * @return a list of declared classes, never <code>null</code>
     * @since 1.3
     */
    List<JavaClass> getNestedClasses();

    JavaClass getNestedClassByName( String name );

    /**
     * @param fullyQualifiedName the FQN to match with
     * @return {@code true} if this is of type FQN, otherwise {@code false}
     * @since 1.3
     */
    boolean isA( String fullyQualifiedName );

    /**
     * @param javaClass the JavaClass to match with
     * @return {@code true} if this is of type {@literal javaClass}, otherwise {@code false}
     * @since 1.3
     */
    boolean isA( JavaClass javaClass );

    /**
     * Returns the depth of this array, 0 if it's not an array
     * 
     * @return The depth of this array, at least <code>0</code>
     * @since 2.0
     */
    int getDimensions();
    
    /**
     * 
     * @return <code>true</code> if this JavaClass is an array, otherwise <code>false</code>
     * @since 2.0
     */
    boolean isArray();
    
    /**
     * 
     * @return <code>true</code> if this JavaClass is a void, otherwise <code>false</code>
     * @since 2.0 (was part of Type since 1.6)
     */
    boolean isVoid();
    
    /**
     * Equivalent of {@link Class#getComponentType()}
     * If this type is an array, return its component type
     * 
     * @return the type of array if it's one, otherwise <code>null</code>
     */
    JavaClass getComponentType();

    /**
     * Gets bean properties without looking in superclasses or interfaces.
     *
     * @return the bean properties
     * @since 1.3
     */
    List<BeanProperty> getBeanProperties();

    /**
     * 
     * @param superclasses to define if superclasses should be included as well
     * @return the bean properties
     * @since 1.3
     */
    List<BeanProperty> getBeanProperties( boolean superclasses );

    /**
     * Gets bean property without looking in superclasses or interfaces.
     *
     * @param propertyName the name of the property
     * @return the bean property
     * @since 1.3
     */
    BeanProperty getBeanProperty( String propertyName );

    /**
     * @param propertyName the name of the property
     * @param superclasses to define if superclasses should be included as well
     * @return the bean property
     * @since 1.3
     */
    BeanProperty getBeanProperty( String propertyName, boolean superclasses );

    /**
     * Equivalent of {@link Class#getClasses()}
     * Gets the known derived classes. That is, subclasses or implementing classes.
     * @return the derived classes
     */
    List<JavaClass> getDerivedClasses();

    List<DocletTag> getTagsByName( String name, boolean superclasses );

    ClassLibrary getJavaClassLibrary();
    
    /**
     * A list if {@link JavaInitializer}, either static or instance initializers.  
     * 
     * @return a List of initializers
     */
    List<JavaInitializer> getInitializers();

    /**
     * Equivalent of {@link java.lang.Class#getName()}.
     * 
     * @return the name of the entity (class, interface, array class, primitive type, or void) represented by this Class object, as a String.
     */
    String getName();
    
    /**
     * Equivalent of {@link java.lang.Class#getSimpleName()}.
     * 
     * @return the simple name of the underlying class as given in the source code.
     * @since 2.0
     */
    String getSimpleName();
    
    /**
     * Equivalent of {@link Class#getModifiers()}
     * 
     * <strong>This does not follow the java-api</strong>
     * The Class.getModifiers() returns an <code>int</code>, which should be decoded with the {@link java.lang.reflect.Modifier}.
     * This method will return a list of strings representing the modifiers.
     * If this member was extracted from a source, it will keep its order. 
     * Otherwise if will be in the preferred order of the java-api.
     * 
     * @return all modifiers is this member
     */
    List<String> getModifiers();
    
    /**
     * (API description of {@link java.lang.reflect.Modifier#isPublic(int)})
     * <p>
     * Return <code>true</code> if the class includes the public modifier, <code>false</code> otherwise.
     * <p>
     * 
     * @return <code>true</code> if class has the public modifier, otherwise <code>false</code>
     */
    boolean isPublic();
    
    /**
     * (API description of {@link java.lang.reflect.Modifier#isProtected(int)})
     * <p>
     * Return <code>true</code> if the class includes the protected modifier, <code>false</code> otherwise.
     * </p>
     * 
     * @return <code>true</code> if class has the protected modifier, otherwise <code>false</code>
     */
    boolean isProtected();
    
    /**
     * (API description of {@link java.lang.reflect.Modifier#isPrivate(int)})
     * <p>
     * Return <code>true</code> if the class includes the private modifier, <code>false</code> otherwise.
     * </p>
     * 
     * @return <code>true</code> if class has the private modifier, otherwise <code>false</code>
     */
    boolean isPrivate();
    
    /**
     * (API description of {@link java.lang.reflect.Modifier#isFinal(int)})
     * <p>
     * Return <code>true</code> if the class includes the final modifier, <code>false</code> otherwise.
     * </p>
     * 
     * @return <code>true</code> if class has the final modifier, otherwise <code>false</code>
     */
    boolean isFinal();
    
    /**
     * (API description of {@link java.lang.reflect.Modifier#isStatic(int)})
     * <p>
     * Return <code>true</code> if the class includes the static modifier, <code>false</code> otherwise.
     * </p>
     * 
     * @return <code>true</code> if class the static modifier, otherwise <code>false</code>
     */
    boolean isStatic();
    
    /**
     * (API description of {@link java.lang.reflect.Modifier#isAbstract(int)})
     * 
     * Return <code>true</code> if the class includes the abstract modifier, <code>false</code> otherwise.
     * 
     * @return <code>true</code> if class has the abstract modifier, otherwise <code>false</code>
     */
    boolean isAbstract();
    
    /**
     *  Equivalent of  {@link java.lang.Class#isPrimitive()}
     *  
     * @return <code>true</code> if this class represents a primitive, otherwise <code>false</code>
     */
    boolean isPrimitive();
    
    /**
     * (API description of {@link java.lang.Class#toString()})
     * 
     * Converts the object to a string. 
     * The string representation is the string "class" or "interface", followed by a space, and then by the fully qualified name of the class in the format returned by <code>getName</code>. 
     * If this <code>Class</code> object represents a primitive type, this method returns the name of the primitive type. 
     * If this <code>Class</code> object represents void this method returns "void".
     *  
     * @return a string representation of this class object.
     */
    @Override
    String toString();
}

可以看到 JavaClass 提供的方法還是很多的,主要有如下這些靠柑,大概可以分成兩類:

一類是getXXX 這個是通過獲得類中不同的組成部分的寨辩,比較常用有 getFields ,可以獲得類所有的 Field 變量的對象歼冰,通過 Field 又可以獲得 Field 上的注解以及注釋靡狞,類型。隔嫡。甸怕。所有關(guān)于 field 的信息。又比如 getTags腮恩、getMethods 顧名思義是獲得 javadoc 的注釋及方法列表梢杭。

另一類是 isXXX ,這個是用來判斷類的一定特性的秸滴,比如 isEnum 判斷類是否是枚舉武契,isInterface 判斷是否是接口。

// 這些方法名,其實(shí)也是用 QDox 打印出來的
getBeanProperties
getBeanProperty
getCodeBlock
getComponentType
getConstructor
getConstructors
getDeclaringClass
getDerivedClasses
getDimensions
getEnumConstantByName
getEnumConstants
getFieldByName
getFields
getImplements
getInitializers
getInterfaces
getJavaClassLibrary
getMethod
getMethodBySignature
getMethods
getMethodsBySignature
getModifiers
getName
getNestedClassByName
getNestedClasses
getPackage
getPackageName
getParentSource
getSimpleName
getSource
getSuperClass
getSuperJavaClass
getTagsByName
isA
isAbstract
isAnnotation
isArray
isEnum
isFinal
isInner
isInterface
isPrimitive
isPrivate
isProtected
isPublic
isStatic
isVoid

完整 Demo

/*
 *
 *  * *
 *  *  * blog.coder4j.cn
 *  *  * Copyright (C) 2016-2020 All Rights Reserved.
 *  *
 *
 */
package cn.coder4j.study.example.qdox;

import com.thoughtworks.qdox.JavaProjectBuilder;
import com.thoughtworks.qdox.model.JavaClass;
import com.thoughtworks.qdox.model.JavaMethod;

import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author buhao
 * @version DemoParser.java, v 0.1 2020-03-22 19:03 buhao
 */
public class DemoParser {
    public static void main(String[] args) throws IOException {
        // 創(chuàng)建 java 項目 builder 對象
        JavaProjectBuilder javaProjectBuilder = new JavaProjectBuilder();
        // 添加 java 源文件
        javaProjectBuilder.addSource(new File("/Users/kiwi/study/code/study-example/study-qdox-example/src/main/java/cn/coder4j/study/example/qdox/Demo.java"));
        // 獲得解析后的類
        Collection<JavaClass> classes = javaProjectBuilder.getClasses();
        for (JavaClass javaClass : classes) {
            // 打印類相關(guān)信息
            System.out.println("類名:" + javaClass.getName());
            System.out.println("實(shí)現(xiàn)了哪些類:" + javaClass.getImplements());
            System.out.println("繼承哪個類:" + javaClass.getSuperJavaClass());
            System.out.println("注釋:" + javaClass.getComment());
            // 獲得方法列表
            List<JavaMethod> methods = javaClass.getMethods();
            for (JavaMethod method : methods) {
                System.out.println("方法名是:" + method.getName());
                System.out.println("方法的 Tags 有哪些:" + method.getTags().stream().map(it -> it.getName() + "->"+ it.getValue()).collect(Collectors.joining("\n")));
                System.out.println("方法的參數(shù)有哪些:" + method.getParameters());
                System.out.println("方法的返回值有哪些:" + method.getReturns());
            }
        }
    }
}

執(zhí)行結(jié)果

類名:Demo
實(shí)現(xiàn)了哪些類:[java.io.Serializable]
繼承哪個類:class java.lang.Object
注釋:這是一個 demo 類
方法名是:hello
方法的 Tags 有哪些:param->name 姓名
return->hello {name}
方法的參數(shù)有哪些:[String name]
方法的返回值有哪些:java.lang.String

其它

項目代碼

因為篇幅有限咒唆,無法貼完所有代碼届垫,如遇到問題可到 github 上查看源碼谱轨。

關(guān)于我

image.png

歡迎關(guān)注我的個人公眾號 KIWI的碎碎念 春贸,關(guān)注后回復(fù) 學(xué)習(xí)資料莱褒,海量學(xué)習(xí)內(nèi)容直接分享腾务!

image.png

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市汽久,隨后出現(xiàn)的幾起案子奈辰,更是在濱河造成了極大的恐慌丈屹,老刑警劉巖糟袁,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異躺盛,居然都是意外死亡项戴,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進(jìn)店門槽惫,熙熙樓的掌柜王于貴愁眉苦臉地迎上來周叮,“玉大人,你說我怎么就攤上這事界斜》碌ⅲ” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵各薇,是天一觀的道長项贺。 經(jīng)常有香客問我,道長峭判,這世上最難降的妖魔是什么开缎? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮林螃,結(jié)果婚禮上奕删,老公的妹妹穿的比我還像新娘。我一直安慰自己疗认,他們只是感情好完残,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著横漏,像睡著了一般谨设。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上绊茧,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天铝宵,我揣著相機(jī)與錄音,去河邊找鬼。 笑死鹏秋,一個胖子當(dāng)著我的面吹牛尊蚁,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播侣夷,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼横朋,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了百拓?” 一聲冷哼從身側(cè)響起琴锭,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎衙传,沒想到半個月后决帖,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蓖捶,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年地回,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片俊鱼。...
    茶點(diǎn)故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡刻像,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出并闲,到底是詐尸還是另有隱情细睡,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布帝火,位于F島的核電站溜徙,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏犀填。R本人自食惡果不足惜萌京,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望宏浩。 院中可真熱鬧知残,春花似錦、人聲如沸比庄。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽佳窑。三九已至制恍,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間神凑,已是汗流浹背净神。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工何吝, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人鹃唯。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓爱榕,卻偏偏與公主長得像,于是被迫代替她去往敵國和親坡慌。 傳聞我的和親對象是個殘疾皇子黔酥,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評論 2 355

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