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

发布于:2025-07-17 ⋅ 阅读:(15) ⋅ 点赞:(0)

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)
    @Autowired
    private 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/beans
       http://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 {
    @Override
    public 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 {
    @Override
    public void insert() {
        System.out.println("UserDaoImpl:插入用户");
    }
}

// 3. UserService(用@Service标记,并通过@Autowired注入UserDao)
@Service // 等价于<bean id="userService" class="..."/>
public class UserService {
    // 字段注入:直接在字段上标记@Autowired(无需Setter或构造器)
    @Autowired
    private 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+支持单构造器自动注入)
    @Autowired
    public UserService(UserDao userDao) {
        this.userDao = userDao;
    }
}

优势

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

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

@Service
public class UserService {
    private UserDao userDao;

    @Autowired
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
}

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

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

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

@Service
public class UserService {
    @Autowired
    private 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)
    @Autowired
    private UserDao userDao;

    // 3. 初始化方法(@PostConstruct标记)
    @PostConstruct
    public void init() {
        System.out.println("UserService:初始化");
    }

    // 5. 销毁方法(@PreDestroy标记)
    @PreDestroy
    public 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 Bean
    private 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 {
    @Autowired
    private BService bService;
}

@Service
public class BService {
    @Autowired
    private AService aService;
}

解决方案

  1. @Lazy延迟注入(打破即时依赖):
@Service
public class AService {
    @Autowired
    @Lazy // 延迟注入BService
    private 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、事务等功能,都需要以容器为基础。

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


网站公告

今日签到

点亮在社区的每一天
去签到