javascript
五分钟,手撸一个Spring容器!
Spring是我們最常用的開源框架,經過多年發展,Spring已經發展成枝繁葉茂的大樹,讓我們難以窺其全貌。
這節,我們回歸Spring的本質,五分鐘手擼一個Spring容器,揭開Spring神秘的面紗!
從什么是IOC開始?
Spring——春天,Java編程世界的春天是由一位音樂家——Rod Johnson帶來的。
Rod Johnson先后編寫了兩本巨著《Expert One-on-One J2EE Design and Development》、《Expert One-on-One J2EE Development without EJB》,拉起了挑戰正統Java EE框架EJB的大旗。
Rod Johnson兩大著作-來自百度百科Rod Johnson不僅是一名旗手,更是開發了Spring這一輕量級框架,像一名勇敢的龍騎兵一樣,對EJB發動了沖鋒,并最終戰勝了EJB,讓Spring成為Java EE事實上的標準。
Spring LogoSpring的兩大內核分別是IOC和AOP,其中最最核心的是IOC。
所謂的IOC(控制反轉):就是由容器來負責控制對象的生命周期和對象間的關系。以前是我們想要什么,就自己創建什么,現在是我們需要什么,容器就給我們送來什么。
引入IOC之前和引入IOC之后也就是說,控制對象生命周期的不再是引用它的對象,而是容器。對具體對象,以前是它控制其它對象,現在所有對象都被容器控制,所以這就叫控制反轉。
控制反轉示意圖也許你還聽到另外一個概念DI(依賴注入),它指的是容器在實例化對象的時候把它依賴的類注入給它,我們也可以認為,DI是IOC的補充和實現。
工廠和Spring容器
Spring是一個成熟的框架,為了滿足擴展性、實現各種功能,所以它的實現如同枝節交錯的大樹一樣,現在讓我們把視線從Spring本身移開,來看看一個萌芽版的Spring容器怎么實現。
Spring的IOC本質就是一個大工廠,我們想想一個工廠是怎么運行的呢?
工廠運行生產產品:一個工廠最核心的功能就是生產產品。在Spring里,不用Bean自己來實例化,而是交給Spring,應該怎么實現呢?——答案毫無疑問,反射。
那么這個廠子的生產管理是怎么做的?你應該也知道——工廠模式。
庫存產品:工廠一般都是有庫房的,用來庫存產品,畢竟生產的產品不能立馬就拉走。Spring我們都知道是一個容器,這個容器里存的就是對象,不能每次來取對象,都得現場來反射創建對象,得把創建出的對象存起來。
訂單處理:還有最重要的一點,工廠根據什么來提供產品呢?訂單。這些訂單可能五花八門,有線上簽簽的、有到工廠簽的、還有工廠銷售上門簽的……最后經過處理,指導工廠的出貨。
在Spring里,也有這樣的訂單,它就是我們bean的定義和依賴關系,可以是xml形式,也可以是我們最熟悉的注解形式。
那對應我們的萌芽版的Spring容器是什么樣的呢?
mini版本Spring IOC訂單:Bean定義
Bean可以通過一個配置文件定義,我們會把它解析成一個類型。
Bean定義beans.properties
為了偷懶,這里直接用了最方便解析的properties,用一個<key,value>類型的配置來代表Bean的定義,其中key是beanName,value是class
userDao:cn.fighter3.bean.UserDaoBeanDefinition.java
bean定義類,配置文件中bean定義對應的實體
public?class?BeanDefinition?{private?String?beanName;private?Class?beanClass;//省略getter、setter??}
獲取訂單:資源加載
接下訂單之后,就要由銷售向生產部門交接,讓生產部門知道商品的規格、數量之類。
資源加載器,就是來完成這個工作的,由它來完成配置文件中配置的加載。
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注冊
對象注冊器,這里用于單例bean的緩存,我們大幅簡化,默認所有bean都是單例的。可以看到所謂單例注冊,也很簡單,不過是往HashMap里存對象。
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);}/***?注冊單例bean**?@param?beanName*?@param?bean*/public?void?registerSingletonBean(String?beanName,?Object?bean)?{if?(singletonMap.containsKey(beanName))?{return;}singletonMap.put(beanName,?bean);}}生產車間:對象工廠
好了,到了我們最關鍵的生產部門了,在工廠里,生產產品的是車間,在IOC容器里,生產對象的是BeanFactory。
BeanFactory對象工廠,我們最核心的一個類,在它初始化的時候,創建了bean注冊器,完成了資源的加載。
獲取bean的時候,先從單例緩存中取,如果沒有取到,就創建并注冊一個bean
public?class?BeanFactory?{private?Map<String,?BeanDefinition>?beanDefinitionMap?=?new?HashMap<>();private?BeanRegister?beanRegister;public?BeanFactory()?{//創建bean注冊器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;}//根據bean定義,創建beanreturn?createBean(beanDefinitionMap.get(beanName));}/***?創建Bean**?@param?beanDefinition?bean定義*?@return*/private?Object?createBean(BeanDefinition?beanDefinition)?{try?{Object?bean?=?beanDefinition.getBeanClass().newInstance();//緩存beanbeanRegister.registerSingletonBean(beanDefinition.getBeanName(),?bean);return?bean;}?catch?(InstantiationException?|?IllegalAccessException?e)?{e.printStackTrace();}return?null;} }
生產銷售:測試
UserDao.java
我們的Bean類,很簡單
public?class?UserDao?{public?void?queryUserInfo(){System.out.println("A?good?man.");} }單元測試
public?class?ApiTest?{@Testpublic?void?test_BeanFactory()?{//1.創建bean工廠(同時完成了加載資源、創建注冊單例bean注冊器的操作)BeanFactory?beanFactory?=?new?BeanFactory();//2.第一次獲取bean(通過反射創建bean,緩存bean)UserDao?userDao1?=?(UserDao)?beanFactory.getBean("userDao");userDao1.queryUserInfo();//3.第二次獲取bean(從緩存中獲取bean)UserDao?userDao2?=?(UserDao)?beanFactory.getBean("userDao");userDao2.queryUserInfo();} }運行結果
A?good?man. A?good?man.
至此,我們一個萌芽版的Spring容器就完成了。
考慮一下,它有哪些不足呢?是否還可以抽象、擴展、解耦……
細細想想這些東西,你是不是對真正的Spring IOC容器為何如此復雜,有所理解了呢?
參考:
[1]. 《Spring揭秘》
[2].小傅哥 《手擼Spring》
[3].《精通Spring4.X企業應用開發實戰》
完
往期推薦
避開10個面試大坑,接offer成功率提升至99%
知乎高贊:從源碼層,拆解OracleJDK和OpenJDK有什么區別?網友:不愧是大神的回答~
開源作者去世后,代碼誰來繼承?
有道無術,術可成;有術無道,止于術
歡迎大家關注Java之道公眾號
好文章,我在看??
總結
以上是生活随笔為你收集整理的五分钟,手撸一个Spring容器!的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: c++十进制转二进制_二进制与十进制相互
- 下一篇: 突发!Spring 也沦陷了。。。