Springboot-starter-xxx原理解析

前言

首先我們來看一個(gè)redis整合springboot最簡(jiǎn)單的例子,主要包括pom依賴疼蛾、配置文件和使用示例三個(gè)部分黍判。

pom依賴
    
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>

配置參數(shù)
spring:
  redis:
    password: redis
    timeout: 2000ms
    cluster:
      max-redirects: 5 #獲取失敗時(shí)最大重定向次數(shù)
      nodes:
      - 172.28.19.80:6380
      - 172.28.19.80:6381
      - 172.28.19.85:6380
      - 172.28.19.85:6381
      - 172.28.19.89:6380
      - 172.28.19.89:6381
    lettuce:
      pool:
        max-active: 50 #連接池最大連接數(shù)(使用負(fù)值表示沒有限制)
        max-idle: 10 #連接池中的最大空閑連接
        max-wait: 1000ms #連接池最大阻塞等待時(shí)間(使用負(fù)值表示沒有限制)
        min-idle: 5 #連接池中的最小空閑連接
代碼示例
@Slf4j
@RestController
public class TestController {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @GetMapping("/set")
    public void set(){
        redisTemplate.opsForValue().set("test.goods","電視");
    }

    @GetMapping("/get")
    public String get(){
        String result = redisTemplate.opsForValue().get("test.goods");
        return result;
    }

}

依賴分析

為了分析spring-boot-starter的工作原理是钥,我們首先來看一下spring-boot-starter-data-redis的定義

  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starters</artifactId>
    <version>2.1.3.RELEASE</version>
  </parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-redis</artifactId>
  <version>2.1.3.RELEASE</version>
                  ******
                  
                省略部分內(nèi)容
              
                  ******
  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter</artifactId>
      <version>2.1.3.RELEASE</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework.data</groupId>
      <artifactId>spring-data-redis</artifactId>
      <version>2.1.5.RELEASE</version>
      <scope>compile</scope>
      <exclusions>
        <exclusion>
          <artifactId>jcl-over-slf4j</artifactId>
          <groupId>org.slf4j</groupId>
        </exclusion>
      </exclusions>
    </dependency>
    <dependency>
      <groupId>io.lettuce</groupId>
      <artifactId>lettuce-core</artifactId>
      <version>5.1.4.RELEASE</version>
      <scope>compile</scope>
    </dependency>
  </dependencies>
</project>

這里比較重要的內(nèi)容其實(shí)都在spring-boot-starters中了,來看一下其內(nèi)容

                  ******
                  
                省略部分內(nèi)容
              
                  ******
  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot</artifactId>
      <version>2.1.3.RELEASE</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-autoconfigure</artifactId>
      <version>2.1.3.RELEASE</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-logging</artifactId>
      <version>2.1.3.RELEASE</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>javax.annotation</groupId>
      <artifactId>javax.annotation-api</artifactId>
      <version>1.3.2</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
      <version>5.1.5.RELEASE</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>org.yaml</groupId>
      <artifactId>snakeyaml</artifactId>
      <version>1.23</version>
      <scope>runtime</scope>
    </dependency>
  </dependencies>
</project>

包加載機(jī)制

這里最核心的部分是引入了spring-boot-autoconfigure包掀亩,那么這個(gè)包里的內(nèi)容又是如何被加載的呢,這里就要回到我之前的文章《Springboot初始化流程解析》留下的伏筆欢顷,文章中說到springboot初始化時(shí)會(huì)調(diào)用到AbstractApplicationContext類的refresh()方法槽棍,其中有許多用于刷新的方法,而invokeBeanFactoryPostProcessors(beanFactory)方法就是我們要找的加載入口

    protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
        PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());

        // Detect a LoadTimeWeaver and prepare for weaving, if found in the meantime
        // (e.g. through an @Bean method registered by ConfigurationClassPostProcessor)
        if (beanFactory.getTempClassLoader() == null && beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
            beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
            beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
        }
    }

由于這個(gè)調(diào)用鏈比較長(zhǎng)抬驴,我們簡(jiǎn)單梳理一下這個(gè)調(diào)用流程炼七,首先調(diào)用到PostProcessorRegistrationDelegate類的invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors)方法,其部分代碼片段如下

            for (BeanFactoryPostProcessor postProcessor : beanFactoryPostProcessors) {
                if (postProcessor instanceof BeanDefinitionRegistryPostProcessor) {
                    BeanDefinitionRegistryPostProcessor registryProcessor =
                            (BeanDefinitionRegistryPostProcessor) postProcessor;
                    registryProcessor.postProcessBeanDefinitionRegistry(registry);
                    registryProcessors.add(registryProcessor);
                }
                else {
                    regularPostProcessors.add(postProcessor);
                }
            }

通過循環(huán)的方式調(diào)用了registryProcessor.postProcessBeanDefinitionRegistry(registry)并將registryProcessor對(duì)象依次加入registryProcessors這個(gè)list中布持,這里的registryProcessors的實(shí)現(xiàn)類為ConfigurationClassPostProcessor豌拙,調(diào)用其postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)方法

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
        int registryId = System.identityHashCode(registry);
        if (this.registriesPostProcessed.contains(registryId)) {
            throw new IllegalStateException(
                    "postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);
        }
        if (this.factoriesPostProcessed.contains(registryId)) {
            throw new IllegalStateException(
                    "postProcessBeanFactory already called on this post-processor against " + registry);
        }
        this.registriesPostProcessed.add(registryId);

        processConfigBeanDefinitions(registry);
    }

關(guān)聯(lián)調(diào)用processConfigBeanDefinitions(BeanDefinitionRegistry registry)方法

    public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
    
                          ******
                          
            // Parse each @Configuration class
            ConfigurationClassParser parser = new ConfigurationClassParser(
                    this.metadataReaderFactory, this.problemReporter, this.environment,
                    this.resourceLoader, this.componentScanBeanNameGenerator, registry);
    
            Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
            Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
        
            do {
            parser.parse(candidates);
            parser.validate();

            Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
            configClasses.removeAll(alreadyParsed);

            // Read the model and create bean definitions based on its content
            if (this.reader == null) {
                this.reader = new ConfigurationClassBeanDefinitionReader(
                        registry, this.sourceExtractor, this.resourceLoader, this.environment,
                        this.importBeanNameGenerator, parser.getImportRegistry());
            }
            this.reader.loadBeanDefinitions(configClasses);
            alreadyParsed.addAll(configClasses);

            candidates.clear();
            if (registry.getBeanDefinitionCount() > candidateNames.length) {
                String[] newCandidateNames = registry.getBeanDefinitionNames();
                Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));
                Set<String> alreadyParsedClasses = new HashSet<>();
                for (ConfigurationClass configurationClass : alreadyParsed) {
                    alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
                }
                for (String candidateName : newCandidateNames) {
                    if (!oldCandidateNames.contains(candidateName)) {
                        BeanDefinition bd = registry.getBeanDefinition(candidateName);
                        if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
                                !alreadyParsedClasses.contains(bd.getBeanClassName())) {
                            candidates.add(new BeanDefinitionHolder(bd, candidateName));
                        }
                    }
                }
                candidateNames = newCandidateNames;
            }
        }
        while (!candidates.isEmpty());
        
                          ******
    }

這里循環(huán)調(diào)用ConfigurationClassParser類的parse(Set<BeanDefinitionHolder> configCandidates)方法

    public void parse(Set<BeanDefinitionHolder> configCandidates) {

                         ******

        this.deferredImportSelectorHandler.process();
    }
        public void process() {
            List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
            this.deferredImportSelectors = null;
            try {
                if (deferredImports != null) {
                    DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
                    deferredImports.sort(DEFERRED_IMPORT_COMPARATOR);
                    deferredImports.forEach(handler::register);
                    handler.processGroupImports();
                }
            }
            finally {
                this.deferredImportSelectors = new ArrayList<>();
            }
        }

這里的handler對(duì)象是ConfigurationClassParser的內(nèi)部類DeferredImportSelectorGroupingHandler的實(shí)例,會(huì)調(diào)用其對(duì)應(yīng)的方法processGroupImports()

        public void processGroupImports() {
            for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
                grouping.getImports().forEach(entry -> {
                    ConfigurationClass configurationClass = this.configurationClasses.get(
                            entry.getMetadata());
                    try {
                        processImports(configurationClass, asSourceClass(configurationClass),
                                asSourceClasses(entry.getImportClassName()), false);
                    }
                    catch (BeanDefinitionStoreException ex) {
                        throw ex;
                    }
                    catch (Throwable ex) {
                        throw new BeanDefinitionStoreException(
                                "Failed to process import candidates for configuration class [" +
                                        configurationClass.getMetadata().getClassName() + "]", ex);
                    }
                });
            }
        }

改方法中的groupingConfigurationClassParser類的內(nèi)部類DeferredImportSelectorGrouping的實(shí)例题暖,因此會(huì)調(diào)用其對(duì)應(yīng)的getImports()方法

        public Iterable<Group.Entry> getImports() {
            for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
                this.group.process(deferredImport.getConfigurationClass().getMetadata(),
                        deferredImport.getImportSelector());
            }
            return this.group.selectImports();
        }

當(dāng)這里的groupAutoConfigurationImportSelector類的內(nèi)部類AutoConfigurationGroup的實(shí)例時(shí)按傅,process方法的調(diào)用如下

        @Override
        public void process(AnnotationMetadata annotationMetadata,
                DeferredImportSelector deferredImportSelector) {
            Assert.state(
                    deferredImportSelector instanceof AutoConfigurationImportSelector,
                    () -> String.format("Only %s implementations are supported, got %s",
                            AutoConfigurationImportSelector.class.getSimpleName(),
                            deferredImportSelector.getClass().getName()));
            AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
                    .getAutoConfigurationEntry(getAutoConfigurationMetadata(),
                            annotationMetadata);
            this.autoConfigurationEntries.add(autoConfigurationEntry);
            for (String importClassName : autoConfigurationEntry.getConfigurations()) {
                this.entries.putIfAbsent(importClassName, annotationMetadata);
            }
        }

然后調(diào)用到AutoConfigurationImportSelectorgetAutoConfigurationEntry方法

    protected AutoConfigurationEntry getAutoConfigurationEntry(
            AutoConfigurationMetadata autoConfigurationMetadata,
            AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        }
        AnnotationAttributes attributes = getAttributes(annotationMetadata);
        List<String> configurations = getCandidateConfigurations(annotationMetadata,
                attributes);
        configurations = removeDuplicates(configurations);
        Set<String> exclusions = getExclusions(annotationMetadata, attributes);
        checkExcludedClasses(configurations, exclusions);
        configurations.removeAll(exclusions);
        configurations = filter(configurations, autoConfigurationMetadata);
        fireAutoConfigurationImportEvents(configurations, exclusions);
        return new AutoConfigurationEntry(configurations, exclusions);
    }

接著調(diào)用了getCandidateConfigurations方法

    protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
            AnnotationAttributes attributes) {
        List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
                getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
        Assert.notEmpty(configurations,
                "No auto configuration classes found in META-INF/spring.factories. If you "
                        + "are using a custom packaging, make sure that file is correct.");
        return configurations;
    }

到這里我們看到了一個(gè)非常熟悉的方法SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()),在之前的文章《Springboot初始化流程解析》中有分析過胧卤,loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader)方法會(huì)掃描所有jar包下的META-INF/spring.factories文件唯绍,并將其中的類加載到內(nèi)存中,而這里的getSpringFactoriesLoaderFactoryClass()方法返回的是EnableAutoConfiguration.class灌侣,因此可以推斷,此處會(huì)加載spring.factories文件中EnableAutoConfiguration下對(duì)應(yīng)的類裂问。

配置類加載

經(jīng)過上述分析侧啼,我們直接找到spring-boot-autoconfigure包下的META-INF/spring.factories文件來看看

                  ******
                  
                省略部分內(nèi)容
              
                  ******
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.cloud.CloudServiceConnectorsAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\
org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration,\
                  
                  ******

可以看到在EnableAutoConfiguration下springboot官方已經(jīng)預(yù)先定義了許多的Configuration,其中有3個(gè)是redis打頭的堪簿,我們以其中最重要的RedisAutoConfiguration為例來進(jìn)行分析痊乾。

@Configuration
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean(name = "redisTemplate")
    public RedisTemplate<Object, Object> redisTemplate(
            RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

    @Bean
    @ConditionalOnMissingBean
    public StringRedisTemplate stringRedisTemplate(
            RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

}

這里用到了條件注解@ConditionalOnClass@ConditionalOnMissingBean,那就一起羅列一下幾個(gè)常用的條件注解:

  • @ConditionalOnBean:僅在當(dāng)前spring上下文中存在參數(shù)中的bean時(shí)椭更,才會(huì)實(shí)例化被注解的Bean

  • @ConditionalOnClass:參數(shù)中的class位于classpath上哪审,才會(huì)實(shí)例化被注解的Bean

  • @ConditionalOnExpression:當(dāng)表達(dá)式為true的時(shí)候,才會(huì)實(shí)例化被注解的Bean

  • @ConditionalOnMissingBean:僅在當(dāng)前spring上下文中不存在參數(shù)中的bean時(shí)虑瀑,才會(huì)實(shí)例化被注解的Bean

  • @ConditionalOnMissingClass:參數(shù)中的class不存在于classpath上湿滓,才會(huì)實(shí)例化被注解的Bean

  • @ConditionalOnNotWebApplication:不是web應(yīng)用時(shí)才會(huì)實(shí)例化被注解的Bean

  • @AutoConfigureAfter:在參數(shù)中的bean完成自動(dòng)配置后實(shí)例化被注解的bean

  • @AutoConfigureBefore:在參數(shù)中的bean完成自動(dòng)配置前實(shí)例化被注解的bean

除了上述條件注解外,還要提一下@EnableConfigurationProperties(RedisOperations.class)舌狗,該用法通常說明參數(shù)bean(這里指RedisOperations)上標(biāo)有@ConfigurationProperties注解叽奥,而@EnableConfigurationProperties能使該注解生效,從而將對(duì)應(yīng)的配置文件轉(zhuǎn)化為參數(shù)bean對(duì)象(這里指RedisOperations)痛侍。

根據(jù)上述說明不難看出朝氓,類RedisAutoConfiguration的加載依賴于RedisOperations類,而這個(gè)類位于spring-data-redis包中,由spring-boot-starter-data-redis包依賴引入赵哲,所以待德,雖然RedisAutoConfiguration類可能會(huì)由于spring-boot-autoconfigure包被依賴而被提前引入,但只有依賴了spring-boot-starter-data-redis包后它才會(huì)被真正初始化枫夺。

注:這里有一點(diǎn)要說明的是将宪,RedisOperations類是redis的基本參數(shù)類,其中有一些屬性是帶有默認(rèn)值的筷屡,這些值就允許你不進(jìn)行設(shè)置涧偷,這也就是通常所說的springboot約定大于配置特點(diǎn)的一個(gè)體現(xiàn)。

總結(jié)

本文根據(jù)redis整合springboot的簡(jiǎn)單例子分析了Springboot-starter-xxx的一般工作原理毙死,說明了springboot啟動(dòng)過程中操作redis的客戶端是如何根據(jù)pom文件中的依賴被實(shí)例化出來的燎潮。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市扼倘,隨后出現(xiàn)的幾起案子确封,更是在濱河造成了極大的恐慌,老刑警劉巖再菊,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件爪喘,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡纠拔,警方通過查閱死者的電腦和手機(jī)秉剑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來稠诲,“玉大人侦鹏,你說我怎么就攤上這事⊥涡穑” “怎么了略水?”我有些...
    開封第一講書人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)劝萤。 經(jīng)常有香客問我渊涝,道長(zhǎng),這世上最難降的妖魔是什么床嫌? 我笑而不...
    開封第一講書人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任跨释,我火速辦了婚禮,結(jié)果婚禮上厌处,老公的妹妹穿的比我還像新娘煤傍。我一直安慰自己,他們只是感情好嘱蛋,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開白布蚯姆。 她就那樣靜靜地躺著五续,像睡著了一般。 火紅的嫁衣襯著肌膚如雪龄恋。 梳的紋絲不亂的頭發(fā)上疙驾,一...
    開封第一講書人閱讀 51,631評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音郭毕,去河邊找鬼它碎。 笑死,一個(gè)胖子當(dāng)著我的面吹牛显押,可吹牛的內(nèi)容都是我干的扳肛。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼乘碑,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼挖息!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起兽肤,我...
    開封第一講書人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤套腹,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后资铡,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體电禀,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年笤休,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了尖飞。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡店雅,死狀恐怖政基,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情底洗,我是刑警寧澤腋么,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布咕娄,位于F島的核電站亥揖,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏圣勒。R本人自食惡果不足惜费变,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望圣贸。 院中可真熱鬧挚歧,春花似錦、人聲如沸吁峻。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至矮慕,卻和暖如春帮匾,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背痴鳄。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工瘟斜, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人痪寻。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓螺句,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親橡类。 傳聞我的和親對(duì)象是個(gè)殘疾皇子蛇尚,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容