怎麼閱讀Spring源碼?
建議不要硬著頭皮看spring代碼,本身的代碼800多m,就是不上班開始看也不知道什麼時候看完。如果想學學ioc,控制反轉這些建議看看jodd項目,比較簡練,但是我仍然不建議過多的看這些框架的代碼,因為這些代碼要完成任務需要很多瑣碎的類實現,比如讀取某個包下面的所有類,解析class的頭文件,反射各種信息,再加上封裝,很有可能在讀源碼的過程中掉到各種細節里出不來,所以讀這種源碼要事無巨細,理解原理即可。基本原理其實就是通過反射解析類及其類的各種信息,包括構造器、方法及其參數,屬性。然後將其封裝成bean定義信息類、constructor信息類、method信息類、property信息類,最終放在一個map里,也就是所謂的container,池等等,其實就是個map。。汗。。。。當你寫好配置文件,啟動項目後,框架會先按照你的配置文件找到那個要scan的包,然後解析包裡面的所有類,找到所有含有@bean,@service等註解的類,利用反射解析它們,包括解析構造器,方法,屬性等等,然後封裝成各種信息類放到一個map里。每當你需要一個bean的時候,框架就會從container找是不是有這個類的定義啊?如果找到則通過構造器new出來(這就是控制反轉,不用你new,框架幫你new),再在這個類找是不是有要注入的屬性或者方法,比如標有@autowired的屬性,如果有則還是到container找對應的解析類,new出對象,並通過之前解析出來的信息類找到setter方法,然後用該方法注入對象(這就是依賴注入)。如果其中有一個類container里沒找到,則拋出異常,比如常見的spring無法找到該類定義,無法wire的異常。還有就是嵌套bean則用了一下遞歸,container會放到servletcontext裡面,每次reQuest從servletcontext找這個container即可,不用多次解析類定義。如果bean的scope是singleton,則會重用這個bean不再重新創建,將這個bean放到一個map里,每次用都先從這個map裡面找。如果scope是session,則該bean會放到session裡面。僅此而已,沒必要花更多精力。建議還是多看看底層的知識。
Spring 是一個很大的生態。以 Spring Core 為例子
先滿足前提:
一,最基本
1. 了解 Java語法2. 了解設計模式二,項目基礎1. 已經了解 Spring Core 的功能2. 已經過了一遍 public api3. 已經根據自己對 IoC container 的理解,實現了一個最簡單的 IoC container滿足了之後,再進入 spring-core 和 spring-beans 的世界吧。可以先看一下黃勇老師的《架構探險——從零開始寫Java Web框架》。
因為Spring的源碼太複雜了,直接看很難理解。先理解Spring的思想,再去看實現,就會比較容易。
《結構探險》這本書,自造輪子,實現了一個小型的Java Web框架,實現了類似於Spring的註解式IoC和AOP,實現了聲明式事務處理,對理解Spring的架構思想很有幫助。
spring不是一個東西,spring是一堆東西
1. spring代碼太多,先確定你想了解哪塊,tx,aop,core還是啥的,然後做的最簡單能運行的demo用上這些模塊。2. 看spring對目標模塊的文檔
3. 加斷點跟蹤調試
As far as I"m concerned, 閱讀好項目源代碼,有幾個前提條件要做好:
1). 知道該項目的用途,是幹什麼的
2). 了解該項目的架構,包含什麼模塊,各模塊是幹什麼的
3). 英文水平最低限度:閱讀無障礙(因為在閱讀源碼的時候,你需要看英文注釋來輔助理解)
如果以上三個條件做好了,閱讀源碼事半功倍。
回歸正題:怎麼閱讀Spring源碼?
1)、Spring Framework 是一個開源框架,能幫助企業快速搭建一棧式(Full Stack)企業級項目應用框架。
2)、Spring Framework 項目架構圖:
3)、上面的圖展示出,Spring框架包含了非常多的功能,不能漫無目的,一股腦地閱讀,這樣很容易頭暈。了解完Spring架構、模塊以及模塊對應的功能後,可以針對性閱讀部分源碼。逐一攻破。
4)、另外Spring在代碼設計中運用了大量的設計模式,可以用事件驅動去學習一下設計模式。
建議在這個頁面code4craft/tiny-spring下載下來所有step1--step10所有的項目,全部導入到工程,看看作者是怎樣一步步把spring整個框架搭起來的,一步步順著spring的功能完善代碼,順便學學spring類的組織結構,學到很多,等學完後頭腦很清晰,確實受益匪淺,還有同學沒有github基礎的,請先下載git客戶端Git - Download,花半個小時看下Git教程,相信我,等學會了你會大開眼界的。debug斷點運行是我針對這個項目學習最好的方式,相信你也一樣
========以下是原回答=========
初學者java建議先搞懂think in java,如果有一定基礎github搜索tiny-spring-&>copy-&>debug一步步跟進,這裡是地址code4craft/tiny-spring,不過這裡只是讓你搞明白spring最核心的ioc和aop兩個功能,建議從ioc看起,相對來說相關的代碼較少,類少,能讓你快速有個大概的了解,增加點信心,然後再看aop相關的代碼,它的測試類可以都可以運行,這個項目只是模擬和抽調出來了spring龐大的功能和代碼,只保留了最核心的ioc和aop相關的功能,不過你懂了這兩個功能還有什麼對spring不了解的呢,對你有幫助了記得點個贊
推薦一本書《SPRING技術內幕:深入解析SPRING架構與設計原理》
這個我有經驗。手機,就簡單說了:找一個你感興趣的模塊,查明白這個模塊是什麼作用,然後導入工程,編譯不報錯後,一個一個調試testcase. 基本就能把實現原理搞明白了。比讀讀碼效率高。
。。。。debug進去不就行了。。。。
我也是一直在閱讀Spring的源代碼,主要在看Spring IOC的實現部分,主要分為三個部分:
第一步:
Resouce:首先我們需要獲取beanDefinition的資源,我們將通過Spring的resource介面獲得bean的資源,這一步也叫做bean資源的定位,下面是Spring內置的一些典型實現,當然,你也可以根據自己的需要實現自己的resource:
ClassPathResource可用來獲取類路徑下的資源文件。假設我們有一個資源文件test.txt在類路徑下,我們就可以通過給定對應資源文件在類路徑下的路徑path來獲取它,new ClassPathResource(「test.txt」)。
FileSystemResource可用來獲取文件系統裡面的資源。我們可以通過對應資源文件的文件路徑來構建一個FileSystemResource。FileSystemResource還可以往對應的資源文件裡面寫內容,當然前提是當前資源文件是可寫的,這可以通過其isWritable()方法來判斷。FileSystemResource對外開放了對應資源文件的輸出流,可以通過getOutputStream()方法獲取到。
UrlResource可用來代表URL對應的資源,它對URL做了一個簡單的封裝。通過給定一個URL地址,我們就能構建一個UrlResource。
ByteArrayResource是針對於位元組數組封裝的資源,它的構建需要一個位元組數組。
ServletContextResource是針對於ServletContext封裝的資源,用於訪問ServletContext環境下的資源。ServletContextResource持有一個ServletContext的引用,其底層是通過ServletContext的getResource()方法和getResourceAsStream()方法來獲取資源的。
InputStreamResource是針對於輸入流封裝的資源,它的構建需要一個輸入流。
第二步:
第二步叫做bean Resource的解析:就是將我們獲取到的resource轉換成bean存在於Spring中的數據結構,也就是BeanDefinition,在我自己實現的Spring中,beanDefinition實現的比較簡單,主要有bean的作用域,持有bean的class對象,以及用一個String數組保存的它所依賴的bean的名字,現在也只能支持singleton和prototype兩種作用域;(Spring中beanDefinition的實現類並不多,大家可以去看下這部分的源代碼,弄懂了beanDefinition,IOC部分就明白一半了)
第三步:
在第二步bean資源的解析完成之後,我們需要將beanDefinition註冊到我們的工廠,用一個map集合進行保存;註冊完成之後,工廠的初始化也就完成了,接下來就是我們熟悉的通過getBean方法從容器中獲取我們所需要的bean了;getBean方法也正式觸發Spring依賴注入的起點,在這裡也是有兩個問題:
1.如何保證Spring bean的創建順序,即被依賴的bean總是要先被完整的創建
2.當bean之間出現了循環依賴又該如何解決? 在這裡我用了組合模式,將Spring中的bean分為兩種,一種是依賴於其他bean的bean,一種是獨立的bean,它並不依賴於其他的bean,採用了遞歸的方式進行解決;(這部分說起來比較複雜,後面在貼代碼進行分析)
題主水平有限,對Spring的理解也很淺,如果講有錯誤的地方,歡迎指正
我自己實現的一個Spring地址:MySixGod/SpringImpl_v2.0 ,目前較好的解決了依賴的問題,但是依賴注入的方式單一,方法不靈活,對於屬性的命名,set方法的命名都有嚴格的要求,因為在此之前重心放在bean的創建上,後續的話會實現別名介面,添加更多的注入方式,爭取能完成一個小而美的簡單版Spring吧
首先,你要知道一個javaweb項目,在沒有spring或者其他框架的情況下,是如何運行的。
如果沒有框架,肯定是在web.xml文件里配置若干個servlet。
如果使用了springmvc,會只配一個servlet(org.springframework.web.servlet.DispatcherServlet),然後所有的請求都會首先進入這個servlet,從這裡開始看,先看spring的配置如何載入,bean的創建也在這裡(Spring IOC);然後看一個請求是怎樣從DispatcherServlet到你自己的Controller的;然後看你的方法的返回,是怎樣渲染到頁面的,如果視圖文件是模板文件,那麼最後肯定有一個out.println( 渲染後的字元串) ;如果視圖文件是jsp文件,那麼最後肯定有一個request.forward(jsp文件),這是Spring MVC。數據持久化層有人用mybatis,有人用hibernate,也有人用spring jdbc template,想看spring jdbc template就從dao層看起,看到sql怎樣執行,最後怎樣返回為止
嘗試著,自己寫一個最簡的版本。對你理解原理有很大幫助。
搭一個application項目,弄一個最簡單的bean,通過debug bean如何從spring的bean factory中取得來學習源碼,這樣有個主線, 如我這裡的做法http://darkjune.iteye.com/blog/1929171
他山之石:Spring源碼解析——如何閱讀源碼在這篇博文里,讀者可以了解到:
1 Spring jar包以及源碼使用
2 簡單的spring運行示例
3 利用斷點調試程序,如何快速的閱讀程序【快捷鍵等的使用】
請看我的系列文章,從設計模式入手,從整體到局部,一點點剝離Spring的源碼
最近在看ioc部分的源碼,本學渣唯一體驗就是太變態了……
從BeanFactory到ApplicationContext的介面實現怎麼可以定義的這麼複雜……我這麼沒有耐心的人……方法調用各種跳來跳去的……
目前以XmlBeanFactory為起點,看定位解析xml,註冊bean,到實例化bean的過程主要關注的類有:DefaultListableBeanFactory實現了BeanFactory下各家介面的基本功能、BeanDefinitionParserDelegate主要由它來做的xml解析,還有理解BeanDefinition持有Bean實例的封裝啊,還有Resourse介面體系啊等等等等……我已經炸了!第二天了……好變態啊……從入門到放棄……我每天都在告訴自己……幹掉spring!以後各家源碼看起來都不虛!求大佬告知輔助讀源碼的好的學習方法或者書籍(《深度解析》和《技術內幕》好像評價都沒多好……),或者梳理這個體系結構的什麼路線……debug我是搞不定的嚶嚶嚶……推薦《Spring 源碼深度解析》,很不錯的一本書,目前正在看
有本《spring揭秘》,網上有電子書。講spring3的,但原理講的很清楚,比一般貼代碼的書好多了
邊看邊畫類圖和時序圖,還有寫筆記記錄一些核心類的作用,第二遍就清晰多了。
在閱讀spring源碼之前要明確兩個問題:
1.閱讀源碼的入口在哪裡?
2.入門前必備知識了解:IOC和AOP
一、我們從哪裡開始
1.準備工作:在官網上下載了spring源代碼之後,導入Eclipse,以方便查詢。
2.打開我們使用Spring的項目工程,找到Web.xml這個網站系統配置文件,在其中找到Spring的初始化信息:
&
&
&
由配置信息可知,我們開始的入口就這裡ContextLoaderListener這個監聽器。
在源代碼中我們找到了這個類,它的定義是:
public class ContextLoaderListener extends ContextLoader
implements ServletContextListener {
…
/**
* Initialize the root web application context.
*/
public void contextInitialized(ServletContextEvent event) {
this.contextLoader = createContextLoader();
if (this.contextLoader == null) {
this.contextLoader = this;
}
this.contextLoader.initWebApplicationContext(event.getServletContext());
}
...
}
該類繼續了ContextLoader並實現了監聽器,關於Spring的信息載入配置、初始化便是從這裡開始了,具體其他閱讀另外寫文章來深入了解。
二、關於IOC和AOP
關於Spring IOC 網上很多相關的文章可以閱讀,那麼我們從中了解到的知識點是什麼?
1)IOC容器和AOP切面依賴注入是Spring是核心。
IOC容器為開發者管理對象之間的依賴關係提供了便利和基礎服務,其中Bean工廠(BeanFactory)和上下文(ApplicationContext)就是IOC的表現形式。BeanFactory是個介面類,只是對容器提供的最基本服務提供了定義,而DefaultListTableBeanFactory、XmlBeanFactory、ApplicationContext等都是具體的實現。
介面:
public interface BeanFactory {
//這裡是對工廠Bean的轉義定義,因為如果使用bean的名字檢索IOC容器得到的對象是工廠Bean生成的對象,
//如果需要得到工廠Bean本身,需要使用轉義的名字來向IOC容器檢索
String FACTORY_BEAN_PREFIX = "";
//這裡根據bean的名字,在IOC容器中得到bean實例,這個IOC容器就象一個大的抽象工廠,用戶可以根據名字得到需要的bean
//在Spring中,Bean和普通的JAVA對象不同在於:
//Bean已經包含了我們在Bean定義信息中的依賴關係的處理,同時Bean是已經被放到IOC容器中進行管理了,有它自己的生命周期
Object getBean(String name) throws BeansException;
//這裡根據bean的名字和Class類型來得到bean實例,和上面的方法不同在於它會拋出異常:如果根名字取得的bean實例的Class類型和需要的不同的話。
Object getBean(String name, Class requiredType) throws BeansException;
//這裡提供對bean的檢索,看看是否在IOC容器有這個名字的bean
boolean containsBean(String name);
//這裡根據bean名字得到bean實例,並同時判斷這個bean是不是單件,在配置的時候,默認的Bean被配置成單件形式,如果不需要單件形式,需要用戶在Bean定義信息中標註出來,這樣IOC容器在每次接受到用戶的getBean要求的時候,會生成一個新的Bean返回給客戶使用 - 這就是Prototype形式
boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
//這裡對得到bean實例的Class類型
Class getType(String name) throws NoSuchBeanDefinitionException;
//這裡得到bean的別名,如果根據別名檢索,那麼其原名也會被檢索出來
String[] getAliases(String name);
}
實現:
XmlBeanFactory的實現是這樣的:
public class XmlBeanFactory extends DefaultListableBeanFactory {
//這裡為容器定義了一個默認使用的bean定義讀取器,在Spring的使用中,Bean定義信息的讀取是容器初始化的一部分,但是在實現上是和容器的註冊以及依賴的注入是分開的,這樣可以使用靈活的 bean定義讀取機制。
private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);
//這裡需要一個Resource類型的Bean定義信息,實際上的定位過程是由Resource的構建過程來完成的。
public XmlBeanFactory(Resource resource) throws BeansException {
this(resource, null);
}
//在初始化函數中使用讀取器來對資源進行讀取,得到bean定義信息。這裡完成整個IOC容器對Bean定義信息的載入和註冊過程
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws
BeansException {
super(parentBeanFactory);
this.reader.loadBeanDefinitions(resource);
}
我們可以看到IOC容器使用的一些基本過程:
如:DefaultListableBeanFactory
ClassPathResource res = new ClassPathResource("beans.xml");//讀取配置文件
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(res);
這些代碼演示了以下幾個步驟:
1. 創建IOC配置文件的抽象資源
2. 創建一個BeanFactory,這裡我們使用DefaultListableBeanFactory
3. 創建一個載入bean定義信息的讀取器,這裡使用XmlBeanDefinitionReader來載入XML形式
的bean定義信息,配置給BeanFactory
4. 從定義好的資源位置讀入配置信息,具體的解析過程由XmlBeanDefinitionReader來完成,這
樣完成整個載入和註冊bean定義的過程。我們的IoC容器就建立起來了。
再簡單的說,我的系統在啟動時候,會完成的動作就是
1.由ResourceLoader獲取資源文件,也即bean的各種配置文件
2.由BeanDefintion對配置文件的定義信息的載入
3.用BeanDefinitionRegistry介面來實現載入bean定義信息並向IOC容器進行註冊。
注意,IOC容器和上下文的初始化一般不包含Bean的依賴注入的實現。
2)AOP這個過程並不是在註冊bean的過程實現的。
我們只看到在處理相關的Bean屬性的時候,使用了RuntimeBeanReference對象作為依賴信息的紀錄。
在IOC容器已經載入了用戶定義的Bean信息前提下,這個依賴注入的過程是用戶第一次向IOC容器索要Bean的時候觸發的,或者是我們可以在Bean定義信息中通過控制lazy-init屬性來使得容器完成對Bean的預實例化 - 這個預實例化也是一個完成依賴注入的過程。
我們說明一下過程:
1.用戶想IOC容器請求Bean。
2.系統先在緩存中查找是否有該名稱的Bean(去各個BeanFactory去查找)
3.沒有的話就去創建Bean並進行依賴注入,並且這個請求將被記錄起來。
請求Bean具體的實現:
代碼入口在DefaultListableBeanFactory的基類AbstractBeanFactory中:
public Object getBean(String name, Class requiredType, final Object[] args) throwsBeansException {
...
Object sharedInstance = getSingleton(beanName);//先去緩存取
if (sharedInstance != null) {
...
if (containsBeanDefinition(beanName)) {
RootBeanDefinition mergedBeanDefinition = getMergedBeanDefinition(beanName, false);
bean = getObjectForBeanInstance(sharedInstance, name,mergedBeanDefinition);
}
else {
bean = getObjectForBeanInstance(sharedInstance, name, null);
}
}
else {
}
...
}
注入Bean具體的實現:
具體的bean創建過程和依賴關係的注入在createBean中,這個方法在AbstractAutowireCapableBeanFactory中給出了實現:
protected Object createBean(String beanName, RootBeanDefinition
mergedBeanDefinition, Object[] args)
throws BeanCreationException {
// Guarantee initialization of beans that the current one depends on.
// 這裡對取得當前bean的所有依賴bean,確定能夠取得這些已經被確定的bean,如果沒有被創建,那麼這個createBean會被這些IOC
// getbean時創建這些bean
if (mergedBeanDefinition.getDependsOn() != null) {
for (int i = 0; i &< mergedBeanDefinition.getDependsOn().length; i++) {
getBean(mergedBeanDefinition.getDependsOn()[i]);
}
}
........
// 這裡是實例化bean對象的地方,注意這個BeanWrapper類,是對bean操作的主要封裝類
if (instanceWrapper == null) {
instanceWrapper = createBeanInstance(beanName, mergedBeanDefinition,args);
}
Object bean = instanceWrapper.getWrappedInstance();
......
//這個populate方法,是對已經創建的bean實例進行依賴注入的地方,會使用到在loadBeanDefinition的時候得到的那些propertyValue來對bean進行注入。
if (continueWithPropertyPopulation) {
populateBean(beanName, mergedBeanDefinition, instanceWrapper);
}
//這裡完成客戶自定義的對bean的一些初始化動作
Object originalBean = bean;
bean = initializeBean(beanName, bean, mergedBeanDefinition);
// Register bean as disposable, and also as dependent on specified "dependsOn"beans.
registerDisposableBeanIfNecessary(beanName, originalBean,mergedBeanDefinition);
return bean;
}
.........
}
這就是整個依賴注入的部分處理過程,在這個過程中起主要作用的是WrapperImp ,這個Wrapper不是一
個簡單的對bean對象的封裝,因為它需要處理在beanDefinition中的信息來迭代的處理依賴注入。
到這裡,這是簡單的,大概的對IOC和AOP進行了解,入門先到這一點便已經有了大概的印象了。
推薦閱讀:
※關於spring,mybatis,mvc等等框架?
※如何理解 ssh 三大框架?
※Github上有沒有關於springmvc框架的項目?
※如何用最簡單的方式解釋依賴注入?依賴注入是如何實現解耦的?