Dubbo源码之Spring整合

本文主要介绍了在不同的配置模式下,dubbo与spring整合的原理,即:xml配置、注解配置、自动化配置 三种模式下的配置生效原理。

XML启动

Schema扩展机制

Spring提供了 Schema 扩展机制,用户可以自定义 Schema 文件,并自定义 Schema 解析器,然后集成到SpringIOC容器中。
创建自定义扩展,主要有以下步骤:

  1. 创建 Schema 文件,描述自定义的合法构建模块,也就是xsd文件,主要用于定义数据约束;
  2. 自定义个处理器类,并实现NamespaceHandler接口,在里面注册各个标签对应的BeanDefinitionParser;
  3. 自定义一个或多个解析器,实现 BeanDefinitionParser 接口,用于定义Bean的解析逻辑;

解析流程

有关于 Spring 对这部分内容的实现细节,可以参考 Schema解析,下面我对这部分内容做一个简单的梳理:

  1. Spring 中对Bean的解析主要是通过 DefaultBeanDefinitionDocumentReader#parseBeanDefinitions 方法,具体的解析逻辑委托给 BeanDefinitionParserDelegate 进行;
  2. DefaultBeanDefinitionDocumentReader#parseBeanDefinitions 会区分 默认的Namespace和自定义的Namesapce(除Spring的一些默认标签外,其它的都是自定义Namespace)
  3. 在解析自定义Namespace的时候会调用 DefaultNamespaceHandlerResolver#resolve 方法, DefaultNamespaceHandlerResolver 中会加载所有 META-INF/spring.handlers 文件里面的内容,然后维护一套 NamespaceURL => NamespaceHandler 的映射关系。然后在 DefaultNamespaceHandlerResolver#resolve 方法中调用 当前NamespaceURL对应的 NamespaceHandler#init 方法。
  4. dubbo对应的 NamespaceHandler 是 DubboNamespaceHandler,在 DubboNamespaceHandler#init 方法中,会找到各个标签对应的 BeanDefinitionParser 接口,这里对应 DubboBeanDefinitionParser 并缓存起来;
  5. 在解析标签的时候会调用 DubboNamespaceHandler#parse 方法,而真正的解析逻辑委托给内部的 DubboBeanDefinitionParser#parse 方法;

补充部分关键代码:

// DefaultBeanDefinitionDocumentReader#parseBeanDefinitions
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
    if (delegate.isDefaultNamespace(root)) {
        NodeList nl = root.getChildNodes();
        for (int i = 0; i < nl.getLength(); i++) {
            Node node = nl.item(i);
            if (node instanceof Element) {
                Element ele = (Element) node;
                if (delegate.isDefaultNamespace(ele)) {
                    // 默认解析
                    parseDefaultElement(ele, delegate);
                }else {
                    // 自定义解析
                    delegate.parseCustomElement(ele);
                }
            }
        }
    }
    else {
        // 自定义解析
        delegate.parseCustomElement(root);
    }
}
// DubboNamespaceHandler.java
// NamespaceHandlerSupport是一个抽象类,实现了NamespaceHandler接口
public class DubboNamespaceHandler extends NamespaceHandlerSupport {

    static {
        Version.checkDuplicate(DubboNamespaceHandler.class);
    }

    @Override
    public void init() {
        registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.class, true));
        registerBeanDefinitionParser("module", new DubboBeanDefinitionParser(ModuleConfig.class, true));
        registerBeanDefinitionParser("registry", new DubboBeanDefinitionParser(RegistryConfig.class, true));
        registerBeanDefinitionParser("monitor", new DubboBeanDefinitionParser(MonitorConfig.class, true));
        registerBeanDefinitionParser("provider", new DubboBeanDefinitionParser(ProviderConfig.class, true));
        registerBeanDefinitionParser("consumer", new DubboBeanDefinitionParser(ConsumerConfig.class, true));
        registerBeanDefinitionParser("protocol", new DubboBeanDefinitionParser(ProtocolConfig.class, true));
        registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));
        registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false));
        registerBeanDefinitionParser("annotation", new AnnotationBeanDefinitionParser());
    }

}

至此,整个dubbo的xml标签解析流程就非常清晰了,如果你想通过XML配置的方式来使用dubbo,那么当你配置好xml之后,随着 Spring 的启动,就会自动解析dubbo对应的那些标签了。

注解启动

注解是为了让我们摆脱繁琐的XML配置,但对代码有一定侵入,高版本的dubbo和springboot整合其实非常方便,引入依赖之后只需要在启动类上添加 @EnableDubbo 注解即可。以 dubbo 2.7.2springboot 2.1.4.RELEASE 为例:

使用示例

依赖

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter'
    implementation 'org.springframework.boot:spring-boot-starter-web'

    implementation 'org.apache.dubbo:dubbo:2.7.2'
    implementation 'org.apache.dubbo:dubbo-registry-zookeeper:2.7.2'
    implementation 'org.apache.dubbo:dubbo-metadata-report-zookeeper:2.7.2'

    implementation 'org.apache.zookeeper:zookeeper:3.4.12'
    implementation 'org.apache.curator:curator-recipes:2.12.0'

    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

启动类


@SpringBootApplication
@EnableDubbo
public class SDubboApplication {

    public static void main(String[] args) {
        SpringApplication.run(SDubboApplication.class, args);
    }

    @Configuration
    @PropertySource("classpath:/dubbo-provider.properties")
    static class ProviderConfiguration {
        @Bean
        public RegistryConfig registryConfig() {
            RegistryConfig registryConfig = new RegistryConfig();
            registryConfig.setAddress("zookeeper://10.9.44.133:2181");

            // 注册简化版的的url到注册中心
            registryConfig.setSimplified(true);
            return registryConfig;
        }

        @Bean
        public MetadataReportConfig metadataReportConfig() {
            MetadataReportConfig metadataReportConfig = new MetadataReportConfig();
            metadataReportConfig.setAddress("zookeeper://10.9.44.133:2181");
            return metadataReportConfig;
        }

        @Bean
        public ConfigCenterConfig configCenterConfig() {
            ConfigCenterConfig configCenterConfig = new ConfigCenterConfig();
            configCenterConfig.setAddress("zookeeper://10.9.44.133:2181");
            return configCenterConfig;
        }
    }

}

dubbo-provider.properties

dubbo.application.name=sdubbo
dubbo.protocol.name=dubbo
dubbo.protocol.port=20882

有关于通过注解定义Provider和Consumer这里就不介绍了。从上面的代码中可以看到,那三个Bean只是一些配置工作,这不是我们关注的重点,重点在 @EnableDubbo 注解,为什么添加这个注解之后dubbo服务就自动注册了?

@EnableDubbo

不妨先看看这个注解,可以发现它引用了 @EnableDubboConfig@DubboComponentScan ,前者与配置相关,后者与 服务注册和服务引用相关。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@EnableDubboConfig
@DubboComponentScan
public @interface EnableDubbo {
......
}

@EnableDubboConfig

这个注解和 外部化配置相关 ,可以参考一篇博文: 外部化配置

即:一些通用的配置信息全部配置在 application.properties 或者 bootstrap.properties 配置文件中,dubbo会根据这些配置信息自动创建 ApplicationConfigRegistryConfigProviderConfig 等Bean,而不需要我们通过注解的方式硬编码去创建。

其核心原理在 DubboConfigConfigurationRegistrar 类中,这个不是本篇文章的重点,不过多介绍。

其实在上面的示例中,就已经用到了外部化配置特性,虽然没有在 application.yaml 中定义dubbo的这些属性,但是在注解类中通过 @PropertySource("classpath:/dubbo-provider.properties") 将这些属性导入进来了,所以dubbo会自动根据这些属性去创建相应的Bean, 比如ApplicationConfig,虽然在示例中没有通过硬编码的方式创建ApplicationConfig,但是dubbo在读到 dubbo-provider.properties 文件中的 dubbo.application 属性时会自动创建一个 ApplicationConfig

外部化配置下,dubbo和springboot整合如下:

依赖

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter'
    implementation 'org.springframework.boot:spring-boot-starter-web'

    implementation 'org.apache.dubbo:dubbo:2.7.2'
    implementation 'org.apache.dubbo:dubbo-registry-zookeeper:2.7.2'
    implementation 'org.apache.dubbo:dubbo-metadata-report-zookeeper:2.7.2'

    implementation 'org.apache.zookeeper:zookeeper:3.4.12'
    implementation 'org.apache.curator:curator-recipes:2.12.0'

    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

application.yml

server:
  port: 8786
spring:
  main:
    allow-bean-definition-overriding: true

dubbo:
  application:
    name: sdubbo
  protocol:
    name: dubbo
    port: 20882
  registry:
    address: zookeeper://10.9.44.133:2181
    simplified: true
  metadata-report:
    address: zookeeper://10.9.44.133:2181
  config-center:
    address: zookeeper://10.9.44.133:2181

启动类

@SpringBootApplication
@EnableDubbo
public class SDubboApplication {
    public static void main(String[] args) {
        SpringApplication.run(SDubboApplication.class, args);
    }
}

@DubboComponentScan

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(DubboComponentScanRegistrar.class)
public @interface DubboComponentScan {
......
}

小扩展

在 Spring 中,通过 @Import 导入一个外部类有三种方式

  1. 直接导入;
@Configuration
@Import(ExternalBean.class)
public class TestImportConfiguration {
}
  1. 导入一个 ImportSelector 接口的实现类,然后重写 selectImports 方法,在该方法中返回要导入类的全类名;
@Configuration
@Import(TestImportSelect.class)
public class TestImportSelectConfiguration {
}

public class TestImportSelect implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{"com.sxy.spring.register.ExternalBean"};
    }
}
  1. 导入一个 ImportBeanDefinitionRegistrar 接口的实现类,然后重写 registerBeanDefinitions 方法,在该方法中通过 BeanDefinitionRegistry 注册 BeanDefinition;
@Configuration
@Import(TestImportBeanDefinitionRegistrar.class)
public class TestImportBeanDefinitionRegistrarCongiguration {
}

public class TestImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        // 注册一个bean, 指定bean name
        registry.registerBeanDefinition("externalBean", new RootBeanDefinition(ExternalBean.class));
    }
}

DubboComponentScanRegistrar

DubboComponentScanRegistrar 实现了 ImportBeanDefinitionRegistrar 接口,而在它重写的 registerBeanDefinitions 方法中做了两件事:

  1. 注册 ServiceAnnotationBeanPostProcessor;
  2. 注册 ReferenceAnnotationBeanPostProcessor;

ServiceAnnotationBeanPostProcessor 实现了 BeanDefinitionRegistryPostProcessor 接口;ReferenceAnnotationBeanPostProcessor 实现了 InstantiationAwareBeanPostProcessorAdapter 接口。 了解Spring的同学都知道这是Spring的扩展接口。

小扩展

  1. BeanFactoryPostProcessor:在实例化bean之前,可以修改BeanDefinition信息;
  2. BeanDefinitionRegistryPostProcessor: BeanFactoryPostProcessor 接口的子类,在BeanFactoryPostProcessor之前执行,可用于创建 BeanDefinition;
  3. BeanPostProcessor: Bean初始化前后执行。
  4. InstantiationAwareBeanPostProcessor:BeanPostProcessor 的子类,实例化前后执行;
  5. ApplicationContextAwareProcessor:实现了BeanPostProcessor,在postProcessBeforeInitialization中注入各种Aware接口;
ServiceAnnotationBeanPostProcessor

以一个dubbo provider为例

@org.apache.dubbo.config.annotation.Service
public class LeannImpl implements ILearn {
    @Override
    public String learn(String name) {
        return "学习: " + name;
    }
}

ServiceAnnotationBeanPostProcessor#postProcessBeanDefinitionRegistry 的方法中,主要做了两件事:

  1. 根据配置的包扫描路径找到所有带有 @org.apache.dubbo.config.annotation.Service 注解的类,为这些类创建 BeanDefinition ,然后注册到IOC容器中;这部分实现隐藏在 DubboClassPathBeanDefinitionScanner#scan 方法中。
  2. 为每个原始类再创建一个 ServiceBean 类型的 BeanDefinition 信息。

即:每一个dubbo服务最终会在IOC容器中对应两个Bean,一个是原始类型,一个是 ServiceBean 类型, ServiceBean 其实是一个 FactoryBean , 是实现服务暴露的关键,这里不展开。
以上面的例子为例,最终两个Bean对应的BeanName分别为:leannImplServiceBean:com.sxy.sdubbo.service.ILearn

ReferenceAnnotationBeanPostProcessor

以一个dubbo consumer为例

@Component("demoServiceComponent")
public class DemoServiceComponent implements DemoService {
    @Reference(timeout = 3000)
    private DemoService demoService;

    @Override
    public String sayHello(String name) {
        return demoService.sayHello(name);
    }
}

ReferenceAnnotationBeanPostProcessor 主要做了两件事:

  1. 创建一个代理服务;
  2. DemoServiceComponent 注入值,即 DemoServiceComponent.demoService = 代理服务;

即: DemoServiceComponent 仅仅代表 Spring 容器中的一个普通Bean;而 @Reference注解标注demoService属性 最终指向的是动态创建的一个代理服务,就是通过这个代理服务实现与provider通信。

至此,注解模式下,dubbo服务注册与引用流程已经很清晰了,具体的实现细节可以查看源码。

自动化配置

自动化配置其实是springboot提供的一个特性,其目的就是尽量让用户原理各种繁琐配置, 其核心原理就是读取 META-INF/spring.factories 中的自动化配置类,下面简单介绍一下。

Spring的自动化配置

启动springboot应用的时候会添加一个 @SpringBootApplication 注解,而该注解中包含了 @EnableAutoConfiguration 注解,而 @EnableAutoConfiguration 就是实现自动化配置的关键

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
......
}

核心就在 AutoConfigurationImportSelector 类,它实现了 ImportSelector 接口,其实主要就做了一件事情:

  1. 通过 SpringFactoriesLoader#loadFactories 方法加载 classpath 下所有 JAR 文件的 META-INF/spring.factories 文件,然后提取出文件中的所有 xxxEnableAutoConfiguration,这样就相当于将所有的 xxxEnableAutoConfiguration 注册到 Spring 容器中了。当然,这些 xxxEnableAutoConfiguration 一般会结合各种 @Conditional 来判断是否创建Bean。

dubbo-spring-boot-starter

springboot 项目就是由一个个 starter 组成的,一个 starter 通常包含了该模块需要的依赖,通常自动化配置也是在 starter 中完成的。

dubbo在springboot应用中的自动化配置也是通过一个 starter 来完成了,官方Git地址:dubbo-spring-boot-project

那么,在自动化配置模式先,dubbo与springboot整合应该怎么做? 可以用外部化配置,也可以用注解的方式来配置,这里以注解的方式配置为例:

依赖

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    
    // 因为目前(2019/07/19)dubbo-spring-boot-starter最新只有2.7.1版本
    implementation 'org.apache.dubbo:dubbo-spring-boot-starter:2.7.1'
    implementation 'org.apache.dubbo:dubbo:2.7.2'
    implementation 'org.apache.dubbo:dubbo-registry-zookeeper:2.7.2'
    implementation 'org.apache.dubbo:dubbo-metadata-report-zookeeper:2.7.2'

    implementation 'org.apache.zookeeper:zookeeper:3.4.12'
    implementation 'org.apache.curator:curator-recipes:2.12.0'

    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

启动类

// @EnableDubbo 不需要加这个注解
@SpringBootApplication
public class SDubboApplication {
    public static void main(String[] args) {
        SpringApplication.run(SDubboApplication.class, args);
    }

    @Configuration
    @PropertySource("classpath:/dubbo-provider.properties")
    static class ProviderConfiguration {
        @Bean
        public RegistryConfig registryConfig() {
            RegistryConfig registryConfig = new RegistryConfig();
            registryConfig.setAddress("zookeeper://10.9.44.133:2181");

            // 注册简化版的的url到注册中心
            registryConfig.setSimplified(true);
            return registryConfig;
        }
        @Bean
        public MetadataReportConfig metadataReportConfig() {
            MetadataReportConfig metadataReportConfig = new MetadataReportConfig();
            metadataReportConfig.setAddress("zookeeper://10.9.44.133:2181");
            return metadataReportConfig;
        }
        @Bean
        public ConfigCenterConfig configCenterConfig() {
            ConfigCenterConfig configCenterConfig = new ConfigCenterConfig();
            configCenterConfig.setAddress("zookeeper://10.9.44.133:2181");
            return configCenterConfig;
        }
    }
}

dubbo-provider.properties

dubbo.application.name=sdubbo
dubbo.protocol.name=dubbo
dubbo.protocol.port=20882
# 多了一个包扫描,在当前 starter 版本中,这个属性必须配置
dubbo.scan.base-packages =com.sxy.sdubbo

与注解方式的区别:

  1. 添加 dubbo-spring-boot-starter 依赖;
  2. 不需要配置 @EnableDubbo 注解;
  3. 需要配置包扫描路径 dubbo.scan.base-packages

其实现原理就是springboot中的自动化配置:
dubbo-spring-boot-starter 的pom.xml文件中引入了 dubbo-spring-boot-autoconfigure 模块, dubbo-spring-boot-autoconfigure 的pom.xml文件中引入了 dubbo-spring-boot-autoconfigure-compatible 模块;

  1. dubbo-spring-boot-autoconfigure 模块中,META-INF/spring.factories 文件内容如下:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.apache.dubbo.spring.boot.autoconfigure.DubboRelaxedBinding2AutoConfiguration
  1. dubbo-spring-boot-autoconfigure-compatible 模块中,META-INF/spring.factories 文件内容如下:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.apache.dubbo.spring.boot.autoconfigure.DubboAutoConfiguration,\
org.apache.dubbo.spring.boot.autoconfigure.DubboRelaxedBindingAutoConfiguration
org.springframework.context.ApplicationListener=\
org.apache.dubbo.spring.boot.context.event.OverrideDubboConfigApplicationListener,\
org.apache.dubbo.spring.boot.context.event.WelcomeLogoApplicationListener,\
org.apache.dubbo.spring.boot.context.event.AwaitingNonWebApplicationListener
org.springframework.boot.env.EnvironmentPostProcessor=\
org.apache.dubbo.spring.boot.env.DubboDefaultPropertiesEnvironmentPostProcessor
org.springframework.context.ApplicationContextInitializer=\
org.apache.dubbo.spring.boot.context.DubboApplicationContextInitializer

DubboRelaxedBindingAutoConfiguration 主要是和属性解析有关,这里不做介绍;核心还是 DubboAutoConfiguration , 在该类中有一下两段关键代码

/**
 * @ConditionalOnProperty(prefix = DUBBO_SCAN_PREFIX, name = BASE_PACKAGES_PROPERTY_NAME) 强制要求了我们需要配置包扫描路径,否则该Bean不会被创建
 * Creates {@link ServiceAnnotationBeanPostProcessor} Bean
 *
 * @param propertyResolver {@link PropertyResolver} Bean
 * @return {@link ServiceAnnotationBeanPostProcessor}
 */
@ConditionalOnProperty(prefix = DUBBO_SCAN_PREFIX, name = BASE_PACKAGES_PROPERTY_NAME)
@ConditionalOnBean(name = BASE_PACKAGES_PROPERTY_RESOLVER_BEAN_NAME)
@Bean
public ServiceAnnotationBeanPostProcessor serviceAnnotationBeanPostProcessor(
        @Qualifier(BASE_PACKAGES_PROPERTY_RESOLVER_BEAN_NAME) PropertyResolver propertyResolver) {
    Set<String> packagesToScan = propertyResolver.getProperty(BASE_PACKAGES_PROPERTY_NAME, Set.class, emptySet());
    return new ServiceAnnotationBeanPostProcessor(packagesToScan);
}

/**
 * Creates {@link ReferenceAnnotationBeanPostProcessor} Bean if Absent bean工厂不存在referenceAnnotationBeanPostProcessor时创建
 *
 * @return {@link ReferenceAnnotationBeanPostProcessor}
 */
@ConditionalOnMissingBean
@Bean(name = ReferenceAnnotationBeanPostProcessor.BEAN_NAME)
public ReferenceAnnotationBeanPostProcessor referenceAnnotationBeanPostProcessor() {
    return new ReferenceAnnotationBeanPostProcessor();
}

其实核心就是通过自动化的方式创建了 ServiceAnnotationBeanPostProcessorReferenceAnnotationBeanPostProcessor,没有通过 @EnableDubbo 注解触发。但是感觉这样不太好用。

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

推荐阅读更多精彩内容