动态代理

动态代理的解释本文不再赘述,在许许多多的框架代码中,我们都可以看到动态代理的应用,重要性可见一斑,理解动态代理,对于我们理解各种框架的原理具有重要意义。下文将从动态代理常见的几种方式来剖析。

jdk动态代理

使用jdk动态代理的步骤

1.通过实现InvocationHandler接口来自定义自己的InvocationHandler;

2.通过Proxy.newProxyInstance获得动态代理类

3.通过代理对象调用目标方法

下面以一个具体demo来演示:

1.设置
设置idea启动vm options: -Dsun.misc.ProxyGenerator.saveGeneratedFiles=true(会保存生成的动态代理类到项目下)

2.测试代码
public interface User {
    void sayHello();
}

public class Man implements User {

    @Override
    public void sayHello() {
        System.out.println("hello,i am a man");
    }
}

public class UserHandler implements InvocationHandler {
    //代理的目标类
    private Object target;
    //通过构造方法注入目标类
    public UserHandler(Object target) {
        this.target = target;
    }
    @Override
    /**
     * proxy:代理类
     * method:代理的方法
     * args:方法参数
     */
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("调用前...");
        Object object = method.invoke(target, args);
        System.out.println("调用后...");
        return object;
    }
}


public class ProxyTest {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        User user = new Man();

        User userProxy = (User)Proxy.newProxyInstance(user.getClass().getClassLoader(),user.getClass().getInterfaces(),new UserHandler(user));
        userProxy.sayHello();

    }
}


3.控制台打印结果结果:
调用前...
hello,i am a man
调用后...

我们先看结果,找到生成的动态代理类反编译看下源码:

package com.sun.proxy;

import com.company.User;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0 extends Proxy implements User {

    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m0;


    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return ((Boolean)super.h.invoke(this, m1, new Object[]{var1})).booleanValue();
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final void sayHello() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return ((Integer)super.h.invoke(this, m0, (Object[])null)).intValue();
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});
            m3 = Class.forName("com.company.User").getMethod("sayHello", new Class[0]);
            m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
            m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

从生成的动态代理类我们可以看出代理类之所以能实现代理功能,其实是实现了User接口,在实现方法中,通过super.h 也就是之前我们实现的 InvocationHandler 来调用被代理类的方法。InvocationHandler实际上就是一个中间的代理,我们在创建代理对象时,就把接口的实例通过构造方法传给了它,它拥有接口实例对象,就可以操作接口的方法了。这其实和装饰者模式是一个道理。你可以理解成我们生成的动态代理类对于具体的方法执行,其实是交给了它,由它来执行具体的方法。这里也理解了,我们经常说的 jdk的动态代理是基于接口的动态代理,是通过实现目标接口来实现代理。

下面看一下Proxy.newProxyInstance的源码:

    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        Objects.requireNonNull(h);
        //获取所有需要动态代理的接口
        final Class<?>[] intfs = interfaces.clone();
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            //安全校验
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }

        /* 
         * (核心): 生成代理类的Class对象
         */
        Class<?> cl = getProxyClass0(loader, intfs);

        try {
            if (sm != null) {
                //安全校验
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }
            //反射获取构造器
            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            //判断构造方法权限修饰符.如果没有权限,强制访问
            if (!Modifier.isPublic(cl.getModifiers())) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
            //通过构造方法创建对象。h是InvocationHandler,代理对象的处理器
            return cons.newInstance(new Object[]{h});
        } catch (IllegalAccessException|InstantiationException e) {
            throw new InternalError(e.toString(), e);
        } catch (InvocationTargetException e) {
            Throwable t = e.getCause();
            if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            } else {
                throw new InternalError(t.toString(), t);
            }
        } catch (NoSuchMethodException e) {
            throw new InternalError(e.toString(), e);
        }
    }

走读以上代码,可以看见,jdk的动态代理,其实就是首先通过动态生成字节码的方式,然后通过字节码拿到构造方法,最后实例化对象。
核心流程就是动态生成字节码的过程,有兴趣的同学可以沿着getProxyClass0 这个方法继续点下去,这里不再过多说明。这里推荐一篇源码分析的博客:深度解析jdk动态代理:https://blog.csdn.net/qq_26026985/article/details/118892358

问题1: 怎么样操作字节码?

通常方式有两种.第一种是ASM框架,第二种方式是javassist框架.
ASM框架是指令层面的框架,使用技术要求高,必须懂得JVM指令和字节码.运行速度更快,spring的aop默认使用的cglib代理就是基于ASM。
javassist框架是高度封装的框架,使用门槛低,不需懂JVM.但是据说运行速度偏慢(笔者没有验证过),dubbo中的动态代理默认就是使用javassist。

问题二:jdk中动态代理是如何实现的呢?

它既没有采用ASM框架,也没有采用javassist框架.它简单粗暴按照JVM字节码规范,强行用字节流进行写入.整个过程,非常原始和底层.没有进行任何过多的二次封装.
总结就是jdk的动态代理通过动态生成字节码和反射相结合来生成代理对象。

CGLIB动态代理

先来动手写一个demo

1.先引入jar包
        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.3.0</version>
        </dependency>

2.测试代码

public interface IUserService {
    void sayHello();
}

public class UserServiceImpl implements IUserService{
    @Override
    public void sayHello() {
        System.out.println("hello");
    }
}

/**
 * 拦截器,代理方法就是通过此拦截器实现调用目标对象的方法
 * Object obj为目标对象
 * Method method为目标方法
 * Object[] params 为参数,
 * MethodProxy proxy CGlib方法代理对象
 */
public class MyMethodInterceptor implements MethodInterceptor {

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("调用前");
        Object result = proxy.invokeSuper(obj,args);
        System.out.println(" 调用后"+result);
        return result;
    }
}

    public static void main(String[] args) {

        //保存生成的动态代理类
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/Users/zhangxiaohu/workspace/openSourceProject/transactiondemo");

        Enhancer enhancer = new Enhancer();
        //设置代理的父类
        enhancer.setSuperclass(UserServiceImpl.class);
        /**
         *定义一个拦截器。在调用目标方法时,CGLib会回调MethodInterceptor接口方法拦截,
         * 来实现你自己的代理逻辑,类似于JDK中的InvocationHandler接口。
         */
        enhancer.setCallback(new MethodIntercepter());
        //生成动态代理类
        UserServiceImpl proxy = (UserServiceImpl)enhancer.create();
        proxy.sayHello();
    }

3.结果
当我们跑起来此测试代码时,控制台打印:
CGLIB debugging enabled, writing to '/Users/zhangxiaohu/workspace/openSourceProject/transactiondemo'
调用前
hello
调用后

然后我们找到目标路径下的代理类,反编译看一下代理类:

package com.example.transactiondemo.service;

import com.example.transactiondemo.service.UserServiceImpl;
import java.lang.reflect.Method;
import net.sf.cglib.core.ReflectUtils;
import net.sf.cglib.core.Signature;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.Factory;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
/**
 * 代理类继承了UserServiceImpl
 */
public class UserServiceImpl$$EnhancerByCGLIB$$2772de75 extends UserServiceImpl implements Factory {

   private boolean CGLIB$BOUND;
   public static Object CGLIB$FACTORY_DATA;
   private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
   private static final Callback[] CGLIB$STATIC_CALLBACKS;
   private MethodInterceptor CGLIB$CALLBACK_0;
   private static Object CGLIB$CALLBACK_FILTER;
   private static final Method CGLIB$sayHello$0$Method;
   private static final MethodProxy CGLIB$sayHello$0$Proxy;
   private static final Object[] CGLIB$emptyArgs;
   private static final Method CGLIB$equals$1$Method;
   private static final MethodProxy CGLIB$equals$1$Proxy;
   private static final Method CGLIB$toString$2$Method;
   private static final MethodProxy CGLIB$toString$2$Proxy;
   private static final Method CGLIB$hashCode$3$Method;
   private static final MethodProxy CGLIB$hashCode$3$Proxy;
   private static final Method CGLIB$clone$4$Method;
   private static final MethodProxy CGLIB$clone$4$Proxy;


   static void CGLIB$STATICHOOK1() {
      CGLIB$THREAD_CALLBACKS = new ThreadLocal();
      CGLIB$emptyArgs = new Object[0];
      Class var0 = Class.forName("com.example.transactiondemo.service.UserServiceImpl$$EnhancerByCGLIB$$2772de75");
      Class var1;
      Method[] var10000 = ReflectUtils.findMethods(new String[]{"equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;"}, (var1 = Class.forName("java.lang.Object")).getDeclaredMethods());
      CGLIB$equals$1$Method = var10000[0];
      CGLIB$equals$1$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$1");
      CGLIB$toString$2$Method = var10000[1];
      CGLIB$toString$2$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/String;", "toString", "CGLIB$toString$2");
      CGLIB$hashCode$3$Method = var10000[2];
      CGLIB$hashCode$3$Proxy = MethodProxy.create(var1, var0, "()I", "hashCode", "CGLIB$hashCode$3");
      CGLIB$clone$4$Method = var10000[3];
      CGLIB$clone$4$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/Object;", "clone", "CGLIB$clone$4");
      CGLIB$sayHello$0$Method = ReflectUtils.findMethods(new String[]{"sayHello", "()V"}, (var1 = Class.forName("com.example.transactiondemo.service.UserServiceImpl")).getDeclaredMethods())[0];
      CGLIB$sayHello$0$Proxy = MethodProxy.create(var1, var0, "()V", "sayHello", "CGLIB$sayHello$0");
   }

   final void CGLIB$sayHello$0() {
      super.sayHello();
   }

   /**
     * 重写了父类方法,并且定义成final类型(子类不可继承)
    */
   public final void sayHello() {
      MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
      if(this.CGLIB$CALLBACK_0 == null) {
         CGLIB$BIND_CALLBACKS(this);
         var10000 = this.CGLIB$CALLBACK_0;
      }

      if(var10000 != null) {
         var10000.intercept(this, CGLIB$sayHello$0$Method, CGLIB$emptyArgs, CGLIB$sayHello$0$Proxy);
      } else {
         super.sayHello();
      }
   }

   final boolean CGLIB$equals$1(Object var1) {
      return super.equals(var1);
   }

   public final boolean equals(Object var1) {
      MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
      if(this.CGLIB$CALLBACK_0 == null) {
         CGLIB$BIND_CALLBACKS(this);
         var10000 = this.CGLIB$CALLBACK_0;
      }

      if(var10000 != null) {
         Object var2 = var10000.intercept(this, CGLIB$equals$1$Method, new Object[]{var1}, CGLIB$equals$1$Proxy);
         return var2 == null?false:((Boolean)var2).booleanValue();
      } else {
         return super.equals(var1);
      }
   }

   final String CGLIB$toString$2() {
      return super.toString();
   }

   public final String toString() {
      MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
      if(this.CGLIB$CALLBACK_0 == null) {
         CGLIB$BIND_CALLBACKS(this);
         var10000 = this.CGLIB$CALLBACK_0;
      }

      return var10000 != null?(String)var10000.intercept(this, CGLIB$toString$2$Method, CGLIB$emptyArgs, CGLIB$toString$2$Proxy):super.toString();
   }

   final int CGLIB$hashCode$3() {
      return super.hashCode();
   }

   public final int hashCode() {
      MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
      if(this.CGLIB$CALLBACK_0 == null) {
         CGLIB$BIND_CALLBACKS(this);
         var10000 = this.CGLIB$CALLBACK_0;
      }

      if(var10000 != null) {
         Object var1 = var10000.intercept(this, CGLIB$hashCode$3$Method, CGLIB$emptyArgs, CGLIB$hashCode$3$Proxy);
         return var1 == null?0:((Number)var1).intValue();
      } else {
         return super.hashCode();
      }
   }

   final Object CGLIB$clone$4() throws CloneNotSupportedException {
      return super.clone();
   }

   protected final Object clone() throws CloneNotSupportedException {
      MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
      if(this.CGLIB$CALLBACK_0 == null) {
         CGLIB$BIND_CALLBACKS(this);
         var10000 = this.CGLIB$CALLBACK_0;
      }

      return var10000 != null?var10000.intercept(this, CGLIB$clone$4$Method, CGLIB$emptyArgs, CGLIB$clone$4$Proxy):super.clone();
   }

   public static MethodProxy CGLIB$findMethodProxy(Signature var0) {
      String var10000 = var0.toString();
      switch(var10000.hashCode()) {
      case -508378822:
         if(var10000.equals("clone()Ljava/lang/Object;")) {
            return CGLIB$clone$4$Proxy;
         }
         break;
      case 1535311470:
         if(var10000.equals("sayHello()V")) {
            return CGLIB$sayHello$0$Proxy;
         }
         break;
      case 1826985398:
         if(var10000.equals("equals(Ljava/lang/Object;)Z")) {
            return CGLIB$equals$1$Proxy;
         }
         break;
      case 1913648695:
         if(var10000.equals("toString()Ljava/lang/String;")) {
            return CGLIB$toString$2$Proxy;
         }
         break;
      case 1984935277:
         if(var10000.equals("hashCode()I")) {
            return CGLIB$hashCode$3$Proxy;
         }
      }

      return null;
   }

   public UserServiceImpl$$EnhancerByCGLIB$$2772de75() {
      CGLIB$BIND_CALLBACKS(this);
   }

   public static void CGLIB$SET_THREAD_CALLBACKS(Callback[] var0) {
      CGLIB$THREAD_CALLBACKS.set(var0);
   }

   public static void CGLIB$SET_STATIC_CALLBACKS(Callback[] var0) {
      CGLIB$STATIC_CALLBACKS = var0;
   }

   private static final void CGLIB$BIND_CALLBACKS(Object var0) {
      UserServiceImpl$$EnhancerByCGLIB$$2772de75 var1 = (UserServiceImpl$$EnhancerByCGLIB$$2772de75)var0;
      if(!var1.CGLIB$BOUND) {
         var1.CGLIB$BOUND = true;
         Object var10000 = CGLIB$THREAD_CALLBACKS.get();
         if(var10000 == null) {
            var10000 = CGLIB$STATIC_CALLBACKS;
            if(CGLIB$STATIC_CALLBACKS == null) {
               return;
            }
         }

         var1.CGLIB$CALLBACK_0 = (MethodInterceptor)((Callback[])var10000)[0];
      }

   }

   public Object newInstance(Callback[] var1) {
      CGLIB$SET_THREAD_CALLBACKS(var1);
      UserServiceImpl$$EnhancerByCGLIB$$2772de75 var10000 = new UserServiceImpl$$EnhancerByCGLIB$$2772de75();
      CGLIB$SET_THREAD_CALLBACKS((Callback[])null);
      return var10000;
   }

   public Object newInstance(Callback var1) {
      CGLIB$SET_THREAD_CALLBACKS(new Callback[]{var1});
      UserServiceImpl$$EnhancerByCGLIB$$2772de75 var10000 = new UserServiceImpl$$EnhancerByCGLIB$$2772de75();
      CGLIB$SET_THREAD_CALLBACKS((Callback[])null);
      return var10000;
   }

   public Object newInstance(Class[] var1, Object[] var2, Callback[] var3) {
      CGLIB$SET_THREAD_CALLBACKS(var3);
      UserServiceImpl$$EnhancerByCGLIB$$2772de75 var10000 = new UserServiceImpl$$EnhancerByCGLIB$$2772de75;
      switch(var1.length) {
      case 0:
         var10000.<init>();
         CGLIB$SET_THREAD_CALLBACKS((Callback[])null);
         return var10000;
      default:
         throw new IllegalArgumentException("Constructor not found");
      }
   }

   public Callback getCallback(int var1) {
      CGLIB$BIND_CALLBACKS(this);
      MethodInterceptor var10000;
      switch(var1) {
      case 0:
         var10000 = this.CGLIB$CALLBACK_0;
         break;
      default:
         var10000 = null;
      }

      return var10000;
   }

   public void setCallback(int var1, Callback var2) {
      switch(var1) {
      case 0:
         this.CGLIB$CALLBACK_0 = (MethodInterceptor)var2;
      default:
      }
   }

   public Callback[] getCallbacks() {
      CGLIB$BIND_CALLBACKS(this);
      return new Callback[]{this.CGLIB$CALLBACK_0};
   }

   public void setCallbacks(Callback[] var1) {
      this.CGLIB$CALLBACK_0 = (MethodInterceptor)var1[0];
   }

   static {
      CGLIB$STATICHOOK1();
   }
}

通过生成代理类的源码不难看出,代理类是继承了父类,并且对其中的方法重写。总结下cglib的大致原理:
动态生成一个要代理类的子类,子类继承父类并重写要代理的类的所有不是final的方法。在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。底层使用字节码处理框架ASM,来转换字节码并生成新的类。需要注意的是,对于final方法(final定义的方法无法被子类重写)cglib是无法进行代理的。

顺便提一下,我们常说静态方法是无法进行代理的,这是问什么呢?

这个问题我们需要分为两个部分讨论,
一、对于jdk这种通过实现接口的动态代理来说,因为已经继承了Proxy这个类,而在java中是不能多重继承的,所以静态方法自然是不可能被代理。
二、对于cglib这种通过继承的方式来实现动态代理的方式来说,因为在 Java 编程规范中“静态方法是可继承但不可被重写的”。所以不能被代理。
举个例子:A类有静态方法 a(),B类继承A类,B类继承了A类的静态方法,所以B类可直接使用A类的静态方法。此时若在B类中尝试重写静态方法 a(),新的静态方法 a()将变成独属于B类的静态方法,而失去了原属于A类静态方法 a() 的继承关系。注意,静态方法是独属于当前类的,你若定义便失去了父类静态方法的继承关系,新的静态方法只与当前所属类挂钩。Spring AOP 的 CGLIB 代理在于对父类方法的重写,而对静态方法的重写,会使其失去与父类静态方法的继承关系,违背了代理的核心目的,因此 CGLIB 直接排除了静态方法。

扩展一下,所谓动态代理,最重要的其实就是动态生成一个对象而已。那么思考一个问题,在java中,生成一个对象,除了new出来一个,还有什么其他的方式呢?

1、反射当然可以动态生成一个类,这应该是我们最常用的一个手段了。
2、而除了通过反射可以动态获取类,我们还可以通过生成类的字符串再动态编译类生成一个类,在dubbo的SPI自适应特性中,就是使用了动态编译来得到真正的Class对象。在dubbo的官方书籍:深入理解Apache Dubbo与实战中有说道,相比于反射生成一个类,在性能上和直接编译好的Class会有一定差距(笔者暂未验证过)。
3、当然我们也可以通过动态字节码来进行操作生成类对象。常见的字节码操作类库有:
ASM:是一个轻量级java字节码操作框架,直接涉及到JVM底层的操作和指令
CGLIB:是一个强大的,高性能,高质量的code生成类库,基于ASM实现。spring中默认使用了cglib的动态代理方式。
Javassist:是一个开源的分析,编辑和创建字节码的类库,但是据说运行速度偏慢(笔者没有验证过),使用简单,dubbo中的动态代理即默认使用了此框架。

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

推荐阅读更多精彩内容