【拿來吧你】JDK動態(tài)代理

java proxy

【Spring Boot】一個注解實現(xiàn)下載接口

【Java】異步回調(diào)轉(zhuǎn)為同步返回

因為最近一段時間準備將這幾年做的一些業(yè)務(wù)和技術(shù)做個沉淀阔馋,也自己造的一些輪子呕寝,發(fā)現(xiàn)時不時就會需要用到動態(tài)代理和反射,所以今天打算先對jdk的動態(tài)代理這部分內(nèi)容做個簡單的整理

介紹

先來說說怎么理解動態(tài)代理吧

首先在java中有一種模式叫代理模式,代理的定義是

代理是指以他人的名義塞蹭,在授權(quán)范圍內(nèi)進行對被代理人直接發(fā)生法律效力的法律行為

對應(yīng)到代理模式

  • 以他人名義 > 代理對象
  • 授權(quán)范圍內(nèi) > 基于接口或類的規(guī)范限制(如無法代理接口或類中不存在的方法等)或是可實現(xiàn)的功能范圍(如無法獲得被代理方法的中間變量等)
  • 被代理人 > 被代理的對象
  • 法律 > java中的規(guī)范(如訪問限制等)
  • 直接發(fā)生法律效力 > 直接影響方法執(zhí)行邏輯(如在前后打印日志或是修改入?yún)⒎祷刂捣纾瑢Ψ椒ǖ淖罱K執(zhí)行結(jié)果產(chǎn)生了影響)
  • 法律行為 > 及代理本身的行為(符合java規(guī)范)

所以代理模式就是在通過代理對象調(diào)用被代理對象的方法過程中改變原有方法的執(zhí)行邏輯

好了辆琅,完全被自己繞暈了婉烟,其實代理能做到的功能完全可以枚舉出來

  • 修改入?yún)?/li>
  • 修改返回值
  • 異常處理
  • 日志打印
  • 完全重寫

一般也就用到這些功能

而所謂的動態(tài)代理育瓜,就是這個代理對象是通過代碼動態(tài)生成的叉存,也就是用代碼生成代碼扬霜,經(jīng)典套娃了屬于是

使用場景

對于動態(tài)代理的使用場景绒尊,主要還是用于一些大型框架中

  • 其中一個場景就是Spring利用動態(tài)代理實現(xiàn)強大的切面功能

  • 另一個場景就是Feign在接口上添加注解來實現(xiàn)HTTP的調(diào)用婴谱,或是MyBatis在接口上添加注解來執(zhí)行SQL等等

方式

主流的動態(tài)代理有2種:jdk動態(tài)代理和cglib動態(tài)代理

說說這兩種方式的區(qū)別

  • jdk動態(tài)代理只能代理接口(統(tǒng)一繼承Proxy類)躯泰,cglib動態(tài)代理可以代理接口也可以代理類(通過生成子類,所以不能被final修飾)
  • jdk動態(tài)代理直接寫Class文件瘟裸,cglib借助ASM框架處理Class文件
  • jdk動態(tài)代理方法內(nèi)調(diào)用其他方法無法被代理(通過某個實現(xiàn)類調(diào)用方法時)话告,cglib動態(tài)代理方法內(nèi)調(diào)用的其他方法也可以被代理

接下來詳細聊聊jdk動態(tài)代理吧卵慰,由于篇幅原因cglib動態(tài)代理考慮另開一篇

JDK動態(tài)代理

基于jdk8

用法

用法就簡單過一下吧

public interface JdkDynamicProxy {

    String methodToProxy();
}

JdkDynamicProxy target = new JdkDynamicProxy() {
    @Override
    public String methodToProxy() {
        return "Target method to proxy";
    }
};

JdkDynamicProxy proxy = (JdkDynamicProxy) Proxy
        .newProxyInstance(JdkDynamicProxy.class.getClassLoader(),
                new Class[]{JdkDynamicProxy.class},
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, 
                                         Method method, 
                                         Object[] args)
                            throws Throwable {
                        System.out.println(method.getName());
                        return method.invoke(target, args);
                    }
                });
System.out.println(proxy.methodToProxy());

用法很簡單裳朋,調(diào)用Proxy.newProxyInstance方法就能生成一個代理對象,然后就可以在被代理對象的方法調(diào)用的前后做一些有限的事情

參數(shù)

接下來說說這個方法的參數(shù)

先說第二個參數(shù)送挑,傳入一個類型為Class<?>[],名稱為interfaces的對象纺裁,也就是你要代理的接口司澎,因為java可以實現(xiàn)多個接口惭缰,所以是一個數(shù)組

再來說一下第三個參數(shù)InvocationHandler,這是一個接口络凿,當你通過代理對象調(diào)用接口的方法時昂羡,就會回調(diào)該接口的invoke方法并回傳代理對象虐先,方法和方法入?yún)?/p>

最后說第一個參數(shù),需要傳入一個ClassLoader撰洗,一般情況下你用這個項目里面任何一個能得到的類加載器都沒問題腐芍,這個類加載器的主要作用就是校驗其可以訪問指定的接口以及加載這個代理類

//省略部分代碼
private static final class ProxyClassFactory
    implements BiFunction<ClassLoader, Class<?>[], Class<?>> {
    
    @Override
    public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {

        //校驗類加載器可以訪問指定的接口
        for (Class<?> intf : interfaces) {
            /*
             * Verify that the class loader resolves the name of this
             * interface to the same Class object.
             */
            Class<?> interfaceClass = null;
            try {
                interfaceClass = Class.forName(intf.getName(), false, loader);
            } catch (ClassNotFoundException e) {
            }
            if (interfaceClass != intf) {
                throw new IllegalArgumentException(
                    intf + " is not visible from class loader");
            }
        }
        
        //使用類加載器定義類
        try {
            return defineClass0(loader, proxyName,
                                proxyClassFile, 0, proxyClassFile.length);
        } catch (ClassFormatError e) {
            /*
             * A ClassFormatError here means that (barring bugs in the
             * proxy class generation code) there was some other
             * invalid aspect of the arguments supplied to the proxy
             * class creation (such as virtual machine limitations
             * exceeded).
             */
            throw new IllegalArgumentException(e.toString());
        }
    }
}

流程

接下來就說說生成代理對象的整個流程

克隆接口數(shù)組

final Class<?>[] intfs = interfaces.clone();

首先會對我們傳入的第二個參數(shù)猪勇,也就是接口數(shù)組進行拷貝

這里我猜想可能是防止數(shù)組中的元素在生成代理的過程中被修改導(dǎo)致出現(xiàn)一些問題泣刹,不得不佩服果然是心思縝密啊

接口數(shù)量檢查

if (interfaces.length > 65535) {
    throw new IllegalArgumentException("interface limit exceeded");
}

這邊限制了一個接口的數(shù)量為65535,那么這個65535是怎么來的呢

如果大家對Class文件結(jié)構(gòu)有一定了解的話就知道文件中會通過interfaces_count定義接口數(shù)量外冀,該數(shù)據(jù)項占2個字節(jié)襟沮,所以最大能表示65535

查找緩存

// If the proxy class defined by the given loader implementing
// the given interfaces exists, this will simply return the cached copy;
// otherwise, it will create the proxy class via the ProxyClassFactory
return proxyClassCache.get(loader, interfaces);

然后會在緩存中查找,這是肯定的膀跌,不然每次都重新生成一遍也太蠢了

這個緩存用的是WeakCache對象捅伤,通過兩個key來定位一個value,就是通過我們傳入的類加載器接口數(shù)組來定位一個動態(tài)生成的代理類祠汇,類似于Map<ClassLoader, Map<Class<?>[], Class<? entends Proxy>>>

// the key type is Object for supporting null key
private final ConcurrentMap<Object, ConcurrentMap<Object, Supplier<V>>> map
    = new ConcurrentHashMap<>();

而他使用的keyvalue都是WeakReference類型可很,防止當動態(tài)生成的類不再使用時導(dǎo)致內(nèi)存泄漏的問題

第一個key凰浮,使用我們傳入的ClassLoaderkey

//key為我們傳入的ClassLoader
Object cacheKey = CacheKey.valueOf(key, refQueue);

private static final class CacheKey<K> extends WeakReference<K> {
    //代碼省略
}

其中refQueueReferenceQueue類型(不了解的可以看一下WeakReference的構(gòu)造器)而且都是同一個對象,ReferenceQueue是屬于gc回收這部分的內(nèi)容了菜拓,先不展開了吧

猜想由于ClassLoader可能是URLClassLoader甚至一些自定義的類加載器纳鼎,就有可能導(dǎo)致內(nèi)存泄漏裳凸,所以也用了弱引用

另外需要注意這個key也就是類加載器是可以為null的,猜測為null時被判定為bootstrap類加載器

但是如果我們傳入的類加載器為null就可能會報錯xxx is not visible from class loader贰逾,這是為什么呢

/*
 * Verify that the class loader resolves the name of this
 * interface to the same Class object.
 */
Class<?> interfaceClass = null;
try {
    interfaceClass = Class.forName(intf.getName(), false, loader);
} catch (ClassNotFoundException e) {
}
if (interfaceClass != intf) {
    throw new IllegalArgumentException(
        intf + " is not visible from class loader");
}

可以看到通過用我們傳入的類加載器重新加載接口并判斷和我們傳入的接口是否相等來做校驗

我們都知道兩個Class是否相等是需要滿足全限定名相等類加載器相等這兩個條件的,而如果我們傳入null也就是使用bootstrap類加載器氯迂,那么和接口的類加載器AppClassLoader是兩個不同的類加載器嚼蚀,導(dǎo)致兩個類不相等拋出異常

簡單來說,就是直接使用接口的類加載器就完事兒了

那么這個類加載器到底應(yīng)該怎么傳呢

如果是我們自定義的接口弄捕,那么需要傳入AppClassLoader(或者是加載該接口的類加載器,當然也可以是以對應(yīng)類加載器為父類加載器的自定義類加載器)而不能傳入null

如果我們要代理的接口是java.util.List這種穿铆,那就可以傳null(本身就是由bootstrap類加載器加載)或者AppClassLoader(基于雙親委派模型能夠加載java.util.List

這里就不展開類加載器了(強制拉回)

繼續(xù)講第二個key荞雏,第二個key使用ClassLoader和接口數(shù)組生成

// create subKey and retrieve the possible Supplier<V> stored by that
// subKey from valuesMap
Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));

這里通過subKeyFactory來生成一個subKey平酿,subKeyFactoryKeyFactory對象

private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
    proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

不過KeyFactory完全沒用到ClassLoader,所以其實就是接口數(shù)組作為key

動態(tài)生成代理類

如果緩存中不存在筑辨,那么就需要通過ProxyClassFactory來生成一個代理類

value = Objects.requireNonNull(valueFactory.apply(key, parameter));

valueFactory也就是ProxyClassFactory對象挖垛,我們來看看ProxyClassFactory是怎么生成代理類的

接口校驗

首先會對我們傳入的接口進行ClassLoader的校驗

/*
 * Verify that the class loader resolves the name of this
 * interface to the same Class object.
 */
Class<?> interfaceClass = null;
try {
    interfaceClass = Class.forName(intf.getName(), false, loader);
} catch (ClassNotFoundException e) {
}
if (interfaceClass != intf) {
    throw new IllegalArgumentException(
        intf + " is not visible from class loader");
}

這塊前面已經(jīng)大致分析過了秉颗,即我們傳入的ClassLoader需要是這些接口的類加載器

然后判斷這些接口(由于接口也是通過Class表示所以需要額外校驗)必須為接口

/*
 * Verify that the Class object actually represents an
 * interface.
 */
if (!interfaceClass.isInterface()) {
    throw new IllegalArgumentException(
        interfaceClass.getName() + " is not an interface");
}

最后這些接口不能重復(fù)

/*
 * Verify that this interface is not a duplicate.
 */
if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
    throw new IllegalArgumentException(
        "repeated interface: " + interfaceClass.getName());
}
設(shè)置訪問標志
int accessFlags = Modifier.PUBLIC | Modifier.FINAL;

首先會將代理類的訪問標志設(shè)置為publicfinal

然后會判斷需要代理的接口蚕甥,如果存在不是public的接口,則把代理類的訪問標志改為final凭舶,并且將代理類的包名設(shè)置為非public接口的包名爱沟,如果有多個非public接口呼伸,就需要判斷這些非public的接口包名是否一樣,否則拋出異常(因為如果不是public就無法被其他包訪問到)

/*
 * Record the package of a non-public proxy interface so that the
 * proxy class will be defined in the same package.  Verify that
 * all non-public proxy interfaces are in the same package.
 */
for (Class<?> intf : interfaces) {
    int flags = intf.getModifiers();
    if (!Modifier.isPublic(flags)) {
        accessFlags = Modifier.FINAL;
        String name = intf.getName();
        int n = name.lastIndexOf('.');
        String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
        if (proxyPkg == null) {
            proxyPkg = pkg;
        } else if (!pkg.equals(proxyPkg)) {
            throw new IllegalArgumentException(
                "non-public interfaces from different packages");
        }
    }
}
確定最終包名
if (proxyPkg == null) {
    // if no non-public proxy interfaces, use com.sun.proxy package
    proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
}

如果上一步?jīng)]有非public包名時搂根,那么指定包名為com.sun.proxy剩愧,否則使用那個非public接口的包名

指定類名
/*
 * Choose a name for the proxy class to generate.
 */
long num = nextUniqueNumber.getAndIncrement();
String proxyName = proxyPkg + proxyClassNamePrefix + num;

其中proxyClassNamePrefix為常量$Proxy

// prefix for all proxy class names
private static final String proxyClassNamePrefix = "$Proxy";

所以最后生成的代理類的全名就是com.sun.proxy.$Proxy0娇斩,com.sun.proxy.$Proxy1com.sun.proxy.$Proxy2以此類推(所有接口都是public的情況下)

生成類文件
/*
 * Generate the specified proxy class.
 */
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
    proxyName, interfaces, accessFlags);
    
public static byte[] generateProxyClass(final String name,
                                        Class<?>[] interfaces,
                                        int accessFlags) {
    ProxyGenerator gen = new ProxyGenerator(name, interfaces, accessFlags);
    final byte[] classFile = gen.generateClassFile();
    //省略部分代碼
}

將代理類的全限定名藏否,需要實現(xiàn)的接口充包,訪問標志交給ProxyGenerator去生成類文件

接下來看看generateClassFile方法做了什么

添加Object方法
/*
 * Record that proxy methods are needed for the hashCode, equals,
 * and toString methods of java.lang.Object.  This is done before
 * the methods from the proxy interfaces so that the methods from
 * java.lang.Object take precedence over duplicate methods in the
 * proxy interfaces.
 */
addProxyMethod(hashCodeMethod, Object.class);
addProxyMethod(equalsMethod, Object.class);
addProxyMethod(toStringMethod, Object.class);

首先會添加hashCode基矮,equalstoString這3個Object的方法

添加接口方法
/*
 * Now record all of the methods from the proxy interfaces, giving
 * earlier interfaces precedence over later ones with duplicate
 * methods.
 */
for (Class<?> intf : interfaces) {
    for (Method m : intf.getMethods()) {
        addProxyMethod(m, intf);
    }
}

接著將需要被代理的所有接口方法也添加進去

方法返回值校驗
/*
 * For each set of proxy methods with the same signature,
 * verify that the methods' return types are compatible.
 */
for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
    checkReturnTypes(sigmethods);
}

之前我們添加的所有方法都會保存在proxyMethods中,然后會校驗所有同名同入?yún)⒎椒ǖ姆祷刂?/p>

由于java中不允許方法名相同本砰,入?yún)⑾嗤欠祷刂挡煌姆椒ǘx钢悲,比如

public interface Demo {

    String demo(String s);
    
    int demo(String s);
}

而我們平時寫代碼時莺琳,上面的寫法就直接報紅了

添加構(gòu)造方法
methods.add(generateConstructor());

接著添加構(gòu)造方法,generateConstructor里面的內(nèi)容已經(jīng)是用byte[]來拼Class文件的內(nèi)容了珍手,就不跟進去講Class文件結(jié)構(gòu)的內(nèi)容了

添加靜態(tài)方法變量
for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
    for (ProxyMethod pm : sigmethods) {

        // add static field for method's Method object
        fields.add(new FieldInfo(pm.methodFieldName,
            "Ljava/lang/reflect/Method;",
             ACC_PRIVATE | ACC_STATIC));

        // generate code for proxy method and add it
        methods.add(pm.generateMethod());
    }
}

這里其實光看代碼會有點困惑辞做,不過如果看過最終生成的代理類就會比較好理解

這邊會把所有的方法都添加為代理類的privatestatic屬性字段秤茅,大家還記得InvocationHandlerinvoke方法傳回來的其中一個參數(shù)就是Method對象,實際上這個Method對象就是這里添加的字段

而最后的generateMethod方法也是拼接Class文件內(nèi)容

添加靜態(tài)代碼塊的初始化方法
methods.add(generateStaticInitializer());

這里主要就是初始化上面定義的Method類型的字段课幕,因為我們上面只是為每個方法都添加了一個Method類型的字段定義帖努,但是這個字段并沒有賦值拼余,就是在靜態(tài)代碼塊中對這些字段進行了賦值

校驗字段和方法的數(shù)量
if (methods.size() > 65535) {
    throw new IllegalArgumentException("method limit exceeded");
}
if (fields.size() > 65535) {
    throw new IllegalArgumentException("field limit exceeded");
}

和接口數(shù)量一樣亩歹,方法數(shù)量和字段數(shù)量在Class文件中也是用2個字節(jié)表示的凡橱,所以都有65535的限制

拼接類文件
/* ============================================================
 * Step 3: Write the final class file.
 */

/*
 * Make sure that constant pool indexes are reserved for the
 * following items before starting to write the final class file.
 */
cp.getClass(dotToSlash(className));
cp.getClass(superclassName);
for (Class<?> intf: interfaces) {
    cp.getClass(dotToSlash(intf.getName()));
}

/*
 * Disallow new constant pool additions beyond this point, since
 * we are about to write the final constant pool table.
 */
cp.setReadOnly();

ByteArrayOutputStream bout = new ByteArrayOutputStream();
DataOutputStream dout = new DataOutputStream(bout);

try {
    /*
     * Write all the items of the "ClassFile" structure.
     * See JVMS section 4.1.
     */
                                // u4 magic;
    dout.writeInt(0xCAFEBABE);
                                // u2 minor_version;
    dout.writeShort(CLASSFILE_MINOR_VERSION);
                                // u2 major_version;
    dout.writeShort(CLASSFILE_MAJOR_VERSION);

    cp.write(dout);             // (write constant pool)

                                // u2 access_flags;
    dout.writeShort(accessFlags);
                                // u2 this_class;
    dout.writeShort(cp.getClass(dotToSlash(className)));
                                // u2 super_class;
    dout.writeShort(cp.getClass(superclassName));

                                // u2 interfaces_count;
    dout.writeShort(interfaces.length);
                                // u2 interfaces[interfaces_count];
    for (Class<?> intf : interfaces) {
        dout.writeShort(cp.getClass(
            dotToSlash(intf.getName())));
    }

                                // u2 fields_count;
    dout.writeShort(fields.size());
                                // field_info fields[fields_count];
    for (FieldInfo f : fields) {
        f.write(dout);
    }

                                // u2 methods_count;
    dout.writeShort(methods.size());
                                // method_info methods[methods_count];
    for (MethodInfo m : methods) {
        m.write(dout);
    }

                                 // u2 attributes_count;
    dout.writeShort(0); // (no ClassFile attributes for proxy classes)

} catch (IOException e) {
    throw new InternalError("unexpected I/O Exception", e);
}

這里其實沒啥好說的,這是Class文件結(jié)構(gòu)的內(nèi)容了坝撑,包括上面的generateConstructor粮揉,generateMethodgenerateStaticInitializer都是類似的代碼

重點講一下這句代碼

// u2 super_class;
dout.writeShort(cp.getClass(superclassName));

這里的superclassName為常量java/lang/reflect/Proxy

/** name of the superclass of proxy classes */
private final static String superclassName = "java/lang/reflect/Proxy";

所以所有的代理類的父類都是Proxy這個類侨拦,這也就是jdk動態(tài)代理只能代理接口的原因(java不允許多繼承)

定義類
try {
    return defineClass0(loader, proxyName,
                        proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
    /*
     * A ClassFormatError here means that (barring bugs in the
     * proxy class generation code) there was some other
     * invalid aspect of the arguments supplied to the proxy
     * class creation (such as virtual machine limitations
     * exceeded).
     */
    throw new IllegalArgumentException(e.toString());
}

這個方法是native的狱从,就不深入了

總結(jié)

是不是突然覺得可以手擼一套定制化的動態(tài)代理框架了(如果對Class文件比較熟悉叠纹,或者借助ASM

其實jdk動態(tài)代理的整個邏輯并沒有多復(fù)雜,無非就是按照Class文件的結(jié)構(gòu)要求拼接各個部分數(shù)據(jù)训貌,但是在整個過程中做了很多校驗的邏輯

相對應(yīng)我們平時的開發(fā)冒窍,業(yè)務(wù)功能其實很多情況下都不復(fù)雜甚至還非常簡單综液,但是業(yè)務(wù)定義之外的邊緣數(shù)據(jù)的校驗和適配也不能馬虎,又或是對于一些并發(fā)等其他場景下的風險考慮檩奠,邏輯嚴密性比功能實現(xiàn)來的更為重要


其他的文章

【Spring Boot】一個注解實現(xiàn)下載接口

【Java】異步回調(diào)轉(zhuǎn)為同步返回

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末附帽,一起剝皮案震驚了整個濱河市蕉扮,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌屁使,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,284評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蔽午,死亡現(xiàn)場離奇詭異酬蹋,居然都是意外死亡,警方通過查閱死者的電腦和手機骄恶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評論 3 395
  • 文/潘曉璐 我一進店門叠蝇,熙熙樓的掌柜王于貴愁眉苦臉地迎上來年缎,“玉大人,你說我怎么就攤上這事蜕该≈摒” “怎么了?”我有些...
    開封第一講書人閱讀 164,614評論 0 354
  • 文/不壞的土叔 我叫張陵绢淀,是天一觀的道長皆的。 經(jīng)常有香客問我蹋盆,道長,這世上最難降的妖魔是什么楞抡? 我笑而不...
    開封第一講書人閱讀 58,671評論 1 293
  • 正文 為了忘掉前任召廷,我火速辦了婚禮,結(jié)果婚禮上柱恤,老公的妹妹穿的比我還像新娘梗顺。我一直安慰自己车摄,他們只是感情好,可當我...
    茶點故事閱讀 67,699評論 6 392
  • 文/花漫 我一把揭開白布变屁。 她就那樣靜靜地躺著意狠,像睡著了一般环戈。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上院塞,一...
    開封第一講書人閱讀 51,562評論 1 305
  • 那天拦止,我揣著相機與錄音,去河邊找鬼萧求。 笑死顶瞒,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的搁拙。 我是一名探鬼主播,決...
    沈念sama閱讀 40,309評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼酪碘,長吁一口氣:“原來是場噩夢啊……” “哼兴垦!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起狡赐,我...
    開封第一講書人閱讀 39,223評論 0 276
  • 序言:老撾萬榮一對情侶失蹤钦幔,失蹤者是張志新(化名)和其女友劉穎鲤氢,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體卷玉,經(jīng)...
    沈念sama閱讀 45,668評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,859評論 3 336
  • 正文 我和宋清朗相戀三年威恼,在試婚紗的時候發(fā)現(xiàn)自己被綠了寝并。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片食茎。...
    茶點故事閱讀 39,981評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖附迷,靈堂內(nèi)的尸體忽然破棺而出哎媚,到底是詐尸還是另有隱情拨与,我是刑警寧澤,帶...
    沈念sama閱讀 35,705評論 5 347
  • 正文 年R本政府宣布捻悯,位于F島的核電站淤毛,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏姓言。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,310評論 3 330
  • 文/蒙蒙 一囱淋、第九天 我趴在偏房一處隱蔽的房頂上張望餐塘。 院中可真熱鬧唠倦,春花似錦涮较、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,904評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至亚皂,卻和暖如春国瓮,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背禁漓。 一陣腳步聲響...
    開封第一講書人閱讀 33,023評論 1 270
  • 我被黑心中介騙來泰國打工播歼, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留掰读,地道東北人。 一個月前我還...
    沈念sama閱讀 48,146評論 3 370
  • 正文 我出身青樓烁试,卻偏偏與公主長得像雾狈,于是被迫代替她去往敵國和親廓潜。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,933評論 2 355

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