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

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.xdnf.cn/news/15511.html

相关文章:

  • LeetCode|Day15|125. 验证回文串|Python刷题笔记
  • 912. 排序数组
  • 基于docker的redis集群
  • web前端用MVP模式搭建项目
  • Redisson实现限流器详解:从原理到实践
  • Vue加密文章密码 VuePress
  • 数据结构 双向链表(1)
  • 基于Matlab的四旋翼无人机动力学PID控制仿真
  • PyTorch 参数初始化详解:从理论到实践
  • ZYNQ Petalinux系统FLASH固化终极指南:创新多分区与双系统切换实战
  • 如何区分Bug是前端问题还是后端问题?
  • UE5多人MOBA+GAS 24、创建属性UI(一)
  • 插板式系统的“生命线“:EtherCAT分布式供电该如何实现?
  • 第13章 AB实验平台的建设
  • 解锁高效Excel技能:摆脱鼠标,快速编辑单元格
  • 凯伦股份融合复合瓦:新时代可焊接物理防腐金属屋面系统方案
  • Mysql练习
  • Linux命令大全
  • 第五章 管道工程 5.4 管道安全质量控制
  • 设计一款用于捕捉动态产品视频的摄像机器人
  • 元宇宙经济:虚实融合引发经济新变革
  • 前端学习7:CSS过渡与动画--补间动画 (Transition) vs 关键帧动画 (Animation)
  • Linux切换到Jenkins用户解决Jenkins Host key verification failed
  • 工业相机GigE数据接口的优势及应用
  • 以太网供电与自愈网络对音视频系统的益处
  • 重学前端006 --- 响应式网页设计 CSS 弹性盒子
  • ssl相关命令生成证书
  • 阿里云 RabbitMQ 可观测性最佳实践
  • 蓝光三维扫描技术:手机闪光灯模块全尺寸3D检测的高效解决方案
  • 逆功率检测设备防逆流解决方案守护电网安全