一、前言
本文,我们来看看 Spring
对 IoC
(控制反转)的实现。
二、内容
2.1 回顾
前面我们已经讲到了控制反转(IoC)的概念,其实它就是一种思想,核心目的是通过反转对象的创建和管理权来实现降低程序的耦合度、提高程序的扩展性和可维护性。
控制反转,实际上反转的就是对象的创建权和管理权,交给第三方来实现。
控制反转思想的实现方式:DI(Dependency Injection,依赖注入)。
Spring 通过依赖注入的方式来完成 Bean 的管理,包括对象的创建、属性的赋值以及对象之间关系的维护。
对于依赖注入来说,依赖其实指的就是对象与对象之间的关联关系,注入则是一种数据传递行为,该行为能让对象与对象产生关系。
依赖注入常见的实现方式包括两种:
- 第一种:set注入
- 第二种:构造注入
2.2 set注入
(1)概念
Spring的依赖注入方式之一就是set注入。
什么是set注入?
简单来说,就是通过调用JavaBean
的setter
方法来实现属性的注入。这种方式被称为 "setter注入" 。它要求类的属性必须提供setter
方法,以便Spring容器可以通过反射机制调用这些方法来设置属性的值。
通常,这些
setter
方法会遵循命名约定(通过 IDEA 自动生成),比如你有一个属性名为name
,那么对应的setter方法应该命名为setName
。
(2)举例
举一个例子,现在有一个类为 User
,它有一个name
属性和相应的setter方法:
java
public class User {
private String name;
public void setName(String name) {
this.name = name;
}
// 其他属性和方法
}
在Spring配置文件中,我们需要使用<property>
元素来注入属性值:
xml
<bean id="userBean" class="com.example.User">
<property name="name" value="John Doe" />
</bean>
当Spring容器初始化这个bean时,它将通过反射调用setName
方法,将值 "John Doe" 设置到name
属性上。
测试代码如下:
java
package com.example;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class UserTest {
public static void main(String[] args) {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
User userBean = applicationContext.getBean("userBean", User.class);
System.out.println(userBean); // com.example.User@7e4204e2
}
}
因此可以看到,通过setter注入,我们可以非常灵活地配置bean的属性,并且可以满足属性值的动态变化。这种方式是Spring依赖注入的核心方式之一,但需要确保你的bean类提供了适当的setter方法,以便Spring容器能够注入属性值。
我们再来看下面这个例子。
简单模拟一下,假设 UserService
是一个业务逻辑类,它依赖于 UserDao
类来执行数据库操作。
在 UserService
类中,有两个方法:
setUserDao(UserDao userDao)
方法是用来进行依赖注入的。通过该方法,Spring框架会将一个UserDao
对象注入到UserService
类中,从而使UserService
类能够访问和使用UserDao
对象。save()
: 这个方法实际上是在内部调用了userDao.insert()
方法,用于保存用户数据。
现在,我们在配置文件中这样写:
xml
<bean id="userDaoBean" class="com.example.dao.UserDao" />
<bean id="userServiceBean" class="com.example.service.UserService">
<property name="userDao" ref="userDaoBean"/>
</bean>
这样配置后,Spring框架会在初始化 userServiceBean
实例时,自动注入 UserDao
对象到 UserService
类的 userDao
属性中。
测试代码如下:
java
package com.example;
import com.example.service.UserService;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class UserTest {
public static void main(String[] args) {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
UserService userService = applicationContext.getBean("userServiceBean", UserService.class);
userService.save(); // 正在保存用户数据...
}
}
(3)原理
前面讲过,Spring容器解析配置文件时,会根据配置文件中的 <bean>
元素来创建和管理Java对象的实例。对于每个 <bean>
元素,Spring会根据 class
属性指定的类全名来创建一个对象实例。
如果 <bean>
元素中包含 <property>
子元素,Spring将会使用反射机制来调用目标对象的setter方法,以将依赖的对象注入到目标对象中。步骤如下:
- 通过
property
标签获取到属性名:userDao
- 通过属性名推断出
set
方法名:setUserDao
- 通过反射机制调用
setUserDao()
方法给属性赋值 - 将依赖对象的实例作为参数传递给该setter方法
- 完成依赖注入
因此概括地说,set注入的核心原理就是通过反射机制调用set方法来为属性赋值,让两个对象产生关系。
2.3 构造注入
(1)概念
现在,我们来看一下构造注入。
构造注入是一种常见的依赖注入方式,核心就是通过调用构造方法来给属性赋值。
通过构造方法来注入时,可以有三种方式:
- 可以指定下标
- 可以通过参数名
- 甚至可以不指定下标或参数名,Spring会自动进行类型匹配。
什么意思呢?具体往下看例子。
(2)举例
首先演示单个参数的构造注入。
首先来看下面两个类:
接下来,在Spring配置文件中,我们定义了两个bean,并使用构造注入将 orderDaoBean
注入到 orderServiceBean
中。
最后来跑一下测试程序:
通过测试程序,我们可以看到构造方法成功将依赖对象注入到 OrderService
中,从而实现了删除订单的功能。
如果构造方法有多个参数,我们也可以使用构造注入来注入多个依赖对象。
来看下面这个 OrderService
类 :
在Spring配置文件中,我们使用构造注入将 orderDaoBean
和 userDaoBean
注入到 orderServiceBean
中。其中可以通过指定构造方法的参数下标来指定注入。
通过下面这个测试程序,我们可以看到构造方法成功将多个依赖对象注入到 OrderService
中,从而实现了删除订单和插入用户数据的功能。
即使我们不指定参数下标index,不指定参数名字name,甚至连配置顺序都和构造方法中的参数顺序不一致,同样可以实现依赖注入。
因此我们来总结一下。
java
public class MyService {
private DependencyA dependencyA;
private DependencyB dependencyB;
// 构造函数注入,依赖两个对象
public MyService(DependencyA a, DependencyB b) {
this.dependencyA = a;
this.dependencyB = b;
}
// 其他业务逻辑方法
}
public class DependencyA {
// 依赖A的逻辑
}
public class DependencyB {
// 依赖B的逻辑
}
比如上面 MyService
类依赖于 DependencyA
类和 DependencyB
类,当Spring容器创建 MyService
实例时,会自动将这两个依赖对象传递给构造函数。
在XML配置中,可以这样配置:
xml
<bean id="dependencyA" class="com.example.DependencyA" />
<bean id="dependencyB" class="com.example.DependencyB" />
<bean id="myService" class="com.example.MyService">
<!--这里使用了构造方法上参数的名字-->
<constructor-arg name="a" ref="dependencyA" />
<constructor-arg name="b" ref="dependencyB" />
</bean>
也可以这样配置:
xml
<bean id="dependencyA" class="com.example.DependencyA" />
<bean id="dependencyB" class="com.example.DependencyB" />
<bean id="myService" class="com.example.MyService">
<!--第一个参数下标是0-->
<constructor-arg index="0" ref="dependencyA"/>
<!--第二个参数下标是1-->
<constructor-arg index="1" ref="dependencyB"/>
</bean>
还可以这样配置:
xml
<bean id="dependencyA" class="com.example.DependencyA" />
<bean id="dependencyB" class="com.example.DependencyB" />
<bean id="myService" class="com.example.MyService">
<constructor-arg ref="dependencyA" />
<constructor-arg ref="dependencyB" />
</bean>
(3)原理
构造注入是依赖注入(Dependency Injection)的一种实现方式,其核心原理在于通过调用构造方法(构造函数)来为类的属性赋值。这种注入方式是一种将依赖关系传递给目标类的方式,允许我们在创建对象时提供所需的依赖,从而实现松散耦合和更容易测试的代码。
在类的构造函数中,你可以定义参数,每个参数代表一个依赖对象。当实例化一个类时,构造函数的参数用于传递依赖关系。在Spring中,当你请求从容器获取一个特定类型的bean时,容器会查找该类型的构造函数,检查其参数,然后尝试解析这些参数所需的依赖关系。
解析完成后,Spring会自动装配构造函数参数所需的依赖对象。通过查找容器中与参数类型匹配的bean,将它们传递给构造函数来完成对象的创建。
因此我们说,构造函数注入是依赖传递的一种方式。通过构造函数,依赖对象被传递给目标对象,这样目标对象可以在初始化时访问这些依赖对象,并在其生命周期内使用它们来完成其功能。
三、总结
本文介绍了Spring框架中的IoC(控制反转)实现,探讨了依赖注入的两种主要方式:构造注入和Setter注入。
总的来说,Setter注入通过setter方法为对象属性赋值,更加灵活,而构造注入则通过构造方法的调用来为对象属性赋值,它可以实现单个参数或多个参数的注入,甚至无需指定参数下标或参数名,Spring会自动进行类型匹配。