SpringMVC对@Controller的处理

1、AnnotationConfigApplicationContext与AnnotationConfigWebApplicationContext

2、Spring如何解析@RequestMapping

3、web的自动配置

4、SpringMVC响应json数据

上一篇文章中说道DispatcherServlet会随着Tomcat容器启动,会调用DispatcherServlet的init初始化方法,在这个初始化方法中,这个servlet回去初始化spring的环境。

// FrameworkServlet#initServletBean
protected final void initServletBean() throws ServletException {
    // 初始化web环境 WebApplicationContext  ==child==> ConfigurableWebApplicationContext
    this.webApplicationContext = initWebApplicationContext();
    // 调用了initFrameworkServlet方法,这是一个空方法
    initFrameworkServlet();
}
// FrameworkServlet#configureAndRefreshWebApplicationContext
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
    // 设置ServletContext等属性
    wac.setServletContext(getServletContext());
    wac.setServletConfig(getServletConfig());
    wac.setNamespace(getNamespace());
    // 添加了一个监听器,监听容器刷新完成事件  DispatcherServlet#onRefresh 初始化SpringMVC组件
    wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
    // refresh的前置处理 设置环境的servlet上下文和servletConfig
    ConfigurableEnvironment env = wac.getEnvironment();
    if (env instanceof ConfigurableWebEnvironment) {
        ((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
    }
    // 空方法
    postProcessWebApplicationContext(wac);
    // 调用容器的初始化方法 ApplicationContextInitializer#initialize接口的实现类
    applyInitializers(wac);
    // 刷新容器,走到AbstractApplicationContext中的refresh方法
    wac.refresh();
}

调用了AbstractApplicationContext#refresh,在这个方法中AnnotationConfigWebApplicationContext的webContext重写了postProcessBeanFactory方法

// AbstractRefreshableWebApplicationContext#postProcessBeanFactory
protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
    // 添加了一个BeanPostProcessor
    beanFactory.addBeanPostProcessor(new ServletContextAwareProcessor(this.servletContext, this.servletConfig));
    beanFactory.ignoreDependencyInterface(ServletContextAware.class);
    beanFactory.ignoreDependencyInterface(ServletConfigAware.class);
    // 注册web环境
    WebApplicationContextUtils.registerWebApplicationScopes(beanFactory, this.servletContext);
    WebApplicationContextUtils.registerEnvironmentBeans(beanFactory, this.servletContext, this.servletConfig);
}

ServletContextAwareProcessor这个BeanPostProcessor会在init方法执行之前为ServletContextAware设置servletContext属性

// ServletContextAwareProcessor#postProcessBeforeInitialization
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    if (getServletContext() != null && bean instanceof ServletContextAware) {
        ((ServletContextAware) bean).setServletContext(getServletContext());
    }
    if (getServletConfig() != null && bean instanceof ServletConfigAware) {
        ((ServletConfigAware) bean).setServletConfig(getServletConfig());
    }
    return bean;
}

处理@RequestMapping

处理入口:RequestMappingHandlerMapping


RequestMappingHandlerMapping.png

从上图可以看出RequestMappingHandlerMapping实现了InitializingBean接口,而这个对象会在SpringMVC初始化的时候有Spring来创建。

// RequestMappingHandlerMapping#afterPropertiesSet
public void afterPropertiesSet() {
    // 设置配置信息
    this.config = new RequestMappingInfo.BuilderConfiguration();
    // url路径解析工具  比如截取出uri ...
    this.config.setUrlPathHelper(getUrlPathHelper());
    // AntPathMatcher 路径匹配工具
    this.config.setPathMatcher(getPathMatcher());
    // 是否开启后缀匹配模式  默认true
    this.config.setSuffixPatternMatch(this.useSuffixPatternMatch);
    // 是否开启尾部/ 匹配   默认true
    this.config.setTrailingSlashMatch(this.useTrailingSlashMatch);
    this.config.setRegisteredSuffixPatternMatch(this.useRegisteredSuffixPatternMatch);
    this.config.setContentNegotiationManager(getContentNegotiationManager());
    super.afterPropertiesSet();
}
// 父类 AbstractHandlerMethodMapping#afterPropertiesSet
public void afterPropertiesSet() {
    initHandlerMethods();
}
protected void initHandlerMethods() {
    // 遍历spring中所有的object beanName
    for (String beanName : getCandidateBeanNames()) {
        // 是否以 scopedTarget. 开头
        if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
            processCandidateBean(beanName);
        }
    }
    handlerMethodsInitialized(getHandlerMethods());
}
protected void processCandidateBean(String beanName) {
    // bean class
    Class<?> beanType = obtainApplicationContext().getType(beanName);
    if (beanType != null && isHandler(beanType)) {
        // 筛选出Controller或者RequestMapping注解
        detectHandlerMethods(beanName);
    }
}
// 注意这里的isHandler判断
protected boolean isHandler(Class<?> beanType) {
    return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
            AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}

解析加了Controller或者RequestMapping注解的类

// AbstractHandlerMethodMapping#detectHandlerMethods  handler就是传入的bean对象
protected void detectHandlerMethods(Object handler) {
    Class<?> handlerType = (handler instanceof String ? obtainApplicationContext().getType((String) handler) : handler.getClass());
    if (handlerType != null) {
        Class<?> userType = ClassUtils.getUserClass(handlerType);
        // 筛选出请求方法  合并请求uri
        Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
                                (MethodIntrospector.MetadataLookup<T>) method -> {
                                    return getMappingForMethod(method, userType);
                                });
        // 注册方法映射
        methods.forEach((method, mapping) -> {
            Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
            registerHandlerMethod(handler, invocableMethod, mapping);
        });
    }
}
// 方法的解析
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
    // 解析@RequestMapping 包括内部的各种属性 请求类型 映射路径 参数 ...
    RequestMappingInfo info = createRequestMappingInfo(method);
    if (info != null) {
        // 获取类上面的@RequestMapping注解
        RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
        if (typeInfo != null) {
            // requestMapping合并
            info = typeInfo.combine(info);
        }
        // 获取类上面的前缀
        String prefix = getPathPrefix(handlerType);
        if (prefix != null) {
            // 进行前缀合并
            info = RequestMappingInfo.paths(prefix).build().combine(info);
        }
    }
    return info;
}

注册Controller映射对应关系

// 方法映射的注册
methods.forEach((method, mapping) -> {
    Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
    registerHandlerMethod(handler, invocableMethod, mapping);
});
// Controller中对方法的要求
public static Method selectInvocableMethod(Method method, @Nullable Class<?> targetType) {
    // 当前类的方法
    Method methodToUse = MethodIntrospector.selectInvocableMethod(method, targetType);
    // 私有方法 + 非静态方法 + SpringProxy子类 -> SpringProxy子类的私有方法会直接抛出异常
    if (Modifier.isPrivate(methodToUse.getModifiers()) 
        && !Modifier.isStatic(methodToUse.getModifiers()) 
        && SpringProxy.class.isAssignableFrom(targetType)) {
        throw new IllegalStateException("xxxx");
    }
}
// 映射的注册
protected void registerHandlerMethod(Object handler, Method method, T mapping) {
    this.mappingRegistry.register(mapping, handler, method);
}

注册的具体逻辑:AbstractHandlerMethodMapping.MappingRegistry#register

public void register(T mapping, Object handler, Method method) {
    // 写锁
    this.readWriteLock.writeLock().lock();
    try {
        HandlerMethod handlerMethod = createHandlerMethod(handler, method);
        assertUniqueMethodMapping(handlerMethod, mapping);
        // 把 [mapping - handlerMethod] 放入mappingLookup
        // mapping就是当前方法的汇总注解信息
        this.mappingLookup.put(mapping, handlerMethod);
        List<String> directUrls = getDirectUrls(mapping);
        // urlLookup中放入的是 [uri - mapping]  {"/index",RequestMappingInfo}
        for (String url : directUrls) {
            this.urlLookup.add(url, mapping);
        }
        // 解析其他形式的uri 正则 后缀 矩阵 等
        String name = null;
        if (getNamingStrategy() != null) {
            name = getNamingStrategy().getName(handlerMethod, mapping);
            addMappingName(name, handlerMethod);
        }
        CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
        if (corsConfig != null) {
            this.corsLookup.put(handlerMethod, corsConfig);
        }
        // 缓存已注册的信息
        this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));
    } finally {
        this.readWriteLock.writeLock().unlock();
    }
}

@EnableWebMvc注解

@EnableWebMvc表示开启webmvc的自动配置,并且我们可以实现WebMvcConfigurer接口去添加SpringMVC的组件,比如:异常处理器、视图解析器、拦截器、消息转换器等等。

为什么我们实现并重写WebMvcConfigurer接口中的方法,spring就可以用到这些组件,就和这个注解有关。

spring在解决DelegatingWebMvcConfiguration的依赖时,回去容器中所有实现了WebMvcConfigurer接口的bean对象,返回注入到这里来。

源码参考:AutowiredAnnotationBeanPostProcessor.AutowiredMethodElement#inject

@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {}
// 导入的配置类
@Configuration
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
    // WebMvcConfigurerComposite实现了WebMvcConfigurer接口
    private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();
    // 为set方法注入配置集合
    @Autowired(required = false)
    public void setConfigurers(List<WebMvcConfigurer> configurers) {
        if (!CollectionUtils.isEmpty(configurers)) {
            this.configurers.addWebMvcConfigurers(configurers);
        }
    }
    @Override
    protected void addInterceptors(InterceptorRegistry registry) {
        this.configurers.addInterceptors(registry);
    }
    //省略很多方法
}

重点分析WebMvcConfigurerComposite类

class WebMvcConfigurerComposite implements WebMvcConfigurer {
    // 配置类集合
    private final List<WebMvcConfigurer> delegates = new ArrayList<>();
    public void addWebMvcConfigurers(List<WebMvcConfigurer> configurers) {
        if (!CollectionUtils.isEmpty(configurers)) {
            this.delegates.addAll(configurers);
        }
    }
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        for (WebMvcConfigurer delegate : this.delegates) {
            delegate.addInterceptors(registry);
        }
    }
}

在这个DelegatingWebMvcConfiguration类中只有一个@Bean,明显靠着一个bean对象是无法完成springmvc的,所以我们继续分析它的父类WebMvcConfigurationSupport。

// 这里面注入了很多的web组件 拦截器 异常处理器 视图解析器 处理器映射器 等等
public class WebMvcConfigurationSupport implements ApplicationContextAware, ServletContextAware {
    // 主要看RequestMappingHandlerMapping
    @Bean
    public RequestMappingHandlerMapping requestMappingHandlerMapping() {
        // new RequestMappingHandlerMapping()
        RequestMappingHandlerMapping mapping = createRequestMappingHandlerMapping();
        mapping.setOrder(0);
        // 设置拦截器
        mapping.setInterceptors(getInterceptors());
        mapping.setContentNegotiationManager(mvcContentNegotiationManager());
        mapping.setCorsConfigurations(getCorsConfigurations());
        // .... 很多组件
        return mapping;
    }
    // 拦截器的获取逻辑
    protected final Object[] getInterceptors() {
        if (this.interceptors == null) {
            // 拦截器注册器
            InterceptorRegistry registry = new InterceptorRegistry();
            // 向configurers中添加注册器,这里其实就是注册程序中自己写的拦截
            addInterceptors(registry);
            // 注册两个拦截器 这两个拦截器都会被包装为InterceptorRegistration
            registry.addInterceptor(new ConversionServiceExposingInterceptor(mvcConversionService()));
            registry.addInterceptor(new ResourceUrlProviderExposingInterceptor(mvcResourceUrlProvider()));
            // 获取所有的拦截器(包括使用WebMvcConfigurer注册的)
            this.interceptors = registry.getInterceptors();
        }
        return this.interceptors.toArray();
    }
}

当使用WebMvcConfigurer去添加拦截器的时候,拦截器会被包装为InterceptorRegistration。

// InterceptorRegistry#addInterceptor
public InterceptorRegistration addInterceptor(HandlerInterceptor interceptor) {
    InterceptorRegistration registration = new InterceptorRegistration(interceptor);
    this.registrations.add(registration);
    return registration;
}
enableWebMvc拦截器的处理.png

SpringMVC响应json数据

我们都知道,要使springMVC可以响应json数据,必须要添加一个消息转换器。

@Configuration
public class WebMvcConfig  implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new MyInterceptor()).addPathPatterns("/index/2");
    }
    // 配置消息转换器
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.add(jsonHttpMessageConverter());
    }
    @Bean
    public FastJsonHttpMessageConverter jsonHttpMessageConverter() {
        return new FastJsonHttpMessageConverter();
    }
}

分析处理器处理请求的时候,以@RequestMapping为例,核心处理逻辑:RequestMappingHandlerAdapter#handleInternal

// RequestMappingHandlerAdapter的构造方法
public RequestMappingHandlerAdapter() {
    StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
    stringHttpMessageConverter.setWriteAcceptCharset(false);  // see SPR-7316
    // 添加了四个 messageConverter
    this.messageConverters = new ArrayList<>(4);
    this.messageConverters.add(new ByteArrayHttpMessageConverter());
    this.messageConverters.add(stringHttpMessageConverter);
    this.messageConverters.add(new SourceHttpMessageConverter<>());
    this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());
}

处理请求的方法调用过程

RequestMappingHandlerAdapter#handleInternal
 => RequestMappingHandlerAdapter#invokeHandlerMethod
    => ServletInvocableHandlerMethod#invokeAndHandle

主要看ServletInvocableHandlerMethod#invokeAndHandle方法

public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {
    // 调用了具体controller中的方法,获取了返回值
    Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
    // 根据@ResponseStatus code 设置status
    setResponseStatus(webRequest);
    // 返回值为 null
    if (returnValue == null) {
        if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
            disableContentCachingIfNecessary(webRequest);
            mavContainer.setRequestHandled(true);
            return;
        }
    // 检查@ResponseStatus注解的reason
    } else if (StringUtils.hasText(getResponseStatusReason())) {
        mavContainer.setRequestHandled(true);
        return;
    }
    // 标记请求是否处理完成
    mavContainer.setRequestHandled(false);
    Assert.state(this.returnValueHandlers != null, "No return value handlers");
    try {
        // 选择合适的结果处理器来处理结果集
        this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
    } catch (Exception ex) {
        if (logger.isTraceEnabled()) {
            logger.trace(formatErrorForReturnValue(returnValue), ex);
        }
        throw ex;
    }
}

结果处理器的选择

// HandlerMethodReturnValueHandlerComposite#handleReturnValue
public void handleReturnValue(Object returnValue, MethodParameter returnType,
                              ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
    // 获取一个可以处理 returnType 的结果集处理器
    HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
    if (handler == null) {
        // 如果找不到一个结果集处理器来处理,就会在这里抛出异常
        throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
    }
    // 调用 handleReturnValue 来处理结果
    handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}
// HandlerMethodReturnValueHandlerComposite#selectHandler
private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, MethodParameter returnType) {
    boolean isAsyncValue = isAsyncReturnValue(value, returnType);
    // 循环所有的结果处理器
    for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
        if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) {
            continue;
        }
        // 如果找到一个可以处理这个结果类型的处理器,就直接将这个处理器返回 supportsReturnType
        if (handler.supportsReturnType(returnType)) {
            return handler;
        }
    }
    return null;
}

已知存在的结果处理器有以下几种

# 处理ModelAndView类型的返回值
ModelAndViewMethodReturnValueHandler
# 处理Model类型的返回值
ModelMethodProcessor
# 处理View类型的返回值
ViewMethodReturnValueHandler
# 处理ResponseBodyEmitter类型
ResponseBodyEmitterReturnValueHandler
# 处理StreamingResponseBody类型
StreamingResponseBodyReturnValueHandler
# 处理HttpEntity和RequestEntity类型
HttpEntityMethodProcessor
# 处理HttpHeaders类型
HttpHeadersReturnValueHandler
# 处理Callable类型
CallableMethodReturnValueHandler
# 处理DeferredResult ListenableFuture和CompletionStage类型
DeferredResultMethodReturnValueHandler
# 处理WebAsyncTask
AsyncTaskMethodReturnValueHandler
# 处理ModelAttribute
ModelAttributeMethodProcessor
# 处理加了 @RequestBody 注解的返回值
RequestResponseBodyMethodProcessor
# 处理没有添加 @RequestBody 并返回string类型的返回  会解析成视图名称
ViewNameMethodReturnValueHandler
# 处理map返回值
MapMethodProcessor

判定规则就是调用每一个处理器的supportsReturnType方法,来找到可以处理这个返回值类型的处理器。

如果我们添加了@RequestBody注解,那么就会进入RequestResponseBodyMethodProcessor处理器。

// RequestResponseBodyMethodProcessor#supportsReturnType
public boolean supportsReturnType(MethodParameter returnType) {
    // 类或者方法上存在ResponseBody注解
    return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||
            returnType.hasMethodAnnotation(ResponseBody.class));
}
// 调用 RequestResponseBodyMethodProcessor#handleReturnValue 方法处理返回值
public void handleReturnValue(Object returnValue, MethodParameter returnType,
                              ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
    throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
    // 标记为已处理
    mavContainer.setRequestHandled(true);
    ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
    ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
    // Try even with null return value. ResponseBodyAdvice could get involved.
    // 如果这个的返回值是void,那么会在 ResponseBodyAdvice 中处理
    writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}

确定响应的类型,调用spring中所有的messageConverters进行处理。下面贴出关键代码

// AbstractMessageConverterMethodProcessor#writeWithMessageConverters
// InputStreamResource 或者 Resource 类型
if (isResourceType(value, returnType)) {
    outputMessage.getHeaders().set(HttpHeaders.ACCEPT_RANGES, "bytes");
}
MediaType contentType = outputMessage.getHeaders().getContentType();
// 遍历 messageConverters 来处理结果集
for (HttpMessageConverter<?> converter : this.messageConverters) {
   // 最终调用 messageConverter 将结果集write出去 -> FastJsonHttpMessageConverter#write
    genericConverter.write(body, targetType, selectedMediaType, outputMessage);
}

// AbstractHttpMessageConverter#write  t就是方法的返回值
public final void write(final T t, MediaType contentType, HttpOutputMessage outputMessage) {
    final HttpHeaders headers = outputMessage.getHeaders();
    // 添加默认的头信息
    addDefaultHeaders(headers, t, contentType);
    if (outputMessage instanceof StreamingHttpOutputMessage) {
        StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) outputMessage;
        // 调用FastJsonHttpMessageConverter#writeInternal
        streamingOutputMessage.setBody(outputStream -> writeInternal(t, new HttpOutputMessage() {
            @Override
            public OutputStream getBody() {
                return outputStream;
            }
            @Override
            public HttpHeaders getHeaders() {
                return headers;
            }
        }));
    }
}

最后就是使用一个ByteArrayOutputStream输出流将返回值写到HttpResponse的body中。

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

推荐阅读更多精彩内容