[Spring实战系列](7)Spring注入方式之构造器注入

基于构造器的注入通过调用带参数的构造器来实现,每个参数代表着一个协作者。

## 1. 通过构造器注入简单类型

### 1.1 默认方式

我们以下面的 Book 实体类为例进行说明:
```java
package com.spring.example.domain;

public class Book {
    private Integer id;
    private String type;
    private String name;

    public Book() {
    }

    public Book(Integer id, String type, String name) {
        this.id = id;
        this.type = type;
        this.name = name;
    }

    @Override
    public String toString() {
        return "Book{" +
                "id=" + id +
                ", type='" + type + '\'' +
                ", name='" + name + '\'' +
                '}';
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

}
```
Book 类有三个属性:id、name、type。通过如下配置实现构造器注入:
```xml
<bean id="book" class="com.spring.example.domain.Book">
    <constructor-arg value="1"/>
    <constructor-arg value="计算机理论"/>
    <constructor-arg value="深入理解 Mybatis"/>
</bean>
```
我们通过 ClassPathXmlApplicationContext 加载配置文件得到容器对象,从而获取 Book bean:
```java
// 加载配置文件得到上下文对象 即 容器对象
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
// 获取资源
Book book = (Book) ctx.getBean("book");
System.out.println(book);
```
实际执行效果如下所示:
```java
Book{id=1, type='计算机理论', name='深入理解 Mybatis'}
```
从上面我们就可以知道通过构造器注入成功了。


### 1.2 根据 name 属性传值

bean 中的 constructor-arg 元素用于通过构造器注入来设置属性。因为 Book 类中只有一个构造器,所以该代码运行没有任何问题,但是当有相同个数参数的构造器时,将会出现冲突。考虑如下 Book 类提供了两个构造器:
```java
public Book(String name, String type) {
    this.name = name;
    this.type = type;
}

public Book(Integer id, String name) {
    this.id = id;
    this.name = name;
}
```
假设我们进行如下配置:
```xml
<bean id="book" class="com.spring.example.domain.Book">
    <constructor-arg value="1"/>
    <constructor-arg value="深入理解 Mybatis"/>
</bean>
```
那么现在你认为哪个构造器将被调用?按照我们的思维应该是第二个带 Integer 和 String 参数的构造器,真的吗?实际上 Spring 将调用第一个构造器。即使我们知道第一个参数是 Integer,第二个参数是 String,但是 Spring 将其都解析为 String。实际运行结果如下所示:
```java
Book{id=null, type='深入理解 Mybatis', name='1'}
```
针对上面的这种情况,我们可以在构造器参数定义中使用 name 属性与参数进行匹配。现在如下配置,第二个构造器将被调用:
```xml
<bean id="book-name" class="com.spring.example.domain.Book">
    <constructor-arg name="id" value="1"/>
    <constructor-arg name="name" value="深入理解 Mybatis"/>
</bean>
```
这样设置之后只能匹配 `public Book(Integer id, String name)` 构造器。实际运行结果如下所示:
```java
Book{id=1, type='null', name='深入理解 Mybatis'}
```

### 1.3 根据 type 属性传值

通过 name 属性这种方式匹配虽然解决了上述问题,但是如果我们的参数名称发生修改,配置文件也需要对应修改,没办法实现解耦。这时候我们可以在构造器参数定义中使用 type 属性来显式指定参数所对应的简单类型,如下所示:
```xml
<bean id="book-type" class="com.spring.example.domain.Book">
    <constructor-arg type="java.lang.Integer" value="1"/>
    <constructor-arg type="java.lang.String" value="深入理解 Mybatis"/>
</bean>
```
这样设置之后 `value="1"` 则是 Integer 类型,所以只能匹配 `public Book(Integer id, String name)` 构造器。实际运行结果如下所示:
```java
Book{id=1, type='null', name='深入理解 Mybatis'}
```

### 1.4 根据 index 属性传值

现在考虑如下情况,有如下两个数据类型一样的构造器:
```java
public Book(String type, Integer id) {
    this.id = id;
    this.type = type;
}

public Book(Integer id, String name) {
    this.id = id;
    this.name = name;
}
```
我们还是使用上述在构造器参数定义中使用 type 属性来显式指定参数所对应的简单类型:
```xml
<bean id="book-type" class="com.spring.example.domain.Book">
    <constructor-arg type="java.lang.String" value="深入理解 Mybatis"/>
    <constructor-arg type="java.lang.Integer" value="1"/>
</bean>
```
那么现在你认为哪个构造器将被调用?按照我们的思维应该是第二个带 Integer 和 String 参数的构造器,真的吗?实际上 Spring 可能会调用第一个构造器,实际运行结果如下所示:
```java
Book{id=1, type='深入理解 Mybatis', name='null'}
```
针对上面的这种情况,我们可以在构造器参数定义中使用 index 属性显式的指定构造器参数的顺序:
```xml
<bean id="book-index" class="com.spring.example.domain.Book">
    <constructor-arg index="0" value="1"/>
    <constructor-arg index="1" value="深入理解 Mybatis"/>
</bean>
```
这样设置之后只能匹配 `public Book(Integer id, String name)` 构造器。实际运行结果如下所示:
```java
Book{id=1, type='null', name='深入理解 Mybatis'}
```

> 不会调用第一个构造器的原因是不能将index=1的参数转换为 Integer:Could not convert argument value of type [java.lang.String] to required type [java.lang.Integer]


## 2. 通过构造器注入对象引用

我们为 Student 提供一个最喜欢的书 Book 实体类:
```java
public class Student {
    private int id;
    private String name;
    private Book book;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Book getBook() {
        return book;
    }

    public void setBook(Book book) {
        this.book = book;
    }
}
```
我们使用构造器注入的方式 School 类进行配置:
```xml
<bean id="student" class="com.spring.example.domain.Student">
    <constructor-arg value="10001"/>
    <constructor-arg value="Lucy"/>
    <constructor-arg ref="book"/>
</bean>
```
在这我们不能使用 value 属性为第三个构造参数赋值,因为 Book 不是简单类型。取而代之的是,我们使用 ref 属性将 ID 为 book 的 Bean 引用传递给构造器。实际运行效果如下:
```java
Student{id=10001, name='Lucy', book=Book{id=1, type='计算机理论', name='深入理解 Mybatis'}}
```
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

@SmartSi

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值