Java 八股文:SSM 篇

1 - SSM 篇

1.1 Spring

1.1.1 Spring
1.1.1.1 Spring 框架中的单例 bean 是线程安全的吗?
  • 注解 @Scope 的默认值是 singleton,单例的。一般 bean 都是注入无状态的对象,没有线程安全问题。

  • 但如果在 bean 中定义了可修改的成员变量,是线程不安全的,可以使用 多例 或者 加锁 来解决。

    • 例如,如果多个线程同时访问和修改 Bean 的成员变量,可能会导致数据不一致或竞态条件。
@Component                                                // 1. 单例
public class Counter {
    private int count = 0;                                // 可修改状态变量,线程不安全
    private final int count = 0;                      // 不可修改状态变量,线程安全

    public void increment() {count++;}
    public void increment(int count) {count++;}           // 无状态变量,线程安全
}
@Component
@Scope("prototype")                                       // 2. 多例,线程安全
public class Counter {
    private int count = 0;
    public void increment() {count++;}
}
@Component
public class Counter {
    private int count = 0;
    public synchronized void increment() {count++;}       // 3. 加锁,线程安全
}

Bean的单例和非单例,生命周期是否一样?

  • 单例 Bean:Spring 容器在启动时创建一个实例,并在整个生命周期内共享,因此对象的生命周期是整个应用程序的生命周期。
  • 多例 Bean:每次请求(或注入)都会创建一个新的实例,生命周期仅限于每次请求或注入时存在。
1.1.1.2 AOP 实现原理?自己实现过动态代理么?

1. AOP 实现原理?

  • AOP 是一种编程范式,用来处理那些在多个地方都要用到的代码,比如日志记录、权限检查、事务管理等。

    • AOP 通过一种特殊的类(切面)来定义这些横切关注点,然后在程序运行时,动态地将这些关注点应用到目标代码上。
  • Java 中,动态代理 是一种在运行时动态创建代理对象的技术,它允许你在不修改原代码的情况下,给类添加额外功能。

    • JDK 动态代理:基于反射生成代理对象,要求被代理类实现一个或多个接口。
    • CGLIB 动态代理:基于继承生成代理对象,适用于被代理类没有实现接口。
  • Java 反射是一种在运行时检查或修改程序行为的能力。反射可以用来动态创建对象、调用方法、访问字段等。

    • 使用java.lang.reflect包中的类,如ClassMethodField等,可以实现反射功能。
    • 例如,使用Class.forName("com.example.MyClass").newInstance()来创建对象。

2. 自己实现过动态代理么?

我自己实现过动态代理来进行性能统计。(没有使用注解 @Aspect@Pointcut@Around 等)

  1. 首先,定义一个注解@PerformanceMethod,用于标记需要进行性能统计的方法。

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface PerformanceMethod {
    }
    
  2. 然后,创建一个动态代理处理器,用于在方法执行前后采集性能数据。

    public class PerformanceInvocationHandler implements InvocationHandler {
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            // ... (模拟自动采集并打印出方法的执行时间)
        }
        public static Object createProxy(Object target) {
            return Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new PerformanceInvocationHandler(target)
            );
        }
    }
    
  3. 最后,在接口的方法上使用@PerformanceMethod注解,并使用动态代理来创建代理对象。

    public interface Service {
        @PerformanceMethod
        void performTask();
    }
    
    public class ServiceImpl implements Service {
        @Override
        public void performTask() {
            // ... (模拟业务操作)
        }
    }
    
    public class Test {
        public static void main(String[] args) {
            Service service = new ServiceImpl();
            Service proxyService = (Service) PerformanceInvocationHandler.createProxy(service);
            proxyService.performTask();
        }
    }
    
1.1.1.4 @Transactional 原理?事务失效的场景?

1. @Transactional 原理:

  • @Transactional 是 Spring 框架提供的声明式事务管理注解,其工作原理基于 AOP(面向切面编程)。

  • 它通过创建代理对象来拦截方法调用,并在方法执行前后进行事务的管理和控制。

2. 事务失效的场景:

  1. 类或方法没有被 Spring 管理:没有被标注为 @Service@Component等,那么 @Transactional 注解将不会生效。
  2. 非事务方法内部调用:如果在一个非事务方法中通过 `this 调用事务方法,那么种调用不会经过 Spring 的代理对象。
  3. 方法不是 public@Transactional 注解只适用于 public 方法。如果方法不是 public 的,事务注解将不会生效。
  4. 异常被捕获:如果事务方法内部捕获了应该导致事务回滚的异常,那么事务将不会回滚。
  5. 异常类型不对:@Transactional 注解默认只对运行时异常(RuntimeException)和错误(Error)进行回滚。
  6. 事务传播行为不正确:如果事务的传播行为设置不正确,可能会导致事务不按预期生效。
1.1.1.5 Spring 的 bean 的生命周期?
  1. 定义Bean:容器启动,加载配置文件或类中的 Bean 定义。
  2. Bean 的实例化:根据配置创建 Bean 实例(如 BeanDefinition)。
  3. Bean 的依赖注入:通过依赖注入,将依赖的属性赋值给 Bean。
  4. Bean 的初始化:执行自定义的初始化方法(如 InitializingBeanAOP/动态代理)。
  5. Bean 的使用:Bean 可以被其他组件使用。
  6. Bean 的销毁:容器关闭时,执行销毁方法。
1.1.1.6 Spring 中的循环引用?
  • 循环引用是指在Spring容器中,两个或多个bean相互依赖,形成一个闭环。例如,A依赖于B,同时B又依赖于A。
  • 循环引用会导致Spring容器无法完成bean的创建和注入,因为每个bean都在等待对方先完成初始化。

解决方案:

  1. 使用Setter注入: 如果循环引用发生在构造方法注入中,可以使用Setter注入代替,避免在构造方法中就创建所有依赖。
  2. 使用@Lazy注解: 可以在依赖注入的地方使用@Lazy注解,这样依赖的bean会延迟加载,直到第一次被使用时才创建。
  3. 使用ApplicationContextgetBean方法: 通过编程方式获取bean时,可以避免循环依赖的问题。
1.1.1.7 谈谈你对 Spring IOC 和 DI 的理解?

1. 控制反转(Inversion of Control,IOC):

  • 控制反转是一种设计原则,用来减少程序中各个组件之间的耦合度。

  • 在传统的程序设计中,组件之间的依赖关系是由组件自身在内部创建或者直接实例化其依赖的类来实现的。

  • 而在控制反转中,组件不再负责创建或管理它们的依赖关系,而是将这种控制权反转给第三方,通常是Spring容器。

实现原理:

  1. 容器管理: Spring容器负责管理对象的生命周期和依赖关系。

    当容器启动时,它会读取配置文件(XML、注解或Java配置类),并根据这些配置创建对象以及设置它们的依赖关系。

  2. 工厂模式: Spring容器充当了一个工厂,它使用工厂模式来创建对象。

    容器中有一个工厂方法,根据配置文件中的信息来决定实例化哪个类的实例。

  3. 单例和原型: Spring容器可以管理对象的生命周期,它可以创建单例(Singleton)或原型(Prototype)对象。

    单例意味着该对象在容器中只有一个实例,而原型则每次请求都会创建一个新的实例。

2. 依赖注入(Dependency Injection,DI):

  • 依赖注入是实现控制反转的一种手段,它将组件的依赖关系从组件内部转移到外部进行管理。
  • 依赖注入可以通过构造函数注入、setter方法注入、接口注入等方式实现。

实现原理:

  1. 反射: Spring使用Java反射API来创建对象和调用方法。在运行时动态地访问对象的属性和方法,从而实现依赖注入。
  2. 核心接口: Spring容器有两个核心接口,BeanFactoryApplicationContext
  3. 自动装配: Spring支持自动装配功能,它可以通过类型、名称或构造函数参数来自动装配bean,减少了配置的复杂性。
  4. 注解: 常见的注解包括@Autowired@Inject@Resource等,它们可以应用于构造函数、字段或setter方法。
1.1.1.8 Spring 的事务传播机制?
  • 事务传播机制定义了当多个事务方法存在调用关系时,事务如何在这些方法之间进行传播。

  • Spring 支持以下 7 种事务传播机制,通过 @Transactional 注解的 propagation 属性来指定:

    1. REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。(默认)
    2. SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
    3. MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
    4. REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起。
    5. NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
    6. NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
    7. NESTED:如果当前存在事务,则创建一个事务作为嵌套事务来运行。如果当前没有事务,则该取值 REQUIRED
1.1.2 Spring MVC
1.1.2.1 Spring MVC 执行流程?
  1. 前端控制器DispatcherServlet):用来接收用户的请求。
  2. 请求映射处理器HandlerMapping):用来确定请求映射到哪个 Handler
  3. 处理器适配器HandlerAdapter):用来调用 Handler 方法。
  4. 处理器Handler):用来处理请求并返回模型和视图名称。
  5. 视图解析器View Resolver):用来解析视图名称,渲染视图。
1.1.2.2 过滤器(Filter)与拦截器(Interceptor)的区别?

1. 过滤器(Filter)

  • Filter 过滤器是 Java Web 的三大组件之一,用于拦截资源请求以实现特定功能,如登录校验、编码处理等。

  • 过滤器通常包含三个方法:init(初始化)、doFilter(拦截请求并处理)、destroy(销毁)。

  • 例如,登录校验过滤器可以验证请求头中的 JWT 令牌,未登录用户会收到相应错误信息。

2. 拦截器(Interceptor)

  • 拦截器是动态拦截控制器方法调用的机制,类似于过滤器。它可以在方法调用前后执行设定的代码。
  • 例如,Interceptor 实现 preHandle 方法来决定是否放行请求,未登录用户会收到错误信息。

3. 拦截器与过滤器的区别

  • 接口不同:过滤器实现 Filter 接口,拦截器实现 HandlerInterceptor 接口。
  • 拦截范围不同:过滤器拦截所有资源,拦截器仅拦截 Spring 环境中的资源。
1.1.3 Spring Boot
1.1.3.1 Spring Boot 的自动配置原理?
  1. @SpringBootApplication 注解是 Spring Boot 的核心注解,它组合了下面三个注解的功能:

    • @SpringBootConfiguration 表示这是一个 Spring Boot 的配置类,

    • @EnableAutoConfiguration 负责开启自动配置,

    • @ComponentScan 则是扫描指定包路径下的组件并注册为 Spring 容器的 Bean。

  2. @EnableAutoConfiguration 是自动配置机制的核心注解。它的源码中使用了 @Import 注解来导入配置选择器。

    这个配置选择器会读取项目 classpath 路径下 META-INF/spring.factories 文件中列出的自动配置类全限定名。

  3. 在这些自动配置类中,定义的 Bean 会根据条件注解来决定是否被注册到 Spring 容器中。

    例如,@ConditionalOnClass 注解会检查指定的类是否存在于 classpath 中,如果存在,则会将该类注册为 Bean。

1.1.3.2 Spring、Spring MVC、Spring Boot 常见注解?
Spring 说明
@Controller、@Service、@Repository、@Component 使用在类上用于实例化 Bean
@Autowired 使用在字段上用于根据类型依赖注入
@Configuration 指定当前类是 Spring 配置类,当创建容器时会从该类上加载注解
@Qualifier 结合 @Autowired 一起使用用于根据名称进行依赖注入
@Scope 标注 Bean 的作用范围
@ComponentScan 用于指定 Spring 在初始化容器时要扫描的包
@Bean 使用在方法上,标注将该方法的返回值存储到 Spring 容器中
@Import 使用 @Import 导入的类会被 Spring 加载到 IOC 容器中
@Aspect、@Before、@After、@Around、@Pointcut 用于切面编程(AOP)
Spring MVC 说明
@RequestMapping 用于映射请求路径,可以定义在类和方法上。用于类上,则表示所有方法都是以该地址作为父路径
@RequestBody 注解实现接收 http 请求的 json 数据,将 json 转换为 java 对象
@RequestParam 指定请求参数的名称
@PathViriable 从请求路径下中获取请求参数 /user/{id},传递给方法的形式参数
@ResponseBody 注解实现将 controller 方法返回对象转化为 json 对象响应给客户端
@RequestHeader 获取指定的请求头数据
Spring Boot 说明
@SpringBootConfiguration 组合了@Configuration 注解,实现配置文件的功能
@RestController @Controller + @ResponseBody
@Value 用来将配置文件中的值注入到 bean 的字段中
@EnableAutoConfiguration 打开自动配置的功能,也可以关闭某个自动配置的选
@ComponentScan Spring 组件扫描
1.1.3.3 Spring、SpringMVC、SpringBoot 区别和联系?
  1. Spring 是一个开源的企业级应用程序开发框架,提供了全面的基础设施支持。

    • 依赖注入(DI):通过控制反转(IoC)管理对象的生命周期。

    • 面向切面编程(AOP):支持事务管理、日志记录等功能。

  2. Spring MVC 是 Spring 框架的一部分,专注于构建基于 MVC(模型-视图-控制器)设计模式的 Web 应用。

    • 提供了请求处理、视图解析、数据绑定等功能。

    • 通过注解(如 @Controller@RequestMapping)简化开发。

  3. Spring Boot:Spring Boot 是基于 Spring 的一个框架,旨在简化 Spring 应用的配置和开发。

    • 提供了自动配置功能,减少了繁琐的配置。

    • 内嵌服务器(如 Tomcat、Jetty),可以直接运行 Spring Boot 应用。

  • 区别和联系:
    • Spring 是基础框架,提供核心功能。
    • Spring MVC 是构建 Web 应用的模块。
    • Spring Boot 是简化 Spring 应用开发的工具,集成了 Spring 和 Spring MVC 的功能。
1.1.3.4 @Autowired 和 @Resource 区别和联系?
  • 联系:

    • @Autowired@Resource 都是用于依赖注入的注解,它们都可以用来装配 Spring 容器中的 Bean。
  • 区别:

    • 来源:@Autowired 是 Spring 特有的,而 @Resource 是 Java EE 的一部分。

    • 装配方式:@Autowired 默认按类型装配,而 @Resource 默认按名称装配。

    • 限定符:@Autowired 可以通过 @Qualifier 来指定注入的具体 Bean,而 @Resource 通过名称来指定。

    • 异常处理:@Autowired 在找不到 Bean 时可以设置为非必须的,而 @Resource 在找不到 Bean 时会抛出异常。

1.2 Mybatis

1.2.1 MyBatis 的执行流程?
  1. 读取配置文件 mybatis-config.xml,加载运行环境和映射文件
  2. 构造会话工厂 SqlSessionFactory
  3. 会话工厂创建 SqlSession 对象
  4. 操作数据库的接口 Executor 执行器,同时负责查询缓存的维护
  5. Executor 接口的执行方法中有一个 MappedStatement 类型的参数,封装了映射信息
  6. 输入参数映射,输出结果映射
1.2.2 Mybatis 是否支持延迟加载?

延迟加载是指在需要时才加载数据,而不是在一开始就加载所有数据。这对于减少数据库查询和提高性能非常有用。

MyBatis支持延迟加载,可以通过配置实现:

  1. 配置mybatis-config.xml文件: 在配置文件中设置lazyLoadingEnabled属性为true
  2. 使用<association><collection>标签: 在映射文件中使用这些标签来指定哪些属性应该延迟加载。
延迟加载的底层原理:
  • MyBatis的延迟加载是通过代理模式实现的。当访问一个延迟加载的属性时,会检查这个属性是否已经被初始化。
  • 如果没有,它会创建一个代理对象,并在代理对象的方法被调用时,去数据库查询数据并初始化这个属性。
1.2.3 Mybatis 的一级、二级缓存?
一级缓存 (Local Cache) 二级缓存 (Second Level Cache)
作用范围 单个 SQL Session 多个 SQL Session,跨 Session 共享
存储机制 PerpetualCacheHashMap PerpetualCacheHashMap
默认开启
清空时机 Session 执行 flush 或 close 操作时清空 通常为 SQL 增删改操作后清空
配置位置 核心配置文件 Mapper 映射文件

1.3 其他题目

1.3.1 Spring容器里存的是什么?
  1. Bean 实例:容器创建并管理的对象。
  2. Bean 配置:定义 Bean 的属性、初始化方法、销毁方法等信息。
  3. Bean 依赖关系:管理 Bean 之间的依赖注入。
  4. 生命周期回调:处理 Bean 的初始化和销毁方法。
  5. 作用域和上下文信息:如单例、原型等作用域,及应用上下文的相关信息。
1.3.2 哪个注解可以让工具类获取配置文件的值?
  1. 配置文件定义:

    db.url=jdbc:mysql://localhost:3306/mydb
    
  2. 在 Spring Boot 应用中,可以使用 @Value 注解来注入配置文件中的值。

    @Component
    public class DatabaseConfig {
        @Value("${db.url}")
        private String url;
    }
    
  3. 在 Spring 管理的其他Bean中,你可以直接自动装配工具类,并使用其属性。

1.3.3 操作数据库要引入哪些 jar 包?
  1. MyBatis 核心 jar 包:这是MyBatis框架的核心库,负责实现MyBatis的基本功能。例如,mybatis 包。
  2. 数据库驱动 jar 包:这是特定数据库的JDBC驱动,用于Java程序与数据库进行通信。例如,mysql-connector-java驱动。
  3. 日志相关 jar 包(可选):MyBatis在执行过程中可能会使用日志框架来记录日志,例如 log4j 包来支持。
1.3.4 Maven 依赖传递、依赖冲突、工程继承、工程聚合?
  1. Maven 依赖传递

    • 当你的项目依赖了一个库,而这个库又依赖了其他的库时,Maven 会自动将这些间接依赖的库也添加到你的项目中。
    • 例如,如果你的项目依赖了 A 库,而 A 库又依赖了 B 库,那么 B 库也会被传递性地添加到你的项目依赖中。
  2. Maven 依赖冲突

    • 依赖冲突发生在项目的多个依赖之中存在相同 groupIdartifactId 的库,但是版本号不同。

    • Maven 使用一套规则来解决依赖冲突,通常它会根据路径最短者优先的原则来选择版本。

  3. Maven 工程继承

    • Maven 工程继承是指一个 Maven 项目可以继承另一个 Maven 项目的构建配置。
    • 继承是通过 pom.xml 文件中的 <parent> 标签来实现的。主要用于模块化项目,其中子项目共享一些公共配置。
  4. Maven 工程聚合

    • 工程聚合是指在多模块项目中,你可以将多个子项目聚合到一个父项目中,以便一次性构建所有的子项目。
    • 聚合是通过 pom.xml 文件中的 <modules> 标签来实现的。主要用于管理多模块项目的构建过程。
1.3.5 调用 mapper 接口的抽象方法操作数据库的原理?
  1. Mapper接口定义:开发者定义一个Mapper接口,里面声明了需要执行的数据库操作方法。

  2. XML映射文件:包含了SQL语句和对应的接口方法。这个文件告诉MyBatis如何将接口方法映射到具体的SQL语句。

  3. MyBatis配置文件:在MyBatis的全局配置文件中,需要指定mapper接口和对应的XML映射文件。

  4. SqlSessionFactory:MyBatis通过SqlSessionFactory创建SqlSession对象,它包含了执行映射操作的方法。

  5. 动态代理:当调用mapper接口的方法时,MyBatis使用动态代理机制来拦截这些方法调用。

    它创建mapper接口的代理对象,当调用接口中的方法时,代理对象会根据XML映射文件中的配置找到对应的SQL语句并执行。

1.3.6 Bean注入和xml注入最终得到了相同的效果,它们在底层是怎样做的?
  • XML:Spring 容器使用 BeanFactory 或 ApplicationContext 解析 XML 配置,并根据配置创建 Bean 实例及其依赖。
  • 注解:Spring 容器通过扫描类路径中的注解,生成相应的 BeanDefinition,并自动进行 Bean 的创建和注入。

两种方式,Spring 容器底层都会生成 BeanDefinition 对象,依据这些定义来创建和管理 Bean 实例。

1.3.7 MyBatis 中使用 # 和 $ 有什么区别?
  • 使用#是防止SQL注入的推荐方式,因为它能够自动处理参数的转义。
    • 例如:SELECT * FROM users WHERE username = '#{username}',#{username} 是一个参数占位符。
  • 使用$不会将用户输入直接插入到SQL语句中,需要对参数值进行严格的验证和清理,以避免SQL注入攻击。
    • 例如:SELECT * FROM users WHERE username = '${username}',${username} 为 ' OR '1'='1 会导致SQL注入攻击。
1.3.8 介绍一下 mybatis plus?

简化开发,内置 CRUD、分页查询、条件构造器 Wrapper、代码生成器等。

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

推荐阅读更多精彩内容