maven---7生命周期和插件

  • 所有项目的构建都是有生命周期的,这个生命周期包括:项目清理、初始化、编译、测试、打包、集成测试、验证、部署、站点生成等几乎所有的构建步骤。就像人的生命周期都有出生、少年、青年、壮年、老年、死亡这些阶段,而在这些阶段我们都可以做不同的事情,比如青年阶段就可以结婚了。

  • Maven作为项目构建工具,它也具有这样的生命周期。比如执行mvn package就表示执行默认生命周期阶段package。maven的生命周期是抽象的,生命周期中包含多个有次序的阶段,每个阶段都可能会执行多个行为,而实际行为都是由插件来完成的,如package阶段的任务可能就是由maven-jar-plugin插件完成,在maven中生命周期和插件两者协同工作,密不可分。

1Maven生命周期设计思想(模板方法模式)

  • Maven的生命周期是抽象的,这意味着生命周期本身不做任何实际工作,在maven的设计中,实际的任务(如编译源代码)都交由插件来完成。这种设计思想如设计模式中的模板方法相似。
  • 模板方法模式在父类中定义算法的整体结构,子类可以通过实现或者重写父类的方法来控制实际的行为,这样既保证了算法有足够的可扩展性,又严格控制算法的整体结构,如下模板方法抽象类能够很好地体现Maven声明周期概念:
package com.cnu.offline.template.method;

public abstract class AbstractBuild {
    /**
     * 项目构建
     */
    public void build(){
        initialize();
        compile();
        test();
        packagee();
        integrationTest();
        deploy();
    }

    protected abstract void initialize();
    protected abstract void compile();
    protected abstract void test() ;
    protected abstract void packagee();
    protected abstract void integrationTest();
    protected abstract void deploy();
}
  • 这段代码非常简单,build()方法定义了整个构建过程,依次初始化、编译、测试、打包、集成测试、部署行为,它们都交由子类去实现。上述代码和maven虽然相差甚远,但是这种思想和maven很相似。
  • maven的生命周期更多更复杂。maven声明周期抽象了构建的各个步骤,定义了次序,但是没有提供具体实现,那么谁来实现呢?插件。生命周期的每个阶段都可以绑定一个或者多个插件行为,而且maven为大多数构建步骤绑定了默认的插件。比如:针对编译的插件有maven-compileer-plugin,针对测试的插件有maven-surefire-plugin等。
  • maven的生命周期和插件机制一方面保证了所有maven项目都有一致的构建标准(步骤)。另一方面有通过默认插件简化和稳定了实际项目的构建,此外,该机制还提供了足够的扩展空间,用户可以通过配置现有插件或者自行编写插件来完成自定义构建行为。

2生命周期详解

2.1三套生命周期

  • 项目构建的生命周期一般包括:项目清理、初始化、编译、测试、打包、集成测试、验证、部署、站点生成等阶段,但是maven为了更方便构建,maven使用了3套相互独立的生命周期来包含这些阶段,名称分别为:clean、default、site。clean生命周期的目的是清理项目,default生命周期目的是构建项目,site生命周期是建立项目站点。3套生命周期相互独立。每套生命周期中各个阶段都是由次序关系,后面阶段执行前必须先执行前面的阶段,就像人在死亡前他必须经历出生、少年、青年、壮年、老年这些阶段。
  • 每个生命周期都包含多个阶段,但是maven默认不是在所有阶段上都绑定了插件行为,只是在关键的周期阶段绑定了插件行为,没绑定插件行为的阶段在构建时就不会执行

2.2clean生命周期

  • clean生命周期的目的是清理项目,包含阶段:
  • 1 pre-clean 执行一些清理前需要完成的工作。
  • 2 clean(绑定默认插件目标maven-clean-plugin:clean) 清理上一次构建生成的文件(target/目录)
  • 3 post-clean 执行一些清理后需要完成的工作。

2.3default生命周期

  • default生命周期定义了真正构建时所需要执行的所有步骤,最核心生命周期。包含阶段,默认绑定的插件与打包类型packaging有关,下面以jar为例:

  • 1.validate:validate the project is correct and all necessary information is available.

  • 2.initialize:initialize build state, e.g. set properties or create directories.

  • 3.generate-sources:generate any source code for inclusion in compilation.

  • 4.process-sources :process the source code, for example to filter any values.

  • 5.genrate-resources:generate resources for inclusion in the package.

  • 6.process-resources(绑定默认插件目标maven-resources-plugin:resources)处理项目主资源文件,一般来说,是对src/main/resources目录的内容进行变量替换等工作后,然后复制到项目输出的主classpath目录中。

  • 7.compile(绑定默认插件目标maven-compiler-plugin:compile)编译项目的主源码,编译src/main/java目录下的Java文件至项目输出的主classpath目录中。

  • 8.process-classes:post-process the generated files from compilation, for example to do bytecode enhancement on Java classes.

  • 9.generate-test-sources:generate any test source code for inclusion in compilation.

  • 10.process-test-sources:process the test source code, for example to filter any values.

  • 11.generate-test-resources:create resources for testing.

  • 12.process-test-resources(绑定默认插件目标maven-resources-plugin:testResources)处理项目测试资源文件,一般来说,是对src/test/resources目录的内容进行变量替换等工作后,然后复制到项目输出的测试classpath目录中。

  • 13.test-compile(绑定默认插件目标maven-compiler-plugin:testCompile)编译项目的测试代码。一般来说,是编译src/test/java目录下的Java文件至项目输出的测试classpath目录中。

  • 14.process-test-classes:post-process the generated files from test compilation, for example to do bytecode enhancement on Java classes. For Maven 2.0.5 and above.

  • 15.test(绑定默认插件目标maven-surefire-plugin:test)使用单元测试框架运行测试,测试代码不会被打包部署。

  • 16.prepare-package:perform any operations necessary to prepare a package before the actual packaging. This often results in an unpacked, processed version of the package. (Maven 2.1 and above)

  • 17.package(绑定默认插件目标maven-jar-plugin:jar)接受编译好的代码,打包成可发布的格式,如JAR

  • 18.pre-integraton-test:perform actions required before integration tests are executed. This may involve things such as setting up the required environment.

  • 19.integration-test:process and deploy the package if necessary into an environment where integration tests can be run.

  • 20.post-integration-test: perform actions required after integration tests have been executed. This may including cleaning up the environment.

  • 21.verify:run any checks to verify the package is valid and meets quality criteria.

  • 22.install(绑定默认插件目标maven-install-plugin:install)将包安装到Maven仓库,供本地项目使用

  • 23.deploy(绑定默认插件目标maven-deploy-plugin:deploy)将最终的包复制到远程仓库,供其他开发人员和maven项目使用。部署至远程仓库配置

  • 执行mvn命令到某一个生命周期

mvn clean test-compile

进一步了解其他阶段信息

2.4site生命周期

  • site生命周期是建立和发布项目站点,maven能够基于POM所包含的信息自动生成一个友好的站点,方便团队交流和发布项目信息。
  • 1.pre-site : execute processes needed prior to the actual project site generation
  • 2.site :(绑定默认插件目标maven-site-plugin:site) generate the project's site documentation
  • 3.post-site :execute processes needed to finalize the site generation, and to prepare for site deployment
  • 4.site-deploy:(绑定默认插件目标maven-site-plugin:deploy) deploy the generated site documentation to the specified web server

2.5命令行与生命周期

  • 从命令行执行Maven任务最主要的方式就是调用Maven声明周期阶段

  • $mvn clean:该命令调用clean生命周期的clean阶段,实际执行的阶段为clean生命周期的pre-clean 和clean阶段

  • $mvn clean deploy site-deploy:该命令调用clean生命周期的clean阶段、default周期的deploy阶段、site生命周期的site-deploy阶段。实际执行clean生命周期pre-clean,clean 阶段,default生命周期所有阶段,site生命周期所有阶段。

  • 执行mvn clean install所执行的任务。

E:\Repository\iqasproject\iqasweb>mvn clean install
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building iqasweb Maven Webapp 0.0.1-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ iqasweb ---
[INFO] Deleting E:\Repository\iqasproject\iqasweb\target
[INFO]
[INFO] --- maven-resources-plugin:2.7:resources (default-resources) @ iqasweb --
-
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 14 resources
[INFO]
[INFO] --- maven-compiler-plugin:3.3:compile (default-compile) @ iqasweb ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 388 source files to E:\Repository\iqasproject\iqasweb\target\cl
asses
[INFO]
[INFO] --- maven-resources-plugin:2.7:testResources (default-testResources) @ iq
asweb ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory E:\Repository\iqasproject\iqasweb\src
\test\resources
[INFO]
[INFO] --- maven-compiler-plugin:3.3:testCompile (default-testCompile) @ iqasweb
 ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 8 source files to E:\Repository\iqasproject\iqasweb\target\test
-classes
[INFO]
[INFO] --- maven-surefire-plugin:2.19:test (default-test) @ iqasweb ---
[INFO] Tests are skipped.
[INFO]
[INFO] --- maven-war-plugin:2.1.1:war (default-war) @ iqasweb ---
[INFO] Packaging webapp
[INFO] Assembling webapp [iqasweb] in [E:\Repository\iqasproject\iqasweb\target\
iqasweb]
[INFO] Processing war project
[INFO] Copying webapp resources [E:\Repository\iqasproject\iqasweb\src\main\weba
pp]
[INFO] Webapp assembled in [7681 msecs]
[INFO] Building war: E:\Repository\iqasproject\iqasweb\target\iqasweb.war
[WARNING] Warning: selected war files include a WEB-INF/web.xml which will be ig
nored
(webxml attribute is missing from war task, or ignoreWebxml attribute is specifi
ed as 'true')
[INFO]
[INFO] --- maven-install-plugin:2.4:install (default-install) @ iqasweb ---
[INFO] Installing E:\Repository\iqasproject\iqasweb\target\iqasweb.war to D:\Sof
t\maven\maven_jar\repository\com\cnu\iqas\iqasweb\0.0.1-SNAPSHOT\iqasweb-0.0.1-S
NAPSHOT.war
[INFO] Installing E:\Repository\iqasproject\iqasweb\pom.xml to D:\Soft\maven\mav
en_jar\repository\com\cnu\iqas\iqasweb\0.0.1-SNAPSHOT\iqasweb-0.0.1-SNAPSHOT.pom

[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------

3插件目标

  • Maven的核心仅仅定义了抽象生命周期,具体工作由插件完成,对于插件本身,为了能够实现代码复用,它往往能完成多个任务,也就具有多个功能,这些功能聚集在一个插件中,每个功能就叫做一个插件目标
  • maven-dependency-plugin插件有十多个目标,每个目标对应一个功能,目标有:dependency:analyze、dependency:tree、dependency:list。这是一种通用写法,冒号前面是插件前缀,后面是插件目标

4插件绑定

我们把插件的目标绑定到maven生命周期的某个阶段用于完成实际任务,叫做插件绑定。例如:maven-compiler-plugin这一插件的compile目标能够完成项目编译这一任务,因此就把该目标绑定到default生命周期的compile这一阶段。

4.1内置绑定

  • Maven在核心的生命周期阶段绑定了很多插件的目标,当用户通过命令调用生命周期阶段的时候对应的插件目标就会执行相应的任务。

  • 对于默认的插件绑定在上面介绍3个生命周期时已经有备注了,下面对default生命周期的内置插件绑定关系做一些说明。
    default生命周期与插件的绑定关系由打包类型决定packaging,因为jar项目需要打包成JAR包,war项目需要打包成war包,还有pom、maven-plugin、ear等类型。
    default生命周期插件目标绑定关系

4.2自定义绑定

除了内置绑定外,用户还可以选择将某个插件目标绑定到生命周期的某个阶段。

需求:创建项目源码包
*内置的插件没有涉及这一任务,需要自己配置。

  • 1.确定插件目标:
    去哪里找插件?基本上所有的插件插件都来源于ApacheCodehaus
    首先通过查看插件发现maven-source-plugin插件的jar-no-fork目标能够将项目的主代码(包括主资源)打包成jar文件,
  • 2.确定生命周期阶段:
    查看生命周期各阶段介绍,发现绑定到default生命周期的verify阶段上,在执行完集成测试后和安装构件之前创建源码jar包。
  • 在pom.xml中配置
<build>
   <plugins>
         <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-source-plugin</artifactId>
                 <version>2.1.1</version>
                 <executions>
                    <execution>
                     <id>my-attach-sources</id>
                     <phase>verify</phase>
                     <goals>
                        <goal>jar-no-fork</goal>
                     </goals>
                    </execution>
                 </executions>
            </plugin>
    </plugins>
</build>

解释

  • groupId为org.apache.maven.plugins(maven官方插件),凡是gourpId为org.apache.maven.plugins可以省略(不推荐).

  • executions下每个execution元素可以用来配置执行一个任务,该例中配置一个id为my-attach-sources的任务。

  • 通过phase将其绑定到verify生命周期阶段上。不配置phase也会执行目标(不推荐),因为该插件编写时默认阶段是package,而package在install之前.

  • 通过goals配置指定要执行的插件目标。

  • 执行mvn clean install,多了如下两行,生成了源码包iqasweb-sources.jar。

[INFO] --- maven-source-plugin:2.1.1:jar-no-fork (my-attach-sources) @ iqasweb ---
[INFO] Building jar: E:\Repository\iqasproject\iqasweb\target\iqasweb-sources.jar
  • 多个目标被绑定到同一目标上时,执行顺序安装声明先后执行。

5插件配置

  • 插件和生命周期绑定后,用户可以配置插件和插件目标的参数,进一步刁征插件目标所执行的任务,以满足项目需求,几乎所有的Maven插件都有一些参数。

  • 用户可以通过命令行或者POM来配置插件参数,注意,命令行和POM配置的参数的名称一般都不一样。就需要查看插件的信息,下面有介绍。

  • 配置之前需要查看插件有哪些参数可以配置,配置参数的意义。

5.1命令行插件配置

  • 很多插件参数都支持命令行配置。但不是所有。
  • 在Maven命令中使用-D参数,并伴随一个key=value的形式来配置插件目标参数。

案例
maven-surefire-plugin提供了一个maven.test.skip的命令行参数(在pom中参数为skip),当其值为true时会跳过执行测试。

$mvn install -Dmaven.test.skip=true

5.2POM中插件全局配置

  • 对于参数很少改变的配置应该配置在pom中。

  • 对插件的配置有全局配置和对插件目标的配置。全局配置就是所有基于该插件目标的任务,都会使用这些配置。对插件目标配置就只是对该目标的任务执行该配置。

案例

  • 配置maven-compiler-plugin告诉它编译java1.7版本的源文件,生成与JVM1.7兼容的字节码文件。
  • 配置之前查看插件有哪些全局配置参数参数配置
<plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>3.3</version>
          <configuration>
           <!-- if you want to use the Java 8 language features (-source 1.8) and also want the compiled classes to be compatible with JVM 1.8 (-target 1.8),  -->
              <source>1.7</source>
              <target>1.7</target>
          </configuration>
      </plugin>
  • 全局配置在plugin下面的configuration
  • 这样不管绑定到compile阶段的maven-compiler-plugin:compile任务,还是绑定到testCompiler阶段maven-compiler-plugin:testCompiler任务,就都是用该配置。

5.3POM中插件任务配置

  • 为插件的某个目标配置参数,配置在plugin/executions/execution下面的configuration
 <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>3.3</version>
          <executions>
            <execution>
                <id>my-compile</id>
                <phase>compile</phase>
                <goals>
                   <goal>compile</goal>
                </goals>
                <configuration>
              <source>1.7</source>
              <target>1.7</target>
          </configuration>
            </execution>
          </executions>
      </plugin>

6获取插件信息

  • 去哪里寻找合适的插件?,详细了解插件的配置点?

  • maven插件很多,大部分没有完善的文档,一般maven官方的插件文档比较完善一点。

  • 获取插件信息有两种,取网站上查看插件信息,如果网站信息没有,就通过调用命令行来获取插件的描述信息。

6.1在线插件信息

  • 基本上所有的插件插件都来源于ApacheCodehaus

  • 以maven官方插件查看为例。查看maven-surefire-plugin为例。如下图:

    maven-surefire-plugin插件官方介绍

一般来说,通过阅读插件文档中的使用介绍和实例就可以很好的使用插件了,如果想要了解目标参数,就需要访问插件的每个目标的文档。

6.2使用maven-help-plugin描述插件

  • maven-help-plugin插件的describe目标可以查看插件的详细信息。更多目标
  • 用-D参数,并伴随一个key=value的形式来配置查询参数。

常见使用方法

案例:查看maven-compiler-plugin的信息
方法1:
mvn help:describe -Dplugin=org.apache.maven.plugins:maven-compiler-plugin:2.1

Name: Maven Compiler Plugin
Description: The Compiler Plugin is used to compile the sources of your
  project.
Group Id: org.apache.maven.plugins
Artifact Id: maven-compiler-plugin
Version: 2.1
Goal Prefix: compiler

This plugin has 3 goals:

compiler:compile
  Description: Compiles application sources

compiler:help
  Description: Display help information on maven-compiler-plugin.
    Call
      mvn compiler:help -Ddetail=true -Dgoal=<goal-name>
    to display parameter details.
  
compiler:testCompile
  Description: Compiles application test sources.

方法2:
使用前缀,maven-compiler-plugin的前缀为compiler,在描述插件时可以省略版本信息,maven就会获取最新版的进行表达:
mvn help:describe -Dplugin=compiler

方法3:
如果想输出更详细信息,包括插件每个目标的参数,加上detail
mvn help:describe -Dplugin=compiler -Ddetail.

7从命令行调用插件

如果在命令行运行mvn -h来显示mvn命令帮助,就可以看到如下信息:告诉了mvn命令的基本用法

usage: mvn [options] [<goal(s)>] [<phase(s)>]
  • options:表示可用的选项,mvn有20多个。如 mvn -v
  • phase生命周期阶段,如 mvn clean install
  • goal插件目标,支持直接从命令行调用插件目标。因为有些任务不适合绑定在生命周期上,如maven-help-plugin:describe,我们不需要再构建项目的时候去显示依赖树。
    • 调用案例如:mvn dependency:tree
      mvn help:describe -Dplugin=compiler
      help是maven-help-plugin插件的前缀。

8插件解析机制

8.1插件仓库

  • 与依赖构件一样,插件构件同样基于坐标存在于Maven仓库中,需要时从本地仓库寻找,不存在,从远程仓库下载到本地使用。
  • 插件仓库使用pluginRepositories和pluginRepository配置,和依赖的中央仓库类似,Maven在超级POM中同样内置了插件远程仓库配置:
<pluginRepositories>
    <pluginRepository>
      <id>central</id>
      <name>Central Repository</name>
      <url>https://repo.maven.apache.org/maven2</url>
      <layout>default</layout>
      <snapshots>
        <enabled>false</enabled>
      </snapshots>
      <releases>
        <updatePolicy>never</updatePolicy>
      </releases>
    </pluginRepository>
  </pluginRepositories>
  • 当然maven配置的中央插件仓库已经能满足我们需求,但是如果使用的插件找不到,也可以配置其它,在POM中或者settings.xml(在settings.xml配置在nexus私服中介绍)中加入其它插件仓库配置。

8.2插件的默认groupId

  • 在pom中配置插件时,如果该插件时maven官方插件(即groupid=org.apache.maven.plugins),可以省略(不推荐)

8.3解析插件版本。

  • 同样为了简化插件配置和使用,在用户没有提供插件版本情况下,maven会自动解析插件版本。
  • 情况1:核心插件。超级POM中有配置。
    超级POM中为所有核心插件设定了版本,所有maven项目都会继承超级pom,所以在maven项目中使用核心插件的版本已经确定了。
  • 情况2:非核心插件,超级POM中没有配置
    使用的插件不是核心插件,超级POM中没有配置,maven会去所有仓库寻找可用版本,然后选择release版,即最新发布版(而不是latest最新版,最新版有可能是快照版)
Paste_Image.png
Paste_Image.png

8.4插件前缀

  • 前面使用mvn命令时支持插件前缀来简化插件的调用,现在解释如何根据插件前缀解析得到插件的坐标。
  • 插件前缀与groupId:artifactId是一一对应的,这种匹配关系存储在仓库元数据中。
Paste_Image.png
Paste_Image.png

有什么不懂的一起探讨一下吧,我也是在学习的路上。喜欢给我点个赞吧(哈哈),我会继续努力的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 156,757评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,478评论 1 289
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,540评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,593评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,903评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,329评论 1 210
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,659评论 2 309
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,383评论 0 195
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,055评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,337评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,864评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,227评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,820评论 3 231
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,999评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,750评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,365评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,260评论 2 258

推荐阅读更多精彩内容