聊聊Spring核心
你好,我是yes。
猶記我當年初學 Spring 時,還需寫一個個 XML 檔案,當時心裡不知所以然,跟著網上的步驟一個一個配置下來,配錯一個看著 error 懵半天,不知所謂地瞎改到最後能跑就行,暗自感嘆 tmd 這玩意真複雜。
到後來用上 SpringBoot,看起來少了很多 XML 配置,心裡暗暗高興。起初根據預設配置跑的很正常,後面到需要改動的時候,我都不知道從哪下手。
稀裡糊塗地在大部分時候也能用,但是遇到奇怪點的問題都得找老李幫忙解決。
到後面發現還有 SpringCloud ,微服務的時代來臨了,我想不能再這般“猶抱琵琶半遮面”地使用 Spring 全家桶了。
一時間就鑽入各種 SpringCloud 細節原始碼中,希望能領悟框架真諦,最終無功而返且黯然傷神,再次感嘆 tmd 這玩意真複雜。
其間我已經意識到了是對 Spring 基礎框架的不熟悉,導致很多封裝點都不理解。
畢竟 SpringCloud 是基於 SpringBoot,而 SpringBoot 是基於 Spring。
於是乎我又回頭重學 Spring,不再一來就是扎入各種細節中,我換了個策略,先從高緯角度總覽 Spring ,理解核心原理後再攻克各種分支脈路。
於是我,我變強了。
其實學任何東西都是一樣,先要總覽全貌再深入其中,等回過頭之後再進行總結。
這篇我打算用自己的理解來闡述下 Spring 的核心(思想),礙於個人表達能力可能有不對或囉嗦的地方,還請擔待,如有錯誤懇請指出。
拋開IOC、DI去想為什麼要有Spring
在初學 Java 時,我們理所當然得會寫出這樣的程式碼:
public class ServiceA {
private ServiceB serviceB = new ServiceB();
}
我們把一些邏輯封裝到 ServiceB
中,當 ServiceA
需用到這些邏輯時候,在 ServiceA
內部 new
個ServiceB
。
如果 ServiceB
封裝的邏輯非常通用,還會有 ServiceC
.....ServiceF
等都需要依賴它,也就是說程式碼裡面各個地方都需要 new 個ServiceB
,這樣一來如果它的構造方法發生變化,你就要在所有用到它的地方進行程式碼修改。
比如 ServiceB
例項的建立需要 ServiceC
,程式碼就改成這樣:
public class ServiceA {
private ServiceB serviceB = new ServiceB(new ServiceC());
}
確實有這個問題。
但實際上如若我們封裝通用的service
邏輯,沒必要每次都 new 個例項,也就是說單例就夠了,我們的系統只需要 new
一個 ServiceB
供各個物件使用,就能解決這個問題。
public class ServiceA {
private ServiceB serviceB = ServiceB.getInstance();
}
public class ServiceB {
private static ServiceB instance = new ServiceB(new ServiceC());
private ServiceB(){}
public static ServiceB getInstance(){
return instance;
}
}
看起來好像解決問題了,其實不然。
當專案比較小時,例如大學的大作業,上面這個操作其實問題不大,但是一到企業級應用上來說就複雜了。
因為涉及的邏輯多,封裝的服務類也多,之間的依賴也複雜,程式碼中可能要有ServiceB1
、ServiceB2
...ServiceB100
,而且相互之間還可能有依賴關係。
拋開依賴不說,就拿 ServiceB
單純的單例邏輯程式碼,重複的邏輯可能需要寫成百上千份。
且擴充套件不易,以前可能 ServiceB 的操作都不需要事務,後面要上事務了,因此需要改 ServiceB
的程式碼,嵌入事務相關邏輯。
沒過多久 ServiceC
也要事務,一模一樣關於事務的程式碼又得在 ServiceC 上重複一遍,還有D、E、F...
對幾個 Service
事務要求又不一樣,還有巢狀事務的問題,總之有點麻煩。
忙了一段時間滿足事務需求,上線了,想著終於脫離了重複程式碼的噩夢可以好好休息一波。
緊接著又來了個需求,因為經常要排查線上問題,所以介面入參要打個日誌,方便問題排查,又得大刀闊斧操作一波全部改一遍。
有需求要改動很正常,但是每次改動需要做一大堆重複性工作,又累又沒技術含量還容易漏,這就不夠優雅了。
所以有人就開始想辦法,想從這個耦合泥沼中脫離出來。
拔高和剝離
人類絕大部分的發明都是因為懶,人們討厭重複的工作,而計算機最喜歡也最適合做重複的工作。
既然之前的開發會有很多重複的工作,那為什麼不製造一個“東西”出來幫我們做這類重複的事情呢?
就像以前人們手工一步一步組裝製造產品,每天一樣的步驟可能要重複上萬次,到後面人們研究出全自動機器來幫我們製造產品,解放了人們的雙手還提高了生產效率。
拔高了這個思想後,編碼的邏輯就從我們程式設計師想著且寫著 ServiceA 依賴具體的 ServiceB ,且一個字母一個字母的敲完 ServiceB 具體是如何例項化的程式碼,變成我們只關心 ServiceA 依賴 ServiceB,但 ServiceB 是如何生成的我們不管,由那個“東西”幫我們生成它且關聯好 ServiceA 和 ServiceB。
public class ServiceA {
@注入
private ServiceB serviceB;
}
聽起來好像有點懸乎,其實不然。
還是拿機器說事,我們創造這臺機器,如果要生產產品 A,我們只要畫好圖紙 A,將圖紙 A 塞到這個機器裡,機器識別圖紙 A,按照我們圖紙 A 的設計製造出我們要的產品 A。
Spring就是這臺機器,圖紙就是依託 Spring 管理的物件程式碼以及那些 XML 檔案(或標註了@Configuration
的類)。
這時候邏輯就轉變了。程式設計師知道 ServiceA
具體依賴哪個 ServiceB
,但是我們不需要顯示的在程式碼中寫上完整的關於如何建立 ServiceB 的邏輯,我們只需要寫好配置檔案,具體地建立和關聯由 Spring 幫我們做。
繼續拿機器舉例,我們給了圖紙(配置),機器幫我們製造產品,具體如何製造出來不需要我們操心,但是我們心裡是有數的,因為我們的圖紙寫明瞭製造 ServiceA
需要哪樣的 ServiceB
,而那樣的 ServiceB
又需要哪樣的 ServiceC
等等邏輯。
我找個圖紙例子,Spring 裡關於資料庫的配置:
可以看到我們的圖紙寫的很清楚,建立 mybatis
的MapperScannerConfigurer
需要告訴它兩個屬性的值,比如第一個是sqlSessionFactoryBeanName
,值是 sqlSessionFactory
。
而sqlSessionFactory
又依賴 dataSource
,而 dataSource
又需要配置好 driverClassName
、url
等等。
所以,其實我們心裡很清楚一個產品(Bean)要建立的話具體需要什麼東西,只過不這個建立過程由 Spring 代勞了,我們只需要清楚的告訴它即可。
因此,不是說用了 Spring 我們不再關心 ServiceA
具體依賴怎樣的 ServiceB
、ServiceB
具體是如何建立成功的,而是說這些物件組裝的過程由 Spring 幫我們做好。
我們還是需要清楚地知道物件是如何建立的,因為我們需要畫好正確的圖紙告訴 Spring。
所以 Spring 其實就是一臺機器,根據我們給它的圖紙,自動幫我們建立關聯物件供我們使用,我們不需要顯示得在程式碼中寫好完整的建立程式碼。
這些由 Spring 建立的物件例項,叫作 Bean。
我們如果要使用這些 Bean 可以從 Spring 中拿,Spring 將這些建立好的單例 Bean 放在一個 Map 中,透過名字或者型別我們可以獲取這些 Bean。
這就是 IOC。
也正因為這些 Bean 都需要經過 Spring 這臺機器建立,不再是懶散地在程式碼的各個角落建立,我們就能很方便的基於這個統一收口做很多事情。
比如當我們的 ServiceB
標註了 @Transactional
註解,由 Spring 解析到這個註解就能明白這個 ServiceB
是需要事務的,於是乎織入的事務的開啟、提交、回滾等操作。
但凡標記了 @Transactional
註解的都自動新增事務邏輯,這對我們而言減輕了太多重複的程式碼,只要在需要事務的方法或類上新增 @Transactional
註解即可由 Spring 幫我們補充上事務功能,重複的操作都由 Spring 完成。
再比如我們需要在所有的 controller 上記錄請求入參,這也非常簡單,我們只要寫個配置,告訴 Spring xxx路徑(controller包路徑)下的類的每個方法的入參都需要記錄在 log 裡,並且把日誌列印邏輯程式碼也寫上。
Spring 解析完這個配置後就得到了這個命令,於是乎在建立後面的 Bean 時就看看它所處的包是否符合上述的配置,若符合就把我們新增日誌列印邏輯和原有的邏輯編織起來。
這樣就把重複的日誌列印動作操作抽象成一個配置,Spring 這臺機器識別配置後執行我們的命令完成這些重複的動作。
這就叫 AOP。
至此我相信你對 Spring 的由來和核心概念有了一定的瞭解,基於上面的特效能做的東西有很多。
因為有了 Spring 這個機器統一收口處理,我們就可以靈活在不同時期提供很多擴充套件點,比如配置檔案解析的時候、Bean初始化的前後,Bean例項化的前後等等。
基於這些擴充套件點就能實現很多功能,例如 Bean 的選擇性載入、佔位符的替換、代理類(事務等)的生成。
好比 SpringBoot Redis
客戶端的選擇,預設會匯入 lettuce
和 jedis
兩個客戶端配置
基於配置的先後順序會優先匯入 lettuce,然後再匯入 jedis。
如果掃描發現有 lettuce 那麼就用 lettuce 的 RedisConnectionFactory,而後面再載入 jedis 時,會基於@ConditionalOnMissingBean(RedisConnectionFactory.class)
來保證 jedis不會被注入,反之就會被注入。
ps:@ConditionalOnMissingBean(xx.class) 如果當前沒有xx.class才能生成被這個註解修飾的bean
就上面這個特性就是基於 Spring 提供的擴充套件點來實現的。
很靈活地讓我們替換所需的 redis 客戶端,不用改任何使用的程式碼,只需要改個依賴,比如要從預設的 lettuce 變成 jedis,只需要改個 maven 配置,去除 lettuce 依賴,引入 jedis:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<exclusions>
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
說這麼多其實就是想表達:Spring 全家桶提供的這些擴充套件和封裝可以靈活地滿足我們的諸多需求,而這些靈活都是基於 Spring 的核心 IOC 和 AOP 而來的。
最後
最後我用一段話來簡單描述下 Spring 的原理:
Spring 根據我們提供的配置類和XML配置檔案,解析其中的內容,得到它需要管理的 Bean 的資訊以及之間的關聯,並且 Spring 暴露出很多擴充套件點供我們定製,如 BeanFactoryPostProcessor
、BeanPostProcessor
,我們只需要實現這個介面就可以進行一些定製化的操作。
Spring 得到 Bean 的資訊後會根據反射來建立 Bean 例項,組裝 Bean 之間的依賴關係,其中就會穿插進原生的或我們定義的相關PostProcessor
來改造Bean,替換一些屬性或代理原先的 Bean 邏輯。
最終建立完所有配置要求的Bean,將單例的 Bean 儲存在 map 中,提供 BeanFactory 供我們獲取使用 Bean。
使得我們編碼過程無需再關注 Bean 具體是如何建立的,也節省了很多重複性地編碼動作,這些都由我們建立的機器——Spring幫我們代勞。
大概就說這麼多了,我自己讀了幾遍也不知道到底有沒有把我想表達的東西說明白,其實我本來從原始碼層面來聊這個核心的,但是怕更難說清。
最後關於 Spring 的 IOC和 DI 概念的面試回答我之前也寫過了,可以看下,網址是:%E7%B2%BE%E9%80%89%E9%9D%A2%E8%AF%95%E9%A2%98%E8%A7%A3
。
我是yes,從一點點到億點點,我們下篇見~
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70024420/viewspace-2934192/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 聊聊Dubbo(四):核心原始碼-切入Spring原始碼Spring
- 聊聊spring的UnexpectedRollbackExceptionSpringException
- 聊聊spring cloud的DefaultEurekaServerContextSpringCloudServerContext
- 聊聊spring security的role hierarchySpring
- spring 核心概述Spring
- Spring framework核心SpringFramework
- 聊聊Dubbo(六):核心原始碼-Filter鏈原理原始碼Filter
- 聊聊spring事務的propagationSpring
- 聊聊spring cloud gateway的XForwardedHeadersFilterSpringCloudGatewayForwardHeaderFilter
- 聊聊arthas的spring-boot-starterSpringboot
- 4、spring核心AOPSpring
- Spring Boot核心配置Spring Boot
- Spring Boot 核心(二)Spring Boot
- Spring Boot 核心(一)Spring Boot
- 從紅芯事件聊聊瀏覽器核心(一)事件瀏覽器
- 從Dubbo核心-SPI聊聊雙親委派機制
- 聊聊Dubbo(八):核心原始碼-容器啟動/停止原始碼
- 聊聊Dubbo(五):核心原始碼-SPI擴充套件原始碼套件
- 聊聊spring security的permitAll以及webIgnoreSpringMITWeb
- 【Mysql核心技術】聊聊事務的實現原理MySql
- Spring Cloud的核心特性SpringCloud
- Spring Boot核心技術Spring Boot
- 聊聊Spring Reactor反應式程式設計SpringReact程式設計
- 聊聊spring的那些擴充套件機制Spring套件
- 聊聊Spring擴充套件點BeanPostProcessor和BeanFactoryPostProcessorSpring套件Bean
- 聊聊Spring Boot幾個版本的區別Spring Boot
- 聊聊spring-cloud-kubernetes-client-discoverySpringCloudclient
- 淺析Spring Security 核心元件Spring元件
- Spring MVC 核心類和介面SpringMVC
- 聊聊spring bean名稱命名的那些事兒SpringBean
- 聊聊Spring中的那些擴充套件機制Spring套件
- 聊聊Spring的FactoryBean其實沒那麼難SpringBean
- 聊聊spring-cloud-kubernetes-client-loadbalancerSpringCloudclient
- Spring原始碼剖析2:初探Spring IOC核心流程Spring原始碼
- Spring原始碼剖析1:初探Spring IOC核心流程Spring原始碼
- Spring 高階原始碼核心思想:Spring IoCSpring原始碼
- Spring Boot學習筆記:Spring Boot核心配置Spring Boot筆記
- Spring核心系列之Spring中的事務Spring