Spring?IOC功能詳解
概述
為什么叫控制反轉(zhuǎn)
Spring Bean是什么:Spring里面的bean就類似是定義的一個(gè)組件,而這個(gè)組件的作用就是實(shí)現(xiàn)某個(gè)功能的,這里所定義的bean就相當(dāng)于給了你一個(gè)更為簡便的方法來調(diào)用這個(gè)組件去實(shí)現(xiàn)你要完成的功能
Spring框架管理這些Bean的創(chuàng)建工作,即由用戶管理Bean轉(zhuǎn)變?yōu)榭蚣芄芾鞡ean,這個(gè)就叫控制反轉(zhuǎn) - Inversion of Control (IoC)
- 這些托管創(chuàng)建的Bean放在IoC Container
- 為了更好讓用戶配置Bean,引入不同方式來配置Bean:xml配置,Java配置,注解配置等支持
- Spring也管理整個(gè)Bean的生命周期
- 應(yīng)用程序代碼從Ioc Container中獲取依賴的Bean,注入到應(yīng)用程序中,這個(gè)過程叫 依賴注入(Dependency Injection,DI)
控制反轉(zhuǎn)是通過依賴注入實(shí)現(xiàn)的,IoC是設(shè)計(jì)思想,DI是實(shí)現(xiàn)方式
IOC不是什么技術(shù),是一種設(shè)計(jì)思想。
在Java開發(fā)中,Ioc意味著將你設(shè)計(jì)好的對(duì)象交給容器控制,而不是傳統(tǒng)的在你的對(duì)象內(nèi)部直接控制。
正轉(zhuǎn):傳統(tǒng)應(yīng)用程序是由我們自己在對(duì)象中主動(dòng)控制去直接獲取依賴對(duì)象反轉(zhuǎn):由容器來幫忙創(chuàng)建及注入依賴對(duì)象- 由IoC容器幫對(duì)象找相應(yīng)的依賴對(duì)象并注入,而不是由對(duì)象主動(dòng)去找
Spring 中的 bean 的作用域
基礎(chǔ)概念
singleton : 唯一 bean 實(shí)例,Spring 中的 bean 默認(rèn)都是單例的。
prototype : 每次請(qǐng)求都會(huì)創(chuàng)建一個(gè)新的 bean 實(shí)例。
Spring并不管理作用域?yàn)閜rototype的bean的整個(gè)生命周期,使用者必須自己銷毀 這個(gè)bean并釋放資源。
下述作用域只有在web環(huán)境下才有用
- request :
每一次 HTTP 請(qǐng)求都會(huì)產(chǎn)生一個(gè)新的 bean,該 bean 僅在當(dāng)前 HTTP request 內(nèi)有效。 - session :
每一次 HTTP 請(qǐng)求都會(huì)產(chǎn)生一個(gè)新的 bean,該 bean 僅在當(dāng)前 HTTP session 內(nèi)有效。 - global-session:
全局 session 作用域,僅僅在基于 Portlet 的 web 應(yīng)用中才有意義,Spring5 已經(jīng)沒有了。 - Portlet 是能夠生成語義代碼(例如:HTML)片段的小型 Java Web 插件。它們基于 portlet 容器,可以像 servlet 一樣處理 HTTP 請(qǐng)求。但是,與 servlet 不同,每個(gè) portlet 都有不同的會(huì)話
對(duì)比
| 作用域 | 說明 | 典型應(yīng)用場景 |
|---|---|---|
| singleton | 默認(rèn)作用域,容器內(nèi)唯一實(shí)例 | 無狀態(tài)Service、工具類 |
| prototype | 每次注入創(chuàng)建新實(shí)例 | 有狀態(tài)Bean、含可變狀態(tài)的組件 |
| request | 每個(gè)HTTP請(qǐng)求創(chuàng)建新實(shí)例 | Web請(qǐng)求上下文相關(guān)對(duì)象 |
| session | 每個(gè)用戶會(huì)話創(chuàng)建新實(shí)例 | 用戶購物車、登錄信息 |
| application | 整個(gè)Web應(yīng)用生命周期單例 | 全局配置 |
有狀態(tài)和無狀態(tài)的bean
基本概念
在 Spring 框架中,有狀態(tài) Bean 和 無狀態(tài) Bean 是兩種重要的對(duì)象設(shè)計(jì)模式,它們的核心區(qū)別在于 是否保存可變狀態(tài),直接影響線程安全性和作用域管理。
有狀態(tài) Bean 注意事項(xiàng)
- 作用域控制:必須顯式聲明作用域(如 @Scope(“prototype”))。
- 避免單例陷阱:禁止在單例 Bean 中注入有狀態(tài) Bean(除非使用 ThreadLocal)。
- 防御性編程:返回?cái)?shù)據(jù)時(shí)返回副本而非引用
無狀態(tài) Bean 最佳實(shí)踐
- 方法參數(shù)化:所有數(shù)據(jù)通過方法參數(shù)傳遞。
- 依賴注入:通過構(gòu)造函數(shù)注入其他 Bean。
- 工具類設(shè)計(jì):將狀態(tài)抽取到參數(shù)中
建議
- 優(yōu)先無狀態(tài)設(shè)計(jì):90%的業(yè)務(wù)場景應(yīng)使用無狀態(tài)Bean
- 有狀態(tài)Bean最小化:必須使用時(shí)有明確的作用域控制
- 避免混合狀態(tài):不要在同一Bean中混用有狀態(tài)和無狀態(tài)代碼
對(duì)比
| 特征 | 有狀態(tài) Bean | 無狀態(tài) Bean |
|---|---|---|
| 實(shí)例變量/狀態(tài)存儲(chǔ) | 包含可變的成員變量(會(huì)隨操作改變) | 無成員變量,或只有不可變常量 |
| 線程安全 | 需自行保證線程安全(如加鎖、隔離) | 天然線程安全(無共享狀態(tài)) |
| 作用域 | 通常用 prototype 作用域(每次注入新實(shí)例) | 通常用 singleton 作用域(全局共享) |
| 復(fù)用性 | 不同請(qǐng)求需不同實(shí)例(需頻繁創(chuàng)建實(shí)例)、資源消耗較高 | 全局共享單個(gè)實(shí)例、高性能(單例復(fù)用) |
| 典型場景 | 用戶會(huì)話、購物車、事務(wù)上下文 | 工具類、Service層業(yè)務(wù)邏輯 |
Ioc 配置的三種方式
總體上目前的主流方式是: 注解 + Java 配置
xml 配置
將bean的信息配置.xml文件里,通過Spring加載文件為我們創(chuàng)建bean。
- 優(yōu)點(diǎn): 可以使用于任何場景,結(jié)構(gòu)清晰,通俗易懂
- 缺點(diǎn): 配置繁瑣,不易維護(hù),枯燥無味,擴(kuò)展性差
<?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">
<!-- services -->
<bean id="userService" class="com.service.UserServiceImpl">
<property name="userDao" ref="userDao"/>
<!-- additional collaborators and configuration for this bean go here -->
</bean>
<!-- more bean definitions for services go here -->
</beans>Java 配置
將類的創(chuàng)建交給我們配置的JavcConfig類來完成,Spring只負(fù)責(zé)維護(hù)和管理,采用純Java創(chuàng)建方式。其本質(zhì)上就是把在XML上的配置聲明轉(zhuǎn)移到Java配置類中
- 優(yōu)點(diǎn):適用于任何場景,配置方便,因?yàn)槭羌僇ava代碼,擴(kuò)展性高,十分靈活
- 缺點(diǎn):由于是采用Java類的方式,聲明不明顯,如果大量配置,可讀性比較差
@Configuration
public class BeansConfig {
/**
* @return user dao
*/
@Bean("userDao")
public UserDaoImpl userDao() {
return new UserDaoImpl();
}
/**
* @return user service
*/
@Bean("userService")
public UserServiceImpl userService() {
UserServiceImpl userService = new UserServiceImpl();
userService.setUserDao(userDao());
return userService;
}
}注解配置
通過在類上加注解的方式,來聲明一個(gè)類交給Spring管理,Spring會(huì)自動(dòng)掃描帶有@Component,@Controller,@Service,@Repository這四個(gè)注解的類,然后幫我們創(chuàng)建并管理,前提是需要先配置Spring的注解掃描器。
- 優(yōu)點(diǎn):開發(fā)便捷,通俗易懂,方便維護(hù)。
- 缺點(diǎn):具有局限性,對(duì)于一些第三方資源,無法添加注解。只能采用XML或JavaConfig的方式配置
依賴注入的三種方式
常用的注入方式主要有三種:構(gòu)造方法注入(Construct注入),setter注入,基于注解的注入(接口注入)
setter方式
通過對(duì)應(yīng)變量的setXXX()方法以及在方法上面使用注解,來完成依賴注入。
Setter方法注入是通過在類中提供setter方法來注入依賴項(xiàng)的。Spring容器會(huì)調(diào)用這些setter方法來設(shè)置依賴項(xiàng)的值。這種方式更加靈活,因?yàn)榭梢栽趯?duì)象創(chuàng)建后的任何時(shí)候注入依賴項(xiàng)。
比如:在 Spring 4.3 及以后的版本中,setter 上面的 @Autowired 注解是可以不寫的。
private Helper helper;
@Autowired
public void setHelper(Helper helper) {
this.helper = helper;
}優(yōu)點(diǎn):
- 可以在對(duì)象創(chuàng)建后的任何時(shí)候注入依賴項(xiàng)。
- 可以重新配置已存在的對(duì)象。
缺點(diǎn):
- 可能導(dǎo)致對(duì)象狀態(tài)的不確定性,因?yàn)橐蕾図?xiàng)可以在任何時(shí)候被更改。
- 不支持不可變對(duì)象。
構(gòu)造方法注入
將各個(gè)必需的依賴全部放在帶有注解構(gòu)造方法的參數(shù)中,并在構(gòu)造方法中完成對(duì)應(yīng)變量的初始化,這種方式,就是基于構(gòu)造方法的注入。
比如:在 Spring 4.3 及以后的版本中,如果這個(gè)類只有一個(gè)構(gòu)造方法,那么這個(gè)構(gòu)造方法上面也可以不寫 @Autowired 注解。
private final Svc svc;
@Autowired
public HelpService(@Qualifier("svcB") Svc svc) {
this.svc = svc;
}優(yōu)點(diǎn):
- 確保依賴項(xiàng)在對(duì)象創(chuàng)建時(shí)就被初始化。
- 可以設(shè)置依賴項(xiàng)為final,確保它們不會(huì)被修改。
- 支持不可變對(duì)象。
缺點(diǎn):
- 如果類有很多依賴項(xiàng),構(gòu)造函數(shù)可能會(huì)變得很長且難以閱讀
基于注解(字段)的注入
示例
public class MyService {
@Autowired
private MyRepository repository;
}優(yōu)點(diǎn):
- 代碼簡單明了,易于實(shí)現(xiàn)。
缺點(diǎn):根據(jù)Spring官方的建議,推薦使用構(gòu)造器注入和Setter注入,而盡量避免使用字段注入。字段注入被認(rèn)為是一種不太理想的方式,因?yàn)樗哂幸韵氯秉c(diǎn):
- 不利于不變性:與構(gòu)造器注入不同,字段注入無法將不變的依賴關(guān)系傳遞給組件,因?yàn)樽侄慰梢栽谌魏螘r(shí)候更改。
- 可見性問題:外部無法明確看到組件所依賴的內(nèi)容,因?yàn)橐蕾囮P(guān)系被隱藏在組件內(nèi)部。這可能導(dǎo)致代碼可讀性下降。
- 緊密綁定:字段注入會(huì)導(dǎo)致組件與IoC容器緊密綁定,使組件在沒有容器的情況下難以使用,也使單元測試更加復(fù)雜。
- 不明顯的依賴關(guān)系:當(dāng)依賴關(guān)系變得復(fù)雜時(shí),使用字段注入可能導(dǎo)致依賴關(guān)系不夠明顯,難以理解組件的需求。
推薦@Resource而不是@Autowired注解
@Resource具有更廣泛的兼容性:@Autowired是Spring特定的注解,與Spring框架緊密相關(guān)。如果以后決定更換不同的IoC容器,那么@Autowired可能不再適用,因?yàn)槠渌萜骺赡懿恢С炙?/li>@Resource是Java標(biāo)準(zhǔn)(JSR-250)提供的注解,幾乎所有的IoC容器都會(huì)支持它。因此,即使你將來更換了容器,使用@Resource的代碼仍然可以正常工作,不需要類似的警告。
- 提高了代碼的可移植性:認(rèn)為
@Resource更符合Java的標(biāo)準(zhǔn)化。
IOC設(shè)計(jì)體系
流程圖

IOC初始化流程
- 初始化的入口在容器實(shí)現(xiàn)中的 refresh()調(diào)用來完成
- 對(duì) bean 定義載入 IOC 容器使用的方法是 loadBeanDefinition,其中的大致過程如下:
- 通過 ResourceLoader 來完成資源文件位置的定位,bean 定義文件時(shí)通過抽象成 Resource 來被 IOC 容器處理的
- 通過 BeanDefinitionReader來完成定義信息的解析和 Bean 信息的注冊(cè)
- 容器解析得到 BeanDefinition 以后,需要把它在 IOC 容器中注冊(cè),這由 IOC 實(shí)現(xiàn) BeanDefinitionRegistry 接口來實(shí)現(xiàn)。
注冊(cè)過程就是在 IOC 容器內(nèi)部維護(hù)的一個(gè)HashMap 來保存得到的 BeanDefinition 的過程。- 這個(gè) HashMap 是 IoC 容器持有 bean 信息的場所,以后對(duì) bean 的操作都是圍繞這個(gè)HashMap 來實(shí)現(xiàn)的.
- 流程圖如下:

- 然后我們就可以通過 BeanFactory 和 ApplicationContext 來享受到 Spring IOC 的服務(wù)了
Bean實(shí)例化(生命周期,循環(huán)依賴等)
最終的將Bean的定義即BeanDefinition放到beanDefinitionMap中,本質(zhì)上是一個(gè)ConcurrentHashMap<String, Object>;
并且BeanDefinition接口中包含了這個(gè)類的Class信息以及是否是單例等;
解決循環(huán)依賴問題
Spring如何解決循環(huán)依賴問題
Spring只是解決了單例模式下屬性依賴的循環(huán)問題:Spring為了解決單例的循環(huán)依賴問題,使用了三級(jí)緩存。
Spring為什么不能解決多例的循環(huán)依賴:多實(shí)例Bean是每次調(diào)用一次getBean都會(huì)執(zhí)行一次構(gòu)造方法并且給屬性賦值,根本沒有三級(jí)緩存,因此不能解決循環(huán)依賴。
- 這類循環(huán)依賴問題可以
通過把bean改成單例的解決。
Spring為什么不能解決prototype作用域循環(huán)依賴:spring不會(huì)緩存prototype作用域的bean,而spring中循環(huán)依賴的解決正是通過緩存來實(shí)現(xiàn)的。
- 這類循環(huán)依賴問題可以通過使用@Lazy注解解決。 生成代理對(duì)象產(chǎn)生的循環(huán)依賴
生成代理對(duì)象產(chǎn)生的循環(huán)依賴,解決方法很多,主要有:
- 使用@Lazy注解,延遲加載
- 使用@DependsOn注解,指定加載先后關(guān)系
- 修改文件名稱,改變循環(huán)依賴類的加載順序
使用@DependsOn產(chǎn)生的循環(huán)依賴:這類循環(huán)依賴問題要找到@DependsOn注解循環(huán)依賴的地方,迫使它不循環(huán)依賴就可以解決問題。
Spring中Bean的生命周期
Spring 只幫我們管理單例模式 Bean 的完整生命周期,對(duì)于 prototype 的 bean ,Spring 在創(chuàng)建好交給使用者之后則不會(huì)再管理后續(xù)的生命周期。
- prototype 是一種 bean 的作用域。它的主要特點(diǎn)是
每次請(qǐng)求都會(huì)創(chuàng)建一個(gè)新的 bean 實(shí)例。 - 與 singleton 作用域不同,
singleton只會(huì)創(chuàng)建一個(gè)共享的實(shí)例。
Bean的完整生命周期經(jīng)歷了各種方法調(diào)用,這些方法可以劃分為以下幾類:(結(jié)合上圖,需要有如下頂層思維)
Bean自身的方法: 這個(gè)包括了Bean本身調(diào)用的方法和通過配置文件中的init-method和destroy-method指定的方法Bean級(jí)生命周期接口方法: 這個(gè)包括了BeanNameAware、BeanFactoryAware、ApplicationContextAware;當(dāng)然也包括InitializingBean和DiposableBean這些接口的方法(可以被@PostConstruct和@PreDestroy注解替代)容器級(jí)生命周期接口方法: 這個(gè)包括了InstantiationAwareBeanPostProcessor 和 BeanPostProcessor 這兩個(gè)接口實(shí)現(xiàn),一般稱它們的實(shí)現(xiàn)類為“后處理器”。工廠后處理器接口方法: 這個(gè)包括了AspectJWeavingEnabler, ConfigurationClassPostProcessor, CustomAutowireConfigurer等等非常有用的工廠后處理器接口的方法。工廠后處理器也是容器級(jí)的。在應(yīng)用上下文裝配配置文件之后立即調(diào)用

到此這篇關(guān)于Spring IOC詳解的文章就介紹到這了,更多相關(guān)Spring IOC詳解內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java中url漢字編碼互相轉(zhuǎn)換實(shí)例
這篇文章介紹了java中url漢字編碼互相轉(zhuǎn)換實(shí)例,有需要的朋友可以參考一下2013-10-10
使用idea遠(yuǎn)程調(diào)試jar包的配置過程
這篇文章主要介紹了使用idea遠(yuǎn)程調(diào)試jar包的配置過程,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-09-09
DTO 實(shí)現(xiàn) service 和 controller 之間值傳遞的操作
這篇文章主要介紹了DTO 實(shí)現(xiàn) service 和 controller 之間值傳遞的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2021-02-02
Java注解詳解及實(shí)現(xiàn)自定義注解的方法
這篇文章主要介紹了Java注解詳解及實(shí)現(xiàn)自定義注解的方法,本文給大家介紹了jdk中預(yù)定義的一些注解及自定義注解的相關(guān)知識(shí),需要的朋友可以參考下2022-06-06
SpringMVC配置攔截器實(shí)現(xiàn)登錄控制的方法
這篇文章主要介紹了SpringMVC配置攔截器實(shí)現(xiàn)登錄控制的方法,SpringMVC讀取Cookie判斷用戶是否登錄,對(duì)每一個(gè)action都要進(jìn)行判斷,有興趣的可以了解一下。2017-03-03
淺談Java方法調(diào)用的優(yōu)先級(jí)問題
這篇文章主要介紹了淺談Java方法調(diào)用的優(yōu)先級(jí)問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-10-10
Java設(shè)計(jì)模式之備忘錄模式實(shí)現(xiàn)對(duì)象狀態(tài)的保存和恢復(fù)
本文介紹Java設(shè)計(jì)模式之備忘錄模式,該模式可以實(shí)現(xiàn)對(duì)象狀態(tài)的保存和恢復(fù)。通過詳細(xì)講解備忘錄模式的原理、實(shí)現(xiàn)方法和應(yīng)用場景,幫助讀者深入理解該設(shè)計(jì)模式,并提供示例代碼和技巧,便于讀者實(shí)際應(yīng)用2023-04-04

