Spring Boot的啟動過程

本文從SpringApplication類開始分析Spring Boot應(yīng)用的啟動過程磕仅,使用的Spring Boot版本是1.5.15.RELEASE贝咙。

成員變量

public class SpringApplication {
    /**
     * The class name of application context that will be used by default for non-web
     * environments.
     */
    public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context."
            + "annotation.AnnotationConfigApplicationContext";

    /**
     * The class name of application context that will be used by default for web
     * environments.
     */
    public static final String DEFAULT_WEB_CONTEXT_CLASS = "org.springframework."
            + "boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext";

    private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet",
            "org.springframework.web.context.ConfigurableWebApplicationContext" };

    /**
     * Default banner location.
     */
    public static final String BANNER_LOCATION_PROPERTY_VALUE = SpringApplicationBannerPrinter.DEFAULT_BANNER_LOCATION;

    /**
     * Banner location property key.
     */
    public static final String BANNER_LOCATION_PROPERTY = SpringApplicationBannerPrinter.BANNER_LOCATION_PROPERTY;

    private static final String SYSTEM_PROPERTY_JAVA_AWT_HEADLESS = "java.awt.headless";

    private static final Log logger = LogFactory.getLog(SpringApplication.class);

    private final Set<Object> sources = new LinkedHashSet<Object>();

    private Class<?> mainApplicationClass;

    private Banner.Mode bannerMode = Banner.Mode.CONSOLE;

    private boolean logStartupInfo = true;

    private boolean addCommandLineProperties = true;

    private Banner banner;

    private ResourceLoader resourceLoader;

    private BeanNameGenerator beanNameGenerator;

    private ConfigurableEnvironment environment;

    private Class<? extends ConfigurableApplicationContext> applicationContextClass;

    private boolean webEnvironment;

    private boolean headless = true;

    private boolean registerShutdownHook = true;

    private List<ApplicationContextInitializer<?>> initializers;

    private List<ApplicationListener<?>> listeners;

    private Map<String, Object> defaultProperties;

    private Set<String> additionalProfiles = new HashSet<String>();
}
  • DEFAULT_CONTEXT_CLASS常量表示非Web環(huán)境默認(rèn)的應(yīng)用上下文是AnnotationConfigApplicationContext類型睬隶;
  • DEFAULT_WEB_CONTEXT_CLASS常量表示W(wǎng)eb環(huán)境下默認(rèn)的應(yīng)用上下文是AnnotationConfigEmbeddedWebApplicationContext類型峭梳;
  • sources表示bean來源箱叁;
  • mainApplicationClass表示啟動類類型肖揣;
  • resourceLoader表示資源加載的策略;
  • beanNameGenerator表示生成bean名稱的策略可免;
  • environment表示使用的環(huán)境抓于;
  • applicationContextClass表示創(chuàng)建的應(yīng)用上下文的類型;
  • webEnvironment表示當(dāng)前是否是Web環(huán)境浇借;
  • initializers表示添加的ApplicationContextInitializer捉撮;
  • listeners表示添加的ApplicationListener;
  • ...

構(gòu)造函數(shù)

SpringApplication類的兩個構(gòu)造函數(shù)如下妇垢,它們都調(diào)用initialize方法做初始化工作巾遭。

public SpringApplication(Object... sources) {
    initialize(sources);
}

public SpringApplication(ResourceLoader resourceLoader, Object... sources) {
    this.resourceLoader = resourceLoader;
    initialize(sources);
}

initialize方法代碼如下:

@SuppressWarnings({ "unchecked", "rawtypes" })
private void initialize(Object[] sources) {
    if (sources != null && sources.length > 0) {
        this.sources.addAll(Arrays.asList(sources));
    }
    this.webEnvironment = deduceWebEnvironment();
    setInitializers((Collection) getSpringFactoriesInstances(
            ApplicationContextInitializer.class));
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    this.mainApplicationClass = deduceMainApplicationClass();
}

該方法做了如下工作:

  1. deduceWebEnvironment方法檢測當(dāng)前是否是Web環(huán)境,只有當(dāng)Servlet接口和ConfigurableWebApplicationContext接口都存在且能被加載時才是Web環(huán)境闯估;
    private boolean deduceWebEnvironment() {
        for (String className : WEB_ENVIRONMENT_CLASSES) {
            if (!ClassUtils.isPresent(className, null)) {
                return false;
            }
        }
        return true;
    }
    
  2. getSpringFactoriesInstances方法從jar包的META/spring.factories文件中獲取特定類型的工廠實現(xiàn)類限定名并實例化它們灼舍,此處是創(chuàng)建了ApplicationContextInitializerApplicationListener兩種類型的工廠實現(xiàn)類,并分別保存到initializers和listeners成員變量中涨薪。每個jar包都可能有spring.factories文件骑素,以spring-boot-1.5.15.RELEASE.jar中的META/spring.factories為例,該文件包含4種ApplicationContextInitializer和9種ApplicationListener尤辱;
    # Application Context Initializers
    org.springframework.context.ApplicationContextInitializer=\
    org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
    org.springframework.boot.context.ContextIdApplicationContextInitializer,\
    org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
    org.springframework.boot.context.embedded.ServerPortInfoApplicationContextInitializer
    
    # Application Listeners
    org.springframework.context.ApplicationListener=\
    org.springframework.boot.ClearCachesApplicationListener,\
    org.springframework.boot.builder.ParentContextCloserApplicationListener,\
    org.springframework.boot.context.FileEncodingApplicationListener,\
    org.springframework.boot.context.config.AnsiOutputApplicationListener,\
    org.springframework.boot.context.config.ConfigFileApplicationListener,\
    org.springframework.boot.context.config.DelegatingApplicationListener,\
    org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener,\
    org.springframework.boot.logging.ClasspathLoggingApplicationListener,\
    org.springframework.boot.logging.LoggingApplicationListener
    
  3. deduceMainApplicationClass方法找出main方法所在的類并保存到mainApplicationClass成員變量砂豌。
    private Class<?> deduceMainApplicationClass() {
        try {
            StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
            for (StackTraceElement stackTraceElement : stackTrace) {
                if ("main".equals(stackTraceElement.getMethodName())) {
                    return Class.forName(stackTraceElement.getClassName());
                }
            }
        }
        catch (ClassNotFoundException ex) {
            // Swallow and continue
        }
        return null;
    }
    

run方法

SpringApplication類的run靜態(tài)方法代碼如下所示,可見其調(diào)用了上文的構(gòu)造函數(shù)和run成員方法光督。

public static ConfigurableApplicationContext run(Object source, String... args) {
    return run(new Object[] { source }, args);
}

public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
    return new SpringApplication(sources).run(args);
}

run成員方法代碼如下:

public ConfigurableApplicationContext run(String... args) {
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    ConfigurableApplicationContext context = null;
    FailureAnalyzers analyzers = null;
    configureHeadlessProperty();
    SpringApplicationRunListeners listeners = getRunListeners(args);
    listeners.starting();
    try {
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                args);
        ConfigurableEnvironment environment = prepareEnvironment(listeners,
                applicationArguments);
        Banner printedBanner = printBanner(environment);
        context = createApplicationContext();
        analyzers = new FailureAnalyzers(context);
        prepareContext(context, environment, listeners, applicationArguments,
                printedBanner);
        refreshContext(context);
        afterRefresh(context, applicationArguments);
        listeners.finished(context, null);
        stopWatch.stop();
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass)
                    .logStarted(getApplicationLog(), stopWatch);
        }
        return context;
    }
    catch (Throwable ex) {
        handleRunFailure(context, listeners, analyzers, ex);
        throw new IllegalStateException(ex);
    }
}

run成員方法的主要流程如下:

  1. 創(chuàng)建SpringApplicationRunListener類型的實例阳距;
  2. 為應(yīng)用上下文準(zhǔn)備環(huán)境;
  3. 創(chuàng)建應(yīng)用上下文结借;
  4. 準(zhǔn)備應(yīng)用上下文筐摘;
  5. 刷新應(yīng)用上下文。

創(chuàng)建SpringApplicationRunListener

在創(chuàng)建SpringApplicationRunListener的過程中船老,首先getRunListeners方法在各META/spring.factories文件查找org.springframework.boot.SpringApplicationRunListener的工廠實現(xiàn)類咖熟,然后以該SpringApplication對象和main方法的命令行參數(shù)為實參調(diào)用這些實現(xiàn)類的構(gòu)造函數(shù)實例化它們,最后各SpringApplicationRunListener的starting回調(diào)方法被調(diào)用柳畔。

private SpringApplicationRunListeners getRunListeners(String[] args) {
    Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
    return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
            SpringApplicationRunListener.class, types, this, args));
}
  • SpringApplicationRunListener接口專門用于監(jiān)聽run方法馍管,其代碼如下所示,該接口的實現(xiàn)類需要聲明一個公有的以SpringApplication和字符串?dāng)?shù)組為參數(shù)的構(gòu)造函數(shù)薪韩。
    public interface SpringApplicationRunListener {
    
        void starting();
    
        void environmentPrepared(ConfigurableEnvironment environment);
    
        void contextPrepared(ConfigurableApplicationContext context);
    
        void contextLoaded(ConfigurableApplicationContext context);
    
        void finished(ConfigurableApplicationContext context, Throwable exception);
    }
    
  • SpringApplicationRunListeners類是SpringApplicationRunListener的集合确沸,其方法內(nèi)部都是依次調(diào)用各SpringApplicationRunListener同名的回調(diào)方法。
    class SpringApplicationRunListeners {
        private final Log log;
        private final List<SpringApplicationRunListener> listeners;
    
        SpringApplicationRunListeners(Log log,
                Collection<? extends SpringApplicationRunListener> listeners) {
            this.log = log;
            this.listeners = new ArrayList<SpringApplicationRunListener>(listeners);
        }
    
        public void starting() {
            for (SpringApplicationRunListener listener : this.listeners) {
                listener.starting();
            }
        }
    
        public void environmentPrepared(ConfigurableEnvironment environment) {
            for (SpringApplicationRunListener listener : this.listeners) {
                listener.environmentPrepared(environment);
            }
        }
    
        // 省略一些代碼
    }
    
  • 以spring-boot-1.5.15.RELEASE.jar中的META/spring.factories為例俘陷,該文件只包含一個EventPublishingRunListener罗捎;
    # Run Listeners
    org.springframework.boot.SpringApplicationRunListener=\
    org.springframework.boot.context.event.EventPublishingRunListener
    
    EventPublishingRunListener類的部分代碼如下,它的構(gòu)造函數(shù)將SpringApplication的listeners成員變量保存的各ApplicationListener加入事件廣播器中拉盾,其他回調(diào)方法在內(nèi)部廣播了相應(yīng)的事件給各ApplicationListener桨菜。
    public EventPublishingRunListener(SpringApplication application, String[] args) {
        this.application = application;
        this.args = args;
        this.initialMulticaster = new SimpleApplicationEventMulticaster();
        for (ApplicationListener<?> listener : application.getListeners()) {
            this.initialMulticaster.addApplicationListener(listener);
        }
    }
    
    @Override
    @SuppressWarnings("deprecation")
    public void starting() {
        this.initialMulticaster
                .multicastEvent(new ApplicationStartedEvent(this.application, this.args));
    }
    
    @Override
    public void environmentPrepared(ConfigurableEnvironment environment) {
        this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(
                this.application, this.args, environment));
    }
    
    @Override
    public void contextPrepared(ConfigurableApplicationContext context) {
    
    }
    
    // 省略一些代碼
    

準(zhǔn)備環(huán)境

準(zhǔn)備環(huán)境是由prepareEnvironment方法完成的,其代碼如下所示捉偏。

private ConfigurableEnvironment prepareEnvironment(
        SpringApplicationRunListeners listeners,
        ApplicationArguments applicationArguments) {
    // Create and configure the environment
    ConfigurableEnvironment environment = getOrCreateEnvironment();
    configureEnvironment(environment, applicationArguments.getSourceArgs());
    listeners.environmentPrepared(environment);
    if (!this.webEnvironment) {
        environment = new EnvironmentConverter(getClassLoader())
                .convertToStandardEnvironmentIfNecessary(environment);
    }
    return environment;
}
  • 首先創(chuàng)建環(huán)境倒得,若是Web環(huán)境則創(chuàng)建StandardServletEnvironment,否則創(chuàng)建StandardEnvironment夭禽;
    private ConfigurableEnvironment getOrCreateEnvironment() {
        if (this.environment != null) {
            return this.environment;
        }
        if (this.webEnvironment) {
            return new StandardServletEnvironment();
        }
        return new StandardEnvironment();
    }
    
  • 然后是配置環(huán)境屎暇,分別配置了屬性源和配置文件;
    protected void configureEnvironment(ConfigurableEnvironment environment,
            String[] args) {
        configurePropertySources(environment, args);
        configureProfiles(environment, args);
    }
    
    protected void configurePropertySources(ConfigurableEnvironment environment,
            String[] args) {
        MutablePropertySources sources = environment.getPropertySources();
        if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) {
            sources.addLast(
                    new MapPropertySource("defaultProperties", this.defaultProperties));
        }
        if (this.addCommandLineProperties && args.length > 0) {
            String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME;
            if (sources.contains(name)) {
                PropertySource<?> source = sources.get(name);
                CompositePropertySource composite = new CompositePropertySource(name);
                composite.addPropertySource(new SimpleCommandLinePropertySource(
                        name + "-" + args.hashCode(), args));
                composite.addPropertySource(source);
                sources.replace(name, composite);
            }
            else {
                sources.addFirst(new SimpleCommandLinePropertySource(args));
            }
        }
    }
    
    protected void configureProfiles(ConfigurableEnvironment environment, String[] args) {
        environment.getActiveProfiles(); // ensure they are initialized
        // But these ones should go first (last wins in a property key clash)
        Set<String> profiles = new LinkedHashSet<String>(this.additionalProfiles);
        profiles.addAll(Arrays.asList(environment.getActiveProfiles()));
        environment.setActiveProfiles(profiles.toArray(new String[profiles.size()]));
    }
    
  • 最后各SpringApplicationRunListener的environmentPrepared回調(diào)方法被調(diào)用驻粟。

創(chuàng)建應(yīng)用上下文

createApplicationContext方法創(chuàng)建上下文根悼,如果applicationContextClass字段指定了上下文類型則創(chuàng)建該類型的應(yīng)用上下文,否則對Web環(huán)境默認(rèn)創(chuàng)建AnnotationConfigEmbeddedWebApplicationContext蜀撑,對非Web環(huán)境創(chuàng)建AnnotationConfigApplicationContext挤巡。

protected ConfigurableApplicationContext createApplicationContext() {
    Class<?> contextClass = this.applicationContextClass;
    if (contextClass == null) {
        try {
            contextClass = Class.forName(this.webEnvironment
                    ? DEFAULT_WEB_CONTEXT_CLASS : DEFAULT_CONTEXT_CLASS);
        }
        catch (ClassNotFoundException ex) {
            throw new IllegalStateException(
                    "Unable create a default ApplicationContext, "
                            + "please specify an ApplicationContextClass",
                    ex);
        }
    }
    return (ConfigurableApplicationContext) BeanUtils.instantiate(contextClass);
}

所創(chuàng)建的應(yīng)用上下文即是DispatcherServlet的應(yīng)用上下文,這與傳統(tǒng)的Spring MVC工程不同酷麦。

準(zhǔn)備應(yīng)用上下文

prepareContext方法用于準(zhǔn)備上面創(chuàng)建的應(yīng)用上下文矿卑,其代碼如下所示。

private void prepareContext(ConfigurableApplicationContext context,
        ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
        ApplicationArguments applicationArguments, Banner printedBanner) {
    context.setEnvironment(environment);
    postProcessApplicationContext(context);
    applyInitializers(context);
    listeners.contextPrepared(context);
    if (this.logStartupInfo) {
        logStartupInfo(context.getParent() == null);
        logStartupProfileInfo(context);
    }

    // Add boot specific singleton beans
    context.getBeanFactory().registerSingleton("springApplicationArguments",
            applicationArguments);
    if (printedBanner != null) {
        context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
    }

    // Load the sources
    Set<Object> sources = getSources();
    Assert.notEmpty(sources, "Sources must not be empty");
    load(context, sources.toArray(new Object[sources.size()]));
    listeners.contextLoaded(context);
}

該方法主要做了以下幾件事:

  • 為創(chuàng)建的應(yīng)用上下文設(shè)置了之前創(chuàng)建的環(huán)境沃饶;
  • postProcessApplicationContext方法做了創(chuàng)建應(yīng)用上下文后的處理工作母廷,為應(yīng)用上下文注冊了bean名稱生成策略轻黑,子類可以重寫該方法自定義其他工作;
  • applyInitializers方法按各ApplicationContextInitializer的添加順序調(diào)用其initialize回調(diào)方法琴昆,請注意除了上文提到j(luò)ar包的META/spring.factories文件中可以指定該接口的工廠實現(xiàn)類之外氓鄙,還可以在調(diào)用run方法之前通過SpringApplication類的addInitializers成員方法添加ApplicationContextInitializer;
  • 各SpringApplicationRunListener的contextPrepared回調(diào)方法被調(diào)用业舍;
  • load方法將bean定義加載到應(yīng)用上下文中(還未實例化)抖拦;
  • 各SpringApplicationRunListener的contextLoaded回調(diào)方法被調(diào)用。

刷新應(yīng)用上下文

refreshContext方法刷新創(chuàng)建的應(yīng)用上下文舷暮,根據(jù)上一步加載的bean定義實例化各單例bean态罪。

private void refreshContext(ConfigurableApplicationContext context) {
    refresh(context);
    if (this.registerShutdownHook) {
        try {
            context.registerShutdownHook();
        }
        catch (AccessControlException ex) {
            // Not allowed in some environments.
        }
    }
}

protected void refresh(ApplicationContext applicationContext) {
    Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
    ((AbstractApplicationContext) applicationContext).refresh();
}

應(yīng)用上下文刷新后

應(yīng)用上下文刷新后run方法調(diào)用了afterRefresh方法執(zhí)行所有的Runner,并調(diào)用各SpringApplicationRunListener的finished回調(diào)方法下面。

afterRefresh(context, applicationArguments);
listeners.finished(context, null);

afterRefresh方法代碼如下所示:

protected void afterRefresh(ConfigurableApplicationContext context,
        ApplicationArguments args) {
    callRunners(context, args);
}

private void callRunners(ApplicationContext context, ApplicationArguments args) {
    List<Object> runners = new ArrayList<Object>();
    runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
    runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
    AnnotationAwareOrderComparator.sort(runners);
    for (Object runner : new LinkedHashSet<Object>(runners)) {
        if (runner instanceof ApplicationRunner) {
            callRunner((ApplicationRunner) runner, args);
        }
        if (runner instanceof CommandLineRunner) {
            callRunner((CommandLineRunner) runner, args);
        }
    }
}
  • 首先找出應(yīng)用上下文中所有的ApplicationRunnerCommandLineRunner复颈,實際使用時這兩個接口的實現(xiàn)類用@Component注解修飾即可;
  • 然后對這些ApplicationRunner和CommandLineRunner按升序排序沥割,它們的實現(xiàn)類都可以選擇地實現(xiàn)Ordered接口或者用@Order注解修飾券膀;
  • 最后按順序調(diào)用這些Runner的run回調(diào)方法。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末驯遇,一起剝皮案震驚了整個濱河市芹彬,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌叉庐,老刑警劉巖舒帮,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異陡叠,居然都是意外死亡玩郊,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進店門枉阵,熙熙樓的掌柜王于貴愁眉苦臉地迎上來译红,“玉大人,你說我怎么就攤上這事兴溜≌旌瘢” “怎么了?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵拙徽,是天一觀的道長刨沦。 經(jīng)常有香客問我,道長膘怕,這世上最難降的妖魔是什么想诅? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上来破,老公的妹妹穿的比我還像新娘篮灼。我一直安慰自己,他們只是感情好徘禁,可當(dāng)我...
    茶點故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布诅诱。 她就那樣靜靜地躺著,像睡著了一般晌坤。 火紅的嫁衣襯著肌膚如雪逢艘。 梳的紋絲不亂的頭發(fā)上旦袋,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天骤菠,我揣著相機與錄音,去河邊找鬼疤孕。 笑死商乎,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的祭阀。 我是一名探鬼主播鹉戚,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼专控!你這毒婦竟也來了抹凳?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤伦腐,失蹤者是張志新(化名)和其女友劉穎赢底,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體柏蘑,經(jīng)...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡幸冻,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了咳焚。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片洽损。...
    茶點故事閱讀 38,163評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖革半,靈堂內(nèi)的尸體忽然破棺而出碑定,到底是詐尸還是另有隱情,我是刑警寧澤又官,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布不傅,位于F島的核電站,受9級特大地震影響赏胚,放射性物質(zhì)發(fā)生泄漏访娶。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一觉阅、第九天 我趴在偏房一處隱蔽的房頂上張望崖疤。 院中可真熱鬧秘车,春花似錦、人聲如沸劫哼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽权烧。三九已至眯亦,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間般码,已是汗流浹背妻率。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留板祝,地道東北人宫静。 一個月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像券时,于是被迫代替她去往敵國和親孤里。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,925評論 2 344

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