五分鐘,手擼一個 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 Logo
Spring 的兩大內核分別是 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.UserDao
-
BeanDefinition.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定義,創建bean return createBean(beanDefinitionMap.get(beanName)); } /** * 創建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; } }
生產銷售:測試
-
UserDao.java
我們的 Bean 類,很簡單
public class UserDao { public void queryUserInfo(){ System.out.println("A good man."); } }
-
單元測試
public class ApiTest { @Test public 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 企業應用開發實戰》
三分惡 CSDN 博客專家、優質創作者,華爲云云享專家;肝過外包、混過國企,目前在一家跨境電商搬磚;寫過詩,打過拳,佛系小碼農。認真講技術,隨性侃人生,關注我,我們一起走的更遠。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/25vybHVeXD-IdzejKYpCVg