当前位置: 首页 > news >正文

Spring之核心容器(IoC,DI,基本操作)详解

Spring之核心容器IoC/DI/基本操作详解

    • 一、核心概念:IoC与DI的本质
      • 1.1 IoC(Inversion of Control,控制反转)
        • 传统开发模式(无IoC)
        • IoC模式(Spring容器管理)
      • 1.2 DI(Dependency Injection,依赖注入)
        • DI的三种实现方式
      • 1.3 IoC容器的核心作用
    • 二、Spring容器的核心接口与实现类
      • 2.1 核心接口关系
      • 2.2 常用容器实现类
    • 三、Bean的定义与依赖注入(DI)实战
      • 3.1 环境准备
      • 3.2 基于XML的Bean定义与注入
        • 3.2.1 定义Bean(XML配置)
        • 3.2.2 目标类(UserDao、UserService)
        • 3.2.3 启动容器并使用Bean
      • 3.3 基于注解的Bean定义与注入(推荐)
        • 3.3.1 核心注解
        • 3.3.2 注解配置实战
        • 3.3.3 启动容器(基于注解配置)
      • 3.4 三种依赖注入方式对比
        • 3.4.1 构造器注入(推荐)
        • 3.4.2 Setter注入
        • 3.4.3 字段注入(简洁但不推荐)
    • 四、Spring容器的基本操作
      • 4.1 容器的创建与关闭
        • 创建容器
        • 关闭容器
      • 4.2 获取Bean的三种方式
      • 4.3 Bean的作用域(Scope)
      • 4.4 Bean的生命周期
        • 生命周期示例
    • 五、常见问题与避坑指南
      • 5.1 Bean的命名冲突
      • 5.2 循环依赖问题
      • 5.3 单实例Bean的线程安全问题

Spring框架的核心是IoC容器,它通过控制反转(IoC)和依赖注入(DI)实现对象的管理与依赖解耦,是Spring所有功能的基础。

一、核心概念:IoC与DI的本质

1.1 IoC(Inversion of Control,控制反转)

IoC是一种设计思想,核心是将对象的创建权由开发者转移给容器,实现“谁用谁创建”到“容器创建后注入”的转变。

传统开发模式(无IoC)
// 传统方式:开发者手动创建对象
public class UserService {// 依赖UserDao,手动创建private UserDao userDao = new UserDaoImpl();public void addUser() {userDao.insert(); // 调用依赖对象的方法}
}

问题

  • 依赖硬编码(new UserDaoImpl()),若更换实现类(如UserDaoMybatisImpl),需修改UserService源码;
  • 对象创建与业务逻辑耦合,难以测试和扩展。
IoC模式(Spring容器管理)
// IoC方式:容器创建对象,开发者仅声明依赖
public class UserService {// 依赖UserDao,由容器注入(无需手动new)@Autowiredprivate UserDao userDao;public void addUser() {userDao.insert();}
}

核心变化

  • 对象创建权转移:UserDao的实例由Spring容器创建,而非UserService手动创建;
  • 依赖解耦:UserService仅依赖UserDao接口,不依赖具体实现,更换实现类无需修改源码。

1.2 DI(Dependency Injection,依赖注入)

DI是IoC的具体实现方式,指容器在创建对象时,自动将依赖的对象注入到当前对象中。简单说:IoC是思想,DI是手段。

DI的三种实现方式
  1. 构造器注入:通过构造方法传入依赖对象;
  2. Setter注入:通过Setter方法设置依赖对象;
  3. 字段注入:通过注解直接标记字段(如@Autowired)。

后续会通过代码示例详细讲解这三种方式。

1.3 IoC容器的核心作用

Spring的IoC容器(如ApplicationContext)本质是一个“对象工厂”,核心功能:

  1. 对象管理:创建、存储、销毁Bean(Spring对对象的称呼);
  2. 依赖注入:自动将依赖的Bean注入到目标对象;
  3. 生命周期管理:控制Bean的初始化、销毁等生命周期节点;
  4. 配置解析:读取XML、注解等配置,解析Bean的定义。

二、Spring容器的核心接口与实现类

Spring提供了两套核心容器接口:BeanFactoryApplicationContext,后者是前者的增强版,实际开发中优先使用ApplicationContext

2.1 核心接口关系

BeanFactory(基础容器)└── ApplicationContext(高级容器,继承BeanFactory)├── ClassPathXmlApplicationContext(XML配置,类路径加载)├── FileSystemXmlApplicationContext(XML配置,文件系统加载)├── AnnotationConfigApplicationContext(注解配置)└── WebApplicationContext(Web环境专用)

2.2 常用容器实现类

容器实现类特点适用场景
ClassPathXmlApplicationContext从类路径加载XML配置文件非Web项目,配置文件在src/main/resources
AnnotationConfigApplicationContext基于注解配置(如@Configuration注解驱动开发,无XML配置

三、Bean的定义与依赖注入(DI)实战

3.1 环境准备

创建Maven项目,添加Spring核心依赖:

<dependencies><!-- Spring核心容器 --><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.3.20</version></dependency>
</dependencies>

3.2 基于XML的Bean定义与注入

3.2.1 定义Bean(XML配置)

创建src/main/resources/spring.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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd"><!-- 定义UserDao的Bean(id:唯一标识,class:全类名) --><bean id="userDao" class="com.example.dao.UserDaoImpl"/><!-- 定义UserService的Bean,并注入UserDao --><bean id="userService" class="com.example.service.UserService"><!-- Setter注入:通过setUserDao方法注入userDao --><property name="userDao" ref="userDao"/></bean>
</beans>
3.2.2 目标类(UserDao、UserService)
// UserDao接口
public interface UserDao {void insert();
}// UserDao实现类
public class UserDaoImpl implements UserDao {@Overridepublic void insert() {System.out.println("UserDaoImpl:插入用户");}
}// UserService(需要注入UserDao)
public class UserService {private UserDao userDao;// Setter方法(用于Setter注入,方法名需对应XML中的property name)public void setUserDao(UserDao userDao) {this.userDao = userDao;}public void addUser() {userDao.insert(); // 调用注入的UserDao}
}
3.2.3 启动容器并使用Bean
public class Main {public static void main(String[] args) {// 1. 加载Spring配置文件,创建容器(ApplicationContext是IoC容器的核心接口)ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");// 2. 从容器中获取UserService(参数为XML中定义的id)UserService userService = context.getBean("userService", UserService.class);// 3. 调用方法(依赖的UserDao已被容器注入)userService.addUser(); // 输出:UserDaoImpl:插入用户}
}

3.3 基于注解的Bean定义与注入(推荐)

注解配置比XML更简洁,是现代Spring开发的主流方式。

3.3.1 核心注解
注解作用
@Component标记类为Bean(通用注解)
@Repository标记DAO层Bean(@Component的特例)
@Service标记Service层Bean(@Component的特例)
@Controller标记Controller层Bean(Web环境)
@Autowired自动注入依赖(默认按类型匹配)
@Configuration标记配置类(替代XML配置文件)
@ComponentScan扫描指定包下的注解Bean
3.3.2 注解配置实战
// 1. 配置类(替代XML,扫描com.example包下的注解Bean)
@Configuration
@ComponentScan("com.example")
public class SpringConfig {// 无需手动定义Bean,通过@Component等注解自动扫描
}// 2. UserDaoImpl(用@Repository标记为Bean)
@Repository // 等价于<bean id="userDaoImpl" class="..."/>
public class UserDaoImpl implements UserDao {@Overridepublic void insert() {System.out.println("UserDaoImpl:插入用户");}
}// 3. UserService(用@Service标记,并通过@Autowired注入UserDao)
@Service // 等价于<bean id="userService" class="..."/>
public class UserService {// 字段注入:直接在字段上标记@Autowired(无需Setter或构造器)@Autowiredprivate UserDao userDao;public void addUser() {userDao.insert();}
}
3.3.3 启动容器(基于注解配置)
public class Main {public static void main(String[] args) {// 加载注解配置类,创建容器ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);// 获取UserService(Bean id默认是类名首字母小写:userService)UserService userService = context.getBean("userService", UserService.class);userService.addUser(); // 输出:UserDaoImpl:插入用户}
}

3.4 三种依赖注入方式对比

3.4.1 构造器注入(推荐)

通过构造方法注入依赖,确保对象创建时依赖已初始化:

@Service
public class UserService {private final UserDao userDao;// 构造器注入(@Autowired可省略,Spring 4.3+支持单构造器自动注入)@Autowiredpublic UserService(UserDao userDao) {this.userDao = userDao;}
}

优势

  • 依赖不可变(final修饰),避免后续被修改;
  • 强制初始化依赖,防止null异常。
3.4.2 Setter注入

通过Setter方法注入,灵活性高(可在对象创建后修改依赖):

@Service
public class UserService {private UserDao userDao;@Autowiredpublic void setUserDao(UserDao userDao) {this.userDao = userDao;}
}

优势:适合可选依赖(可设置默认值)。

3.4.3 字段注入(简洁但不推荐)

直接在字段上注入,代码简洁但存在缺陷:

@Service
public class UserService {@Autowiredprivate UserDao userDao; // 字段注入
}

缺陷

  • 无法注入final字段(构造器注入可以);
  • 依赖隐藏在字段中,不通过构造器或方法暴露,可读性差;
  • 不利于单元测试(难以手动注入模拟对象)。

四、Spring容器的基本操作

4.1 容器的创建与关闭

创建容器
// 1. 基于XML(类路径)
ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");// 2. 基于XML(文件系统路径)
ApplicationContext context = new FileSystemXmlApplicationContext("D:/spring.xml");// 3. 基于注解配置类
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
关闭容器

ApplicationContext无直接关闭方法,需通过ConfigurableApplicationContext

ConfigurableApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
// 关闭容器(触发Bean的销毁方法)
context.close();

4.2 获取Bean的三种方式

// 1. 通过id获取(返回Object,需强转)
UserService userService1 = (UserService) context.getBean("userService");// 2. 通过id+类型获取(推荐,无需强转)
UserService userService2 = context.getBean("userService", UserService.class);// 3. 通过类型获取(适合单实例Bean,存在多个同类型Bean时报错)
UserService userService3 = context.getBean(UserService.class);

4.3 Bean的作用域(Scope)

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

@Service
@Scope("prototype") // 多实例:每次获取Bean时创建新对象
public class UserService { ... }

常用作用域:

作用域说明适用场景
singleton单实例(默认),容器启动时创建无状态Bean(如Service、Dao)
prototype多实例,每次获取时创建有状态Bean(如Model、View)
request每个HTTP请求创建一个实例(Web环境)Web应用请求相关Bean
session每个会话创建一个实例(Web环境)Web应用会话相关Bean

4.4 Bean的生命周期

Spring容器管理Bean的完整生命周期:

  1. 实例化:创建Bean对象(调用构造方法);
  2. 属性注入:注入依赖的Bean;
  3. 初始化:执行初始化方法(如@PostConstruct);
  4. 使用:Bean可被容器获取并使用;
  5. 销毁:容器关闭时执行销毁方法(如@PreDestroy)。
生命周期示例
@Service
public class UserService {// 1. 实例化(构造方法)public UserService() {System.out.println("UserService:构造方法(实例化)");}// 2. 属性注入(@Autowired)@Autowiredprivate UserDao userDao;// 3. 初始化方法(@PostConstruct标记)@PostConstructpublic void init() {System.out.println("UserService:初始化");}// 5. 销毁方法(@PreDestroy标记)@PreDestroypublic void destroy() {System.out.println("UserService:销毁");}
}

执行结果

UserService:构造方法(实例化)
UserService:初始化  // 容器启动时执行
// 使用Bean...
UserService:销毁    // 容器关闭时执行

五、常见问题与避坑指南

5.1 Bean的命名冲突

当容器中存在多个同类型Bean时,注入会报错NoUniqueBeanDefinitionException

// 两个UserDao实现类
@Repository
public class UserDaoImpl1 implements UserDao { ... }@Repository
public class UserDaoImpl2 implements UserDao { ... }// 注入时冲突
@Service
public class UserService {@Autowired // 报错:存在两个UserDao Beanprivate UserDao userDao;
}

解决方案

  1. @Qualifier指定Bean的id:
@Autowired
@Qualifier("userDaoImpl1") // 指定注入id为userDaoImpl1的Bean
private UserDao userDao;
  1. @Primary标记优先注入的Bean:
@Repository
@Primary // 优先注入
public class UserDaoImpl1 implements UserDao { ... }

5.2 循环依赖问题

两个Bean互相依赖(A依赖B,B依赖A)会导致循环依赖:

@Service
public class AService {@Autowiredprivate BService bService;
}@Service
public class BService {@Autowiredprivate AService aService;
}

解决方案

  1. @Lazy延迟注入(打破即时依赖):
@Service
public class AService {@Autowired@Lazy // 延迟注入BServiceprivate BService bService;
}
  1. 改用Setter注入(构造器注入无法解决循环依赖)。

5.3 单实例Bean的线程安全问题

单实例Bean(默认)在多线程环境下,若存在共享状态(如成员变量),会有线程安全问题:

@Service
public class UserService {// 共享状态(多线程访问会冲突)private int count = 0;public void increment() {count++; // 线程不安全操作}
}

解决方案

  1. 避免共享状态(推荐):单实例Bean设计为无状态(不定义成员变量);
  2. 改用prototype作用域(不推荐,性能差);
  3. 使用线程安全容器(如ThreadLocal)。

总结:Spring核心容器通过IoC和DI实现了对象的“按需创建”和“自动注入”:

  1. 依赖解耦:对象之间仅依赖接口,不依赖具体实现,降低耦合度;
  2. 简化开发:开发者无需关注对象创建和依赖管理,专注业务逻辑;
  3. 可扩展性:通过配置或注解轻松更换Bean实现,无需修改业务代码;
  4. 生命周期管理:容器统一管理Bean的创建、初始化、销毁,便于资源控制。
    掌握Spring容器的核心是理解“容器是对象的管理者”:它创建对象、注入依赖、控制生命周期,是整个Spring生态的基础。后续学习Spring的AOP、事务等功能,都需要以容器为基础。

若这篇内容帮到你,动动手指支持下!关注不迷路,干货持续输出!
ヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノ

http://www.dtcms.com/a/282841.html

相关文章:

  • iOS 性能监控工具全解析 选择合适的调试方案提升 App 性能
  • Towards Low Light Enhancement with RAW Images 论文阅读
  • 玩转Docker | 使用Docker部署bender个人导航页工具
  • 力扣-146.LRU缓存机制
  • 主机安全---开源wazuh使用
  • 在 Ubuntu 上安装 GBase 8s 的完整实战指南
  • 立创EDA操作记录
  • Linux网卡与软件仓库快捷配置脚本
  • 基于Hadoop与LightFM的美妆推荐系统设计与实现
  • Leetcode Easy刷题:合并两个连续系列
  • 【Java入门到精通】(五)初识MySql数据库
  • Linux Ubuntu apt包管理器安装K8s1.30.1+Ingress-Nginx
  • 神经网络之权重初始化
  • 【深度学习】神经网络-part3
  • 云原生技术
  • 合成孔径雷达干涉测量InSAR技术流程(星载/地基系统+DEM重建+DInSAR形变监测+时序分析)等
  • 杨辉三角-附C语言实现方法
  • TBT 5、TBT 4 和 USB4 的差异概述
  • 零基础数据结构与算法——第五章:高级算法-动态规划经典-背包问题
  • 单片机学习笔记.IIC通信协议(根据数据手册写IIC驱动程序,这里以普中开发板上的AT24C02为例)
  • HTTP REST API、WebSocket、 gRPC 和 GraphQL 应用场景和底层实现
  • 使用Django框架构建Python Web应用
  • 插入排序及其时间复杂度分析
  • 类模版的相关案例
  • 数字输入缓冲器是如何影响低功耗电流的?
  • 建设大模型应用的方法和理论
  • Lsposed/Xposed
  • 3 习题1
  • 第3章 Excel表格格式设置技巧
  • Weblogic历史漏洞利用