【Java】深入理解Spring的IoC实现:Set注入与构造注入

一、前言

本文,我们来看看 SpringIoC(控制反转)的实现。

二、内容

2.1 回顾

前面我们已经讲到了控制反转(IoC)的概念,其实它就是一种思想,核心目的是通过反转对象的创建和管理权来实现降低程序的耦合度、提高程序的扩展性和可维护性。

控制反转,实际上反转的就是对象的创建权和管理权,交给第三方来实现。

控制反转思想的实现方式:DI(Dependency Injection,依赖注入)。

Spring 通过依赖注入的方式来完成 Bean 的管理,包括对象的创建、属性的赋值以及对象之间关系的维护。

对于依赖注入来说,依赖其实指的就是对象与对象之间的关联关系,注入则是一种数据传递行为,该行为能让对象与对象产生关系。

依赖注入常见的实现方式包括两种:

  • 第一种:set注入
  • 第二种:构造注入

2.2 set注入

(1)概念

Spring的依赖注入方式之一就是set注入。

什么是set注入?

简单来说,就是通过调用JavaBeansetter方法来实现属性的注入。这种方式被称为 "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方法,以将依赖的对象注入到目标对象中。步骤如下:

  1. 通过property标签获取到属性名:userDao
  2. 通过属性名推断出set方法名:setUserDao
  3. 通过反射机制调用setUserDao()方法给属性赋值
  4. 将依赖对象的实例作为参数传递给该setter方法
  5. 完成依赖注入

因此概括地说,set注入的核心原理就是通过反射机制调用set方法来为属性赋值,让两个对象产生关系

2.3 构造注入

(1)概念

现在,我们来看一下构造注入。

构造注入是一种常见的依赖注入方式,核心就是通过调用构造方法来给属性赋值

通过构造方法来注入时,可以有三种方式:

  1. 可以指定下标
  2. 可以通过参数名
  3. 甚至可以不指定下标或参数名,Spring会自动进行类型匹配。

什么意思呢?具体往下看例子。

(2)举例

首先演示单个参数的构造注入。

首先来看下面两个类:

接下来,在Spring配置文件中,我们定义了两个bean,并使用构造注入将 orderDaoBean 注入到 orderServiceBean 中。

最后来跑一下测试程序:

通过测试程序,我们可以看到构造方法成功将依赖对象注入到 OrderService 中,从而实现了删除订单的功能。


如果构造方法有多个参数,我们也可以使用构造注入来注入多个依赖对象。

来看下面这个 OrderService 类 :

在Spring配置文件中,我们使用构造注入将 orderDaoBeanuserDaoBean 注入到 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会自动进行类型匹配。

相关推荐
禁默7 分钟前
深入浅出:AWT的基本组件及其应用
java·开发语言·界面编程
Cachel wood13 分钟前
python round四舍五入和decimal库精确四舍五入
java·linux·前端·数据库·vue.js·python·前端框架
Code哈哈笑16 分钟前
【Java 学习】深度剖析Java多态:从向上转型到向下转型,解锁动态绑定的奥秘,让代码更优雅灵活
java·开发语言·学习
gb421528719 分钟前
springboot中Jackson库和jsonpath库的区别和联系。
java·spring boot·后端
程序猿进阶19 分钟前
深入解析 Spring WebFlux:原理与应用
java·开发语言·后端·spring·面试·架构·springboot
zfoo-framework27 分钟前
【jenkins插件】
java
风_流沙33 分钟前
java 对ElasticSearch数据库操作封装工具类(对你是否适用嘞)
java·数据库·elasticsearch
颜淡慕潇1 小时前
【K8S问题系列 |19 】如何解决 Pod 无法挂载 PVC问题
后端·云原生·容器·kubernetes
ProtonBase1 小时前
如何从 0 到 1 ,打造全新一代分布式数据架构
java·网络·数据库·数据仓库·分布式·云原生·架构