與舊項(xiàng)目共舞(一):聊聊配置最小化

最近完成了手頭上項(xiàng)目的一系列重構(gòu)窍蓝,引入了 Spring Boot 和其他較新的技術(shù)框架穿肄。在此過(guò)程中有很多值得回味和交流的地方狡逢,因此做一個(gè)簡(jiǎn)單的記錄。

為什么要配置最小化

整個(gè)重構(gòu)的起源是因?yàn)橄胍獙㈨?xiàng)目的配置最小化颠锉。那為什么需要最小化配置呢?我的原因有以下幾點(diǎn):

  1. 已有的配置文件數(shù)量巨大史汗,僅 xml 文件就有五六百琼掠。維護(hù)大量的配置文件成本非常高,同時(shí)由于部分配置文件包含了特殊邏輯停撞,更是增加了維護(hù)的復(fù)雜度瓷蛙,如生產(chǎn)環(huán)境和測(cè)試環(huán)境使用了不同的配置文件,改動(dòng)時(shí)僅改動(dòng)了測(cè)試環(huán)境的配置戈毒,導(dǎo)致測(cè)試一切正常艰猬,而發(fā)布以后出現(xiàn)各種問(wèn)題。
  2. 由于不支持注解埋市,新增一個(gè) bean 需要編寫(xiě)相應(yīng)的配置代碼冠桃,新增的配置代碼也不像新項(xiàng)目一樣隨便找一個(gè)文件放,需要遵循原來(lái)的做法道宅,放到相應(yīng)的模塊下食听,一來(lái)二去,增加了無(wú)謂的開(kāi)發(fā)成本污茵。
  3. 同樣的樱报,一個(gè)功能不再使用可能需要移除對(duì)應(yīng)的一個(gè)或多個(gè) bean,前代為了省事可能只會(huì)注釋掉相應(yīng)配置甚至不做任何改動(dòng)泞当,長(zhǎng)年累月下來(lái)項(xiàng)目中包含了很多「無(wú)注釋迹蛤、無(wú)文檔、無(wú)引用」的三無(wú)代碼襟士。
  4. 由于以上原因盗飒,項(xiàng)目的復(fù)雜度也大大提升,增加了新接手項(xiàng)目的同學(xué)的學(xué)習(xí)成本敌蜂,不利于項(xiàng)目持續(xù)健康的發(fā)展箩兽。
  5. 另一方面,Spring Boot 本身具有「零配置即可運(yùn)行」的特性章喉,是我希望改造的方向,同時(shí)在組內(nèi)也做過(guò)其他項(xiàng)目基于 Spring Boot 的項(xiàng)目身坐,也積累了一些經(jīng)驗(yàn)秸脱,團(tuán)隊(duì)內(nèi)也有一些技術(shù)棧的沉淀,使用 Spring Boot 后也可以享受無(wú)縫接入其他服務(wù)的便利部蛇。
Spring Boot 如何做到配置最小化

既然目標(biāo)是精簡(jiǎn)配置摊唇,那么先來(lái)看看 Spring Boot 是如何做到配置最小化的。和大多數(shù)優(yōu)秀的開(kāi)源框架一樣涯鲁,Spring Boot 核心理念之一是「約定大于配置」巷查。和一些遵循這個(gè)理念的框架不同的是有序,有些框架的默認(rèn)配置,只能做到「不報(bào)錯(cuò)」岛请、「跑起來(lái)」旭寿,不一定能提供正確的業(yè)務(wù)邏輯,而 Spring Boot 的默認(rèn)配置具有很高的通用性崇败,真正做到「零配置」可用盅称。

Spring Boot 延續(xù)了 Spring 一貫的可配置性和擴(kuò)展性。通過(guò)配置文件或啟動(dòng)參數(shù)修改配置已經(jīng)可以滿足很大一部分項(xiàng)目的需求后室。此外缩膝,由于接口設(shè)計(jì)的非常優(yōu)雅,那些無(wú)法通過(guò)配置文件配置的需求岸霹,大部分都可以通過(guò)簡(jiǎn)單的繼承重寫(xiě)實(shí)現(xiàn)(相比起來(lái)疾层,老項(xiàng)目中復(fù)制粘貼其他開(kāi)源框架代碼然后做一些細(xì)微改動(dòng)的例子屢見(jiàn)不鮮)。

Spring Boot 使用注解來(lái)啟用一組默認(rèn)配置贡避。這些注解大多是基于 Spring 注解實(shí)現(xiàn)的痛黎。首先來(lái)復(fù)習(xí)下 Spring 中基于注解的配置。我認(rèn)為比較關(guān)鍵的注解有這幾個(gè):

@Component / @Controller / @Service / @Repository

Spring 在 2.x 時(shí)代提供的注解贸桶,可以說(shuō)是最早的一批注解舅逸,是最常見(jiàn)的定義一個(gè) bean 的形式,也能滿足項(xiàng)目中 80% 的需求皇筛。

@Autowired / @Qualifier / @Resource

前兩個(gè)注解也是 Spring 最早的一批注解琉历,@Resource是 JDK 提供的注解。這三個(gè)注解是最常見(jiàn)的屬性注入方式之一(通過(guò)@Value注入也是一種常見(jiàn)方式)水醋。

@ComponentScan / @Configuration / @Bean

@ComponentScan定義了有哪些包下的類需要 Spring 掃描旗笔。@Configuration 把一個(gè)類標(biāo)記為配置類,讓ConfigurationClassPostProcessor類掃描解析配置拄踪,其中以 @Bean 注釋的方法和屬性都會(huì)被解析為 bean 的定義蝇恶,基本能滿足第一條中剩下的 19% 需求。值得注意的一點(diǎn)是惶桐,直接調(diào)用被 bean 注釋的方法也會(huì)認(rèn)為是對(duì) bean 的引用撮弧,直接通過(guò)構(gòu)造函數(shù)構(gòu)造和使用 @Bean 構(gòu)造的的對(duì)象可能是不同的。如

@Bean
public Foo foo(){ return new Foo();}

public void doSth() { doSth(new Foo());}
public void doSth2() {doSth(foo());}

doSthdoSth2 兩個(gè)方法看起來(lái)好像一樣姚糊,但實(shí)際上可能是不同的贿衍。例如當(dāng) Foo 實(shí)現(xiàn)了 InitializingBean 接口,doSth() 可能不會(huì)調(diào)用 afterPropertiesSet() 方法救恨;又例如 foo 通過(guò) AbstractAutoProxyCreator
被定義為一個(gè)代理對(duì)象贸辈,doSth()獲取不到代理對(duì)象,可能導(dǎo)致 aop 邏輯丟失肠槽。所以擎淤,任何類定義時(shí)依賴 Spring 容器托管的 bean奢啥,使用 FactoryBean 構(gòu)造的 bean,預(yù)期通過(guò)代理構(gòu)造的 bean嘴拢,注意不要直接構(gòu)造桩盲。

@Import / @ImportResource

這兩個(gè)注解負(fù)責(zé)將配置類和配置文件關(guān)聯(lián)起來(lái)。

@ImportResource 用于導(dǎo)入配置文件炊汤。Spring 默認(rèn)提供了 xml 和 groovy 類型配置文件的解析正驻,同時(shí)也提供了 BeanDefinitionReader 接口以實(shí)現(xiàn)自定義格式的文件解析。

@Import 用于導(dǎo)入配置類抢腐。一個(gè)配置類要被 Spring 識(shí)別姑曙,除了在 @ComponentScan 掃描路徑下并配置了 @Configuraion 注解外,還有一種方式就是在這些配置類上通過(guò) @Import 導(dǎo)入迈倍。值得一提的是伤靠,Spring 提供了幾個(gè)接口,以實(shí)現(xiàn)對(duì)導(dǎo)入配置類行為的擴(kuò)展啼染。

接口 ImportBeanDefinitionRegistrar 允許我們獲取注解的屬性并手動(dòng)向容器注冊(cè) bean宴合。我們可以通過(guò)一個(gè)例子來(lái)了解這個(gè)接口可以做些什么:

@Import(SongRegistar.class)
public @interface EnableSing {
    String singerName();
    String[] songNames();
    boolean useSingerNameAsPrefix();
}

public SongRegistar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata, 
                                        BeanDefinitionRegistry registry) {
        AnnotationAttributes attrs =
                AnnotationConfigUtils.attributesFor(metadata, EnableSing.class);
        String singerName = attrs.getString("singerName");
        BeanDefinition def = registry.getBeanDefinition("singer");
        def.getPropertyValues().add("name", singerName);
        boolean useSingerName = attrs.getBoolean("useSingerNameAsPrefix");
        for (String songName : attrs.getStringArray("songNames")) {
             BeanDefinition songDef = build(songName);
             String beanName = useSingerName ? (singerName + songName) : songName;
             registry.registerBeanDefinition(beanName, songDef);
        }
    }
}

簡(jiǎn)單的總結(jié)下 demo,這個(gè)接口可以:

  1. 使用注解值生成一個(gè)或一組 bean迹鹅,這意味著我們可以通過(guò)注解定義新的 bean卦洽。
  2. 修改一個(gè)已注冊(cè) bean 的定義。def.getPropertyValues().add("name", singerName)這段代碼并不是只能覆蓋 name 屬性斜棚,如果這個(gè)字段是 Set阀蒂、ListMap弟蚀、Properties蚤霞,還可以向已有屬性里追加值。這意味著我們可以通過(guò)注解對(duì)已有 bean 進(jìn)行修改义钉。
  3. 由于可以拿到 BeanDefinitionRegistry昧绣,這意味著我們可以根據(jù)具體場(chǎng)景決定對(duì) bean 的不同操作,和下面講到的@Conditional相比能做的事情更多捶闸。

接口 ImportSelector 允許 @Import 動(dòng)態(tài)導(dǎo)入配置類夜畴。這允許我們根據(jù)某些條件動(dòng)態(tài)選擇配置類,具體的應(yīng)用后面會(huì)再提到删壮。與此同時(shí)斩启,Spring 為這個(gè)接口提供了一個(gè)特殊的子接口 DeferredImportSelector,實(shí)現(xiàn)了這個(gè)接口的配置類會(huì)在所有 @Configuration bean 處理完成后再處理醉锅,一般應(yīng)用于 Spring Boot 中。

@Conditional

它定義了一組條件发绢,只有滿足這組條件才會(huì)向容器注冊(cè) bean硬耍,這也為我們的配置提供了更靈活的支持垄琐。

舉個(gè)例子,我們需要定義一個(gè) bean经柴,測(cè)試環(huán)境和生產(chǎn)環(huán)境需要配置不同值狸窘。如果 bean 名稱一致,只需要簡(jiǎn)單的在 @Bean 定義時(shí)坯认,判斷環(huán)境再設(shè)置不同值即可翻擒。那么如果 bean 名稱不一致呢?如生產(chǎn)環(huán)境注冊(cè) beanProd牛哺,測(cè)試環(huán)境注冊(cè) beanTest陋气。由于注解的參數(shù)要求是常量,所以無(wú)法通過(guò) @Bean 動(dòng)態(tài)指定名字引润。

在舊項(xiàng)目中的實(shí)現(xiàn)方式是巩趁,不同環(huán)境的 bean 定義在不同配置文件中。通過(guò) ant/maven 將不同的配置文件打包從而生成不同的 war 包淳附,這樣做的缺點(diǎn)就如上面提到的议慰,修改配置時(shí)容易遺漏,還需要額外維護(hù)打包腳本奴曙。

使用@Conditional的話别凹,可以如下這樣配置。

@Configuration
public class CustomConfiguration {
    @Bean
    @Conditional(ProdCondition.class)
    public Foo beanProd() {
        Foo bean = createBasic();
        // 生產(chǎn)環(huán)境配置
        return bean;
    }
    @Bean
    @Conditional(TestCondition.class)
    public Foo beanTest() {
        Foo bean = createBasic();
        // 測(cè)試環(huán)境配置
        return bean;
    }
    private Foo createBasic() {
        // 相同的配置
    }
}

// Conditional 實(shí)現(xiàn)
public abstract class EnvironmentCondition implements Conditional {
    protected abstract String enable();

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        String env = System.getProperty("env");
        return Objects.equals(enable(), env);
    }

    public static class ProdCondition extends EnvironmentCondition {
        @Override
        protected abstract String enable() {
            return "prod";
        }
    }
    public static class TestCondition extends EnvironmentCondition {
        @Override
        protected abstract String enable() {
            return "test";
        }
    }
}

通過(guò)這種方式配置洽糟,有幾個(gè)好處:

  1. 不必再維護(hù)多個(gè)配置文件炉菲,相應(yīng)地也不用額外維護(hù) ant/maven 腳本。
  2. 相同部分的配置可以重用脊框,重用配置的方式比 XML 實(shí)現(xiàn)簡(jiǎn)單(通過(guò) XML 配置暫時(shí)只想到 FactoryBean 和 parent bean 兩種形式來(lái)重用配置)颁督。
  3. 通過(guò) IDE 全局搜索 ProdCondition 和 TestCondition 的引用,可以輕松的知道浇雹,生產(chǎn)/測(cè)試環(huán)境做了哪些特殊的改變沉御。
  4. 當(dāng)條件發(fā)生變更時(shí),只需要改變 Conditional 實(shí)現(xiàn)昭灵,相比更改 ant/maven 配置更簡(jiǎn)單吠裆。

前面也提到 ImportBeanDefinitionRegistrar 可以修改已有的 bean,對(duì)于這個(gè)需求烂完,我們也可以這樣實(shí)現(xiàn):

@Configuration
@Import(TestRegistrar.class)
public class CustomConfiguration{
    @Bean
    public Foo beanProd() {
         Foo bean = new Foo();
         // 生產(chǎn)環(huán)境配置
         return bean;
    }

    public static class TestRegistrar  implements ImportBeanDefinitionRegistrar {
        @Override
        public void registerBeanDefinitions(AnnotationMetadata metadata, 
                                            BeanDefinitionRegistry registry) {
            String env = System.getProperty("env");
            if ("test".equals(env)) {
                BeanDefinition def = registry. getBeanDefinition("foo");
                // 修改為測(cè)試環(huán)境配置
                registry.removeBeanDefinition("beanProd");
                registry.registerBeanDefinition("beanTest", def);
            }
    }
}

不過(guò)顯然试疙,對(duì)于這個(gè)需求而言,從工程的角度看抠蚣,方法一維護(hù)性會(huì)更好祝旷。

@AliasFor

這個(gè)注解自 4.2 版本后才引入,又因?yàn)椴皇潜仨毜淖⒔猓院芏嚅_(kāi)發(fā)者在日常開(kāi)發(fā)中往往忽略了它怀跛。但在簡(jiǎn)化配置中距贷,這個(gè)注解卻不容忽視。

我們知道吻谋,Spring 通過(guò)注解忠蝗、反射和 ClassLoader,隱藏了配置實(shí)現(xiàn)細(xì)節(jié)漓拾,從而達(dá)到精簡(jiǎn)配置的目的阁最。但隨之而來(lái)的問(wèn)題是可用的注解不斷的增多,而 Java 的注解類型是不支持繼承的骇两,這又會(huì)導(dǎo)致新的繁瑣和冗余速种。針對(duì)這一點(diǎn),Spring 定義了自己的注解「繼承」規(guī)則脯颜,簡(jiǎn)單來(lái)說(shuō)就是——如果注解 A 上有注解 B哟旗、C,對(duì)于需要同時(shí)注解 B/C 的場(chǎng)景栋操,可以直接用注解 A 代替闸餐。@AliasFor 的作用就是在這個(gè)場(chǎng)景下,定義將 B/C 上的某些字段映射為 A 的某些字段矾芙。這一點(diǎn)在我們基于 Spring 定制自己的框架時(shí)將會(huì)非常有幫助舍沙。

回顧完 Spring 的注解,我們來(lái)看看 Spring Boot剔宪。如前所述拂铡,Spring 通過(guò) @Configuration@Import,實(shí)現(xiàn)了基礎(chǔ)的自動(dòng)配置葱绒,如@EnableAspectJAutoProxy感帅。Spring Boot 則是在此基礎(chǔ)上,實(shí)現(xiàn)了更豐富的自動(dòng)配置地淀。Spring Cloud 全家桶以及部分開(kāi)源框架如 druid 都實(shí)現(xiàn)了類似的自動(dòng)化配置(spring-boot-*-starter)失球。下面讓我們來(lái)總結(jié)下 Spring Boot 自動(dòng)配置用到的關(guān)鍵注解。

@ConditionalOn*

這一系列的注解是 Spring Boot 提供的 @Conditional 實(shí)現(xiàn)帮毁,覆蓋了大部分日常開(kāi)發(fā)所需要的條件实苞,這里列舉幾個(gè) Spring Boot starter 的注解:

  • @ConditionalOnBean/@ConditionalOnMissingBean,最常用的條件之一烈疚, 表示某些 bean 存在 / 不存在黔牵。后者也是對(duì)配置最小化起到很大幫助的注解之一,相當(dāng)于 Bean 的 Override 機(jī)制爷肝。
  • @ConditionalOnClass/@ConditionalOnMissingClass猾浦,框架類項(xiàng)目最常用的條件之一陆错,表示 ClassLoader 存在 / 不存在某些類。最常見(jiàn)的用法是「弱依賴檢查」跃巡,某個(gè)包可依賴也可以不依賴危号,但是對(duì)兩種情況需要進(jìn)行不同的注冊(cè)操作。對(duì)于一個(gè)業(yè)務(wù)項(xiàng)目來(lái)說(shuō)不太用的到素邪,因?yàn)橐玫陌谴_定的。
  • @ConditionalOnProperty猪半,框架最常用的條件之一兔朦,表示某些屬性存在。是通過(guò)屬性啟動(dòng)配置的重要實(shí)現(xiàn)方式之一磨确。順帶一提沽甥,上面的那個(gè)例子可以簡(jiǎn)化為
@Configuration
public class CustomConfiguration {
    @Bean
    @ConditionalOnProperty(name = "env", havingValue = "prod")
    public Foo beanProd() {
        Foo bean = createBasic();
        // 生產(chǎn)環(huán)境配置
        return bean;
    }

    @Bean
    @ConditionalOnProperty(name = "env", havingValue = "test")
    public Foo beanTest() {
        Foo bean = createBasic();
        // 測(cè)試環(huán)境配置
        return bean;
    }

    private Foo createBasic() {
        // 相同的配置
    }
}
  • @ConditionalOnJava,表示需要 Java 版本滿足特定需求乏奥,可以用來(lái)處理 JDK 升級(jí)的兼容性問(wèn)題摆舟。
  • @ConditionalOnResource,表示存在某個(gè)配置文件邓了。
  • @ConditionalOnExpression恨诱,表示 SpEL 返回某個(gè)確定值。
  • @ConditionalOnSingleCandidate骗炉,表示某個(gè)類型的 Bean 只存在一個(gè)照宝,或是多個(gè)類型的 Bean 中有一個(gè)指定了 @Primary
  • @ConditionalOnJndi句葵,表示存在某些 JNDI 接口厕鹃。
  • @ConditionalOnWebApplication / @ConditionalOnNotWebApplication,表示運(yùn)行環(huán)境是 / 不是 Web 應(yīng)用乍丈。
@EnableAutoConfiguration

啟用自動(dòng)配置的核心注解剂碴。原理其實(shí)非常簡(jiǎn)單,正是用了上面提到的 @ImportDeferredImportSelector 接口轻专。Spring Boot 使用 AutoConfigurationMetadataLoader 解析自動(dòng)配置相關(guān)的配置文件忆矛。它采用「約定大于配置」的做法,讀取 classpath:META-INF/spring-autoconfigure-metadata.properties 并解析為注解配置铭若,這些配置可以簡(jiǎn)單理解為 @Configuration@ConditionOn* 注解洪碳,告訴框架需要識(shí)別哪些類為配置類。

Spring Boot 提供了一個(gè)默認(rèn)的版本叼屠,囊括了所有默認(rèn)配置瞳腌。我們也可以在自己的應(yīng)用或框架下的同樣位置提供配置文件實(shí)現(xiàn)自己的自動(dòng)配置。當(dāng)然通常情況下我們更多的會(huì)使用 spring.factories 配置文件來(lái)實(shí)現(xiàn)自定義的自動(dòng)配置镜雨,有關(guān)其原理會(huì)在其他文章中單獨(dú)討論嫂侍。

簡(jiǎn)而言之,@EnableAutoConfiguration 就是通過(guò) @Import 機(jī)制,導(dǎo)入了大量預(yù)定義的配置挑宠,從而達(dá)到配置最小化的目的菲盾。對(duì)框架使用者而言,只需要使用 @EnableAutoConfiguration 注解各淀,不必關(guān)系哪些配置被啟用哪些沒(méi)有懒鉴。

@EnableConfigurationProperties / @ConfigurationProperties

@ConfigurationProperties 將 Spring Boot 配置項(xiàng)注入配置 Bean,@EnableConfigurationProperties 可以將注入后的配置 Bean 注入到配置類中碎浇。Spring Boot 通過(guò)這兩個(gè)注解簡(jiǎn)化了配置文件的讀取和使用临谱。

最后進(jìn)行一下總結(jié)。在這一期中奴璃,我們對(duì) Spring 的注解配置方式有了一個(gè)全面但并不深入的了解悉默,同時(shí)也對(duì) Spring Boot 的自動(dòng)配置、默認(rèn)配置有了簡(jiǎn)單的認(rèn)識(shí)苟穆。這些不僅能幫我們更好的定制 Spring 框架抄课,還能為我們的應(yīng)用架構(gòu)設(shè)計(jì)帶來(lái)啟發(fā)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末雳旅,一起剝皮案震驚了整個(gè)濱河市跟磨,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌岭辣,老刑警劉巖吱晒,帶你破解...
    沈念sama閱讀 212,816評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異沦童,居然都是意外死亡仑濒,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)偷遗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)墩瞳,“玉大人,你說(shuō)我怎么就攤上這事氏豌『碜茫” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 158,300評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵泵喘,是天一觀的道長(zhǎng)泪电。 經(jīng)常有香客問(wèn)我,道長(zhǎng)纪铺,這世上最難降的妖魔是什么相速? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,780評(píng)論 1 285
  • 正文 為了忘掉前任,我火速辦了婚禮鲜锚,結(jié)果婚禮上突诬,老公的妹妹穿的比我還像新娘苫拍。我一直安慰自己,他們只是感情好旺隙,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,890評(píng)論 6 385
  • 文/花漫 我一把揭開(kāi)白布绒极。 她就那樣靜靜地躺著,像睡著了一般蔬捷。 火紅的嫁衣襯著肌膚如雪垄提。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 50,084評(píng)論 1 291
  • 那天抠刺,我揣著相機(jī)與錄音塔淤,去河邊找鬼。 笑死速妖,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的聪黎。 我是一名探鬼主播罕容,決...
    沈念sama閱讀 39,151評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼稿饰!你這毒婦竟也來(lái)了锦秒?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,912評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤喉镰,失蹤者是張志新(化名)和其女友劉穎旅择,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體侣姆,經(jīng)...
    沈念sama閱讀 44,355評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡生真,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,666評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了捺宗。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片柱蟀。...
    茶點(diǎn)故事閱讀 38,809評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖蚜厉,靈堂內(nèi)的尸體忽然破棺而出长已,到底是詐尸還是另有隱情,我是刑警寧澤昼牛,帶...
    沈念sama閱讀 34,504評(píng)論 4 334
  • 正文 年R本政府宣布术瓮,位于F島的核電站,受9級(jí)特大地震影響贰健,放射性物質(zhì)發(fā)生泄漏胞四。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,150評(píng)論 3 317
  • 文/蒙蒙 一霎烙、第九天 我趴在偏房一處隱蔽的房頂上張望撬讽。 院中可真熱鬧蕊连,春花似錦、人聲如沸游昼。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,882評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)烘豌。三九已至载庭,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間廊佩,已是汗流浹背囚聚。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,121評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留标锄,地道東北人顽铸。 一個(gè)月前我還...
    沈念sama閱讀 46,628評(píng)論 2 362
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像料皇,于是被迫代替她去往敵國(guó)和親谓松。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,724評(píng)論 2 351

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

  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,778評(píng)論 6 342
  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理践剂,服務(wù)發(fā)現(xiàn)鬼譬,斷路器,智...
    卡卡羅2017閱讀 134,638評(píng)論 18 139
  • spring官方文檔:http://docs.spring.io/spring/docs/current/spri...
    牛馬風(fēng)情閱讀 1,653評(píng)論 0 3
  • 晨起跑步逊脯,收獲了美美的風(fēng)景优质。 看似一樣,卻又不完全一樣军洼。 同一片云巩螃,角度不同,光線不同歉眷,看到的就會(huì)不同牺六。 時(shí)而藍(lán)天...
    溪南客閱讀 245評(píng)論 2 3
  • 趙州褝師見(jiàn)地澄澈,證悟淵深汗捡,堪為宗門(mén)泰斗淑际,叢林范式。時(shí)人尊之為“古佛再來(lái)”扇住,贊其“眼光爍破四天下”春缕。每出一言,下一...
    禹音閱讀 327評(píng)論 0 0