SpringBoot(二)自動裝配正文 - @SpringBootApplication、@EnableAutoConfiguration

前言

????????最近在學(xué)習(xí)Spring Boot相關(guān)的課程擅编,過程中以筆記的形式記錄下來攀细,方便以后回憶,同時也在這里和大家探討探討爱态,文章中有漏的或者有補(bǔ)充的谭贪、錯誤的都希望大家能夠及時提出來,本人在此先謝謝了锦担!

開始之前呢俭识,希望大家?guī)е鴰讉€問題去學(xué)習(xí):
1、Spring Boot 自動裝配是什么洞渔?
2套媚、這個功能在什么時代背景下發(fā)明產(chǎn)生的缚态?
3、這個功能有什么用堤瘤?
4玫芦、怎么實(shí)現(xiàn)的?
5宙橱、優(yōu)點(diǎn)和缺點(diǎn)是什么姨俩?
6、這個功能能應(yīng)用在工作中师郑?
這是對自我的提問,我認(rèn)為帶著問題去學(xué)習(xí)调窍,是一種更好的學(xué)習(xí)方式宝冕,有利于加深理解。好了邓萨,接下來進(jìn)入主題地梨。

(一)起源

????????在上篇文章中我們講到 Spring 注解雖然可以代替以往XML的形式,幫助我們自動注冊Bean以及初始化組件缔恳,簡化我們的開發(fā)宝剖,但還是做不到真正意義上的自動裝配,今天我們就來講講 Spring Boot 是如何深度整合 Spring 注解編程模型歉甚、@Enable 模塊驅(qū)動及條件裝配等 Spring 原生特性來實(shí)現(xiàn)自動裝配的万细。

注:本篇文章所用到的 Spring Boot版本是 2.1.6.BUILD-SNAPSHOT

(二)Spring Boot 自動裝配實(shí)現(xiàn)

????????我們都知道 Spring Boot 的啟動過程非常簡單,只需要啟動一個 main 方法纸泄,項(xiàng)目就可以運(yùn)行赖钞,就算依賴了諸多外部模塊如:MVC、Redis等聘裁,也不需要我們進(jìn)行過多的配置雪营,那它的底層原理是什么呢?接下來衡便,我們就一起去看一看献起。

我們先來看一段 Spring Boot 的啟動類代碼:

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

我們需要關(guān)注的是 @SpringBootApplication 這個注解:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {

    @AliasFor(annotation = EnableAutoConfiguration.class)
    Class<?>[] exclude() default {};
    
    @AliasFor(annotation = EnableAutoConfiguration.class)
    String[] excludeName() default {};

    @AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
    String[] scanBasePackages() default {};

    @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
    Class<?>[] scanBasePackageClasses() default {};

}

我們來看一看它的組成部分:

  • @SpringBootConfiguration:它里面標(biāo)注了 @Configuration 注解,上篇文章說過镣陕,表明這是個配置類谴餐,功能與 @Configuration 無異。
  • @EnableAutoConfiguration:這個就是實(shí)現(xiàn)自動裝配的核心注解茁彭,是用來激活自動裝配的总寒,其中默認(rèn)路徑掃描以及組件裝配、排除等都通過它來實(shí)現(xiàn)理肺。
  • @ComponentScan:上篇文章我們講過這是用來掃描被 @Component標(biāo)注的類 摄闸,只不過這里是用來過濾 Bean 的善镰,指定哪些類不進(jìn)行掃描,而且用的是自定義規(guī)則。
  • Class<?>[] exclude():根據(jù)class來排除套才,排除指定的類加入spring容器蚓炬,傳入的類型是class類型。且繼承自 @EnableAutoConfiguration 中的屬性品洛。
  • String[] excludeName():根據(jù)class name來排除,排除特定的類加入spring容器摩桶,參數(shù)類型是class的全類名字符串?dāng)?shù)組桥状。同樣繼承自 @EnableAutoConfiguration
  • String[] scanBasePackages():可以指定多個包名進(jìn)行掃描硝清。繼承自 @ComponentScan 辅斟。
  • Class<?>[] scanBasePackageClasses():可以指定多個類或接口的class,然后掃描 class 所在包下的所有組件芦拿。同樣繼承自 @ComponentScan 士飒。

1、@EnableAutoConfiguration 實(shí)現(xiàn)

????????上面我們說到 @EnableAutoConfiguration 是實(shí)現(xiàn)自動裝配的核心注解蔗崎,是用來激活自動裝配的酵幕,看注解前綴我們應(yīng)該知道是上篇文章中所講的 Spring @Enable 模塊驅(qū)動的設(shè)計(jì)模式,所以它必然會有 @Import 導(dǎo)入的被 @Configuration 標(biāo)注的類或?qū)崿F(xiàn) ImportSelectorImportBeanDefinitionRegistrar 接口的類缓苛。接著芳撒,我們來看看它的定義:

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

    ...
}

可以看到它由兩部分組成:

  • @AutoConfigurationPackage:這是用來將啟動類所在包,以及下面所有子包里面的所有組件掃描到Spring容器中他嫡,這里的組件是指被 @Component或其派生注解標(biāo)注的類番官。這也就是為什么不用標(biāo)注@ComponentScan的原因。
  • @Import(AutoConfigurationImportSelector.class):這里導(dǎo)入的是實(shí)現(xiàn)了 ImportSelector 接口的類钢属,組件自動裝配的邏輯均在重寫的 selectImports 方法中實(shí)現(xiàn)徘熔。

接下來我們就來看看這兩者具體是怎么實(shí)現(xiàn)的。

1.1淆党、獲取默認(rèn)包掃描路徑

我們先來看看 Spring Boot 是如何通過 @AutoConfigurationPackage 注解獲取默認(rèn)包掃描路徑的酷师,進(jìn)入它的實(shí)現(xiàn):

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

可以看到它是通過 @Import 導(dǎo)入了 AutoConfigurationPackages.Registrar 類,該類實(shí)現(xiàn)了 ImportBeanDefinitionRegistrar 接口染乌,所以按照上篇文章所講的山孔,它是在重寫的方法中直接注冊相關(guān)組件。繼續(xù)往下:

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

        @Override
        public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
            register(registry, new PackageImport(metadata).getPackageName());
        }

        ....
    }
private static final class PackageImport {

        private final String packageName;

        PackageImport(AnnotationMetadata metadata) {
            this.packageName = ClassUtils.getPackageName(metadata.getClassName());
        }
        ....
}

這里主要是通過 metadata 元數(shù)據(jù)信息構(gòu)造 PackageImport 類荷憋。先獲取啟動類的類名台颠,再通過 ClassUtils.getPackageName 獲取啟動類所在的包名。我們接著往下看:

public static void register(BeanDefinitionRegistry registry, String... packageNames) {
        if (registry.containsBeanDefinition(BEAN)) {
            BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
            ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues();
            constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames));
        }
        else {
            GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
            beanDefinition.setBeanClass(BasePackages.class);
            beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames);
            beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
            registry.registerBeanDefinition(BEAN, beanDefinition);
        }
    }

最后就是將這個包名保存至 BasePackages 類中,然后通過 BeanDefinitionRegistry 將其注冊串前,進(jìn)行后續(xù)處理瘫里,至此該流程結(jié)束。

1.2荡碾、獲取自動裝配的組件

該部分就是實(shí)現(xiàn)自動裝配的入口谨读,從上面得知這里也是通過 @Import 來實(shí)現(xiàn)的,來看看導(dǎo)入的類:

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
        ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {

    ....
    
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        }
        AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
                .loadMetadata(this.beanClassLoader);
        AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
                annotationMetadata);
        return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
    }
    
    ....
}

主要關(guān)注重寫的 selectImports 方法坛吁,其中 AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader)劳殖;是加載自動裝配的元信息。而AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata)該方法返回的就是自動裝配的組件拨脉,我們進(jìn)去看看:

protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
            AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    }
    
    // 獲取 @EnableAutoConfigoration 標(biāo)注類的元信息哆姻,也就是獲取該注解 exclude 和 excludeName 屬性值
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
    
    // 該方法就是獲取自動裝配的類名集合
    List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
    
    // 去除重復(fù)的自動裝配組件,就是將List轉(zhuǎn)為Set進(jìn)行去重
    configurations = removeDuplicates(configurations);
    
    // 這部分就是根據(jù)上面獲取的 exclude 及 excludeName 屬性值女坑,排除指定的類
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    checkExcludedClasses(configurations, exclusions);
    configurations.removeAll(exclusions);
    
    // 這里是過濾那些依賴不滿足的自動裝配 Class
    configurations = filter(configurations, autoConfigurationMetadata);
    fireAutoConfigurationImportEvents(configurations, exclusions);
    
    // 返回的就是經(jīng)過一系列去重填具、排除、過濾等操作后的自動裝配組件
    return new AutoConfigurationEntry(configurations, exclusions);
}

該方法中就是先獲取待自動裝配組件的類名集合匆骗,然后通過一些列的去重、排除誉简、過濾碉就,最終返回自動裝配的類名集合。主要關(guān)注 getCandidateConfigurations(annotationMetadata, attributes) 這個方法闷串,里面是如何獲取自動裝配的類名集合:

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;
}

其中getSpringFactoriesLoaderFactoryClass()返回的是EnableAutoConfiguration.class瓮钥。
繼續(xù)往下,執(zhí)行的是 SpringFactoriesLoader#loadFactoryNames 方法:

public final class SpringFactoriesLoader {

    ...
    
    public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
    
    public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
    
        // 前面可以看到烹吵,這里的 factoryClass 是 EnableAutoConfiguration.class
        String factoryClassName = factoryClass.getName();
        return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
    }

    private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
        MultiValueMap<String, String> result = cache.get(classLoader);
        if (result != null) {
            return result;
        }

        try {
            Enumeration<URL> urls = (classLoader != null ?
                    classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                    ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
            result = new LinkedMultiValueMap<>();
            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                UrlResource resource = new UrlResource(url);
                Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                for (Map.Entry<?, ?> entry : properties.entrySet()) {
                    String factoryClassName = ((String) entry.getKey()).trim();
                    for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
                        result.add(factoryClassName, factoryName.trim());
                    }
                }
            }
            cache.put(classLoader, result);
            return result;
        }
        catch (IOException ex) {
            throw new IllegalArgumentException("Unable to load factories from location [" +
                    FACTORIES_RESOURCE_LOCATION + "]", ex);
        }
    }
    ...
}

最終的實(shí)現(xiàn)邏輯都在這里碉熄,主要過程如下:

(1)搜索classpath路徑下以及所有外部jar包下的META-INF文件夾中的spring.factories文件。主要是spring-boot-autoconfigur包下的

# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer

# Auto Configuration Import Listeners
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener

# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
org.springframework.boot.autoconfigure.condition.OnClassCondition,\
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration

...

可以看到其中內(nèi)容肋拔,存儲的是key-value格式的數(shù)據(jù)锈津,且key是一個類的全路徑名稱,value是多個類的全路徑名稱琼梆,且以逗號分割。

(2)將所有的spring.factories文件轉(zhuǎn)成Properties格式窿吩,將里面key-value格式的數(shù)據(jù)轉(zhuǎn)成Map茎杂,該Map的value是一個List,之后將相同Key的value合并到List中纫雁,將該Map作為方法返回值返回煌往。

(3)返回到 loadFactoryNames 方法,通過上面得知factoryClassName的值為EnableAutoConfiguration轧邪,所以通過 getOrDefault(factoryClassName, Collections.emptyList())方法刽脖,獲取 key 為EnableAutoConfiguration的類名集合羞海。

ps:getOrDefault第一個入?yún)⑹莐ey的name,如果key不存在曾棕,則直接返回第二個參數(shù)值

至此扣猫,流程結(jié)束,最后返回的就是自動裝配的組件翘地,可以看到一個特點(diǎn)申尤,這些自動裝配的組件都是以 AutoConfiguration 結(jié)尾。但該組件列表只是候選組件衙耕,因?yàn)楹竺孢€有去重昧穿、排除、過濾等一系列操作橙喘,這里就不再詳細(xì)述說时鸵。下面我們來看看自動裝配的組件內(nèi)部是怎么樣的。

2厅瞎、自動裝配的組件內(nèi)部實(shí)現(xiàn)

就拿比較熟悉的 Web MVC 來看饰潜,看看是如何實(shí)現(xiàn) Web MVC 自動裝配的。先來代碼組成部分:

@Configuration
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
        ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
    ...
    @Configuration
    @Import(EnableWebMvcConfiguration.class)
    @EnableConfigurationProperties({WebMvcProperties.class, ResourceProperties.class})
    @Order(0)
    public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer, ResourceLoaderAware {
        ...
        @Bean
        @ConditionalOnBean(View.class)
        @ConditionalOnMissingBean
        public BeanNameViewResolver beanNameViewResolver() {
            ...
        }
        ...
    }

    @Configuration
    public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration {

        @Bean
        @Override
        public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
            ...
        }

        @Bean
        @Primary
        @Override
        public RequestMappingHandlerMapping requestMappingHandlerMapping() {
            ...
        }
    }
    ...
}
  • 注解部分:

    • @Configuration:這個大家都比較熟悉和簸,標(biāo)識該類是一個配置類
    • @ConditionalXXX:這是上篇文章所講的 Spring 條件裝配彭雾,只不過經(jīng)由 Spring Boot 擴(kuò)展形成了自己的條件化自動裝配,且都是@Conditional 的派生注解锁保。
      • @ConditionalOnWebApplication:參數(shù)值是 Type 類型的枚舉薯酝,當(dāng)前項(xiàng)目類型是任意、Web爽柒、Reactive其中之一則實(shí)例化該 Bean吴菠。這里指定如果為 Web 項(xiàng)目才滿足條件。
      • @ConditionalOnClass:參數(shù)是 Class 數(shù)組浩村,當(dāng)給定的類名在類路徑上存在做葵,則實(shí)例化當(dāng)前Bean。這里當(dāng)Servlet.class穴亏、 DispatcherServlet.class蜂挪、 WebMvcConfigurer.class存在才滿足條件。
      • @ConditionalOnMissingBean:參數(shù)是也是 Class 數(shù)組嗓化,當(dāng)給定的類沒有實(shí)例化時棠涮,則實(shí)例化當(dāng)前Bean。這里指定當(dāng) WebMvcConfigurationSupport 該類沒有實(shí)例化時刺覆,才滿足條件严肪。
    • 裝配順序
      • @AutoConfigureOrder:參數(shù)是int類型的數(shù)值,數(shù)越小越先初始化。
      • @AutoConfigureAfter:參數(shù)是 Class 數(shù)組驳糯,在指定的配置類初始化后再加載篇梭。
      • @AutoConfigureBefore:參數(shù)同樣是 Class 數(shù)組,在指定的配置類初始化前加載酝枢。
  • 代碼部分:

    • 這部分就比較直接了恬偷,實(shí)例化了和 Web MVC 相關(guān)的Bean,如 HandlerAdapter帘睦、HandlerMapping袍患、ViewResolver等。其中竣付,出現(xiàn)了 DelegatingWebMvcConfiguration 類诡延,這是上篇文章所講的 @EnableWebMvc@Import導(dǎo)入的配置類。

可以看到古胆,在Spring Boot 自動裝配的類中肆良,經(jīng)過了一系列的 @Conditional 條件判斷,然后實(shí)例化某個模塊需要的Bean逸绎,且無需我們配置任何東西惹恃,當(dāng)然,這都是默認(rèn)實(shí)現(xiàn)棺牧,當(dāng)這些不滿足我們的要求時座舍,我們還得手動操作。

(三)總結(jié)

????????關(guān)于Spring boot自動裝配的內(nèi)容就告一段落陨帆,不難看出Spring Boot自動裝配所依賴的注解驅(qū)動、@Enable模塊驅(qū)動采蚀、條件裝配等特性均來自 Spring Framework疲牵。而自動裝配的配置類均來源于spring.factories文件中。核心則是基于“約定大于配置”理念榆鼠,通俗的說纲爸,就是Spring boot為我們提供了一套默認(rèn)的配置,只有當(dāng)默認(rèn)的配置不滿足我們的需求時妆够,我們再去修改默認(rèn)配置识啦。當(dāng)然它也存在缺點(diǎn)就是組件的高度集成,使用的時候很難知道底層實(shí)現(xiàn)神妹,加深了理解難度颓哮。

以上就是本章的內(nèi)容,如過文章中有錯誤或者需要補(bǔ)充的請及時提出鸵荠,本人感激不盡冕茅。



參考:

《Spring Boot 編程思想》

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子姨伤,更是在濱河造成了極大的恐慌哨坪,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,110評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件乍楚,死亡現(xiàn)場離奇詭異当编,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)徒溪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,443評論 3 395
  • 文/潘曉璐 我一進(jìn)店門忿偷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人词渤,你說我怎么就攤上這事牵舱。” “怎么了缺虐?”我有些...
    開封第一講書人閱讀 165,474評論 0 356
  • 文/不壞的土叔 我叫張陵芜壁,是天一觀的道長。 經(jīng)常有香客問我高氮,道長慧妄,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,881評論 1 295
  • 正文 為了忘掉前任剪芍,我火速辦了婚禮塞淹,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘罪裹。我一直安慰自己饱普,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,902評論 6 392
  • 文/花漫 我一把揭開白布状共。 她就那樣靜靜地躺著套耕,像睡著了一般。 火紅的嫁衣襯著肌膚如雪峡继。 梳的紋絲不亂的頭發(fā)上冯袍,一...
    開封第一講書人閱讀 51,698評論 1 305
  • 那天,我揣著相機(jī)與錄音碾牌,去河邊找鬼康愤。 笑死,一個胖子當(dāng)著我的面吹牛舶吗,可吹牛的內(nèi)容都是我干的征冷。 我是一名探鬼主播,決...
    沈念sama閱讀 40,418評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼裤翩,長吁一口氣:“原來是場噩夢啊……” “哼资盅!你這毒婦竟也來了调榄?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,332評論 0 276
  • 序言:老撾萬榮一對情侶失蹤呵扛,失蹤者是張志新(化名)和其女友劉穎每庆,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體今穿,經(jīng)...
    沈念sama閱讀 45,796評論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡缤灵,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,968評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了蓝晒。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片腮出。...
    茶點(diǎn)故事閱讀 40,110評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖芝薇,靈堂內(nèi)的尸體忽然破棺而出胚嘲,到底是詐尸還是另有隱情,我是刑警寧澤洛二,帶...
    沈念sama閱讀 35,792評論 5 346
  • 正文 年R本政府宣布馋劈,位于F島的核電站,受9級特大地震影響晾嘶,放射性物質(zhì)發(fā)生泄漏妓雾。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,455評論 3 331
  • 文/蒙蒙 一垒迂、第九天 我趴在偏房一處隱蔽的房頂上張望械姻。 院中可真熱鬧,春花似錦机断、人聲如沸楷拳。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,003評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽唯竹。三九已至,卻和暖如春苦丁,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背物臂。 一陣腳步聲響...
    開封第一講書人閱讀 33,130評論 1 272
  • 我被黑心中介騙來泰國打工旺拉, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人棵磷。 一個月前我還...
    沈念sama閱讀 48,348評論 3 373
  • 正文 我出身青樓蛾狗,卻偏偏與公主長得像,于是被迫代替她去往敵國和親仪媒。 傳聞我的和親對象是個殘疾皇子沉桌,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,047評論 2 355

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