Spring MVC Request Scope 是如何实现的

这两天写Jersey遇到瓶颈,主要是没有理解HK2的RequestScoped 这个到底是怎么用的。自然就联想到Spring MVC 的request / session scope 是怎么用的。

Scope

Spring 提供了两种Bean 的scope (singleton 和 prototype) 以及 三种用在web 应用的scope (request, session 和 globalSession)。

singleton 和 prototype的scope 不能再被继承扩展, 但是spring 提供了可以扩展的scope 机制。

下文以HttRequest为例,讲一下spring 怎么处理的request, session 和 globalSession 对象的注入,之后再介绍一下如何定制自己的scope。

1. Request 的注入

Spring MVC环境中的父上下文时:XmlWebApplicationContext。下面我们先看一下XmlWebApplicationContext的UML类图:


image.png

当context refresh 时 会调用

    @Override
    protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
        //注册Bean的生命周期的相关的类(实现了BeanPostProcessor接口)
        beanFactory.addBeanPostProcessor(new ServletContextAwareProcessor(this.servletContext, this.servletConfig));
        //需要忽略依赖检查的接口
        beanFactory.ignoreDependencyInterface(ServletContextAware.class);
        beanFactory.ignoreDependencyInterface(ServletConfigAware.class);
        //这个是这次要重点分析的类
        WebApplicationContextUtils.registerWebApplicationScopes(beanFactory, this.servletContext);
        //注册一些环境变量相关的类
        WebApplicationContextUtils.registerEnvironmentBeans(beanFactory, this.servletContext, this.servletConfig);
    }

WebApplicationContextUtils.registerWebApplicationScopes(beanFactory, this.servletContext):


    public static void registerWebApplicationScopes(ConfigurableListableBeanFactory beanFactory, ServletContext sc) {
        beanFactory.registerScope(WebApplicationContext.SCOPE_REQUEST, new RequestScope());
        beanFactory.registerScope(WebApplicationContext.SCOPE_SESSION, new SessionScope(false));
        beanFactory.registerScope(WebApplicationContext.SCOPE_GLOBAL_SESSION, new SessionScope(true));
        if (sc != null) {
            ServletContextScope appScope = new ServletContextScope(sc);
            beanFactory.registerScope(WebApplicationContext.SCOPE_APPLICATION, appScope);
            // Register as ServletContext attribute, for ContextCleanupListener to detect it.
            sc.setAttribute(ServletContextScope.class.getName(), appScope);
        }
 
        beanFactory.registerResolvableDependency(ServletRequest.class, new RequestObjectFactory());
        beanFactory.registerResolvableDependency(ServletResponse.class, new ResponseObjectFactory());
        beanFactory.registerResolvableDependency(HttpSession.class, new SessionObjectFactory());
        beanFactory.registerResolvableDependency(WebRequest.class, new WebRequestObjectFactory());
        if (jsfPresent) {
            FacesDependencyRegistrar.registerFacesDependencies(beanFactory);
        }
    }

在这个类中注册了Web开发相关的三个Scope:RequestScope、SessionScope和全局的SessionScope以及一个ServletContextScope。接下来了注册了几个实现了ObjectFactory的Bean。RequestObjectFactory、RequestObjectFactory、SessionObjectFactory、WebRequestObjectFactory。这几个类是在Controller中注入Request和Response的关键。我们先记住 RequestObjectFactory这个类。这几个类被放到了resolvableDependencies中。resolvableDependencies这个Map中存放的是在Autowire时所要使用到的bean的类型和对应的Object。

下面我们去看一下在Controller中注入Request的一个过程。首先看一下相应的一些调用链


image.png

DefaultListableBeanFactory.findAutowireCandidates这个方法中:

    protected Map<String, Object> findAutowireCandidates(
            @Nullable String beanName, Class<?> requiredType, DependencyDescriptor descriptor) {

        String[] candidateNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
                this, requiredType, true, descriptor.isEager());
        Map<String, Object> result = new LinkedHashMap<>(candidateNames.length);
        for (Map.Entry<Class<?>, Object> classObjectEntry : this.resolvableDependencies.entrySet()) {
            Class<?> autowiringType = classObjectEntry.getKey();
            if (autowiringType.isAssignableFrom(requiredType)) {
                Object autowiringValue = classObjectEntry.getValue();
                autowiringValue = AutowireUtils.resolveAutowiringValue(autowiringValue, requiredType);
                if (requiredType.isInstance(autowiringValue)) {
                    result.put(ObjectUtils.identityToString(autowiringValue), autowiringValue);
                    break;
                }
            }
        }
        for (String candidate : candidateNames) {
            if (!isSelfReference(beanName, candidate) && isAutowireCandidate(candidate, descriptor)) {
                addCandidateEntry(result, candidate, descriptor, requiredType);
            }
        }
        if (result.isEmpty()) {
            boolean multiple = indicatesMultipleBeans(requiredType);
            // Consider fallback matches if the first pass failed to find anything...
            DependencyDescriptor fallbackDescriptor = descriptor.forFallbackMatch();
            for (String candidate : candidateNames) {
                if (!isSelfReference(beanName, candidate) && isAutowireCandidate(candidate, fallbackDescriptor) &&
                        (!multiple || getAutowireCandidateResolver().hasQualifier(descriptor))) {
                    addCandidateEntry(result, candidate, descriptor, requiredType);
                }
            }
            if (result.isEmpty() && !multiple) {
                // Consider self references as a final pass...
                // but in the case of a dependency collection, not the very same bean itself.
                for (String candidate : candidateNames) {
                    if (isSelfReference(beanName, candidate) &&
                            (!(descriptor instanceof MultiElementDescriptor) || !beanName.equals(candidate)) &&
                            isAutowireCandidate(candidate, fallbackDescriptor)) {
                        addCandidateEntry(result, candidate, descriptor, requiredType);
                    }
                }
            }
        }
        return result;
    }

在上面的代码中我们看到了resolvableDependencies这个属性。当注入HttpServletRequest的时候,requiredType值是HttpServletRequest.class,我们还记得在resolvableDependencies中放入了ServletRequest.class这个key。所以 if (autowiringType.isAssignableFrom(requiredType)) 这个判断会返回true,接着会取得RequestObjectFactory这个对象。接着会调用AutowireUtils.resolveAutowiringValue这个方法进一步的解析Autowire的值。我们进到这个方法中进行看一下

    public static Object resolveAutowiringValue(Object autowiringValue, Class<?> requiredType) {
        if (autowiringValue instanceof ObjectFactory && !requiredType.isInstance(autowiringValue)) {
            ObjectFactory<?> factory = (ObjectFactory<?>) autowiringValue;
            if (autowiringValue instanceof Serializable && requiredType.isInterface()) {
                autowiringValue = Proxy.newProxyInstance(requiredType.getClassLoader(),
                        new Class<?>[] {requiredType}, new ObjectFactoryDelegatingInvocationHandler(factory));
            }
            else {
                return factory.getObject();
            }
        }
        return autowiringValue;
    }

在这个方法中会先判断上一步取得的autowireValue是不是可以实例化为ObjectFactory 对象。上一步取得的autowireValue是RequestObjectFactory。我们看一下RequestObjectFactory这个类的内容:


    private static class RequestObjectFactory implements ObjectFactory<ServletRequest>, Serializable {
 
        @Override
        public ServletRequest getObject() {
            return currentRequestAttributes().getRequest();
        }
 
        @Override
        public String toString() {
            return "Current HttpServletRequest";
        }
    }

实现了ObjectFactory这个接口。由于RequestObjectFactory 是不能实例化为HttpServletRequest对象的。所以会进入到循环体中。接着进一步的判断上一步取得的autowireValue的值能不能被序列化,以及requiredType是不是可以接口,从上面的分析可以看出这个条件也是成立的。所以最终生成的autowireValue的值是一个 JDK动态代理生成的对象。InvocationHandler的实现类为:ObjectFactoryDelegatingInvocationHandler。所以我们在Controller层中所注入的HttpServletRequest其实是一个JDK动态代理生成的对象。 当我们在Controller中调用的时候:

    @RequestMapping("testAutowiredRequest")
    public String testAutowiredRequest() {
        request.getParameter("userNmae");
        return "success";
    }

会进入到ObjectFactoryDelegatingInvocationHandler的invoke方法中

@Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            String methodName = method.getName();
            if (methodName.equals("equals")) {
                // Only consider equal when proxies are identical.
                return (proxy == args[0]);
            }
            else if (methodName.equals("hashCode")) {
                // Use hashCode of proxy.
                return System.identityHashCode(proxy);
            }
            else if (methodName.equals("toString")) {
                return this.objectFactory.toString();
            }
            try {
                return method.invoke(this.objectFactory.getObject(), args);
            }
            catch (InvocationTargetException ex) {
                throw ex.getTargetException();
            }
        }

这里对equals方法、hashcode方法、toString方法进行了特殊处理。其他的方法则是直接执行method.invoke的方法。第一个参数为所调用的对象。是通过调用this.objectFactory.getObject()来获取的。objectFactory,通过我们上面的分析我们知道它是RequestObjectFactory这个对象。所以我们去RequestObjectFactory这个类中看一下objectFactory这个方法:

    private static class RequestObjectFactory implements ObjectFactory<ServletRequest>, Serializable {

        @Override
        public ServletRequest getObject() {
            return currentRequestAttributes().getRequest();
        }

        @Override
        public String toString() {
            return "Current HttpServletRequest";
        }
    }

在上面的代码中调用了currentRequestAttributes().getRequest()。接着去currentRequestAttributes()这个方法中看一下

    private static ServletRequestAttributes currentRequestAttributes() {
        RequestAttributes requestAttr = RequestContextHolder.currentRequestAttributes();
        if (!(requestAttr instanceof ServletRequestAttributes)) {
            throw new IllegalStateException("Current request is not a servlet request");
        }
        return (ServletRequestAttributes) requestAttr;
    }

((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest();看一下RequestContextHolder.currentRequestAttributes()这个方法

    public static RequestAttributes currentRequestAttributes() throws IllegalStateException {
        RequestAttributes attributes = getRequestAttributes();
        if (attributes == null) {
            if (jsfPresent) {
                attributes = FacesRequestAttributesFactory.getFacesRequestAttributes();
            }
            if (attributes == null) {
                throw new IllegalStateException("No thread-bound request found: " +
                        "Are you referring to request attributes outside of an actual web request, " +
                        "or processing a request outside of the originally receiving thread? " +
                        "If you are actually operating within a web request and still receive this message, " +
                        "your code is probably running outside of DispatcherServlet: " +
                        "In this case, use RequestContextListener or RequestContextFilter to expose the current request.");
            }
        }
        return attributes;
    }

通过getRequestAttributes()这个方法来获取RequestAttributes对象。在这个方法中继续看一下

    public static RequestAttributes getRequestAttributes() {
        RequestAttributes attributes = requestAttributesHolder.get();
        if (attributes == null) {
            attributes = inheritableRequestAttributesHolder.get();
        }
        return attributes;
    }

发现是通过requestAttributesHolder或者inheritableRequestAttributesHolder的get()方法,来获取的RequestAttributes对象。那么requestAttributesHolder和inheritableRequestAttributesHolder是什么呢?

    private static final ThreadLocal<RequestAttributes> requestAttributesHolder =
            new NamedThreadLocal<>("Request attributes");

    private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder =
            new NamedInheritableThreadLocal<>("Request context");


这是两个 ThreadLocal的对象。每次都是从ThreadLocal对象中取的值,因此是线程安全的。什么时候往这两个ThreadLocal中放入值的呢?请接着往下看:org.springframework.web.servlet.FrameworkServlet#processRequest这个方法中有这样的几行代码:

                 RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);//创建ServletRequestAttributes 
        initContextHolders(request, localeContext, requestAttributes);//往ThreadLocal中放入值

initContextHolders这个方法的代码如下:


    private void initContextHolders(
            HttpServletRequest request, LocaleContext localeContext, RequestAttributes requestAttributes) {
 
        if (localeContext != null) {
            LocaleContextHolder.setLocaleContext(localeContext, this.threadContextInheritable);
        }
        //把RequestAttributes放入到ThreadLocal中
        if (requestAttributes != null) {
            RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable);
        }
    }

RequestContextHolder.setRequestAttributes这个方法的内容如下:

public static void setRequestAttributes(RequestAttributes attributes, boolean inheritable) {
        if (attributes == null) {
            resetRequestAttributes();
        }
        else {
            if (inheritable) {
                inheritableRequestAttributesHolder.set(attributes);
                requestAttributesHolder.remove();
            }
            else {
                requestAttributesHolder.set(attributes);
                inheritableRequestAttributesHolder.remove();
            }
        }
    }

以上就是create 一个request scope bean 的全过程。不过上文只是提到了HttpRequest, HttpResponse显示在程序里这注册了factory 怎么构造的,对于RequestScope 或者 任意一个Annotation 怎么构造的,其实并没有说明。下面先介绍如何订制一个Scope, 并且如何使用。 再介绍程序是如何运行解析的。

2. Creating a Custom Scope Class

请按照下列顺序实现

2.1. Managing the Scoped Objects and Callbacks

首先写一个scope class:


import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.Scope;

public class TenantScope implements Scope {

    private Map<String, Object> scopedObjects = Collections.synchronizedMap(new HashMap<String, Object>());
    private Map<String, Runnable> destructionCallbacks = Collections.synchronizedMap(new HashMap<String, Runnable>());

    @Override
    public Object get(String name, ObjectFactory<?> objectFactory) {
        if (!scopedObjects.containsKey(name)) {
            scopedObjects.put(name, objectFactory.getObject());
        }
        return scopedObjects.get(name);
    }

    @Override
    public Object remove(String name) {
        destructionCallbacks.remove(name);
        return scopedObjects.remove(name);
    }

    @Override
    public void registerDestructionCallback(String name, Runnable callback) {
        destructionCallbacks.put(name, callback);
    }

    @Override
    public Object resolveContextualObject(String key) {
        return null;
    }

    @Override
    public String getConversationId() {
        return "tenant";
    }
}

}

2.2. Retrieving an Object from Scope

在get 方法里, 如果已经存在,从map 里返回,如果不存在,则用objectFactory 生成。

@Override
public Object get(String name, ObjectFactory<?> objectFactory) {
    if(!scopedObjects.containsKey(name)) {
        scopedObjects.put(name, objectFactory.getObject());
    }
    return scopedObjects.get(name);
}

在Scope 五个方法里,只有 get 必须实现,其他是可选,也可以直接抛出UnSupportedException。

2.3. Registering a Destruction Callback

要实现destroy callback 必须要实现 registerDestructionCallback 方法。

@Override
public void registerDestructionCallback(String name, Runnable callback) {
    destructionCallbacks.put(name, callback);
}

2.4. Removing an Object from Scope

remove方法把object 从 scope在destroy 方法中移除。

@Override
public Object remove(String name) {
    destructionCallbacks.remove(name);
    return scopedObjects.remove(name);
}

2.5. Getting the Conversation ID

现在实现getConversationId 方法. 如果你的scope 有conversation ID的概念, 就在这里创造一个id,否则就返回null

@Override
public String getConversationId() {
    return "tenant";
}

2.6. Resolving Contextual Objects

最后实现resolveContextualObject 方法。如果支持多context object, 需要把每个object 关联一个key,根据key 值返回object。 如果key 不存在返回null。

@Override
public Object resolveContextualObject(String key) {
    return null;
}

2.7. Registering the Custom Scope

需要调用 ConfigurableBeanFactory instance**的registerScope,来让框架知道次Scope。

void registerScope(String scopeName, Scope scope);

第一个参数是Scope 名称,第二个参数是Scope 的实例。需要生成一个自己的BeanFactoryPostProcessor ,同时注册Scope。

public class TenantBeanFactoryPostProcessor implements BeanFactoryPostProcessor {

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory factory) throws BeansException {
        factory.registerScope("tenant", new TenantScope());
    }
}

现在注册写个config,注册BeanFactoryPostProcessor Bean。

@Configuration
public class TenantScopeConfig {

    @Bean
    public static BeanFactoryPostProcessor beanFactoryPostProcessor() {
        return new TenantBeanFactoryPostProcessor();
    }
}

2.8. Using the Custom Scope

先创建 TenantBean class :

public class TenantBean {

    private final String name;

    public TenantBean(String name) {
        this.name = name;
    }

    public void sayHello() {
        System.out.println(
          String.format("Hello from %s of type %s",
          this.name, 
          this.getClass().getName()));
    }
}

写一个配置来定义TenantBean 并且其scope 为tenant。

@Configuration
public class TenantBeansConfig {

    @Scope(scopeName = "tenant")
    @Bean
    public TenantBean foo() {
        return new TenantBean("foo");
    }

    @Scope(scopeName = "tenant")
    @Bean
    public TenantBean bar() {
        return new TenantBean("bar");
    }
}

2.9. Testing the Custom Scope

现在可以load “tenant" scope 的TenantBean。

@Test
public final void whenRegisterScopeAndBeans_thenContextContainsFooAndBar() {
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
    try{
        ctx.register(TenantScopeConfig.class);
        ctx.register(TenantBeansConfig.class);
        ctx.refresh();

        TenantBean foo = (TenantBean) ctx.getBean("foo", TenantBean.class);
        foo.sayHello();
        TenantBean bar = (TenantBean) ctx.getBean("bar", TenantBean.class);
        bar.sayHello();
        Map<String, TenantBean> foos = ctx.getBeansOfType(TenantBean.class);

        assertThat(foo, not(equalTo(bar)));
        assertThat(foos.size(), equalTo(2));
        assertTrue(foos.containsValue(foo));
        assertTrue(foos.containsValue(bar));

        BeanDefinition fooDefinition = ctx.getBeanDefinition("foo");
        BeanDefinition barDefinition = ctx.getBeanDefinition("bar");

        assertThat(fooDefinition.getScope(), equalTo("tenant"));
        assertThat(barDefinition.getScope(), equalTo("tenant"));
    }
    finally {
        ctx.close();
    }
}

2.10 Calling Sequence

从下图可以看出,定制的调用顺序,

image.png

可以看出AbstraactBeanFactory 里的逻辑

                    String scopeName = mbd.getScope();
                    if (!StringUtils.hasLength(scopeName)) {
                        throw new IllegalStateException("No scope name defined for bean ´" + beanName + "'");
                    }
                    Scope scope = this.scopes.get(scopeName);
                    if (scope == null) {
                        throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
                    }
                    try {
                        Object scopedInstance = scope.get(beanName, () -> {
                            beforePrototypeCreation(beanName);
                            try {
                                return createBean(beanName, mbd, args);
                            }
                            finally {
                                afterPrototypeCreation(beanName);
                            }
                        });
                        bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
                    }
                    catch (IllegalStateException ex) {
                        throw new BeanCreationException(beanName,
                                "Scope '" + scopeName + "' is not active for the current thread; consider " +
                                "defining a scoped proxy for this bean if you intend to refer to it from a singleton",
                                ex);
                    }

实际传入的BeanFactory是

 () -> {
                            beforePrototypeCreation(beanName);
                            try {
                                return createBean(beanName, mbd, args);
                            }
                            finally {
                                afterPrototypeCreation(beanName);
                            }
                        });

同时我们再看AbstractBeanDefinition 缺省的scope为singleton.


    /**
     * Set the name of the target scope for the bean.
     * <p>The default is singleton status, although this is only applied once
     * a bean definition becomes active in the containing factory. A bean
     * definition may eventually inherit its scope from a parent bean definition.
     * For this reason, the default scope name is an empty string (i.e., {@code ""}),
     * with singleton status being assumed until a resolved scope is set.
     * @see #SCOPE_SINGLETON
     * @see #SCOPE_PROTOTYPE
     */
    @Override
    public void setScope(@Nullable String scope) {
        this.scope = scope;
    }

那么同样,当@RequestScope 的发生时,也采取的是和定制化一样的逻辑。 先看RequestScope

public class RequestScope extends AbstractRequestAttributesScope {

    @Override
    protected int getScope() {
        return RequestAttributes.SCOPE_REQUEST;
    }

    /**
     * There is no conversation id concept for a request, so this method
     * returns {@code null}.
     */
    @Override
    @Nullable
    public String getConversationId() {
        return null;
    }

}

public abstract class AbstractRequestAttributesScope implements Scope {

    @Override
    public Object get(String name, ObjectFactory<?> objectFactory) {
        RequestAttributes attributes = RequestContextHolder.currentRequestAttributes();
        Object scopedObject = attributes.getAttribute(name, getScope());
        if (scopedObject == null) {
            scopedObject = objectFactory.getObject();
            attributes.setAttribute(name, scopedObject, getScope());
            // Retrieve object again, registering it for implicit session attribute updates.
            // As a bonus, we also allow for potential decoration at the getAttribute level.
            Object retrievedObject = attributes.getAttribute(name, getScope());
            if (retrievedObject != null) {
                // Only proceed with retrieved object if still present (the expected case).
                // If it disappeared concurrently, we return our locally created instance.
                scopedObject = retrievedObject;
            }
        }
        return scopedObject;
    }
....
}

主要看它的Object, 从request attribute里拿对象,如果不存在就创建bean 然后放入request attributes 中。

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

推荐阅读更多精彩内容