八、AOP源码分析

寻找入口

Spring 的AOP 是通过接入BeanPostProcessor 后置处理器开始的,它是Spring IOC 容器经常使用到的一个特性,这个Bean 后置处理器是一个监听器,可以监听容器触发的Bean 声明周期事件。后置处理器向容器注册以后,容器中管理的Bean 就具备了接收IOC 容器事件回调的能力。

BeanPostProcessor 的使用非常简单,只需要提供一个实现接口BeanPostProcessor 的实现类,然后在Bean 的配置文件中设置即可。

1、BeanPostProcessor 源码

public interface BeanPostProcessor {
    //为在Bean 的初始化前提供回调入口
    @Nullable
    default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    //为在Bean 的初始化之后提供回调入口
    @Nullable
    default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
}

这两个回调的入口都是和容器管理的Bean 的生命周期事件紧密相关,可以为用户提供在Spring IOC容器初始化Bean 过程中自定义的处理操作。

2、AbstractAutowireCapableBeanFactory 类对容器生成的Bean 添加后置处理器

BeanPostProcessor 后置处理器的调用发生在Spring IOC 容器完成对Bean 实例对象的创建和属性的依赖注入完成之后,在对Spring 依赖注入的源码分析过程中我们知道,当应用程序第一次调用getBean()方法(lazy-init 预实例化除外)向Spring IOC 容器索取指定Bean 时触发Spring IOC 容器创建Bean 实例对象并进行依赖注入的过程, 其中真正实现创建Bean 对象并进行依赖注入的方法是AbstractAutowireCapableBeanFactory 类的doCreateBean()方法,主要源码如下:

    //真正创建Bean 的方法
    protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
            throws BeanCreationException {
        //创建Bean 实例对象
        ...
        Object exposedObject = bean;
        try {
            //对Bean 属性进行依赖注入
            populateBean(beanName, mbd, instanceWrapper);
            //在对Bean 实例对象生成和依赖注入完成以后,开始对Bean 实例对象
            //进行初始化,为Bean 实例对象应用BeanPostProcessor 后置处理器
            exposedObject = initializeBean(beanName, exposedObject, mbd);
        } catch (Throwable ex) {
            if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
                throw (BeanCreationException) ex;
            } else {
                throw new BeanCreationException(
                        mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
            }
        }
        ...
        //为应用返回所需要的实例对象
        return exposedObject;
    }

从上面的代码中我们知道,为Bean 实例对象添加BeanPostProcessor 后置处理器的入口的是initializeBean()方法。

3、initializeBean()方法为容器产生的Bean 实例对象添加BeanPostProcessor 后置处理器

同样在AbstractAutowireCapableBeanFactory 类中,initializeBean()方法实现为容器创建的Bean实例对象添加BeanPostProcessor 后置处理器,源码如下:

    //初始容器创建的Bean 实例对象,为其添加BeanPostProcessor 后置处理器
    protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) {
        //JDK 的安全机制验证权限
        if (System.getSecurityManager() != null) {
        //实现PrivilegedAction 接口的匿名内部类
            AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
                invokeAwareMethods(beanName, bean);
                return null;
            }, getAccessControlContext());
        } else {
            //为Bean 实例对象包装相关属性,如名称,类加载器,所属容器等信息
            invokeAwareMethods(beanName, bean);
        }
        Object wrappedBean = bean;
        //对BeanPostProcessor 后置处理器的postProcessBeforeInitialization
        //回调方法的调用,为Bean 实例初始化前做一些处理
        if (mbd == null || !mbd.isSynthetic()) {
            wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
        }
        //调用Bean 实例对象初始化的方法,这个初始化方法是在Spring Bean 定义配置
        //文件中通过init-Method 属性指定的
        try {
            invokeInitMethods(beanName, wrappedBean, mbd);
        } catch (Throwable ex) {
            throw new BeanCreationException(
                    (mbd != null ? mbd.getResourceDescription() : null),
                    beanName, "Invocation of init Method failed", ex);
        }
        //对BeanPostProcessor 后置处理器的postProcessAfterInitialization
        //回调方法的调用,为Bean 实例初始化之后做一些处理
        if (mbd == null || !mbd.isSynthetic()) {
            wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
        }
        return wrappedBean;
    }

    @Override
    //调用BeanPostProcessor 后置处理器实例对象初始化之前的处理方法
    public Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName)
            throws BeansException {
        Object result = existingBean;
        //遍历容器为所创建的Bean 添加的所有BeanPostProcessor 后置处理器
        for (BeanPostProcessor beanProcessor : getBeanPostProcessors()) {
            //调用Bean 实例所有的后置处理中的初始化前处理方法,为Bean 实例对象在
            //初始化之前做一些自定义的处理操作
            Object current = beanProcessor.postProcessBeforeInitialization(result, beanName);
            if (current == null) {
                return result;
            }
            result = current;
        }
        return result;
    }

    @Override
    //调用BeanPostProcessor 后置处理器实例对象初始化之后的处理方法
    public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)
            throws BeansException {
        Object result = existingBean;
        //遍历容器为所创建的Bean 添加的所有BeanPostProcessor 后置处理器
        for (BeanPostProcessor beanProcessor : getBeanPostProcessors()) {
            //调用Bean 实例所有的后置处理中的初始化后处理方法,为Bean 实例对象在
            //初始化之后做一些自定义的处理操作
            Object current = beanProcessor.postProcessAfterInitialization(result, beanName);
            if (current == null) {
                return result;
            }
            result = current;
        }
        return result;
    }

BeanPostProcessor 是一个接口,其初始化前的操作方法和初始化后的操作方法均委托其实现子类来实现,在Spring 中,BeanPostProcessor 的实现子类非常的多,分别完成不同的操作,如:AOP 面向切面编程的注册通知适配器、Bean 对象的数据校验、Bean 继承属性、方法的合并等等,我们以最简单的AOP 切面织入来简单了解其主要的功能。下面我们来分析其中一个创建AOP 代理对象的子类AbstractAutoProxyCreator 类。该类重写了postProcessAfterInitialization()方法。

选择代理策略

进入postProcessAfterInitialization()方法,我们发现调到了一个非常核心的方法wrapIfNecessary(),其源码如下:

    @Override
    public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) throws
            BeansException {
        if (bean != null) {
            Object cacheKey = getCacheKey(bean.getClass(), beanName);
            if (!this.earlyProxyReferences.contains(cacheKey)) {
                return wrapIfNecessary(bean, beanName, cacheKey);
            }
        }
        return bean;
    }
    ...

    /**
     * Wrap the given bean if necessary, i.e. if it is eligible for being proxied.
     *
     * @param bean     the raw bean instance
     * @param beanName the name of the bean
     * @param cacheKey the cache key for metadata access
     * @return a proxy wrapping the bean, or the raw bean instance as-is
     */
    protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
        if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
            return bean;
        }
        // 判断是否不应该代理这个bean
        if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
            return bean;
        }
        /**
         *
         *判断是否是一些InfrastructureClass 或者是否应该跳过这个bean。
         *所谓InfrastructureClass 就是指Advice/PointCut / Advisor 等接口的实现类。
         *shouldSkip 默认实现为返回false, 由于是protected 方法,子类可以覆盖。
         */
        if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
            this.advisedBeans.put(cacheKey, Boolean.FALSE);
            return bean;
        }
        // 获取这个bean 的advice
        // Create proxy if we have advice.
        Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName,
                null);
        if (specificInterceptors != DO_NOT_PROXY) {
            this.advisedBeans.put(cacheKey, Boolean.TRUE);
            // 创建代理
            Object proxy = createProxy(
                    bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
            this.proxyTypes.put(cacheKey, proxy.getClass());
            return proxy;
        }
        this.advisedBeans.put(cacheKey, Boolean.FALSE);
        return bean;
    }
    ...

    /**
     * Create an AOP proxy for the given bean.
     *
     * @param beanClass            the class of the bean
     * @param beanName             the name of the bean
     * @param specificInterceptors the set of interceptors that is
     *                             specific to this bean (may be empty, but not null)
     * @param targetSource         the TargetSource for the proxy,
     *                             already pre-configured to access the bean
     * @return the AOP proxy for the bean
     * @see #buildAdvisors
     */
    protected Object createProxy(Class<?> beanClass, @Nullable String beanName,
                                 @Nullable Object[] specificInterceptors, TargetSource targetSource) {
        if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
            AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory,
                    beanName, beanClass);
        }
        ProxyFactory proxyFactory = new ProxyFactory();
        proxyFactory.copyFrom(this);
        if (!proxyFactory.isProxyTargetClass()) {
            if (shouldProxyTargetClass(beanClass, beanName)) {
                proxyFactory.setProxyTargetClass(true);
            } else {
                evaluateProxyInterfaces(beanClass, proxyFactory);
            }
        }
        Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
        proxyFactory.addAdvisors(advisors);
        proxyFactory.setTargetSource(targetSource);
        customizeProxyFactory(proxyFactory);
        proxyFactory.setFrozen(this.freezeProxy);
        if (advisorsPreFiltered()) {
            proxyFactory.setPreFiltered(true);
        }
        return proxyFactory.getProxy(getProxyClassLoader());
    }

整个过程跟下来,我发现最终调用的是proxyFactory.getProxy()方法。到这里我们大概能够猜到proxyFactory 有JDK 和CGLib 的,那么我们该如何选择呢?最终调用的是DefaultAopProxyFactory的createAopProxy()方法:

    public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
        @Override
        public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
            if (config.isOptimize() || config.isProxyTargetClass() ||
                    hasNoUserSuppliedProxyInterfaces(config)) {
                Class<?> targetClass = config.getTargetClass();
                if (targetClass == null) {
                    throw new AopConfigException("TargetSource cannot determine target class: " +
                            "Either an interface or a target is required for proxy creation.");
                }
                if(targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
                    return new JdkDynamicAopProxy(config);
                }
                return new ObjenesisCglibAopProxy(config);
            } else {
                return new JdkDynamicAopProxy(config);
            }
        }

        /**
         * Determine whether the supplied {@link AdvisedSupport} has only the
         * {@link org.springframework.aop.SpringProxy} interface specified
         * (or no proxy interfaces specified at all).
         */
        private boolean hasNoUserSuppliedProxyInterfaces(AdvisedSupport config) {
            Class<?>[] ifcs = config.getProxiedInterfaces();
            return (ifcs.length == 0 || (ifcs.length == 1 &&
                    SpringProxy.class.isAssignableFrom(ifcs[0])));
        }
    }

调用代理方法

分析调用逻辑之前先上类图,看看Spring 中主要的AOP 组件:


image.png

上面我们已经了解到Spring 提供了两种方式来生成代理方式有JDKProxy 和CGLib。下面我们来研究一下Spring 如何使用JDK 来生成代理对象,具体的生成代码放在JdkDynamicAopProxy 这个类中,直接上相关代码:

    /**
     * 获取代理类要实现的接口,除了Advised 对象中配置的,还会加上SpringProxy, Advised(opaque=false)
     * 检查上面得到的接口中有没有定义equals 或者hashcode 的接口
     * 调用Proxy.newProxyInstance 创建代理对象
     */
    @Override
    public Object getProxy(@Nullable ClassLoader classLoader) {
        if (logger.isDebugEnabled()) {
            logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource());
        }
        Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
        findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
        return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
    }

通过注释我们应该已经看得非常明白代理对象的生成过程,此处不再赘述。下面的问题是,代理对象生成了,那切面是如何织入的?

我们知道InvocationHandler 是JDK 动态代理的核心,生成的代理对象的方法调用都会委托到InvocationHandler.invoke()方法。而从JdkDynamicAopProxy 的源码我们可以看到这个类其实也实现了InvocationHandler,下面我们分析Spring AOP 是如何织入切面的,直接上源码看invoke()方法:

    public Object invoke(Object proxy, Method Method, Object[] args) throws Throwable {
        MethodInvocation invocation;
        Object oldProxy = null;
        boolean setProxyContext = false;
        TargetSource targetSource = this.advised.targetSource;
        Object target = null;
        try {
            //eqauls()方法,具目标对象未实现此方法
            if (!this.equalsDefined && AopUtils.isEqualsMethod(Method)) {
                return equals(args[0]);
            }
            //hashCode()方法,具目标对象未实现此方法
            else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(Method)) {
                return hashCode();
            } else if (Method.getDeclaringClass() == DecoratingProxy.class) {
                return AopProxyUtils.ultimateTargetClass(this.advised);
            }
            //Advised 接口或者其父接口中定义的方法,直接反射调用,不应用通知
            else if (!this.advised.opaque && Method.getDeclaringClass().isInterface() &&
                    Method.getDeclaringClass().isAssignableFrom(Advised.class)) {
                return AopUtils.invokeJoinpointUsingReflection(this.advised, Method, args);
            }
            Object retVal;
            if (this.advised.exposeProxy) {
                oldProxy = AopContext.setCurrentProxy(proxy);
                setProxyContext = true;
            }
            //获得目标对象的类
            target = targetSource.getTarget();
            Class<?> targetClass = (target != null ? target.getClass() : null);
            //获取可以应用到此方法上的Interceptor 列表
            List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(Method, targetClass);
            //如果没有可以应用到此方法的通知(Interceptor),此直接反射调用Method.invoke(target, args)
            if (chain.isEmpty()) {
                Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(Method, args);
                retVal = AopUtils.invokeJoinpointUsingReflection(target, Method, argsToUse);
            } else {
                //创建MethodInvocation
                invocation = new ReflectiveMethodInvocation(proxy, target, Method, args, targetClass, chain);
                retVal = invocation.proceed();
            }
            Class<?> returnType = Method.getReturnType();
            if (retVal != null && retVal == target &&
                    returnType != Object.class && returnType.isInstance(proxy) &&
                    !RawTargetAccess.class.isAssignableFrom(Method.getDeclaringClass())) {
                retVal = proxy;
            } else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {
                throw new AopInvocationException(
                        "Null return value from advice does not match primitive return type for: " + Method);
            }
            return retVal;
        } finally {
            if (target != null && !targetSource.isStatic()) {
                targetSource.releaseTarget(target);
            }
            if (setProxyContext) {
                AopContext.setCurrentProxy(oldProxy);
            }
        }
    }

主要实现思路可以简述为:首先获取应用到此方法上的通知链(Interceptor Chain)。如果有通知,则应用通知,并执行JoinPoint;如果没有通知,则直接反射执行JoinPoint。而这里的关键是通知链是如何获取的以及它又是如何执行的呢?现在来逐一分析。首先,从上面的代码可以看到,通知链是通过Advised.getInterceptorsAndDynamicInterceptionAdvice()这个方法来获取的,我们来看下这个方法的实现逻辑:

    public List<Object> getInterceptorsAndDynamicInterceptionAdvice(Method Method, @Nullable Class<?> targetClass) {
        MethodCacheKey cacheKey = new MethodCacheKey(Method);
        List<Object> cached = this.MethodCache.get(cacheKey);
        if (cached == null) {
            cached = this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice(
                    this, Method, targetClass);
            this.MethodCache.put(cacheKey, cached);
        }
        return cached;
    }

通过上面的源码我们可以看到, 实际获取通知的实现逻辑其实是由AdvisorChainFactory 的getInterceptorsAndDynamicInterceptionAdvice()方法来完成的,且获取到的结果会被缓存。下面来分析getInterceptorsAndDynamicInterceptionAdvice()方法的实现:

    /**
     * 从提供的配置实例config 中获取advisor 列表,遍历处理这些advisor.如果是IntroductionAdvisor,
     * 则判断此Advisor 能否应用到目标类targetClass 上.如果是PointcutAdvisor,则判断
     * 此Advisor 能否应用到目标方法Method 上.将满足条件的Advisor 通过AdvisorAdaptor 转化成Interceptor 列表返回.
     */
    @Override
    public List<Object> getInterceptorsAndDynamicInterceptionAdvice(
            Advised config, Method Method, @Nullable Class<?> targetClass) {
        List<Object> interceptorList = new ArrayList<>(config.getAdvisors().length);
        Class<?> actualClass = (targetClass != null ? targetClass : Method.getDeclaringClass());
        //查看是否包含IntroductionAdvisor
        boolean hasIntroductions = hasMatchingIntroductions(config, actualClass);
        //这里实际上注册一系列AdvisorAdapter,用于将Advisor 转化成MethodInterceptor
        AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance();
        for (Advisor advisor : config.getAdvisors()) {
            if (advisor instanceof PointcutAdvisor) {
                PointcutAdvisor pointcutAdvisor = (PointcutAdvisor) advisor;
                if (config.isPreFiltered() || pointcutAdvisor.getPointcut().getClassFilter().matches(actualClass)) {
                    //这个地方这两个方法的位置可以互换下
                    //将Advisor 转化成Interceptor
                    MethodInterceptor[] interceptors = registry.getInterceptors(advisor);
                    //检查当前advisor 的pointcut 是否可以匹配当前方法
                    MethodMatcher mm = pointcutAdvisor.getPointcut().getMethodMatcher();
                    if (MethodMatchers.matches(mm, Method, actualClass, hasIntroductions)) {
                        if (mm.isRuntime()) {
                            for (MethodInterceptor interceptor : interceptors) {
                                interceptorList.add(new InterceptorAndDynamicMethodMatcher(interceptor, mm));
                            }
                        } else {
                            interceptorList.addAll(Arrays.asList(interceptors));
                        }
                    }
                }
            } else if (advisor instanceof IntroductionAdvisor) {
                IntroductionAdvisor ia = (IntroductionAdvisor) advisor;
                if (config.isPreFiltered() || ia.getClassFilter().matches(actualClass)) {
                    Interceptor[] interceptors = registry.getInterceptors(advisor);
                    interceptorList.addAll(Arrays.asList(interceptors));
                }
            } else {
                Interceptor[] interceptors = registry.getInterceptors(advisor);
                interceptorList.addAll(Arrays.asList(interceptors));
            }
        }
        return interceptorList;
    }

这个方法执行完成后,Advised 中配置能够应用到连接点(JoinPoint)或者目标类(Target Object)的Advisor 全部被转化成了MethodInterceptor,接下来我们再看下得到的拦截器链是怎么起作用的。

        if (chain.isEmpty()) {
            Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(Method, args);
            retVal = AopUtils.invokeJoinpointUsingReflection(target, Method, argsToUse);
        } else {
            //创建MethodInvocation
            invocation = new ReflectiveMethodInvocation(proxy, target, Method, args, targetClass, chain);
            retVal = invocation.proceed();
        }

从这段代码可以看出, 如果得到的拦截器链为空, 则直接反射调用目标方法, 否则创建MethodInvocation,调用其proceed()方法,触发拦截器链的执行,来看下具体代码:

 public Object proceed() throws Throwable {
        //如果Interceptor 执行完了,则执行joinPoint
        if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
            return invokeJoinpoint();
        }
        Object interceptorOrInterceptionAdvice =
                this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
        //如果要动态匹配joinPoint
        InterceptorAndDynamicMethodMatcher dm =
                (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
        //动态匹配:运行时参数是否满足匹配条件
        if (dm.MethodMatcher.matches(this.Method, this.targetClass, this.arguments)) {
            return dm.interceptor.invoke(this);
        }
        else {
            //动态匹配失败时,略过当前Intercetpor,调用下一个Interceptor
            return proceed();
        }
    }
else {
        //执行当前Intercetpor
        return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
}

至此,通知链就完美地形成了。我们再往下来invokeJoinpointUsingReflection()方法,其实就是反射调用:

    public static Object invokeJoinpointUsingReflection(@Nullable Object target, Method method,
                                                        Object[] args) throws Throwable {
        // Use reflection to invoke the method.
        try {
            ReflectionUtils.makeAccessible(method);
            return method.invoke(target, args);
        } catch (InvocationTargetException ex) {
            // Invoked method threw a checked exception.
            // We must rethrow it. The client won't see the interceptor.
            throw ex.getTargetException();
        } catch (IllegalArgumentException ex) {
            throw new AopInvocationException("AOP configuration seems to be invalid: tried calling
                    method[" +
                    method + "] on target [" + target + "]", ex);
        } catch (IllegalAccessException ex) {
            throw new AopInvocationException("Could not access method [" + method + "]", ex);
        }
    }
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,504评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,434评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,089评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,378评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,472评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,506评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,519评论 3 413
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,292评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,738评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,022评论 2 329
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,194评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,873评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,536评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,162评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,413评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,075评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,080评论 2 352