【Day38】Spring 框架入门:IOC 容器与 DI 依赖注入

本文收录于「Java 学习日记」专栏,聚焦 Spring 框架的两大核心 ------IOC(控制反转)与 DI(依赖注入),从底层原理拆解到实战落地,帮你打通 Spring 框架的入门关键一步~

一、为什么要学 Spring 框架?

在上一篇手动实现 MVC 框架的过程中,我们遇到了一个核心痛点:对象依赖管理混乱

  • 手动new UserServiceImpl()创建对象,组件之间耦合度极高(修改实现类需改代码);
  • 每个 Controller 都要手动创建 Service 对象,重复代码多,且对象生命周期无法统一管理;
  • 业务扩展时,对象依赖关系越复杂,手动维护成本越高。

Spring 框架的核心价值就是解决这些问题:

  • IOC(控制反转):将对象的创建权从程序员手中 "反转" 给 Spring 容器,由容器统一管理对象的生命周期;
  • DI(依赖注入):Spring 容器自动将对象的依赖(如 Service 注入到 Controller)注入到目标对象中,无需手动 new;
  • 核心目标:降低代码耦合度,实现组件解耦、可复用、可扩展,是 Java EE 开发的事实标准。

今天这篇日记,我们从 IOC/DI 的核心原理入手,手把手完成 Spring 框架的入门实战,彻底搞懂 "容器如何管理对象"。

二、核心概念:IOC 与 DI 的本质

1. 什么是 IOC(控制反转)?

IOC(Inversion of Control)------ 控制反转,是一种设计思想,核心是 **"对象的创建权反转"**:

  • 传统开发 :程序员主动new创建对象(控制权在程序员);
  • Spring 开发:程序员只定义对象,由 Spring 容器负责创建、管理、销毁对象(控制权在容器)。

用通俗的例子理解:

  • 传统方式:你(程序员)亲自去菜市场买菜(new 对象),还要自己洗、切(管理对象);
  • Spring 方式:你告诉餐厅(Spring 容器)要吃什么(定义对象),餐厅帮你准备好所有食材(创建 / 管理对象),你只需直接烹饪(使用对象)。

2. 什么是 DI(依赖注入)?

DI(Dependency Injection)------ 依赖注入,是 IOC 思想的具体实现方式

  • 当一个对象(如 UserController)依赖另一个对象(如 UserService)时,Spring 容器会自动将依赖的对象 "注入" 到目标对象中,无需手动new
  • 核心:"谁需要,容器就给谁",彻底消除对象之间的手动依赖。

3. IOC/DI 的核心优势

特性 传统开发 Spring 开发(IOC+DI)
对象创建 手动 new,耦合高 容器创建,耦合低
依赖管理 手动维护,易出错 容器自动注入,无需关心细节
生命周期 手动控制,重复代码多 容器统一管理(初始化 / 销毁)
扩展性 修改实现类需改代码 配置文件 / 注解修改,无需改代码

三、Spring 入门实战:环境准备与核心配置

1. 开发环境准备

  • JDK:8 及以上(推荐 JDK 8);
  • 构建工具:Maven(推荐)或 Gradle;
  • IDE:IDEA(推荐)或 Eclipse;
  • Spring 版本:5.3.x(稳定版)。

2. Maven 依赖配置(核心)

新建 Maven 项目,在pom.xml中添加 Spring 核心依赖:

xml

XML 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>spring-ioc-demo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <!-- 依赖版本管理 -->
    <properties>
        <spring.version>5.3.29</spring.version>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <!-- Spring核心依赖 -->
    <dependencies>
        <!-- Spring Context(包含IOC容器核心功能) -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <!-- Spring Beans(对象管理核心) -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <!-- Spring Core(核心工具类) -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <!-- Spring Expression Language(EL表达式) -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-expression</artifactId>
            <version>${spring.version}</version>
        </dependency>
    </dependencies>
</project>

3. 编写核心组件(Service 层)

3.1 业务接口:UserService

java

运行

java 复制代码
// src/main/java/com/example/service/UserService.java
package com.example.service;

/**
 * 业务接口(面向接口编程,降低耦合)
 */
public interface UserService {
    void sayHello(String username);
}

3.2 业务实现类:UserServiceImpl

java

运行

java 复制代码
// src/main/java/com/example/service/impl/UserServiceImpl.java
package com.example.service.impl;

import com.example.service.UserService;

/**
 * 业务实现类(由Spring容器管理)
 */
public class UserServiceImpl implements UserService {
    // 无参构造(Spring创建对象默认调用无参构造)
    public UserServiceImpl() {
        System.out.println("UserServiceImpl被创建了!");
    }

    @Override
    public void sayHello(String username) {
        System.out.println("Hello " + username + "!这是Spring IOC容器管理的对象");
    }
}

4. Spring 配置文件(核心)

src/main/resources目录下创建 Spring 核心配置文件applicationContext.xml,用于定义需要由容器管理的对象(Bean):

xml

XML 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- 定义Bean:让Spring容器管理UserServiceImpl对象 -->
    <!-- id:Bean的唯一标识,class:Bean的全类名 -->
    <bean id="userService" class="com.example.service.impl.UserServiceImpl"></bean>

</beans>

5. 测试 IOC 容器获取对象

编写测试类,通过 Spring 容器获取对象,而非手动 new:

java

运行

java 复制代码
// src/main/java/com/example/test/SpringIocTest.java
package com.example.test;

import com.example.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringIocTest {
    public static void main(String[] args) {
        // 1. 加载Spring配置文件,创建IOC容器
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

        // 2. 从容器中获取Bean对象(两种方式)
        // 方式1:根据id获取(需手动强转)
        UserService userService1 = (UserService) context.getBean("userService");
        // 方式2:根据id+类型获取(无需强转,推荐)
        UserService userService2 = context.getBean("userService", UserService.class);

        // 3. 使用对象
        userService1.sayHello("Java日记");
        userService2.sayHello("Spring学习者");

        // 4. 验证:容器中是单例对象(默认)
        System.out.println(userService1 == userService2); // 输出true
    }
}

6. 运行结果与解析

运行测试类,控制台输出:

plaintext

复制代码
UserServiceImpl被创建了!
Hello Java日记!这是Spring IOC容器管理的对象
Hello Spring学习者!这是Spring IOC容器管理的对象
true

核心解析:

  • UserServiceImpl被创建了!:Spring 容器启动时,自动创建了userService对象(默认单例);
  • userService1 == userService2为 true:Spring 默认创建单例 Bean,多次获取的是同一个对象;
  • 全程没有new UserServiceImpl():对象创建权完全由 Spring 容器掌控,实现了 IOC。

四、DI 依赖注入实战:自动注入依赖对象

1. 场景准备:Controller 依赖 Service

新建UserController,依赖UserService,我们通过 DI 让 Spring 自动注入UserService,而非手动 new:

java

运行

java 复制代码
// src/main/java/com/example/controller/UserController.java
package com.example.controller;

import com.example.service.UserService;

public class UserController {
    // 依赖的UserService(由Spring注入)
    private UserService userService;

    // 提供setter方法(用于Set注入)
    public void setUserService(UserService userService) {
        this.userService = userService;
    }

    // 业务方法
    public void hello(String username) {
        userService.sayHello(username);
    }
}

2. 配置 DI(Set 注入)

修改applicationContext.xml,配置UserController的 Bean,并注入userService依赖:

xml

XML 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- 1. 定义Service Bean -->
    <bean id="userService" class="com.example.service.impl.UserServiceImpl"></bean>

    <!-- 2. 定义Controller Bean,并注入Service依赖(Set注入) -->
    <bean id="userController" class="com.example.controller.UserController">
        <!-- property:指定要注入的属性名,name=属性名,ref=依赖的Bean id -->
        <property name="userService" ref="userService"></property>
    </bean>

</beans>

3. 测试 DI 注入效果

修改测试类,获取UserController并调用方法:

java

运行

java 复制代码
// src/main/java/com/example/test/SpringDiTest.java
package com.example.test;

import com.example.controller.UserController;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringDiTest {
    public static void main(String[] args) {
        // 1. 加载配置文件,创建容器
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

        // 2. 获取Controller对象(依赖已自动注入)
        UserController controller = context.getBean("userController", UserController.class);

        // 3. 调用方法(无需手动注入Service)
        controller.hello("DI依赖注入");
    }
}

运行结果:

plaintext

复制代码
UserServiceImpl被创建了!
Hello DI依赖注入!这是Spring IOC容器管理的对象

核心解析:

  • UserControlleruserService属性由 Spring 自动注入,无需手动new UserServiceImpl()
  • 只需在配置文件中通过<property>标签声明依赖关系,Spring 容器会自动完成注入,实现了解耦。

4. 常用 DI 注入方式(扩展)

除了 Set 注入,Spring 还支持构造器注入、注解注入(后续重点讲),这里补充构造器注入:

4.1 修改 UserController(添加有参构造)

java

运行

java 复制代码
public class UserController {
    private UserService userService;

    // 构造器注入:通过构造方法注入依赖
    public UserController(UserService userService) {
        this.userService = userService;
    }

    public void hello(String username) {
        userService.sayHello(username);
    }
}

4.2 配置构造器注入

xml

复制代码
<bean id="userController" class="com.example.controller.UserController">
    <!-- constructor-arg:构造器参数,ref=依赖的Bean id -->
    <constructor-arg ref="userService"></constructor-arg>
</bean>

五、注解开发:简化 IOC/DI 配置(推荐)

XML 配置方式需要编写大量标签,Spring 提供了注解方式,大幅简化配置:

1. 开启注解扫描

修改applicationContext.xml,添加注解扫描配置(扫描指定包下的注解):

xml

XML 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 开启注解扫描:扫描com.example包下的所有注解 -->
    <context:component-scan base-package="com.example"></context:component-scan>

</beans>

2. 使用注解定义 Bean

2.1 Service 层添加@Service注解

java

运行

java 复制代码
import org.springframework.stereotype.Service;

// @Service:标记为Service层Bean,等价于XML中的<bean>标签
// 不指定value时,Bean id默认为类名首字母小写(userServiceImpl)
@Service("userService")
public class UserServiceImpl implements UserService {
    @Override
    public void sayHello(String username) {
        System.out.println("注解版:Hello " + username);
    }
}

2.2 Controller 层添加@Controller+@Autowired注解

java

运行

java 复制代码
import com.example.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

// @Controller:标记为Controller层Bean
@Controller("userController")
public class UserController {
    // @Autowired:自动注入依赖(按类型匹配),无需setter/构造器
    @Autowired
    private UserService userService;

    public void hello(String username) {
        userService.sayHello(username);
    }
}

3. 测试注解开发效果

测试类代码不变,运行后输出:

plaintext

复制代码
UserServiceImpl被创建了!
注解版:Hello 注解开发

核心解析:

  • @Service/@Controller:替代 XML 的<bean>标签,告诉 Spring 容器管理该对象;
  • @Autowired:替代 XML 的<property>/<constructor-arg>,自动注入依赖,无需手动配置;
  • 注解开发大幅简化配置,是实际开发中的主流方式。

六、IOC/DI 核心细节与避坑指南

1. Bean 的作用域(Scope)

Spring 默认创建单例 Bean(singleton),可通过@Scope指定作用域:

java

运行

java 复制代码
// 原型模式:每次获取都创建新对象
@Service
@Scope("prototype")
public class UserServiceImpl implements UserService {
    // ...
}

常用作用域:

作用域 说明
singleton 单例(默认),容器启动时创建,全局唯一
prototype 原型,每次 getBean () 创建新对象
request Web 环境,每个请求创建一个 Bean
session Web 环境,每个会话创建一个 Bean

2. 常见错误与解决方案

错误场景 原因 解决方案
NoSuchBeanDefinitionException 找不到指定 id 的 Bean 检查 Bean id 是否正确,包是否扫描到
NoUniqueBeanDefinitionException 同类型 Bean 有多个,无法自动注入 使用@Qualifier("beanId")指定 Bean
NullPointerException 依赖未注入,调用时为 null 确保@Autowired注解生效,包扫描正确

3. 关键注意点

  • @Autowired默认按类型 注入,若同类型有多个 Bean,需配合@Qualifier("beanId")按名称注入;
  • Spring 创建 Bean 默认调用无参构造 ,若只有有参构造,需添加@Autowired或配置构造器注入;
  • 注解扫描必须配置context:component-scan,否则注解无效;
  • 单例 Bean 是线程不安全的,不要在单例 Bean 中定义成员变量(如用户信息)。

七、今日实战小任务

  1. 基于注解开发,新增UserDao层,让UserService依赖UserDao,通过@Autowired注入;
  2. 测试 Bean 的singletonprototype作用域,验证单例 / 原型的区别;
  3. 尝试使用@Resource注解(JDK 注解)替代@Autowired,对比两者的区别。

总结

  1. IOC(控制反转)是将对象创建权交给 Spring 容器,替代手动 new,核心是 "容器管理对象";
  2. DI(依赖注入)是 IOC 的实现方式,Spring 容器自动将依赖对象注入到目标对象中,实现解耦;
  3. Spring 开发有 XML 和注解两种方式,注解(@Service/@Controller/@Autowired)是主流,大幅简化配置;
  4. Bean 默认单例,可通过@Scope指定作用域,实际开发中需注意线程安全问题。

下一篇【Day39】预告:SpringMVC 核心详解:DispatcherServlet、@RequestMapping 与请求参数处理,关注专栏从 Spring 核心过渡到 Web 框架~若本文对你有帮助,欢迎点赞 + 收藏 + 关注,你的支持是我更新的最大动力💖!

相关推荐
rit84324992 小时前
基于偏振物理模型的水下图像去雾MATLAB实现
开发语言·matlab
爱丽_2 小时前
Spring Bean 管理与依赖注入实践
java·后端·spring
kklovecode2 小时前
数据结构---顺序表
c语言·开发语言·数据结构·c++·算法
独自破碎E2 小时前
什么是Spring Bean?
java·后端·spring
孩子 你要相信光2 小时前
解决:React 中 map 处理异步数据不渲染的问题
开发语言·前端·javascript
程序员小李白2 小时前
js初相识:简介及基本语法
前端·javascript·html
jllllyuz2 小时前
ANPC三电平逆变器损耗计算的MATLAB实现
开发语言·matlab·php
人道领域2 小时前
JavaWeb从入门到进阶(Maven的安装和在idea中的构建)
java·maven
XXOOXRT2 小时前
基于SpringBoot的留言板
java·spring boot·后端