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的三種實現方式
- 構造器注入:通過構造方法傳入依賴對象;
- Setter注入:通過Setter方法設置依賴對象;
- 字段注入:通過注解直接標記字段(如
@Autowired
)。
后續會通過代碼示例詳細講解這三種方式。
1.3 IoC容器的核心作用
Spring的IoC容器(如ApplicationContext
)本質是一個“對象工廠”,核心功能:
- 對象管理:創建、存儲、銷毀Bean(Spring對對象的稱呼);
- 依賴注入:自動將依賴的Bean注入到目標對象;
- 生命周期管理:控制Bean的初始化、銷毀等生命周期節點;
- 配置解析:讀取XML、注解等配置,解析Bean的定義。
二、Spring容器的核心接口與實現類
Spring提供了兩套核心容器接口:BeanFactory
和ApplicationContext
,后者是前者的增強版,實際開發中優先使用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的完整生命周期:
- 實例化:創建Bean對象(調用構造方法);
- 屬性注入:注入依賴的Bean;
- 初始化:執行初始化方法(如
@PostConstruct
); - 使用:Bean可被容器獲取并使用;
- 銷毀:容器關閉時執行銷毀方法(如
@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;
}
解決方案:
- 用
@Qualifier
指定Bean的id:
@Autowired
@Qualifier("userDaoImpl1") // 指定注入id為userDaoImpl1的Bean
private UserDao userDao;
- 用
@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;
}
解決方案:
- 用
@Lazy
延遲注入(打破即時依賴):
@Service
public class AService {@Autowired@Lazy // 延遲注入BServiceprivate BService bService;
}
- 改用Setter注入(構造器注入無法解決循環依賴)。
5.3 單實例Bean的線程安全問題
單實例Bean(默認)在多線程環境下,若存在共享狀態(如成員變量),會有線程安全問題:
@Service
public class UserService {// 共享狀態(多線程訪問會沖突)private int count = 0;public void increment() {count++; // 線程不安全操作}
}
解決方案:
- 避免共享狀態(推薦):單實例Bean設計為無狀態(不定義成員變量);
- 改用
prototype
作用域(不推薦,性能差); - 使用線程安全容器(如
ThreadLocal
)。
總結:Spring核心容器通過IoC和DI實現了對象的“按需創建”和“自動注入”:
- 依賴解耦:對象之間僅依賴接口,不依賴具體實現,降低耦合度;
- 簡化開發:開發者無需關注對象創建和依賴管理,專注業務邏輯;
- 可擴展性:通過配置或注解輕松更換Bean實現,無需修改業務代碼;
- 生命周期管理:容器統一管理Bean的創建、初始化、銷毀,便于資源控制。
掌握Spring容器的核心是理解“容器是對象的管理者”:它創建對象、注入依賴、控制生命周期,是整個Spring生態的基礎。后續學習Spring的AOP、事務等功能,都需要以容器為基礎。
若這篇內容幫到你,動動手指支持下!關注不迷路,干貨持續輸出!
ヾ(′? ˋ)ノヾ(′? ˋ)ノヾ(′? ˋ)ノヾ(′? ˋ)ノヾ(′? ˋ)ノ