五分鐘手?jǐn)]一個(gè)Spring容器(萌芽版)
大家好,我是老三,Spring是我們最常用的開(kāi)源框架,經(jīng)過(guò)多年發(fā)展,Spring已經(jīng)發(fā)展成枝繁葉茂的大樹(shù),讓我們難以窺其全貌。
這節(jié),我們回歸Spring的本質(zhì),五分鐘手?jǐn)]一個(gè)Spring容器,揭開(kāi)Spring神秘的面紗!
從什么是IOC開(kāi)始?
Spring——春天,Java編程世界的春天是由一位音樂(lè)家——Rod Johnson帶來(lái)的。
Rod Johnson先后編寫(xiě)了兩本巨著《Expert One-on-One J2EE Design and Development》、《Expert One-on-One J2EE Development without EJB》,拉起了挑戰(zhàn)正統(tǒng)Java EE框架EJB的大旗。
Rod Johnson不僅是一名旗手,更是開(kāi)發(fā)了Spring這一輕量級(jí)框架,像一名勇敢的龍騎兵一樣,對(duì)EJB發(fā)動(dòng)了沖鋒,并最終戰(zhàn)勝了EJB,讓Spring成為Java EE事實(shí)上的標(biāo)準(zhǔn)。
Spring的兩大內(nèi)核分別是IOC和AOP,其中最最核心的是IOC。
所謂的IOC(控制反轉(zhuǎn)):就是由容器來(lái)負(fù)責(zé)控制對(duì)象的生命周期和對(duì)象間的關(guān)系。以前是我們想要什么,就自己創(chuàng)建什么,現(xiàn)在是我們需要什么,容器就給我們送來(lái)什么。
也就是說(shuō),控制對(duì)象生命周期的不再是引用它的對(duì)象,而是容器。對(duì)具體對(duì)象,以前是它控制其它對(duì)象,現(xiàn)在所有對(duì)象都被容器控制,所以這就叫控制反轉(zhuǎn)。
也許你還聽(tīng)到另外一個(gè)概念DI(依賴注入),它指的是容器在實(shí)例化對(duì)象的時(shí)候把它依賴的類(lèi)注入給它,我們也可以認(rèn)為,DI是IOC的補(bǔ)充和實(shí)現(xiàn)。
工廠和Spring容器
Spring是一個(gè)成熟的框架,為了滿足擴(kuò)展性、實(shí)現(xiàn)各種功能,所以它的實(shí)現(xiàn)如同枝節(jié)交錯(cuò)的大樹(shù)一樣,現(xiàn)在讓我們把視線從Spring本身移開(kāi),來(lái)看看一個(gè)萌芽版的Spring容器怎么實(shí)現(xiàn)。
Spring的IOC本質(zhì)就是一個(gè)大工廠,我們想想一個(gè)工廠是怎么運(yùn)行的呢?
- 生產(chǎn)產(chǎn)品:一個(gè)工廠最核心的功能就是生產(chǎn)產(chǎn)品。在Spring里,不用Bean自己來(lái)實(shí)例化,而是交給Spring,應(yīng)該怎么實(shí)現(xiàn)呢?——答案毫無(wú)疑問(wèn),反射。
那么這個(gè)廠子的生產(chǎn)管理是怎么做的?你應(yīng)該也知道——工廠模式。
- 庫(kù)存產(chǎn)品:工廠一般都是有庫(kù)房的,用來(lái)庫(kù)存產(chǎn)品,畢竟生產(chǎn)的產(chǎn)品不能立馬就拉走。Spring我們都知道是一個(gè)容器,這個(gè)容器里存的就是對(duì)象,不能每次來(lái)取對(duì)象,都得現(xiàn)場(chǎng)來(lái)反射創(chuàng)建對(duì)象,得把創(chuàng)建出的對(duì)象存起來(lái)。
- 訂單處理:還有最重要的一點(diǎn),工廠根據(jù)什么來(lái)提供產(chǎn)品呢?訂單。這些訂單可能五花八門(mén),有線上簽簽的、有到工廠簽的、還有工廠銷(xiāo)售上門(mén)簽的……最后經(jīng)過(guò)處理,指導(dǎo)工廠的出貨。
在Spring里,也有這樣的訂單,它就是我們bean的定義和依賴關(guān)系,可以是xml形式,也可以是我們最熟悉的注解形式。
那對(duì)應(yīng)我們的萌芽版的Spring容器是什么樣的呢?
訂單:Bean定義
Bean可以通過(guò)一個(gè)配置文件定義,我們會(huì)把它解析成一個(gè)類(lèi)型。
beans.properties
為了偷懶,這里直接用了最方便解析的properties,用一個(gè)<key,value>類(lèi)型的配置來(lái)代表Bean的定義,其中key是beanName,value是class
userDao:cn.fighter3.bean.UserDao
BeanDefinition.java
bean定義類(lèi),配置文件中bean定義對(duì)應(yīng)的實(shí)體
public class BeanDefinition { private String beanName; private Class beanClass; //省略getter、setter }
獲取訂單:資源加載
接下訂單之后,就要由銷(xiāo)售向生產(chǎn)部門(mén)交接,讓生產(chǎn)部門(mén)知道商品的規(guī)格、數(shù)量之類(lèi)。
資源加載器,就是來(lái)完成這個(gè)工作的,由它來(lái)完成配置文件中配置的加載。
public class ResourceLoader { public static Map<String, BeanDefinition> getResource() { Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>(16); Properties properties = new Properties(); try { InputStream inputStream = ResourceLoader.class.getResourceAsStream("/beans.properties"); properties.load(inputStream); Iterator<String> it = properties.stringPropertyNames().iterator(); while (it.hasNext()) { String key = it.next(); String className = properties.getProperty(key); BeanDefinition beanDefinition = new BeanDefinition(); beanDefinition.setBeanName(key); Class clazz = Class.forName(className); beanDefinition.setBeanClass(clazz); beanDefinitionMap.put(key, beanDefinition); } inputStream.close(); } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); } return beanDefinitionMap; } }
訂單分配:Bean注冊(cè)
對(duì)象注冊(cè)器,這里用于單例bean的緩存,我們大幅簡(jiǎn)化,默認(rèn)所有bean都是單例的。可以看到所謂單例注冊(cè),也很簡(jiǎn)單,不過(guò)是往HashMap里存對(duì)象。
public class BeanRegister { //單例Bean緩存 private Map<String, Object> singletonMap = new HashMap<>(32); /** * 獲取單例Bean * * @param beanName bean名稱 * @return */ public Object getSingletonBean(String beanName) { return singletonMap.get(beanName); } * 注冊(cè)單例bean * @param beanName * @param bean public void registerSingletonBean(String beanName, Object bean) { if (singletonMap.containsKey(beanName)) { return; } singletonMap.put(beanName, bean); }
生產(chǎn)車(chē)間:對(duì)象工廠
好了,到了我們最關(guān)鍵的生產(chǎn)部門(mén)了,在工廠里,生產(chǎn)產(chǎn)品的是車(chē)間,在IOC容器里,生產(chǎn)對(duì)象的是BeanFactory。
- 對(duì)象工廠,我們最核心的一個(gè)類(lèi),在它初始化的時(shí)候,創(chuàng)建了bean注冊(cè)器,完成了資源的加載。
- 獲取bean的時(shí)候,先從單例緩存中取,如果沒(méi)有取到,就創(chuàng)建并注冊(cè)一個(gè)bean
public class BeanFactory { private Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>(); private BeanRegister beanRegister; public BeanFactory() { //創(chuàng)建bean注冊(cè)器 beanRegister = new BeanRegister(); //加載資源 this.beanDefinitionMap = new ResourceLoader().getResource(); } /** * 獲取bean * * @param beanName bean名稱 * @return */ public Object getBean(String beanName) { //從bean緩存中取 Object bean = beanRegister.getSingletonBean(beanName); if (bean != null) { return bean; } //根據(jù)bean定義,創(chuàng)建bean return createBean(beanDefinitionMap.get(beanName)); } /** * 創(chuàng)建Bean * * @param beanDefinition bean定義 * @return */ private Object createBean(BeanDefinition beanDefinition) { try { Object bean = beanDefinition.getBeanClass().newInstance(); //緩存bean beanRegister.registerSingletonBean(beanDefinition.getBeanName(), bean); return bean; } catch (InstantiationException | IllegalAccessException e) { e.printStackTrace(); } return null; } }
生產(chǎn)銷(xiāo)售:測(cè)試
UserDao.java
我們的Bean類(lèi),很簡(jiǎn)單
public class UserDao { public void queryUserInfo(){ System.out.println("A good man."); } }
單元測(cè)試
public class ApiTest { @Test public void test_BeanFactory() { //1.創(chuàng)建bean工廠(同時(shí)完成了加載資源、創(chuàng)建注冊(cè)單例bean注冊(cè)器的操作) BeanFactory beanFactory = new BeanFactory(); //2.第一次獲取bean(通過(guò)反射創(chuàng)建bean,緩存bean) UserDao userDao1 = (UserDao) beanFactory.getBean("userDao"); userDao1.queryUserInfo(); //3.第二次獲取bean(從緩存中獲取bean) UserDao userDao2 = (UserDao) beanFactory.getBean("userDao"); userDao2.queryUserInfo(); } }
運(yùn)行結(jié)果
A good man.
A good man.
至此,我們一個(gè)萌芽版的Spring容器就完成了。
考慮一下,它有哪些不足呢?是否還可以抽象、擴(kuò)展、解耦……
細(xì)細(xì)想想這些東西,你是不是對(duì)真正的Spring IOC容器為何如此復(fù)雜,有所理解了呢?
參考:
[1]. 《Spring揭秘》
[2].小傅哥 《手?jǐn)]Spring》
[3].《精通Spring4.X企業(yè)應(yīng)用開(kāi)發(fā)實(shí)戰(zhàn)》
到此這篇關(guān)于五分鐘,手?jǐn)]一個(gè)Spring容器的文章就介紹到這了,更多相關(guān)Spring容器內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring @Component自定義注解實(shí)現(xiàn)詳解
@Component是一個(gè)元注解,意思是可以注解其他類(lèi)注解,如@Controller @Service @Repository @Aspect。官方的原話是:帶此注解的類(lèi)看為組件,當(dāng)使用基于注解的配置和類(lèi)路徑掃描的時(shí)候,這些類(lèi)就會(huì)被實(shí)例化2022-09-09Mybatis使用JSONObject接收數(shù)據(jù)庫(kù)查詢的方法
這篇文章主要介紹了Mybatis使用JSONObject接收數(shù)據(jù)庫(kù)查詢,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-12-12java計(jì)算任意位水仙花數(shù)示例(回文數(shù))
這篇文章主要介紹了java計(jì)算任意位水仙花數(shù)示例(回文數(shù)),需要的朋友可以參考下2014-05-05mybatis-plus多表分頁(yè)查詢最佳實(shí)現(xiàn)方法(非常簡(jiǎn)單)
這篇文章主要給大家介紹了關(guān)于mybatis-plus多表分頁(yè)查詢最佳實(shí)現(xiàn)方法,文中介紹的方法非常簡(jiǎn)單,MyBatis-Plus中分頁(yè)查詢是比較方便的,這個(gè)功能在網(wǎng)站中也是非常常用的,這方面的知識(shí)點(diǎn)是必備的知識(shí)點(diǎn),需要的朋友可以參考下2023-08-08本地編譯打包項(xiàng)目部署到服務(wù)器并且啟動(dòng)方式
這篇文章主要介紹了本地編譯打包項(xiàng)目部署到服務(wù)器并且啟動(dòng)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-02-02Java多態(tài)成員訪問(wèn)的特點(diǎn)是什么?
在上一篇文章中介紹了方法重載和方法重寫(xiě)的區(qū)別,但是在多態(tài)情況下發(fā)現(xiàn)程序的執(zhí)行結(jié)果和我們預(yù)期的不太一樣,這篇將繼續(xù)介紹多態(tài)場(chǎng)景下,Java成員訪問(wèn)的特點(diǎn),需要的朋友可以參考下2021-06-06Java?FTP協(xié)議實(shí)現(xiàn)文件下載功能
FTP(File?Transfer?Protocol)就是文件傳輸協(xié)議。通過(guò)FTP客戶端從遠(yuǎn)程FTP服務(wù)器上拷貝文件到本地計(jì)算機(jī)稱為下載,將本地計(jì)算機(jī)上的文件復(fù)制到遠(yuǎn)程FTP服務(wù)器上稱為上傳,上傳和下載是FTP最常用的兩個(gè)功能2022-11-11springboot配置項(xiàng)目啟動(dòng)后自動(dòng)打開(kāi)瀏覽器訪問(wèn)項(xiàng)目方式
這篇文章主要介紹了springboot配置項(xiàng)目啟動(dòng)后自動(dòng)打開(kāi)瀏覽器訪問(wèn)項(xiàng)目方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-01-01詳細(xì)講解Java中==與equals的區(qū)別對(duì)比
這篇文章主要為大家詳細(xì)介紹了Java中==與equals的區(qū)別對(duì)比,文中有詳細(xì)的代碼示例供大家參考,具有一定的參考價(jià)值,感興趣的同學(xué)可以參考閱讀下2023-09-09