《Spring Boot極簡教程》第3章 一鍵啟動應(yīng)用程序

第3章 一鍵啟動應(yīng)用程序

3.1 SpringBoot版的Restful Hello,World

3.1.1 Spring Boot CLI groovy版Hello World

首先安裝Spring Boot CLI,參考文檔:http://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#getting-started-installing-the-cli

在命令行驗(yàn)證spring環(huán)境安裝成功:

$ spring --version
Spring CLI v1.3.6.RELEASE

隨便打開編輯器,敲入如下代碼:

 @Controller
 class HelloController{
      @RequestMapping("/hello")
      @ResponseBody
      String hello(){
          return "Hello World!"
      }
 }

然后在命令行執(zhí)行:

$ spring run HelloController.groovy

你將看到如下輸出日志:

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.3.6.RELEASE)

2017-04-04 21:37:29.685  INFO 87317 --- [       runner-0] o.s.boot.SpringApplication               : Starting application on jacks-MacBook-Air.local with PID 87317 (/Users/jack/.m2/repository/org/springframework/boot/spring-boot/1.3.6.RELEASE/spring-boot-1.3.6.RELEASE.jar started by jack in /Users/jack/book)
2017-04-04 21:37:29.699  INFO 87317 --- [       runner-0] o.s.boot.SpringApplication               : No active profile set, falling back to default profiles: default
2017-04-04 21:37:30.367  INFO 87317 --- [       runner-0] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@30cc0018: startup date [Tue Apr 04 21:37:30 CST 2017]; root of context hierarchy
2017-04-04 21:37:33.757  INFO 87317 --- [       runner-0] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat initialized with port(s): 8080 (http)
2017-04-04 21:37:33.790  INFO 87317 --- [       runner-0] o.apache.catalina.core.StandardService   : Starting service Tomcat
2017-04-04 21:37:33.793  INFO 87317 --- [       runner-0] org.apache.catalina.core.StandardEngine  : Starting Servlet Engine: Apache Tomcat/8.0.36
2017-04-04 21:37:33.986  INFO 87317 --- [ost-startStop-1] org.apache.catalina.loader.WebappLoader  : Unknown loader org.springframework.boot.cli.compiler.ExtendedGroovyClassLoader$DefaultScopeParentClassLoader@67cc18dd class org.springframework.boot.cli.compiler.ExtendedGroovyClassLoader$DefaultScopeParentClassLoader
2017-04-04 21:37:34.037  INFO 87317 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2017-04-04 21:37:34.038  INFO 87317 --- [ost-startStop-1] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 3673 ms
2017-04-04 21:37:34.538  INFO 87317 --- [ost-startStop-1] o.s.b.c.e.ServletRegistrationBean        : Mapping servlet: 'dispatcherServlet' to [/]
2017-04-04 21:37:34.552  INFO 87317 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean  : Mapping filter: 'characterEncodingFilter' to: [/*]
2017-04-04 21:37:34.553  INFO 87317 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean  : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
2017-04-04 21:37:34.553  INFO 87317 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean  : Mapping filter: 'httpPutFormContentFilter' to: [/*]
2017-04-04 21:37:34.553  INFO 87317 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean  : Mapping filter: 'requestContextFilter' to: [/*]
2017-04-04 21:37:35.309  INFO 87317 --- [       runner-0] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@30cc0018: startup date [Tue Apr 04 21:37:30 CST 2017]; root of context hierarchy
2017-04-04 21:37:35.482  INFO 87317 --- [       runner-0] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/hello]}" onto public java.lang.String HelloController.hello()
2017-04-04 21:37:35.485  INFO 87317 --- [       runner-0] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2017-04-04 21:37:35.486  INFO 87317 --- [       runner-0] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=[text/html]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
2017-04-04 21:37:35.523  INFO 87317 --- [       runner-0] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2017-04-04 21:37:35.524  INFO 87317 --- [       runner-0] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2017-04-04 21:37:35.606  INFO 87317 --- [       runner-0] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2017-04-04 21:37:36.225  INFO 87317 --- [       runner-0] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2017-04-04 21:37:36.397  INFO 87317 --- [       runner-0] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)
2017-04-04 21:37:36.403  INFO 87317 --- [       runner-0] o.s.boot.SpringApplication               : Started application in 8.263 seconds (JVM running for 12.897)

一個極簡的Restful Hello World就搞定了。瀏覽器訪問:http://localhost:8080/hello(或者curl http://localhost:8080/hello

你會看到輸出:

Hello World!

3.1.2 常規(guī)的Java版的Hello World

1.命令行輸入:

$ spring init -dweb --build gradle
Using service at https://start.spring.io

Content saved to 'demo.zip'

2.解壓zip何址,導(dǎo)入idea中

你將看到如下一個Demo Application

.
├── build.gradle
├── gradle
│   └── wrapper
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── src
    ├── main
    │   ├── java
    │   │   └── com
    │   │       └── example
    │   │           └── DemoApplication.java
    │   └── resources
    │       ├── application.properties
    │       ├── static
    │       └── templates
    └── test
        └── java
            └── com
                └── example
                    └── DemoApplicationTests.java

14 directories, 8 files

其中DemoApplication.java代碼如下:

package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DemoApplication {

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

build.gradle配置文件如下:

buildscript {
    ext {
        springBootVersion = '1.5.2.RELEASE'
    }
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'

version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8

repositories {
    mavenCentral()
}


dependencies {
    compile('org.springframework.boot:spring-boot-starter-web')
    testCompile('org.springframework.boot:spring-boot-starter-test')
}

OK,我們直接Run

Run

同樣的位仁,你講看到如下日志:

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.5.2.RELEASE)

2017-04-04 22:51:58.995  INFO 89676 --- [           main] com.example.DemoApplication              : Starting DemoApplication on jacks-MacBook-Air.local with PID 89676 (/Users/jack/book/demo/build/classes/main started by jack in /Users/jack/book/demo)
2017-04-04 22:51:58.998  INFO 89676 --- [           main] com.example.DemoApplication              : No active profile set, falling back to default profiles: default
2017-04-04 22:51:59.191  INFO 89676 --- [           main] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@6950e31: startup date [Tue Apr 04 22:51:59 CST 2017]; root of context hierarchy
2017-04-04 22:52:02.718  INFO 89676 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat initialized with port(s): 8080 (http)
2017-04-04 22:52:02.750  INFO 89676 --- [           main] o.apache.catalina.core.StandardService   : Starting service Tomcat
2017-04-04 22:52:02.754  INFO 89676 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet Engine: Apache Tomcat/8.5.11
2017-04-04 22:52:02.974  INFO 89676 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2017-04-04 22:52:02.974  INFO 89676 --- [ost-startStop-1] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 3788 ms
2017-04-04 22:52:03.173  INFO 89676 --- [ost-startStop-1] o.s.b.w.servlet.ServletRegistrationBean  : Mapping servlet: 'dispatcherServlet' to [/]
2017-04-04 22:52:03.198  INFO 89676 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'characterEncodingFilter' to: [/*]
2017-04-04 22:52:03.199  INFO 89676 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
2017-04-04 22:52:03.199  INFO 89676 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'httpPutFormContentFilter' to: [/*]
2017-04-04 22:52:03.200  INFO 89676 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'requestContextFilter' to: [/*]
2017-04-04 22:52:03.758  INFO 89676 --- [           main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@6950e31: startup date [Tue Apr 04 22:51:59 CST 2017]; root of context hierarchy
2017-04-04 22:52:03.931  INFO 89676 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2017-04-04 22:52:03.932  INFO 89676 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=[text/html]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
2017-04-04 22:52:03.986  INFO 89676 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2017-04-04 22:52:03.987  INFO 89676 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2017-04-04 22:52:04.076  INFO 89676 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2017-04-04 22:52:04.334  INFO 89676 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2017-04-04 22:52:04.447  INFO 89676 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)
2017-04-04 22:52:04.455  INFO 89676 --- [           main] com.example.DemoApplication              : Started DemoApplication in 6.41 seconds (JVM running for 7.608)

通過日志蛇券,我們可以看到SpringBootApplication大致的啟動流程煤率。這個過程中,很大一部分工作是在做Spring應(yīng)用的初始化蜕依。

Initializing Spring embedded WebApplicationContext

同時,嵌入式的servlet容器tomcat的啟動,org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext是關(guān)鍵類样眠。對細(xì)節(jié)感興趣的可以直接debug一下源碼友瘤。

上面的代碼雖然看不出什么效果。但SpringBootApplication的基本架構(gòu)流程已經(jīng)基本在里面了檐束。SpringBoot通過注解@SpringBootApplication辫秧,完成了自動配置的工作。(源碼參見: org.springframework.boot.autoconfigure.SpringBootApplication)

3.寫個HelloWorldController

package com.example.controller;

import java.util.Date;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * Created by jack on 2017/4/4.
 */
@RestController
public class HelloWorldController {
    @RequestMapping("hello")
    String hello() {
        return "Hello World! " + new Date();
    }
}

直接Run DemoApplication被丧,我們可以看到有如下的日志:

...
Mapped "{[/hello]}" onto java.lang.String com.example.controller.HelloWorldController.hello()
...

瀏覽器訪問:http://localhost:8080/hello

我們將看到如下輸出:

Hello World! Tue Apr 04 23:08:33 CST 2017

另外盟戏,如果是在命令行運(yùn)行,使用SpringBoot gradle插件的執(zhí)行:

$gradle bootRun

使用SpringBoot maven插件的執(zhí)行:

$mvn spring-boot:run

3.2 啟動Springboot的自動配置@EnableAutoConfiguration

SpringBoot AutoConfiguration的原理是通過Spring的@Conditional注解實(shí)現(xiàn)甥桂。@Conditional以編程的方式確定的bean的狀態(tài)柿究,來決定哪些bean是要初始化到容器中(根據(jù)外界條件注冊不同Bean)。

@EnableAutoConfiguration的意思是啟用Spring應(yīng)用程序上下文的自動配置黄选,通過掃描CLASSPATH里面所有的組件蝇摸,然后基于條件來決定是否注冊bean來使得Spring的ApplicationContext自動配置。

比如說办陷,Spring檢測jdbcTemplate是否在classpath貌夕?if true, 看看bean容器里面是否有DataSource ? if true, 配置一個JdbcTemplate的Bean到容器中。

看EnableAutoConfiguration源碼:

package org.springframework.boot.autoconfigure;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Import;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({EnableAutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    Class<?>[] exclude() default {};

    String[] excludeName() default {};
}

可以發(fā)現(xiàn)民镜,這里Import了EnableAutoConfigurationImportSelector啡专。
Import主要是配合Configuration來使用的,用來導(dǎo)出更多的Configuration類殃恒,ConfigurationClassPostProcessor會讀取Import的內(nèi)容來實(shí)現(xiàn)具體的邏輯植旧。
EnableAutoConfigurationImportSelector實(shí)現(xiàn)了DeferredImportSelector接口,并實(shí)現(xiàn)了selectImports方法,用來導(dǎo)出Configuration類离唐。

String[] selectImports(AnnotationMetadata importingClassMetadata);
導(dǎo)出的類是通過SpringFactoriesLoader.loadFactoryNames()讀取了ClassPath下面的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.websocket.WebSocketAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.WebSocketMessagingAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration

其中EmbeddedServletContainerAutoConfiguration是實(shí)現(xiàn)web服務(wù)的主要的配置類。這個類會根據(jù)當(dāng)前存在的類的信息注入必要的EmbeddedServletContainerFactory類亥鬓。
spring-boot-starter-tomcat引入了tomcat的依賴完沪,所以EmbeddedServletContainerAutoConfiguration發(fā)現(xiàn)存在Tomcat.class就會注入TomcatEmbeddedServletContainerFactory來內(nèi)置web容器。

當(dāng)然嵌戈,如果你想排除一些bean的自動注入覆积,你可以用Class<?>[] exclude() 或者String[] excludeName()。

SpringBoot在spring-boot-autoconfigure-{版本號}.jar里面提供了很多AutoConfiguration的類來負(fù)責(zé)注冊各種不同的組件熟呛。這些組件幾乎涵蓋了Spring開發(fā)中需要的絕大部分的配置宽档。

本章源代碼

https://github.com/EasySpringBoot/HelloWorld/tree/hello_world_2017.4.4

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市庵朝,隨后出現(xiàn)的幾起案子吗冤,更是在濱河造成了極大的恐慌又厉,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,430評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件椎瘟,死亡現(xiàn)場離奇詭異覆致,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)肺蔚,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,406評論 3 398
  • 文/潘曉璐 我一進(jìn)店門煌妈,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人宣羊,你說我怎么就攤上這事璧诵。” “怎么了段只?”我有些...
    開封第一講書人閱讀 167,834評論 0 360
  • 文/不壞的土叔 我叫張陵腮猖,是天一觀的道長。 經(jīng)常有香客問我赞枕,道長澈缺,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,543評論 1 296
  • 正文 為了忘掉前任炕婶,我火速辦了婚禮姐赡,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘柠掂。我一直安慰自己项滑,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,547評論 6 397
  • 文/花漫 我一把揭開白布涯贞。 她就那樣靜靜地躺著枪狂,像睡著了一般。 火紅的嫁衣襯著肌膚如雪宋渔。 梳的紋絲不亂的頭發(fā)上州疾,一...
    開封第一講書人閱讀 52,196評論 1 308
  • 那天,我揣著相機(jī)與錄音皇拣,去河邊找鬼严蓖。 笑死,一個胖子當(dāng)著我的面吹牛氧急,可吹牛的內(nèi)容都是我干的颗胡。 我是一名探鬼主播,決...
    沈念sama閱讀 40,776評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼吩坝,長吁一口氣:“原來是場噩夢啊……” “哼毒姨!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起钉寝,我...
    開封第一講書人閱讀 39,671評論 0 276
  • 序言:老撾萬榮一對情侶失蹤弧呐,失蹤者是張志新(化名)和其女友劉穎鸳址,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體泉懦,經(jīng)...
    沈念sama閱讀 46,221評論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,303評論 3 340
  • 正文 我和宋清朗相戀三年疹瘦,在試婚紗的時候發(fā)現(xiàn)自己被綠了崩哩。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,444評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡言沐,死狀恐怖邓嘹,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情险胰,我是刑警寧澤汹押,帶...
    沈念sama閱讀 36,134評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站起便,受9級特大地震影響棚贾,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜榆综,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,810評論 3 333
  • 文/蒙蒙 一妙痹、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧鼻疮,春花似錦怯伊、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,285評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至挪哄,卻和暖如春吧秕,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背中燥。 一陣腳步聲響...
    開封第一講書人閱讀 33,399評論 1 272
  • 我被黑心中介騙來泰國打工寇甸, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人疗涉。 一個月前我還...
    沈念sama閱讀 48,837評論 3 376
  • 正文 我出身青樓拿霉,卻偏偏與公主長得像,于是被迫代替她去往敵國和親咱扣。 傳聞我的和親對象是個殘疾皇子绽淘,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,455評論 2 359

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