七 Spring-IoC 注解

目录

1. Web MVC发展史历程
2.Spring概要
3.Spring-依赖注入概要(IOC)
4.属性注入的三种实现方式
5.Spring-IoC XML装配
6.Spring-XML设置Bean的值
7.Spring-IoC 注解(1)
8.Spring-IoC 注解(2)
9.Spring-AOP切面编程(1)
10.Spring-AOP切面编程(2)
未完待续...

一、简介

注解本身没有功能的,就和xml一样。注解和xml都是一种元数据,元数据即解释数据的数据,这就是所谓配置。

Spring注解方式减少了配置文件内容,更加便于管理,并且使用注解可以大大提高了开发效率!

Spring3的基于注解实现Bean依赖注入支持如下三种注解:

  • Spring自带依赖注入注解:Spring自带的一套依赖注入注解;
  • JSR-250注解:Java平台的公共注解,是Java EE 5规范之一,在JDK6中默认包含这些注解,从Spring2.5开始支持。
  • JSR-330注解:Java 依赖注入标准,Java EE 6规范之一,从Spring3开始支持;

二、前期准备工作

1、创建applicationContext.xml

  1. 示例代码
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">    
    </beans>
    

2、在beans标签下引入schema

  1. 示例代码
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           <!--加入context -->
           xmlns:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
                               http://www.springframework.org/schema/beans/spring-beans.xsd
                               http://www.springframework.org/schema/context 
                               http://www.springframework.org/schema/context/spring-context.xsd">
    </beans>
    

3、设置组件扫描

  1. 示例代码
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           <!--加入context -->
           xmlns:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
                               http://www.springframework.org/schema/beans/spring-beans.xsd
                               http://www.springframework.org/schema/context 
                               http://www.springframework.org/schema/context/spring-context.xsd">
    <!-- base-package 扫描包下所有的注解bean -->
    <context:component-scan base-package="com.wener.example"/>
    </beans>
    
    component-scan具体看component-scan详解

三、声明bean的注解

1、概要

  1. @Component泛指组件,当组件不好归类的时候,我们可以使用这个注解进行标注
  2. @Service用于标注业务层组件
  3. @Controller用于标注控制层组件(如struts中的action)
  4. @Repository用于标注数据访问组件,即DAO组件.

2、Component

  1. 说明
    表示 Spring IoC 会把这个类扫描成一个 bean实例,而其中的 value 属性代表这个类在 Spring 中的 id,这就相当于在 XML 中定义的 Bean 的 id,甚至直接写成 @Component,对于不写的,Spring IoC 容器就默认以类名来命名作为 id,只不过首字母小写,配置到容器中。
  2. 示例代码
    import org.springframework.stereotype.Component;
    // 方式一 设置bean的id,理论上只要符合java的命名规范的名字都可以,但是必须是唯一的
    @Component(value = "user")
    public class User {
    }
    // 方式二 注解中value命名的属性可以省略不写
    @Component("user")
    public class User {
    }
    // 方式三 甚至可以直接什么都不加
    @Component
    public class User {
    }
    

3、Controller

  1. 说明
    当一个组件代表业务层时,可以使用@Service进行注解,bean 的ID 默认为类名称开头字母小写
  2. 示例代码
    @Controller('accountController')
    public class AccountController {
    }
    //或者,
    @Controller
    public class AccountController {
    }
    

4、Service

  1. 说明
    通常用于注解Service类,也就是服务层
  2. 示例代码
    @Service
    public class AccountServiceImpl implements AccountService {
    }
    

5、Repository

  1. 说明
    当一个组件代表数据访问层(DAO)的时候,使用@Repository进行注解 ,bean 的ID默认为类名称开头字母小写
  2. 示例代码
    @Repository
    public class UserDaoImpl implements UserDao {
     ...
    }
    //或者
    @Repository("userDao")
    public class UserDaoImpl implements UserDao {
     ...
    }
    

6、总结

  1. 被注解的java类当做Bean实例,Bean实例的名称默认是Bean类的首字母小写,其他部分不变。
  2. Controller 、@Repository、@Controller、 @Service可以自定义Bean名称,理论上只要符合java的命名规范的名字都可以,但是必须是唯一的,
  3. 尽量使用对应组件注解的类替换@Component注解,在spring未来的版本中,@Controller,@Service,@Repository会携带更多语义。并且便于开发和维护!
  4. 指定了某些类可作为Spring Bean类使用后,在Spring配置文件加入如下配置
    <context:component-scan base-package="自动扫描指定包及其子包下的所有Bean类"/>
    

6、附表

注解 作用域 说明
@Component 注解在类上,可以作用在任何层次。 泛指组件,当组件不好归类的时候,我们可以使用这个注解进行标注。是一个泛化的概念,仅仅表示一个组件 (Bean) ,将一个实体类,放入Spring中。
@ Service 注解在类上 用于标注业务层组件
@ Controller 注解在类上 用于标注控制层组件
@ Repository 注解在类上 用于标注数据访问组件,即DAO组件。

四、自动装配bean时常用的注解

1、概要

自动装配是指Spring 在装配 Bean 的时候,根据指定的自动装配规则,将某个 Bean 所需要引用类型的 Bean 注入进来。可以在类的成员变量上,构造方法,setter方法使用,常用的主要有以下三种

@Autowired:属于Spring 的org.springframework.beans.factory.annotation包下,可用于为类的属性、构造器、方法进行注值

@Resource:不属于spring的注解,而是来自于JSR-250位于java.annotation包下

@Inject:不属于由JSR-330提供

2、@Autowired

2.1、说明

这个注解相当于我们之前在xml文件中配置的autowire="constructor/byName/byType",只不过我们这里使用@Autowired方式注解方式,且默认是通过类型判断,意思就是不使用byName,和construtor。通过@Autowired注解,spring会自动去容器中查找对应的类型,注入到该属性中,且bean类中,使用@Autowired注解其属性,我们可以不用提供getter,setter方法

默认情况下必须要求依赖对象必须存在,如果要允许null值,可以设置它的required属性为false,如果我们想使用按照名称装配,可 以结合@Qualifier注解一起使用

2.2、属性注入(个人喜欢)

  1. 说明
    将Autowired注解声明在属性上面,
  2. 示例代码
    @Component
    public class User {
        private String name;
        private String password;
        @Autowired
        //@Autowired(required = false)
        private Address address;
    @Component
    public class Address {
        private String province;
        private String city;
    }
    // 测试代码
    private static void testUser() {
            ApplicationContext context = new ClassPathXmlApplicationContext("spring-context.xml");
            User user = context.getBean("user", User.class);
            System.out.println(user.getAddress().toString());
        }   
    
  3. 优点
    • 代码简洁
  4. 缺点
    • 对于IOC容器以外的环境,无法复用该实现类

2.3、构造注入(官方推荐)

  1. 说明
    将Autowired注解声明在构造方法上面,在Spring4.x版本中推荐的注入方式
  2. 示例代码
    @Component
    public class User {
        private String name;
        private String password;
        private final Address address;
        @Autowired
        public User(Address address) {
            this.address = address;
        }
    @Component
    public class Address {
        private String province;
        private String city;
    }
    
  3. 优点
    • 能确保依赖组件不可变:主要是属性通过final修饰
    • 能确保依赖不为空:当要实例化Bean的时候,由于自己实现了有参数的构造函数,所以不会调用默认构造函数,那么就需要Spring容器传入所需要的参数,当我们Spring容器中有该种类型的参数直接传入,没有该种类型的参数直接报错,无需判断依赖对象是否null
    • 保证返回客户端(调用)的代码的时候是完全初始化的状态:向构造器传参之前,要确保注入的内容不为空,那么肯定要调用依赖组件的构造方法完成实例化。而在Java类加载实例化的过程中,构造方法是最后一步(之前如果有父类先初始化父类,然后自己的成员变量,最后才是构造方法)所以返回来的都是初始化之后的状态
    • 保证必要属性在Bean实例化时就得到设置
  4. 缺点
    • 可读性较差:当注入参数较多时,代码臃肿。
    • 灵活性不强:在有些属性是可选的情况下,如果通过构造函数注入,也需要为可选的参数提供一个null值
    • 不利于类的继承和拓展:因为子类需要引用父类复杂的构造函数
  5. 备注(官方说明)
    The Spring team generally advocates constructor injection as it enables one to implement application components as immutable objects and to ensure that required dependencies are not null. Furthermore constructor-injected components are always returned to client (calling) code in a fully initialized state.
    Spring团队通常提倡构造函数注入,因为它能够保证注入的组件不可变,并且确保需要的依赖不为null。此外。此外,构造器注入的组件总是以完全初始化的状态返回给客户机(调用)代码。

2.4、方法注入

  1. 说明
    将Autowired注解声明在方法上面,Spring3.x的时候,官方推荐使用的注入
  2. 示例代码
    @Component
    public class User {
        private String name;
        private String password;
        private  Address address;
        @Autowired
        public void setAddress(Address address) {
            this.address = address;
        }
    @Component
    public class Address {
        private String province;
        private String city;
    }
    
  3. 优点
    • 相比构造器注入,当注入参数太多或存在非必须注入的参数时,不会显得太笨重,Spring在创建Bean实例时,需要同时实例化器依赖的全部实例,因而导致性能下降。而使用设值注入,则能避免这些问题
    • 允许在类构造完成后重新注入
  4. 缺点
    • 对一些必要参数需要做代码检查
    • 开发的效率相对来说比较低(增加了代码量)
    • 可读性不是很好

2.5、@Qualifier

1、说明

当Spring容器中存在多个具有相同类型的 bean 时,并且想要用一个属性只为它们其中的一个进行装配,在这种情况下,你可以使用 @Qualifier 注释和 @Autowired 注释通过指定哪一个真正的 bean 将会被装配来消除混乱。

该注解可以使用字段、方法、参数、注解上

2、基础使用
  1. DataSource
    public interface DataSource {
         void connection();
    }
    
  2. MysqlDataSource
    @Component("mysql")
    public class MysqlDataSource implements DataSource {
        @Override
        public void connection() {
            System.out.println("mysql database connecting");
        }
    }
    
  3. OracleDataSource
    @Component("oracle")
    public class OracleDataSource implements DataSource {
        @Override
        public void connection() {
            System.out.println("oracle database connecting");
        }
    }
    
  4. DataSourceManager
    // 在属性上使用
    @Component
    public class DataSourceManager {
        @Autowired
        @Qualifier("oracle")
        private DataSource dataSource;
        public DataSource getDataSource() {
            return dataSource;
        }
    }
    // 或者在参数上使用
    @Component
    public class DataSourceManager {
        private DataSource dataSource;
        @Autowired
        public DataSourceManager(@Qualifier("oracle") DataSource dataSource) {
            this.dataSource = dataSource;
        }
        public DataSource getDataSource() {
            return dataSource;
        }
    }
    // 在方法上使用
    @Component
    public class DataSourceManager {
        private DataSource dataSource;
        @Autowired
        @Qualifier(value = "oracle")
        public void setDataSource(DataSource dataSource) {
            this.dataSource = dataSource;
        }
        public DataSource getDataSource() {
            return dataSource;
        }
    }
    
3、自定义Qualifier
  1. 说明
    对@Qualifier的扩展来提供细粒度选择候选者;具体使用方式就是自定义一个注解并使用@Qualifier注解其即可使用
  2. 示例代码
    import org.springframework.beans.factory.annotation.Qualifier;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    // 定义两个自定义注解类一个OracleQualifier 一个MysqlQualifier
    @Target({ElementType.TYPE, ElementType.FIELD, ElementType.PARAMETER})
    @Retention(RetentionPolicy.RUNTIME)
    @Qualifier
    public @interface OracleQualifier {
    }
    @Target({ElementType.TYPE, ElementType.FIELD, ElementType.PARAMETER})
    @Retention(RetentionPolicy.RUNTIME)
    @Qualifier
    public @interface MysqlQualifier {
    }
    
    // 在实现类上使用自定义Qualifier
    @Component("mysql")
    @MysqlQualifier
    public class MysqlDataSource implements DataSource {
        @Override
        public void connection() {
            System.out.println("mysql database connecting");
        }
    }
    @Component("oracle")
    @OracleQualifier
    public class OracleDataSource implements DataSource {
        @Override
        public void connection() {
            System.out.println("oracle database connecting");
        }
    }
    
    @Component
    public class DataSourceManager {
        @Autowired()
        @MysqlQualifier
        private DataSource mysqlDataSource;
    }
    
    其它方式自行参考其它资料

2.6、循环引入的问题

  1. 属性注入
    @Component
    public class User {
        private String name;
        private String password;
        @Autowired
        private Address address;
        public Address getAddress() {
            return address;
        }
    }
    @Component
    public class Address {
        private String province;
        private String city;
        @Autowired
        private User user;
        public User getUser() {
            return user;
        }
    }
     // 测试代码 
        private static void testUser() {
            ApplicationContext context = new ClassPathXmlApplicationContext("spring-context.xml");
            User user = context.getBean("user", User.class);
            System.out.println(user.getAddress());
        }
    //异常信息 BeanCurrentlyInCreationException
    
  2. 构造方法注入
    @Component
    public class User {
        private String name;
        private String password;
        private Address address;
        @Autowired
        public User(Address address) {
            this.address = address;
        }
    }    
    @Component
    public class Address {
        private String province;
        private String city;
        private User user;
        @Autowired
        public Address(User user) {
            this.user = user;
        }
    // 测试代码
    private static void testUser() {
            ApplicationContext context = new ClassPathXmlApplicationContext("spring-context.xml");
            User user = context.getBean("user", User.class);
            System.out.println(user.toString());
        }  
     // 异常信息 BeanCurrentlyInCreationException
    
  3. 区别
    如果使用构造器注入,在spring项目启动的时候,就会抛出:BeanCurrentlyInCreationException从而提醒你避免循环依赖,如果是属性注入的话,启动的时候不会报错,在使用那个bean的时候才会报错

2.7、总结

@Autowired的三种用法其实没有所谓的孰优孰劣,存在即是合理。个人建议,对于依赖关系无需变化的注入,可以采用构造注入;而其他的依赖关系的注入,则考虑采用设值注入,不过个人比较喜欢用属性多点,简单明了

3、@Resource

3.1、说明

@Resource是JSR250标准中的一个注解,Spring2.5+对其提供了支持。

@Resource的作用相当于 @Autowired,只不过 @Autowired 按 byType 自动注入,而@Resource 默认按 byName 自动注入罢了。

@Resource可以使用在类,属性,set方法上,也可以是普通的非set方法上,注意对应方法只允许接收一个参数

@Resource有两个属性是比较重要的,分是name和type

3.2、属性注入

  1. 说明
    将@Resource注解声明在属性上面
  2. 示例代码
    import org.springframework.stereotype.Component;
    import javax.annotation.Resource;
    @Component
    public class User {
        private String name;
        private String password;
        // 会如果什么都不写 会根据属性的名字来查找
        // 如果找不到与名称匹配的bean时才按照类型进行装配,找不到直接报错
        @Resource
        // 如果写了name,那只会按着name来查找,找不到就直接报错
        // @Resource(name = "address")
        // @Resource(name = "address", type = Address.class)
        private Address address;
     }   
    @Component
    public class Address {
        private String province;
        private String city;
    }
    //测试代码
    private static void testUser() {
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-context.xml");
        User user = context.getBean("user", User.class);
        System.out.println(user.getAddress().toString());
    }
    

3.3、方法注入

  1. 说明
    在方法上使用@Resource
  2. 示例代码
    @Component
    public class User {
        private String name;
        private String password;
        private Address address;
        public Address getAddress() {
            return address;
        }
     // 当注解写在方法上时,默认取字段名进行安装名称查找
        @Resource
        // @Resource(name = "address")
        // @Resource(name = "address", type = Address.class)
        public void setAddress(Address address) {
            this.address = address;
        }
    //    或者
    //    @Resource
    //    @Resource(name = "address")
    //    @Resource(name = "address", type = Address.class)
    //    public void initAddress(Address address) {
    //        this.address = address;
    //    }
       // 错误的案例
       // 注意修饰方法的时候,有且只能有一个参数,多余一个参数直接报错
       @Resource
        public void initAddress(Address address, String name) {
            this.address = address;
            this.name = name;
        }
    }
    // @Resource annotation requires a single-arg method
    

3.4、重要属性说明

  1. 如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常
  2. 如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常
  3. 如果指定了type,则从上下文中找到类型匹配的唯一bean进行装配,找不到或者找到多个,都会抛出异常
  4. 如果既没有指定name,又没有指定type,则自动按照byName方式进行装配;如果没有匹配,则按类型进行匹配,如果匹配则自动装配
  5. 如果既指定了name又指定了type,则按照名字和类型装配,任何一个不匹配都将报错
  6. 如果 @Resource用于方法中,默认使用方法名作为beanName,指定名字则使用名字

3.5、与@Autowired区别

4、@Inject

4.1、说明

@Inject是JSR-330的一部分。在Spring3中开始支持JSR-330的注解

@Inject支持构造函数、方法和字段注解,也可能使用于静态属性。与@Autowired不同的是强制要求示例必须存在

注意: 需要导入第三方的jar包

<dependency>
    <groupId>javax.inject</groupId>
    <artifactId>javax.inject</artifactId>
    <version>1</version>
</dependency>

4.2、属性注入

  1. 说明
    在属性上声明,注意属性不能是final的
  2. 示例代码
    @Component
    public class User {
        @Inject
        private Address address;
    }  
    

4.3、构造方法注入

  1. 说明
    在构造方法上声明,构造函数可以是无参或多个参数的构造函数,@Inject每个类中最多注解一个构造函数。
  2. 示例代码
    @Component
    public class User {
        private Address address;
        @Inject
        public User(Address address) {
            this.address = address;
        }
    }    
    

4.4、方法注入

  1. 说明
    在方法上声明,注意不能是抽象方法,可以有0个或多个参数。
  2. 示例代码
    @Component
    public class User {
        private Address address;
        public Address getAddress() {
            return address;
        }
        @Inject
        public void setAddress(Address address) {
            this.address = address;
        }
    

4.5、配合@Name使用

  1. 说明
    @Inject默认按类型匹配,如果你想按着Bean的名字来使用,可以使用@Name属性使用,一般用来类上面声明Bean的名字@Component的作用,如何在注入的时候在属性上声明相当于@Qualifier
  2. 示例代码
    // 在类上声明 
    @Named("address1")
    public class Address {
    }  
    // 配合@Inject一起使用
    @Component
    public class User {
        @Inject
        @Named("address")
        private Address address1;
    }
    

4.6、与@Autowire的区别

  1. @Autowire 有@required标签,允许对象为空
  2. @Inject没有@required标签,强制要求对象不能为空

4.7、其它

  1. @Inject 与 @Autowired等效(作用上)
  2. @Named 与 @Compenet等效(类上声明时)

5、三种注解区别

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