我們在使用一個bean得時候尊沸,在不用任何框架的情況下都是需要自己new的俊犯,spring框架既然為我們提供bean容器使得我們把bean得管理權(quán)交給它妇多,那么我們看看spring的bean是怎么裝載的,先說xml燕侠,注解的方式其實是一樣的者祖。
接著上篇的test例子<b>CollectionMergingTests</b>其中最開始有個<b>setUp</b>方法,這是junit方法運行前的裝配方法
public class CollectionMergingTests extends TestCase {
private DefaultListableBeanFactory beanFactory;
@Override
protected void setUp() throws Exception {
this.beanFactory = new DefaultListableBeanFactory();
BeanDefinitionReader reader = new XmlBeanDefinitionReader(this.beanFactory);
reader.loadBeanDefinitions(new ClassPathResource("collectionMerging.xml", getClass()));
}
………………
}
這個方法就是在裝配bean到容器中绢彤。
<b>DefaultListableBeanFactory </b>是spring 真正可以獨立使用IOC容器的<b>BeanFactory</b>咸包。繼承和實現(xiàn)的傳遞性可知<b>DefaultListableBeanFactory </b>默認實現(xiàn)了<b>BeanFactory</b>和<b>BeanDefinitionRegistry</b>,<b>DefaultListableBeanFactory </b>類繼承圖(引用自software-architecture-design)
直接debug看下其初始化過程便基本知道繼承關(guān)系。
可見先是到static初始化塊
static {
try {
javaUtilOptionalClass =
ClassUtils.forName("java.util.Optional", DefaultListableBeanFactory.class.getClassLoader());
}
catch (ClassNotFoundException ex) {
// Java 8 not available - Optional references simply not supported then.
}
try {
javaxInjectProviderClass =
ClassUtils.forName("javax.inject.Provider", DefaultListableBeanFactory.class.getClassLoader());
}
catch (ClassNotFoundException ex) {
// JSR-330 API not available - Provider interface simply not supported then.
}
}
加載class java.util.Optional
(JAVA8新加入的)和interface javax.inject.Provider
<b>java.util.Optional</b>是JAVA8新加入的類杖虾,這個類專門用來解決空引用的問題,同時這個類和lambda表達式和函數(shù)式編程也可以比較好的整合在一起使用
java.util.Optional doc
<b>javax.inject.Provider</b>提供了一個 T的實例媒熊。 通常作為一個依賴注入容器器的父接口. 可以注入任何類型的 T, 當然也可以入 Provider<T>
相對于直接注入 T 奇适,注入 Provider<T> 有如下作用(doc的英文大概翻譯):
- 檢索多個實例
- 延遲或者選擇性的檢索一個實例
- 打破循環(huán)依賴
- 抽象的scope,可以從一個包含scope的更小的scope中檢索一個實例
javax.inject.Provider doc
在debug該類初始化的過程中我在類的屬性初始化上打斷點無意間發(fā)現(xiàn)了eclipse的斷點類型
第一處的是<b>Watchpoint</b>這個主要是關(guān)注變量的值得變化過程芦鳍,第二處是<b>Line Breakpoint</b>最簡單的斷點嚷往,即執(zhí)行到此處就斷點,還有柠衅。eclipse 還有條件斷點皮仁,方法斷點 異常斷點 遠程調(diào)試等 這個完了再細化記錄博客吧。
這里想說的是為什么 第一行是<b>Watchpoint</b>二處是<b>Line Breakpoint</b> 同樣是屬性是為什么菲宴? 其實是final關(guān)鍵字的作用贷祈,<b>Watchpoint</b>是觀察值的變化情況 final的變量是不可變的 所以final修飾的參數(shù)斷點都是<b>Line Breakpoint</b> 類似于方法斷點,所以有時候要分清你可以在屬性上設(shè)置的斷點類型喝峦。
接下來到了屬性的初始化
繼續(xù)往下走的時候可以看到 類初始化的關(guān)系 先是<b>DefaultListableBeanFactory</b>static靜態(tài)初始化塊和static屬性势誊,然后是SimpleAliasRegistry的屬性
再是SimpleAliasRegistry的子類<b>DefaultSingletonBeanRegistry</b>
接著是<b>DefaultSingletonBeanRegistry</b>的子類<b>FactoryBeanRegistrySupport</b>
接著<b>FactoryBeanRegistrySupport</b>子類<b>AbstractBeanFactory</b>
接著是<b>AbstractBeanFactory</b>子類<b>AbstractAutowireCapableBeanFactory</b>
最后到<b>DefaultListableBeanFactory</b>自己
由此我們基本可以得出類初始化的順序(為什么要說這個 是因為我看到網(wǎng)上好多說法和我運行的得到的結(jié)果不一致):
先找到根類,根類static靜態(tài)塊和static屬性的初始化 接著根類的子類static靜態(tài)塊和static屬性初始化 最后回到類本身的類static靜態(tài)塊和static屬性初始化然后再到根類 其他屬性的初始化谣蠢,接著根類子類的其他屬性初始化 最后回到自己的其他屬性初始化粟耻。
<b>DefaultListableBeanFactory</b>初始化完畢以后接著是<b>BeanDefinitionReader</b>的初始化 選擇初始化的類實例是<b>XmlBeanDefinitionReader</b>初始化完畢后調(diào)用
reader.loadBeanDefinitions(new ClassPathResource("collectionMerging.xml", getClass()));
方法去加載collectionMerging.xml配置文件。
ClassPathResource類是Resource接口實現(xiàn)類的子類眉踱,如果沒有指定相對的類名挤忙,該類將從類的根路徑開始尋找某個resource,如果指定了相對的類名谈喳,則根據(jù)指定類的相對路徑來查找某個resource册烈。上面的還可以寫成:
reader.loadBeanDefinitions(new ClassPathResource(
"org/springframework/beans/factory/xml/collectionMerging.xml"));
<b>XmlBeanDefinitionReader</b>初始化的時候先找到根類<b>AbstractBeanDefinitionReader</b>
其構(gòu)造方法
protected AbstractBeanDefinitionReader(BeanDefinitionRegistry registry) {
Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
this.registry = registry;
// Determine ResourceLoader to use.
if (this.registry instanceof ResourceLoader) {
this.resourceLoader = (ResourceLoader) this.registry;
}
else {
this.resourceLoader = new PathMatchingResourcePatternResolver();
}
// Inherit Environment if possible
if (this.registry instanceof EnvironmentCapable) {
this.environment = ((EnvironmentCapable) this.registry).getEnvironment();
}
else {
this.environment = new StandardEnvironment();
}
}
在構(gòu)造方法中有instanceof ResourceLoader 判斷 顯然傳入的registry 是<b>DefaultListableBeanFactory</b>的實例 沒有實現(xiàn)ResourceLoader接口 所以走else 邏輯創(chuàng)建<b>PathMatchingResourcePatternResolver</b>類,在<b>PathMatchingResourcePatternResolver</b>初始化的過程中
public PathMatchingResourcePatternResolver() {
this.resourceLoader = new DefaultResourceLoader();
}
其創(chuàng)建的是<b>DefaultResourceLoader</b>類 該類實現(xiàn)了<b>ResourceLoader</b>接口婿禽。所以最后還是創(chuàng)建的是<b>ResourceLoader</b>的實例
public DefaultResourceLoader() {
this.classLoader = ClassUtils.getDefaultClassLoader();
}
這個構(gòu)造方法就是為了初始化private ClassLoader classLoader;
屬性用于load xml資源茄厘。
好了<b>Resource</b>參數(shù)構(gòu)造完畢后進入
@Override
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
return loadBeanDefinitions(new EncodedResource(resource));
}
方法矮冬,這里將<b>Resource</b>包裝成了<b>EncodedResource</b> 其實EncodedResource是對Resource的包裝 增加了encoding和charset屬性而已 這里都為null 相當于默認的null。接著進入方法
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
Assert.notNull(encodedResource, "EncodedResource must not be null");
if (logger.isInfoEnabled()) {
logger.info("Loading XML bean definitions from " + encodedResource.getResource());
}
Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
if (currentResources == null) {
currentResources = new HashSet<EncodedResource>(4);
this.resourcesCurrentlyBeingLoaded.set(currentResources);
}
if (!currentResources.add(encodedResource)) {
throw new BeanDefinitionStoreException(
"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
}
try {
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
finally {
inputStream.close();
}
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"IOException parsing XML document from " + encodedResource.getResource(), ex);
}
finally {
currentResources.remove(encodedResource);
if (currentResources.isEmpty()) {
this.resourcesCurrentlyBeingLoaded.remove();
}
}
}
這里會有一個比較經(jīng)典的同步緩存的方式
Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
this.resourcesCurrentlyBeingLoaded是
private final ThreadLocal<Set<EncodedResource>> resourcesCurrentlyBeingLoaded =
new NamedThreadLocal<Set<EncodedResource>>("XML bean definition resources currently being loaded");
可以看到采用了<b>ThreadLocal</b>的存貯方式次哈。在JDK1.2的時候就提供了<b>java.lang.ThreadLocal</b><b>ThreadLocal</b>為解決多線程問題提供了非常非常大幫助胎署,當使用ThreadLocal維護變量時,ThreadLocal為每個使用該變量的線程提供獨立的變量副本窑滞,所以每一個線程都可以獨立地改變自己的副本琼牧,而不會影響其它線程所對應(yīng)的副本。這樣就完美的解決了多線程的問題哀卫,不過對于計數(shù)的情況不適用 建議使用ATMOIC的變量巨坊。
在沒有取到的情況下 將新的<b>EncodedResource</b>加入ThreadLocal變量的緩存中。
接著從<b>EncodedResource</b>獲取輸入流<b>InputStream</b>構(gòu)造 <b>InputSource</b>,<b>InputSource</b>比較簡單的一個類,封裝了一下<b>InputStream</b>最后調(diào)用 doLoadBeanDefinitions
方法此改。部分源碼:
try {
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
finally {
inputStream.close();
}
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"IOException parsing XML document from " + encodedResource.getResource(), ex);
}
finally {
currentResources.remove(encodedResource);
if (currentResources.isEmpty()) {
this.resourcesCurrentlyBeingLoaded.remove();
}
}
這里的<b>finally</b>方法最后將<b>ThreadLocal</b>變量<b>resourcesCurrentlyBeingLoaded</b> remove掉了趾撵。看了好半天才看懂其中的奇妙之處:<b>這個方法本身存在多線程問題共啃,最簡單的做法就是同步語句塊占调,但是大家知道這會影響性能所以借助<b>ThreadLocal</b>變量做了一個過程同步 在使用完即remove掉</b>。
<b>doLoadBeanDefinitions</b>是java dom加載xml的過程移剪,大家都知道java解析xml的幾種方式究珊,基本的解析方式有兩種,一種叫SAX,另一種叫DOM纵苛,再細一點有
-
DOM生成和解析XML文檔
為 XML 文檔的已解析版本定義了一組接口剿涮。解析器讀入整個文檔,然后構(gòu)建一個駐留內(nèi)存的樹結(jié)構(gòu)攻人,然后代碼就可以使用 DOM 接口來操作這個樹結(jié)構(gòu)取试。
優(yōu)點:整個文檔樹在內(nèi)存中,便于操作怀吻;支持刪除想括、修改、重新排列等多種功能烙博;
缺點:將整個文檔調(diào)入內(nèi)存(包括無用的節(jié)點)瑟蜈,浪費時間和空間;使用場合:一旦解析了文檔還需多次訪問這些數(shù)據(jù)渣窜;硬件資源充足(內(nèi)存铺根、CPU)。 -
SAX生成和解析XML文檔
為解決DOM的問題乔宿,出現(xiàn)了SAX位迂。SAX ,事件驅(qū)動。當解析器發(fā)現(xiàn)元素開始掂林、元素結(jié)束臣缀、文本、文檔的開始或結(jié)束等時泻帮,發(fā)送事件精置,程序員編寫響應(yīng)這些事件的代碼,保存數(shù)據(jù)锣杂。
優(yōu)點:不用事先調(diào)入整個文檔脂倦,占用資源少;SAX解析器代碼比DOM解析器代碼小元莫,適于Applet赖阻,下載。
缺點:不是持久的踱蠢;事件過后火欧,若沒保存數(shù)據(jù),那么數(shù)據(jù)就丟了茎截;無狀態(tài)性苇侵;從事件中只能得到文本,但不知該文本屬于哪個元素稼虎;使用場合:Applet;只需XML文檔的少量內(nèi)容,很少回頭訪問招刨;機器內(nèi)存少霎俩; -
DOM4J生成和解析XML文檔
DOM4J 是一個非常非常優(yōu)秀的Java XML API,具有性能優(yōu)異沉眶、功能強大和極端易用使用的特點打却,同時它也是一個開放源代碼的軟件。如今你可以看到越來越多的 Java 軟件都在使用 DOM4J 來讀寫 XML谎倔,特別值得一提的是連 Sun 的 JAXM 也在用 DOM4J柳击。 -
JDOM生成和解析XML
為減少DOM、SAX的編碼量片习,出現(xiàn)了JDOM捌肴;優(yōu)點:20-80原則,極大減少了代碼量藕咏。使用場合:要實現(xiàn)的功能簡單状知,如解析、創(chuàng)建等孽查,但在底層饥悴,JDOM還是使用SAX(最常用)、DOM、Xanan文檔西设。
這里spring用java dom解析xml 我想原因就是需要加載的xml配置文件都不是大型的xml文件瓣铣,都是比較小的 所以使用java dom反而效果會好一些。
這里定義了<b>DocumentLoader</b>接口贷揽,提供了默認的實現(xiàn)類<b>DefaultDocumentLoader</b>其中的方法:
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
if (logger.isDebugEnabled()) {
logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]");
}
DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
return builder.parse(inputSource);
}
解析過程可以看oracle jom的api文檔
最后返回<b>Document</b>對象棠笑。
進入很重要的方法<b>registerBeanDefinitions</b>該方法將讀取的dom轉(zhuǎn)換成bean definition先進入方法
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
int countBefore = getRegistry().getBeanDefinitionCount();
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
return getRegistry().getBeanDefinitionCount() - countBefore;
}
第一行BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
創(chuàng)建了<b>BeanDefinitionDocumentReader</b>對象 該對象是將document中包含的所偶bean定義解析出來。
protected BeanDefinitionDocumentReader createBeanDefinitionDocumentReader() {
return BeanDefinitionDocumentReader.class.cast(BeanUtils.instantiateClass(this.documentReaderClass));
}
第二行int countBefore = getRegistry().getBeanDefinitionCount();
獲取到解析前的bean的數(shù)量
第三行documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
這里先根據(jù)傳入的<b>Resource</b>參數(shù)創(chuàng)建<b>XmlReaderContext</b>對象,官方給出的解釋是
Extension of {@link org.springframework.beans.factory.parsing.ReaderContext},
* specific to use with an {@link XmlBeanDefinitionReader}. Provides access to the
* {@link NamespaceHandlerResolver} configured in the {@link XmlBeanDefinitionReader}.
大概意思就是配合XmlBeanDefinitionReader使用的類擒滑。繼續(xù)往下走到:
@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
logger.debug("Loading bean definitions");
Element root = doc.getDocumentElement();
doRegisterBeanDefinitions(root);
}
到方法<b>doRegisterBeanDefinitions(root)</b> 進入:
protected void doRegisterBeanDefinitions(Element root) {
// Any nested <beans> elements will cause recursion in this method. In
// order to propagate and preserve <beans> default-* attributes correctly,
// keep track of the current (parent) delegate, which may be null. Create
// the new (child) delegate with a reference to the parent for fallback purposes,
// then ultimately reset this.delegate back to its original (parent) reference.
// this behavior emulates a stack of delegates without actually necessitating one.
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createDelegate(getReaderContext(), root, parent);
if (this.delegate.isDefaultNamespace(root)) {
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
if (StringUtils.hasText(profileSpec)) {
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
return;
}
}
}
preProcessXml(root);
parseBeanDefinitions(root, this.delegate);
postProcessXml(root);
this.delegate = parent;
}
先創(chuàng)建<b>BeanDefinitionParserDelegate</b>
開始解析的三部曲
- 先看<b>preProcessXml</b>
哈哈這真是個好方法 空實現(xiàn) 我喜歡腐晾,不過這算是解析的可擴展性 三部曲的步驟必需保證全 以免擴展需要 - 在看<b>parseBeanDefinitions</b>
這個方法就是最重要的啦,可以看到在遍歷節(jié)點丐一,其中方法<b>parseDefaultElement</b>protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) { if (delegate.isDefaultNamespace(root)) { NodeList nl = root.getChildNodes(); for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); if (node instanceof Element) { Element ele = (Element) node; if (delegate.isDefaultNamespace(ele)) { parseDefaultElement(ele, delegate); } else { delegate.parseCustomElement(ele); } } } } else { delegate.parseCustomElement(root); } }
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
importBeanDefinitionResource(ele);
}
else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
processAliasRegistration(ele);
}
else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
processBeanDefinition(ele, delegate);
}
else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
// recurse
doRegisterBeanDefinitions(ele);
}
}
```
分了好幾種情況去解析 看開頭是import藻糖、alias、bean库车、beans這四種開頭的 標簽去解析 巨柒,我們就找個bean開頭的跟蹤下情況 其它的標簽也是一樣的道理。
進入方法
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
// Register the final decorated instance.
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
}
catch (BeanDefinitionStoreException ex) {
getReaderContext().error("Failed to register bean definition with name '" +
bdHolder.getBeanName() + "'", ele, ex);
}
// Send registration event.
getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
}
其中<b>parseBeanDefinitionElement</b>解析后獲得<b>BeanDefinitionHolder</b>其中過程中比較中要的一個類<b>GenericBeanDefinition</b>每個bean標簽都是解析成這樣的beandefinition了柠衍。獲得這個bean后對應(yīng)解析各種
對應(yīng)的bean部件解析parseMetaElements洋满、parseLookupOverrideSubElements、parseReplacedMethodSubElements珍坊、parseConstructorArgElements牺勾、parsePropertyElements、parseQualifierElements 見名知意的方法沒有多大難度就是對應(yīng)的解析xml即可阵漏,解析完成 bean definition也就構(gòu)造完成了驻民。
- 最后看<b>postProcessXml</b>
哈哈這也真是個好方法 空實現(xiàn) 我喜歡,不過還是那句話這算是解析的可擴展性 三部曲的步驟必需保證全 以免擴展需要履怯。
BeanDefinition裝配過程基本完成 最后是裝配到了最先初始化的<b>DefaultListableBeanFactory</b>的各個map屬性中了回还。 好多東西很粗率的過了 后續(xù)補進。
不早了 go home!