Spring Boot 2.0 Migration Guide

This document is meant to help you migrate your application to Spring Boot 2.0 by providing thematic sections that mirror the developer guide.

Before You Start

First, Spring Boot 2.0 requires Java 8 or later. Java 6 and 7 are no longer supported.

With Spring Boot 2.0, many configuration properties were renamed/removed and developers need to update their application.properties/application.yml accordingly. To help you with that, Spring Boot ships a new spring-boot-properties-migrator module. Once added as a dependency to your project, this will not only analyze your application’s environment and print diagnostics at startup, but also temporarily migrate properties at runtime for you. This is a must have during your application migration:

<dependency>

    <groupId>org.springframework.boot</groupId>

    <artifactId>spring-boot-properties-migrator</artifactId>

    <scope>runtime</scope>

</dependency>

runtime("org.springframework.boot:spring-boot-properties-migrator")

Note:Once you’re done with the migration, please make sure to remove this module from your project’s dependencies.

If you wish to look into specifics, here’s a curated list of resources - otherwise, proceed to the next sections:

Spring Boot 2.0.0 Release Notes

Running Spring Boot on Java 9

Building Your Spring Boot Application

Spring Boot Maven plugin

The plugin configuration attributes that are exposed as properties now all start with a spring-boot prefix for consistency and to avoid clashes with other plugins.

For instance, the following command enables the prod profile using the command line:

mvn spring-boot:run -Dspring-boot.run.profiles=prod

Surefire Defaults

Custom include/exclude patterns have been aligned to latest Surefire’s defaults. If you were relying on ours, update your plugin configuration accordingly. They used to be as follows:

<plugin>

    <groupId>org.apache.maven.plugins</groupId>

    <artifactId>maven-surefire-plugin</artifactId>

    <configuration>

        <includes>

            <include>**/*Tests.java</include>

            <include>  **/*Test.java</include>

        </includes>

        <excludes>

            <exclude>**/Abstract*.java</exclude>

        </excludes>

    </configuration>

</plugin>

Tip :If you are using JUnit 5, you should downgrade Surefire to 2.19.1. The **/*Tests.java pattern is not included in this version so if you are relying on that, make sure to add it in your configuration.

Spring Boot Gradle Plugin

Spring Boot’s Gradle plugin has been largely rewritten to enable a number of significant improvements. You can read more about the plugin’s capabilities in its reference and apidocumentation.

Dependency Management

Spring Boot’s Gradle plugin no longer automatically applies the dependency management plugin. Instead, Spring Boot’s plugin now reacts to the dependency management plugin being applied by importing the correct version of the spring-boot-dependencies BOM. This gives you more control over how and when dependency management is configured.

For most applications applying the dependency management plugin will be sufficient:

apply plugin:'org.springframework.boot'

apply plugin:'io.spring.dependency-management'//<-- add this to your build.gradle

Note:The dependency management plugin remains a transitive dependency of spring-boot-gradle-plugin so there’s no need for it to be listed as a classpath dependency in your buildscript configuration.

Building Executable Jars and Wars

The bootRepackage task has been replaced with bootJar and bootWar tasks for building executable jars and wars respectively. The jar and war tasks are no longer involved.

Configuration Updates

The BootRun, BootJar, and BootWar tasks now all use mainClassName as the property to configure the name of the main class. This makes the three Boot-specific tasks consistent with each other, and also aligns them with Gradle’s own application plugin.

Spring Boot Features

Default Proxying Strategy

Spring Boot now uses CGLIB proxying by default, including for the AOP support. If you need proxy-based proxy, you’ll need to set the spring.aop.proxy-target-class to false.

SpringApplication

Web Environment

Spring Boot applications can now operates in more modes so spring.main.web-environmentproperty is now deprecated in favor of spring.main.web-application-type that provides more control.

If you want to make sure an application doesn’t start a web server you’d have to change the property to:

spring.main.web-application-type=none

Tip:there is also a setWebApplicationType on SpringApplication if you want to do that programmatically.

Spring Boot Application Events Changes

We’ve added a new event, ApplicationStartedEvent. ApplicationStartedEvent is sent after the context has been refreshed but before any application and command-line runners have been called. ApplicationReadyEvent is sent after any application and command-line runners have been called. It indicates that the application is ready to service requests.

See the updated reference documentation.

Banner

In our effort to limit the number of root namespaces that Spring Boot uses, banner-related properties have been relocated to spring.banner.

Externalized Configuration

Relaxed Binding

The rules related to relaxed binding have been tightened. Let’s assume an existing acme.my-project.my-name property:

1.All prefixes must be in kebab-case format (lower-case, hyphen separated), acme.myProjector acme.my_project are invalid - you must use acme.my-project here.

2.Property names can use kebab-case (my-name), camel-case (myName) or snake-case (my_name).

3.Environment properties (from the OS environment variables) must use the usual upper case underscore format where the underscore should only be used to separate parts of the key, ACME_MYPROJECT_MYNAME.

This new relaxed bindings as several advantages:

1.There is no need to worry about the structure of the key in @ConditionalOnProperty: as long as the key is defined in the canonical format, the supported relaxed variants will work transparently. If you were using the prefix attribute you can now simply put the full key using the name or value attributes.

2.RelaxedPropertyResolver is no longer available as the Environment takes care of that automatically: env.getProperty("com.foo.my-bar") will find a com.foo.myBar property.

The org.springframework.boot.bind package is no longer available and is replaced by the new relaxed binding infrastructure. In particular, RelaxedDataBinder and friends have been replaced with a new Binder API. The following samples binds MyProperties from the app.acme prefix.

MyProperties target = Binder.get(environment)

                                    .bind("app.acme",MyProperties.class).orElse(null);

As relaxed binding is now built-in, you can request any property without having to care about the case as long as it’s using one of the supported formats:

FlagType flagType = Binder.get(environment)

            .bind("acme.app.my-flag",FlagType.class).orElse(FlagType.DEFAULT);

@ConfigurationProperties Validation

It is now mandatory that your @ConfigurationProperties object is annotated with @Validatedif you want to turn on validation.

Configuration Location

The behavior of the spring.config.location configuration has been fixed; it previously added a location to the list of default ones, now it replaces the default locations. If you were relying on the way it was handled previously, you should now use spring.config.additional-locationinstead.

Developing Web Applications

Embedded containers package structure

In order to support reactive use cases, the embedded containers package structure has been refactored quite extensively. EmbeddedServletContainer has been renamed to WebServer and the org.springframework.boot.context.embedded package has been relocated to org.springframework.boot.web.embedded. For instance, if you were customizing the embedded Tomcat container using the TomcatEmbeddedServletContainerFactory callback interface, you should now use TomcatServletWebServerFactory.

Servlet-specific server properties

A number of server.* properties that are Servlet-specific have moved to server.servlet:

              Old property                                      New property

server.context-parameters.*                   server.servlet.context-parameters.*

server.context-path                                server.servlet.context-path

server.jsp.class-name                            server.servlet.jsp.class-name

server.jsp.init-parameters.*                   server.servlet.jsp.init-parameters.*

server.jsp.registered                              server.servlet.jsp.registered

server.servlet-path                                 server.servlet.path

Web Starter as a Transitive Dependency

Previously several Spring Boot starters were transitively depending on Spring MVC with spring-boot-starter-web. With the new support of Spring WebFlux, spring-boot-starter-mustache, spring-boot-starter-freemarker and spring-boot-starter-thymeleaf are not depending on it anymore. It is the developer’s responsibility to choose and add spring-boot-starter-web or spring-boot-starter-webflux.

Template Engines

Mustache Templates Default File Extension

The default file extension for Mustache templates was .html, it is now .mustache to align with the official spec and most IDE plugins. You can override this new default by changing the spring.mustache.suffix configuration key.

Jackson / JSON Support

In 2.0, we’ve flipped a Jackson configuration default to write JSR-310 dates as ISO-8601 strings. If you wish to return to the previous behavior, you can add spring.jackson.serialization.write-dates-as-timestamps=true to your configuration.

A new spring-boot-starter-json starter gathers the necessary bits to read and write JSON. It provides not only jackson-databind but also useful modules when working with Java8: jackson-datatype-jdk8, jackson-datatype-jsr310 and jackson-module-parameter-names. If you were manually depending on those modules, you can now depend on this new starter instead.

Spring MVC Path Matching Default Behavior Change

We’ve decided to change the default for suffix path matching in Spring MVC applications (see #11105). This feature is not enabled by default anymore, following a best practice documented in Spring Framework.

If your application expects requests like "GET /projects/spring-boot.json" to be mapped to @GetMapping("/projects/spring-boot") mappings, this change is affecting you.

For more information about this and how to mitigate that change, check out the reference documentation about path matching and content negotiation in Spring Boot.

Servlet Filters

The default dispatcher types for a Servlet Filter are now DipatcherType.REQUEST; this aligns Spring Boot’s default with the Servlet specification’s default. If you wish to map a filter to other dispatcher types, please register your Filter using a FilterRegistrationBean.

Note:Spring Security and Spring Session filters are configured for ASYNC, ERROR, and REQUEST dispatcher types.

RestTemplateBuilder

The requestFactory(ClientHttpRequestFactory) method has been replaced by a new requestFactory(Supplier requestFactorySupplier) method. The use of a Supplier allows every template produced by the builder to use its own request factory, thereby avoiding side-effects that can be caused by sharing a factory. See #11255.

WebJars Locator

Spring Boot 1.x used and provided dependency management for org.webjars:webjars-locator. webjars-locator is a "poorly named library … that wraps the webjars-locator-coreproject". Dependencies on org.webjars:webjars-locator should be updated to use org.webjars:webjars-locator-core instead.

Security

Spring Boot 2 greatly simplifies the default security configuration and makes adding custom security easy. Rather than having several security-related auto-configurations, Spring Boot now has a single behavior that backs off as soon as you add your own WebSecurityConfigurerAdapter.

You are affected if you were using any of the following properties:

security.basic.authorize-mode

security.basic.enabled

security.basic.path

security.basic.realm

security.enable-csrf

security.headers.cache

security.headers.content-security-policy

security.headers.content-security-policy-mode

security.headers.content-type

security.headers.frame

security.headers.hsts

security.headers.xss

security.ignored

security.require-ssl

security.sessions

Tip:If you are or if you want to know more about those changes, refer to the Security migration use cases wiki page.

Default Security

The security auto-configuration no longer exposes options and uses Spring Security defaults as much as possible. One noticeable side effect of that is the use of Spring Security’s content negotiation for authorization (form login).

Default User

Spring Boot configures a single user with a generated password, by default. The user can be configured using properties under spring.security.user.*. To customize the user further or add other users, you will have to expose a UserDetailsService bean instead. This sample demonstrates how to do it.

AuthenticationManager Bean

If you want to expose Spring Security’s AuthenticationManager as a bean, override the authenticationManagerBean method on your WebSecurityConfigurerAdapter and annotate it with @Bean.

OAuth2

Functionality from the Spring Security OAuth project is being migrated to core Spring Security. Dependency management is no longer provided for that dependency and Spring Boot 2 provides OAuth 2.0 client support via Spring Security 5.

If you depend on Spring Security OAuth features that have not yet been migrated, you will need to add a dependency on an additional jar, check the documentation for more details. We’re also continuing to support Spring Boot 1.5 so older applications can continue to use that until an upgrade path is provided.

Actuator Security

There is no longer a separate security auto-configuration for the Actuator (management.security.* property are gone). The sensitive flag of each endpoint is also gone to make things more explicit in the security configuration. If you were relying to this behavior, you need to create or adapt your security configuration to secure endpoints with the role of your choice.

For instance, assuming the following config

endpoints.flyway.sensitive=false

endpoints.info.sensitive=true

management.security.roles=MY_ADMIN

http.authorizeRequests()

        .requestMatchers(EndpointRequest.to("health","flyway"))

        .permitAll()

        .requestMatchers(EndpointRequest.toAnyEndpoint())

        .hasRole("MY_ADMIN")...

Note: that in 2.x, health and info are enabled by default (with health details not shown by default). To be consistent with those new defaults, health has been added to the first matcher.

Working with SQL Databases

Configuring a DataSource

The default connection pool has switched from Tomcat to HikariCP. If you used spring.datasource.type to force the use of Hikari in a Tomcat-based application, you can now remove that override.

In particular, if you had such setup:

<dependency>

    <groupId>org.springframework.boot</groupId>

    <artifactId>spring-boot-starter-data-jpa</artifactId>

    <exclusions>

        <exclusion>

            <groupId>org.apache.tomcat</groupId>

            <artifactId>tomcat-jdbc</artifactId>

        </exclusion>

    </exclusions>

</dependency>

<dependency>

    <groupId>com.zaxxer</groupId>

    <artifactId>HikariCP</artifactId>

</dependency>

you can now replace it with:

<dependency>

    <groupId>org.springframework.boot</groupId>

    <artifactId>spring-boot-starter-data-jpa</artifactId>

</dependency>


WARN Message for Implicit 'open-in-view'

From now on, applications that don’t explicitly enable spring.jpa.open-in-view will get a WARN message during startup. While this behavior is a friendly default, this can lead to issues if you’re not fully aware of what’s that doing for you. This message makes sure that you understand that database queries may be performed during view rendering. If you’re fine with that, you can configure explicitly this property to silence the warning message.

JPA and Spring Data

In Spring Boot 1.x, some users were extending from HibernateJpaAutoConfiguration to apply advanced customizations to the auto-configured EntityManagerFactory. To prevent such faulty use case from happening, it is no longer possible to extend from it in Spring Boot 2.

To support those use cases, you can now define a HibernatePropertiesCustomizer bean that gives you full control over Hibernate properties, including the ability to register Hibernate interceptor declared as beans in the context.

Flyway

Flyway configuration keys were moved to the spring namespace (i.e. spring.flyway)

Upgrading to Spring Boot 2 will upgrade Flyway from 3.x to 5.x. To make sure that the schema upgrade goes smoothly, please follow the following instructions:

First upgrade your 1.5.x Spring Boot application to Flyway 4 (4.2.0 at the time of writing), see the instructions for Maven and Gradle.

Once your schema has been upgraded to Flyway 4, upgrade to Spring Boot 2 and run the migration again to port your application to Flyway 5.

Liquibase

Liquibase configuration keys were moved to the spring namespace (i.e. spring.liquibase)

Database Initialization

Basic DataSource initialization is now only enabled for embedded data sources and will switch off as soon as you’re using a production database. The new spring.datasource.initialization-mode (replacing spring.datasource.initialize) offers more control.

Updated Default 'create-drop' Handling

The spring.jpa.hibernate.ddl-auto property defaults to create-drop with an embedded database only if no schema manager, such as Liquibase or Flyway, is in use. As soon as a schema manager is detected, the default changes to none.

Working with NoSQL Technologies

Redis

Lettuce is now used instead of Jedis as the Redis driver when you use spring-boot-starter-redis. If you are using higher level Spring Data constructs you should find that the change is transparent. We still support Jedis, and you are free to switch dependencies if you prefer by excluding io.lettuce:lettuce-core and adding redis.clients:jedis.

Elasticsearch

Elasticsearch has been upgraded to 5.4+. In line with Elastic’s announcement that embedded Elasticsearch is no longer supported, auto-configuration of a NodeClient has been removed. A TransportClient can be auto-configured by using spring.data.elasticsearch.cluster-nodes to provide the addresses of one or more nodes to connect to.

Caching

Dedicated Hazelcast Auto-configuration for Caching

It is no longer possible to auto-configure both a general HazelcastInstance and a dedicated HazelcastInstance for caching. As a result, the spring.cache.hazelcast.config property is no longer available.

Batch

The CommandLineRunner that executes batch jobs on startup has an order of 0.

Testing

Mockito 1.x

Mockito 1.x is no longer supported for @MockBean and @SpyBean. If you don’t use spring-boot-starter-test to manage your dependencies you should upgrade to Mockito 2.x.

Spring Boot Actuator

Spring Boot 2 brings important changes to the actuator, both internal and user-facing, please check the updated section in the reference guide and the new Actuator API documentation.

You should expect changes in the programming model, configuration keys and the response format of some endpoints. Actuator is now natively supported on Spring MVC, Spring WebFlux and Jersey.

Build

The code of the Actuator has been split in two modules: the existing spring-boot-actuator and a new spring-boot-actuator-autoconfigure. If you were importing the actuator using its original module (spring-boot-actuator), please consider using the spring-boot-starter-actuator starter instead.

Configuration Keys Structure

Endpoints infrastructure key have been harmonized:

                Old property                                           New property

endpoints..*                                                    management.endpoint..*

endpoints.cors.*                                             management.endpoints.web.cors.*

endpoints.jmx.*                                               management.endpoints.jmx.*

management.address                                     management.server.address

management.context-path                              management.server.servlet.context-path

management.ssl.*                                          management.server.ssl.*

management.port                                           management.server.port

Base Path

All endpoints have moved to /actuator by default.

We fixed the meaning of management.server.servlet.context-path: it is now the endpoint management equivalent of server.servlet.context-path (only active when management.server.port is set). Additionally, you can also set the base path for the management endpoints with a new, separate property: management.endpoints.web.base-path.

For example, if you’ve set management.server.servlet.context-path=/management and management.endpoints.web.base-path=/application, you’ll be able to reach the health endpoint at the following path: /management/application/health.

If you want to restore the behavior of 1.x (i.e. having /health instead of /actuator/health), set the following property:

management.endpoints.web.base-path=/

Audit Event API Change

AuditEventRepository now has a single method with all optional arguments.

Endpoints

To make an actuator endpoint available via HTTP, it needs to be both enabled and exposed. By default:

Only the /health and /info endpoints are exposed, regardless of Spring Security being present and configured in your application.

All endpoints but /shutdown are enabled.

You can expose all endpoints as follows:

management.endpoints.web.exposure.include=*

You can explicitly enable the /shutdown endpoint with:

management.endpoint.shutdown.enabled=true

To expose all (enabled) web endpoints but the env endpoint:

management.endpoints.web.exposure.include=*

management.endpoints.web.exposure.exclude=env

Table 1. Endpoint changes

1.x                                                         EndpointChanges

/actuator                        No longer available. There is, however, a mapping available at                                           the root  of management.endpoints.web.base-path that                                             provides links to all the  exposed endpoints.

/auditevents                The after parameter is no longer required

/autoconfig                    Renamed to /conditions

/docsNo                     longer available (the API documentation is part of the published                                     documentation now)

/health                            Rather than relying on the sensitive flag to figure out if                                         the healthendpoint had to show full details or not, there is now                                         a management.endpoint.health.show-                                                                                details option: never, always, when-authenticated. By                                         default, /actuator/health is exposed and details are not shown.

/trace                                         Renamed to /httptrace

Endpoint properties have changed as follows:

endpoints.<id>.enabled has moved to management.endpoint..enabled

endpoints.<id>.id has no replacement (the id of an endpoint is no longer configurable)

endpoints.<id>.sensitive has no replacement (See Actuator Security)

endpoints.<id>.path has moved to management.endpoints.web.path-mapping.

Endpoint Format

Overhaul of the "/actuator/mappings" Actuator Endpoint

The JSON format has changed to now properly include information about context hierarchies, multiple DispatcherServlets, deployed Servlets and Servlet filters. See #9979 for more details.

The relevant section of the Actuator API documentation provides a sample document.

Overhaul of the "/actuator/httptrace" Actuator Endpoint

The structure of the response has been refined to reflect the endpoint’s focus on tracing HTTP request-response exchanges. More details about the endpoint and its response structure can be found in the relevant section of the Actuator API documentation.

Migrate Custom Endpoints

If you have custom actuator endpoints, please check out the dedicated blog post. The team also wrote a wiki page that describes how to migrate your existing Actuator endpoints to the new infrastructure.

Metrics

Spring Boot’s own metrics have been replaced with support, including auto-configuration, for Micrometer and dimensional metrics.

Setting up Micrometer

If your Spring Boot 2.0 application already depends on Actuator, Micrometer is already here and auto-configured. If you wish to export metrics to an external registry like Prometheus, Atlas or Datadog, Micrometer provides dependencies for many registries; you can then configure your application with spring.metrics.* properties to export to a particular registry.

For more on this, check out the Micrometer documentation about Spring Boot 2.0.

Migrating Custom Counters/Gauges

Instead of injecting CounterService or GaugeService instances in your application code, you can create various metrics by:

Injecting a MeterRegistry and calling methods on it.

Directly calling static methods like Counter featureCounter = Metrics.counter("feature");.

Micrometer brings many interesting features - check out the core concepts behind Micrometerand the specifics about Spring Boot integration.

Spring Boot 1.5 Support

You can plug existing Spring Boot 1.5 applications in the same metrics infrastructure by using the Micrometer Legacy support.

Developer Tools

Hot swapping

As the Spring Loaded project has been moved to the attic, its support in Spring Boot has been removed. We advise to use Devtools instead.

Devtools Remote Debug Tunnel

The support for tunnelling remote debugging over HTTP has been removed from Devtools.

Removed Features

The following features are no longer available:

1.CRaSH support

2.Auto-configuration and dependency management for Spring Mobile.

3.Auto-configuration and dependency management for Spring Social. Please check the Spring Social project for more details.

4.Dependency management for commons-digester.

Dependency Versions

The minimum supported version of the following libraries has changed:

Elasticsearch 5.6

Gradle 4

Hibernate 5.2

Jetty 9.4

Spring Framework 5

Spring Security 5

Tomcat 8.5

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

推荐阅读更多精彩内容