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>
构造器注入
新建一个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
50public 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;
}
public String toString() {
return "User{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}xml配置如下,有三种不同的写法
在resource下面新建application-constructor.xml1
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 构造器参数名,根据名称进行匹配注入
- 验证
现在可以启动Spring容器来验证bean是否注入成功1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19public 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
3User{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注入。
- 接下来新建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
51public 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;
}
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
40public 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;
}
public String toString() {
return "Author{" +
"name='" + name + '\'' +
", age=" + age +
", url='" + url + '\'' +
'}';
}
}
- xml配置
setter注入是通过在bean下面配置property元素来完成的,在resource下面新建application-setter.xml1
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
2
3
4
5
6
7
8
9
10
11
12
13
14public 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,一起学习,共同进步,如有不对之处,欢迎指出。