前言
依稀記得在2016年剛畢業的時候,在京面試某公司的時候技術總監和我聊到了spring,我比較欣賞一個音樂人Rod Johnson以個人之力承擔了spring的主開發工程。當時的個人水平僅僅是知道spring,會簡單使用。當面試官問到我對源碼的閱讀時,問我大概多久的時間能吃懂spring源碼,我給了一個答案是1-2年吧。然后這個問題沒有下文了,估計他(是她,是個女技術總監)當時想spring這么簡單的東西你都要2年???
工作已經2年+,是時候兌現當年自己說的話了,日常我們在spring最主要的使用地方只是集成第三方產品(如orm框架等),像一種萬能膠一樣。以及自身業務系統中的bean的管理。以及一些aop的使用。在本篇文章中,主要是通過自身對於源碼的解讀后,抓住核心思想,實現一個mini版的spring。幫助大家以及我自身的理解,畢竟分享與幫助他人也是提高自身的一種手段。在本節中,我們主要講解IOC與DI,后幾篇文章中再繼續探討aop與springMVC以及springBoot的問題(當然,mvc與boot以及cloud是由spring衍生出來的產品)。
本文不是概念型講解(只做簡單的概念說明),如果你是新手概念也是模糊的,那么不太適合閱讀此文章。對於內容中有任何的錯誤之處以及不恰當之處,希望您能指出。我會及時改正。
IOC
Inversion of Control中文譯名:控制反轉。
對於這個詞,我的個人理解是:沒有spring的時候,B類需要對象A,我們需要自己去new A(),可能有10個地方都需要A,那我們需要new10次,加重了GC的壓力。
這是,我們想,如果有一個大的池子來幫我們創建好所有的對象(比如B和A類的對象都創建好),對象直接的依賴關系,也由這個池子來幫我們管理,如在創建B類時,發現需要引用A,則注入(DI)一個A對象。
原本對象的控制權在自己手里,而把這個控制權轉變為給這個池子。更像是一種設計模式,如果這種思想被開發的早,可能就列入GoF的23種設計模式中了。。。實現這種模式的主要方式,則是我們后文的DI(Dependency Injection)
ioc實現
對於spring源碼的閱讀,個人閱讀過相關書籍,第一遍看的時候,全臉懵逼,光是源碼中的調用鏈,就有20幾層+(不過源碼中嚴格遵循了方法單一職責,這點很值得我學習,且設計模式運用的惟妙惟肖)。給大家的學習意見是:先看一些知名的博客,梳理一個整體的邏輯圖,再細微的看里面的實現策略。否則是很難讀懂的。當然,時至今日(個人也是略知一二),只是對大致邏輯做了總結后寫出一個mini版,只是講一個思路,代碼的健壯性還請大家不要太較真。可能有的地方實現的並不是最好的實現方式。
bean:是spring中很重要的一個概念,其實就是對象的實例
首先,我們定義一個BeanFactory,對象實例的工廠,這個工廠提供一個統一的方法,getBean
1 public interface HkBeanFactory { 2 3 Object getBean(String name) throws Exception; 4 5 }
有了工廠后,我們還不行,還需要一個定義bean(BeanDefinition)的接口,用來描述這個bean是如何被定義出來的,在我所寫的代碼中,bean定義只通過類構造器來創建bean,事實上spring源碼中還可通過靜態工廠與成員工廠來創建bean,但因實際中少用這兩中,所以我沒定義。
定義bean,我們需要知道這個bean是哪個類的,是要單例的還是非單例(spring中叫:原型prototype),初始化bean時需要做的工作,以及bean銷毀時的操作(銷毀我的代碼中未實現)
1 public interface HkBeanDefinition { 2 3 final static String SINGLETION = "singleton"; 4 5 final static String PROTOTYPE = "prototype"; 6 7 Class<?> getBeanClass(); 8 9 String getScope(); 10 11 boolean isSingleton(); 12 13 boolean isPrototype(); 14 15 String getInitMethodName(); 16 17 }
有了bean定義的接口,我們還是不能放入工廠中,還得將bean注冊到工廠中,我們再寫一個bean定義注冊(BeanDefinitionRegistry)的接口,此接口要提供注冊的功能,獲取BeanDefinition的功能,另外,防止bean重復定義,需要一個檢測是否已有bean的功能,如下:
1 public interface HkBeanDefinitionRegistry { 2 3 void registerBeanDefinition (String beanName, HkBeanDefinition hkBeanDefinition) throws Exception; 4 5 HkBeanDefinition getBeanDefinition(String beanName); 6 7 boolean containsBeanDefinition(String beanName); 8 9 }
OK,有了以上3個接口,我們可以開始實現具體細節了,首先,我們來實現一個通用的bean定義(GenericBeanDefinition),spring中,默認所有的bean都是單例的。所以我們也給定默認單例

1 public class GenericBeanDefinition implements HkBeanDefinition { 2 3 private Class<?> beanClass; 4 5 private String scope = HkBeanDefinition.SINGLETION; 6 7 private String initMethodName; 8 9 10 11 12 public void setBeanClass(Class<?> beanClass) { 13 this.beanClass = beanClass; 14 } 15 16 public void setScope(String scope) { 17 this.scope = scope; 18 } 19 20 public void setInitMethodName(String initMethodName) { 21 this.initMethodName = initMethodName; 22 } 23 24 25 @Override 26 public Class<?> getBeanClass() { 27 return beanClass; 28 } 29 30 @Override 31 public String getScope() { 32 return scope; 33 } 34 35 @Override 36 public boolean isSingleton() { 37 return Objects.equals(scope, HkBeanDefinition.SINGLETION); 38 } 39 40 @Override 41 public boolean isPrototype() { 42 return Objects.equals(scope, HkBeanDefinition.PROTOTYPE); 43 } 44 45 @Override 46 public String getInitMethodName() { 47 return initMethodName; 48 } 49 50 51 }
在這個類中,我們實現接口中的方法並生成對應的set方法即可!
OK,以上是我們所需要的零件,現在,我們用這些零件來組裝一個bean工廠的實現類,bean工廠中,我們要有注冊bean定義的功能(類似官方spring中,我們在配置文件中組裝bean那一步,指定類,屬性等等。這里我們通過硬編碼實現),
還要有獲取bean的功能getBean(),為了簡化步驟與理解,我們全部用java代碼來實現,更好的幫助大家理解。(即我們這會省去xml解析,省去解析注解諸如@Component@Repository、@Service等)也省掉spring的applicationcontext的門面模式,我們來直接操作工廠。
所以這個類需要實現HkBeanFactory與HkBeanDefinitionRegistry這2個接口。
首先,定義倆個集合,存放bean實例與beanDefinition。如下:因可能存在同時注冊,所以采用與官方版本一致的ConcurrentHashMap
public class DefaultBeanFactory implements HkBeanFactory, HkBeanDefinitionRegistry { private Map<String, Object> beanMap = new ConcurrentHashMap<>(); private Map<String, HkBeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(); }
接下來,寫注冊bean的功能,如上文所說,我們此處用代碼來注冊,不再解析xml,方法如下:
/* * 注冊bean定義,需要給定唯一bean的名稱和bean的定義,放到bean定義集合中 */ @Override public void registerBeanDefinition(String beanName, HkBeanDefinition hkBeanDefinition) throws Exception { Objects.requireNonNull(beanName, "beanName不能為空"); Objects.requireNonNull(hkBeanDefinition, "beanDefinition不能為空"); if (beanDefinitionMap.containsKey(beanName)){ throw new Exception("已存在【"+beanName+ "】的bean定義"+getBeanDefinition(beanName)); } beanDefinitionMap.put(beanName, hkBeanDefinition); }
OK,bean已經再工廠定義好,接下來我們寫關鍵的獲取bean方法getBean(),在spring的官方版本中,創建bean對象有3種方式,通過調用構造器,還可以通過類的靜態工廠方法與成員工廠方法來創建對象。鑒於后2種我們平常使用較少,此處我們采用構造器來創建對象。代碼如下:
1 /* 2 * 獲得bean的門面方法 3 */ 4 @Override 5 public Object getBean(String name) throws Exception { 6 return doGetBean(name); 7 } 8 9 /** 10 * getBean的具體邏輯 11 * 12 * 事實上,在spring的bean定義中,還可以靜態工廠方法和成員工廠方法來創建實例,但在開發中這2種用的較少,所以此處只使用構造器來創建bean 13 */ 14 private Object doGetBean(String beanName) throws Exception{ 15 Objects.requireNonNull(beanName, "beanName不能為空"); 16 Object instance = beanMap.get(beanName); 17 //如果bean已存在,則直接返回 18 if(instance != null){ 19 return instance; 20 } 21 HkBeanDefinition beanDefinition = beanDefinitionMap.get(beanName); 22 Objects.requireNonNull(beanDefinition, "beanDefinition不能為空"); 23 Class<?> class1 = beanDefinition.getBeanClass(); 24 Objects.requireNonNull(class1, "bean定義中class類型不能為空"); 25 instance = class1.newInstance(); 26 27 //實例已創建好,通過反射執行bean的init方法 28 String initMethodName = beanDefinition.getInitMethodName(); 29 if(null!=initMethodName){ 30 Method method = class1.getMethod(initMethodName, null); 31 method.invoke(instance, null); 32 } 33 34 //將單例bean放到map中,下次可直接拿到 35 if(beanDefinition.isSingleton()){ 36 beanMap.put(beanName, instance); 37 } 38 return instance; 39 }
對以上代碼關鍵部分做出解釋,因實例化bean會在之后多次用到,所以單獨抽取出來,事實上官方版本也是這么做的。,所以我們重點看一下doGetBean方法
16~20行,嘗試從beanMap種獲取,如果是單例bean,已創建過對象,則直接返回對象
21~22行,從beanDefinitionMap種獲取bean定義,如果未定義,則拋出異常。
25行,調用bena定義種的類的構造方法來創建實例。
28~32行,某些bean有初始化方法init,如資源加載等,此處我們通過反射執行一次init方法。
35~37行,將單例bean放到集合種,下次獲取此對象即可直接返回。
下面,附上完整代碼:

import java.lang.reflect.Method; import java.util.Map; import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; /** * 默認的bean工廠實現類,需實現bean定義注冊 * @author HK * */ public class DefaultBeanFactory implements HkBeanFactory, HkBeanDefinitionRegistry { private Map<String, Object> beanMap = new ConcurrentHashMap<>(); private Map<String, HkBeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(); /* * 注冊bean定義,需要給定唯一bean的名稱和bean的定義,放到bean定義集合中 */ @Override public void registerBeanDefinition(String beanName, HkBeanDefinition hkBeanDefinition) throws Exception { Objects.requireNonNull(beanName, "beanName不能為空"); Objects.requireNonNull(hkBeanDefinition, "beanDefinition不能為空"); if (beanDefinitionMap.containsKey(beanName)){ throw new Exception("已存在【"+beanName+ "】的bean定義"+getBeanDefinition(beanName)); } beanDefinitionMap.put(beanName, hkBeanDefinition); } /* * 獲得bean定義 */ @Override public HkBeanDefinition getBeanDefinition(String beanName) { return beanDefinitionMap.get(beanName); } /* * 是否存在bean定義 */ @Override public boolean containsBeanDefinition(String beanName) { return beanDefinitionMap.containsKey(beanName); } /* * 獲得bean的門面方法 */ @Override public Object getBean(String name) throws Exception { return doGetBean(name); } /** * getBean的具體邏輯 * * 事實上,在spring的bean定義中,還可以靜態工廠方法和成員工廠方法來創建實例,但在開發中這2種用的較少,所以此處只使用構造器來創建bean */ private Object doGetBean(String beanName) throws Exception{ Objects.requireNonNull(beanName, "beanName不能為空"); Object instance = beanMap.get(beanName); //如果bean已存在,則直接返回 if(instance != null){ return instance; } HkBeanDefinition beanDefinition = beanDefinitionMap.get(beanName); Objects.requireNonNull(beanDefinition, "beanDefinition不能為空"); Class<?> class1 = beanDefinition.getBeanClass(); Objects.requireNonNull(class1, "bean定義中class類型不能為空"); instance = class1.newInstance(); //實例已創建好,通過反射執行bean的init方法 String initMethodName = beanDefinition.getInitMethodName(); if(null!=initMethodName){ Method method = class1.getMethod(initMethodName, null); method.invoke(instance, null); } //將單例bean放到map中,下次可直接拿到 if(beanDefinition.isSingleton()){ beanMap.put(beanName, instance); } return instance; } }
測試IOC容器
接下來,我們來測試這個IOC容器的運行:如下先定義一個簡單bean對象TeacherBean,一個是初始化方法init( ),一個是上課方法teach( )。
/** * @author HK * */ public class TeacherBean { public void teach(){ System.out.println(this+"執行了teach方法,老師要開始上課了!"); } public void init(){ System.out.println("Teacher類的初始化init方法被執行了"); } }
好,我們就將這個teacherBean放到IOC工廠。如下,
1 import org.junit.AfterClass; 2 import org.junit.Test; 3 4 import spring.beans.DefaultBeanFactory; 5 import spring.beans.GenericBeanDefinition; 6 7 /** 8 * @author HK 9 * 10 */ 11 public class TestIOC { 12 static DefaultBeanFactory factory = new DefaultBeanFactory(); 13 14 @Test 15 public void testRegist() throws Exception { 16 GenericBeanDefinition bd = new GenericBeanDefinition(); 17 bd.setBeanClass(TeacherBean.class); 18 // bd.setScope(HkBeanDefinition.PROTOTYPE); 19 bd.setInitMethodName("init"); 20 factory.registerBeanDefinition("teacher", bd); 21 22 } 23 24 @AfterClass 25 public static void testGetBean() throws Exception{ 26 TeacherBean t =(TeacherBean) factory.getBean("teacher"); 27 TeacherBean t1 =(TeacherBean) factory.getBean("teacher"); 28 t.teach(); 29 t1.teach(); 30 System.out.println(t==t1); 31 } 32 33 }
解釋:16~20行,我們主要做了注冊bean定義。而在26~30行,是從IOC中獲取bean,由於默認是單例,所以2次獲取bean后是同1個對象。執行結果如下圖
至此,一個簡單的IOC容器就完成了。但我們發現了一些其他問題,比如Teacher類中有很多屬性,如String name; int age; Student student;等等
此時,學生類Student也是一個bean的時候,如何給Teacher類賦予這些值呢,接下來,就是我們DI依賴注入要做的事情了。
DI
Dependency Injection中文譯名:依賴注入。
由於時間關系,會在之后的周末時間整理 好后更新此處內容(手動捂臉)相信有了上邊的基礎后,此處大家也不難實現了!!!