Spring之IOC的注入方式

Spring之IOC的注入方式

在java中,要使用一个对象,必须先创建一个实例,但是有了IOC之后,对象的创建与销毁都交给了IOC容器,不用我们手动创建,而是直接从IOC容器中获取,达到了解耦的效果。IOC是一种思想,在Spring中,实现IOC的方式是DI(依赖注入),本文会介绍Spring依赖注入的几种方式。

Spring的依赖注入

对象,在Spring中叫做bean,即使是最简单的应用,也需要多个bean共同协作。依赖注入是指对象之间的依赖关系,一起协作的其他对象,通过构造器的参数、工厂方法的参数创建的对象,或者构造函数、工厂方法创建的对象来设置属性。所以容器的工作实际上就是创建bean并注入依赖关系。Spring中的DI方式主要有两种,构造器注入和Setter注入。

项目准备

新建一个maven项目,JDK版本1.8,引入Spring的核心依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>
</dependencies>

构造器注入

  1. 新建一个User类作为注入的例子,添加get,set方法,重写toString方法,添加两个参数不同的构造器。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    public class User {
    private String id;

    private String name;

    private Integer age;

    public User(String id, String name) {
    this.id = id;
    this.name = name;
    }

    public User(String name, Integer age) {
    this.name = name;
    this.age = age;
    }

    public String getId() {
    return id;
    }

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

    public String getName() {
    return name;
    }

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

    public Integer getAge() {
    return age;
    }

    public void setAge(Integer age) {
    this.age = age;
    }

    @Override
    public String toString() {
    return "User{" +
    "id='" + id + '\'' +
    ", name='" + name + '\'' +
    ", age=" + age +
    '}';
    }
    }
  2. xml配置如下,有三种不同的写法
    在resource下面新建application-constructor.xml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://www.springframework.org/schema/beans"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--构造方法注入-->
    <!--使用index属性来显式指定构造参数的索引-->
    <bean id="user1" class="org.spring.ioc.entity.User">
    <constructor-arg index="0" value="1234"/>
    <constructor-arg index="1" value="spring"/>
    </bean>

    <!--使用type属性显式指定简单类型的构造器参数类型,这里对应的是User类中传入name,age的构造器-->
    <bean id="user2" class="org.spring.ioc.entity.User">
    <constructor-arg type="java.lang.String" value="spring"/>
    <constructor-arg type="java.lang.Integer" value="20"/>
    </bean>

    <!--也可以使用构造器参数命名来指定值的类型-->
    <bean id="user3" class="org.spring.ioc.entity.User">
    <constructor-arg name="id" value="1234"/>
    <constructor-arg name="name" value="spring"/>
    </bean>
    </beans>

在bean的constructor-arg元素下进行指定,constructor-arg顾名思义就是构造器参数的意思,其中包括了三个属性配置

  • index 是一个索引顺序,对应构造器参数的索引,根据索引进行注入
  • type 构造器的参数类型,可以通过类型进行匹配注入
  • name 构造器参数名,根据名称进行匹配注入
  1. 验证
    现在可以启动Spring容器来验证bean是否注入成功
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public class Main {
    private final static String APPLICATION = "classpath:application-*.xml";

    public static void main(String[] args) {
    //加载xml配置文件
    ApplicationContext context = new ClassPathXmlApplicationContext(APPLICATION);
    constructorInject(context);
    }

    private static void constructorInject(ApplicationContext context) {
    //获取bean实例,传入的参数值为xml中配置的id
    User user1 = (User) context.getBean("user1");
    System.out.println(user1.toString());
    User user2 = (User) context.getBean("user2");
    System.out.println(user2.toString());
    User user3 = (User) context.getBean("user3");
    System.out.println(user3.toString());
    }
    }

输出结果如下:

1
2
3
User{id='1234', name='spring', age=null}
User{id='null', name='spring', age=20}
User{id='1234', name='spring', age=null}

可以看出我们定义的User对象已经成功交给Spring容器管理

Setter注入

Setter注入也需要在xml中进行配置,在调用了无参的构造方法或者无参的静态工厂方法实例化bean之后,容器通过回调bean的setter方法来完成setter注入。

  1. 接下来新建Blog,Author两个类,添加get,set方法,重写toString方法:
    Blog类:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    public class Blog {
    private String name;

    private String content;

    private Long date;

    private Author author;

    public String getName() {
    return name;
    }

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

    public String getContent() {
    return content;
    }

    public void setContent(String content) {
    this.content = content;
    }

    public Long getDate() {
    return date;
    }

    public void setDate(Long date) {
    this.date = date;
    }

    public Author getAuthor() {
    return author;
    }

    public void setAuthor(Author author) {
    this.author = author;
    }

    @Override
    public String toString() {
    return "Blog{" +
    "name='" + name + '\'' +
    ", content='" + content + '\'' +
    ", date=" + date +
    ", author=" + author +
    '}';
    }
    }

Author类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class Author {
private String name;

private Integer age;

private String url;

public String getName() {
return name;
}

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

public Integer getAge() {
return age;
}

public void setAge(Integer age) {
this.age = age;
}

public String getUrl() {
return url;
}

public void setUrl(String url) {
this.url = url;
}

@Override
public String toString() {
return "Author{" +
"name='" + name + '\'' +
", age=" + age +
", url='" + url + '\'' +
'}';
}
}

  1. xml配置
    setter注入是通过在bean下面配置property元素来完成的,在resource下面新建application-setter.xml
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://www.springframework.org/schema/beans"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--setter注入-->
    <bean id="blog" class="org.spring.ioc.entity.Blog">
    <property name="name" value="spring-ioc"/>
    <property name="content" value="spring"/>
    <property name="date" value="1520232449944"/>
    <property name="author" ref="author"/>
    </bean>

    <bean id="author" class="org.spring.ioc.entity.Author">
    <property name="name" value="luoliang"/>
    <property name="age" value="18"/>
    <property name="url" value="https://meua.github.io"/>
    </bean>
    </beans>

配置很简单,通过制定property元素的name和value属性,设置变量名对应的值,其中author属性引用的另一个bean,所以使用了ref属性。

  1. 验证
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public class Main {
    private final static String APPLICATION = "classpath:application-*.xml";

    public static void main(String[] args) {
    //加载xml配置文件
    ApplicationContext context = new ClassPathXmlApplicationContext(APPLICATION);
    setterInject(context);
    }

    private static void setterInject(ApplicationContext context) {
    Blog blog = (Blog) context.getBean("blog");
    System.out.println(blog.toString());
    }
    }

结果如下:

1
Blog{name='spring-ioc', content='spring', date=1520232449944, author=Author{name='luoliang', age=18, url='https://meua.github.io'}}

所有我们配置的属性都注入成功。

总结

我们可以混合使用构造器注入和Setter注入,最佳实践是强制性依赖关系时使用构造器注入,可选的依赖关系时使用Setter注入,在setter注入中可以使用@Required注解让属性成为必须的依赖项。
有很多小伙伴会觉得很奇怪,明明使用注解进行配置依赖更加的简单。不否认,SpringBoot推出之后,Spring已经不再推荐xml配置,而是提倡java配置和注解,但是xml配置是Spring的基础。正是因为传统的Spring应用xml配置太过于复杂,才会出现SpringBoot这门技术来解决这些问题,一个技术的兴起是有各种原因的。SpringBoot虽然解决了配置复杂的问题,但是对于刚入门的人来说,不知道其中的细节,这可能并不是一个好的开始。
上面用到的ApplicationContext,它所管理的beans支持构造函数注入和setter注入,在一些依赖已经使用构造器注入之后它还支持setter注入。我们也可以用BeanDefinition的形式配置依赖,它能根据指定的PropertyEditor实现将属性从一种格式转化为另外一种格式。但是,在日常的开发中我们不会直接以编程的方式去创建bean,而是采用上面所讲的xml配置创建bean,或者是通过注解(即@Component,@Service等注解类),或者基于@Configuration类的@Bean方法。本质上这些资源会转换成BeanDefinition的实例并且用于加载整个Spring IoC容器实例。所以,不管我们在传统Spring应用还是SpringBoot中,使用Spring的IOC,它的原理都是不会变的。

源码

上面所用到的代码我已放在我的github上,欢迎star,一起学习,共同进步,如有不对之处,欢迎指出。