手把手帶你實現(xiàn)一個萌芽版的Spring容器
從什么是IOC開始?
Spring——春天,Java編程世界的春天是由一位音樂家——Rod Johnson帶來的。
Rod Johnson先后編寫了兩本巨著《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不僅是一名旗手,更是開發(fā)了Spring這一輕量級框架,像一名勇敢的龍騎兵一樣,對EJB發(fā)動了沖鋒,并最終戰(zhàn)勝了EJB,讓Spring成為Java EE事實上的標準。
Spring的兩大內(nèi)核分別是IOC和AOP,其中最最核心的是IOC。
所謂的IOC(控制反轉(zhuǎn)):就是由容器來負責控制對象的生命周期和對象間的關(guān)系。以前是我們想要什么,就自己創(chuàng)建什么,現(xiàn)在是我們需要什么,容器就給我們送來什么。
也就是說,控制對象生命周期的不再是引用它的對象,而是容器。對具體對象,以前是它控制其它對象,現(xiàn)在所有對象都被容器控制,所以這就叫控制反轉(zhuǎn)。
也許你還聽到另外一個概念DI(依賴注入),它指的是容器在實例化對象的時候把它依賴的類注入給它,我們也可以認為,DI是IOC的補充和實現(xiàn)。
工廠和Spring容器
Spring是一個成熟的框架,為了滿足擴展性、實現(xiàn)各種功能,所以它的實現(xiàn)如同枝節(jié)交錯的大樹一樣,現(xiàn)在讓我們把視線從Spring本身移開,來看看一個萌芽版的Spring容器怎么實現(xiàn)。
Spring的IOC本質(zhì)就是一個大工廠,我們想想一個工廠是怎么運行的呢?
生產(chǎn)產(chǎn)品:一個工廠最核心的功能就是生產(chǎn)產(chǎn)品。在Spring里,不用Bean自己來實例化,而是交給Spring,應該怎么實現(xiàn)呢?——答案毫無疑問,反射。
那么這個廠子的生產(chǎn)管理是怎么做的?你應該也知道——工廠模式。
庫存產(chǎn)品:工廠一般都是有庫房的,用來庫存產(chǎn)品,畢竟生產(chǎn)的產(chǎn)品不能立馬就拉走。Spring我們都知道是一個容器,這個容器里存的就是對象,不能每次來取對象,都得現(xiàn)場來反射創(chuàng)建對象,得把創(chuàng)建出的對象存起來。
訂單處理:還有最重要的一點,工廠根據(jù)什么來提供產(chǎn)品呢?訂單。這些訂單可能五花八門,有線上簽簽的、有到工廠簽的、還有工廠銷售上門簽的……最后經(jīng)過處理,指導工廠的出貨。
在Spring里,也有這樣的訂單,它就是我們bean的定義和依賴關(guān)系,可以是xml形式,也可以是我們最熟悉的注解形式。
那對應我們的萌芽版的Spring容器是什么樣的呢?
訂單:Bean定義
Bean可以通過一個配置文件定義,我們會把它解析成一個類型。
- beans.properties
為了偷懶,這里直接用了最方便解析的properties,用一個<key,value>類型的配置來代表Bean的定義,其中key是beanName,value是class
userDao:cn.fighter3.bean.UserDao
- BeanDefinition.java
bean定義類,配置文件中bean定義對應的實體
public class BeanDefinition { private String beanName; private Class beanClass; //省略getter、setter }
獲取訂單:資源加載
接下訂單之后,就要由銷售向生產(chǎn)部門交接,讓生產(chǎn)部門知道商品的規(guī)格、數(shù)量之類。
資源加載器,就是來完成這個工作的,由它來完成配置文件中配置的加載。
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); } }
生產(chǎn)車間:對象工廠
好了,到了我們最關(guān)鍵的生產(chǎn)部門了,在工廠里,生產(chǎn)產(chǎn)品的是車間,在IOC容器里,生產(chǎn)對象的是BeanFactory。
對象工廠,我們最核心的一個類,在它初始化的時候,創(chuàng)建了bean注冊器,完成了資源的加載。
獲取bean的時候,先從單例緩存中取,如果沒有取到,就創(chuàng)建并注冊一個bean
public class BeanFactory { private Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>(); private BeanRegister beanRegister; public BeanFactory() { //創(chuàng)建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; } //根據(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)銷售:測試
UserDao.java
我們的Bean類,很簡單
public class UserDao { public void queryUserInfo(){ System.out.println("A good man."); } }
單元測試
public class ApiTest { @Test public void test_BeanFactory() { //1.創(chuàng)建bean工廠(同時完成了加載資源、創(chuàng)建注冊單例bean注冊器的操作) BeanFactory beanFactory = new BeanFactory(); //2.第一次獲取bean(通過反射創(chuàng)建bean,緩存bean) UserDao userDao1 = (UserDao) beanFactory.getBean("userDao"); userDao1.queryUserInfo(); //3.第二次獲取bean(從緩存中獲取bean) UserDao userDao2 = (UserDao) beanFactory.getBean("userDao"); userDao2.queryUserInfo(); } }
運行結(jié)果
A good man.
A good man.
至此,我們一個萌芽版的Spring容器就完成了。
考慮一下,它有哪些不足呢?是否還可以抽象、擴展、解耦……
細細想想這些東西,你是不是對真正的Spring IOC容器為何如此復雜,有所理解了呢?
參考:
[1].《Spring揭秘》
[2].小傅哥 《手擼Spring》
[3].《精通Spring4.X企業(yè)應用開發(fā)實戰(zhàn)》
到此這篇關(guān)于手把手帶你實現(xiàn)一個萌芽版的Spring容器的文章就介紹到這了,更多相關(guān)Java Spring容器內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring?Boot?Actuator管理日志的實現(xiàn)
本文主要介紹了Spring?Boot?Actuator管理日志的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2022-07-07Java使用poi組件導出Excel格式數(shù)據(jù)
這篇文章主要介紹了Java使用poi組件導出Excel格式數(shù)據(jù),需要的朋友可以參考下2020-02-02Spring學習筆記之RedisTemplate的配置與使用教程
這篇文章主要給大家介紹了關(guān)于Spring學習筆記之RedisTemplate配置與使用的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家學習或者使用spring具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2018-06-06Java線程池隊列PriorityBlockingQueue原理分析
這篇文章主要介紹了Java線程池隊列PriorityBlockingQueue原理分析,PriorityBlockingQueue隊列是?JDK1.5?的時候出來的一個阻塞隊列,但是該隊列入隊的時候是不會阻塞的,永遠會加到隊尾,需要的朋友可以參考下2023-12-12