怎麼閱讀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 api

3. 已經根據自己對 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的初始化信息:

&

&org.springframework.web.context.ContextLoaderListener&

&

由配置信息可知,我們開始的入口就這裡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框架的項目?
如何用最簡單的方式解釋依賴注入?依賴注入是如何實現解耦的?

TAG:Java | Java編程 | Spring | JavaEE |