序言
之前的已經分析過在不使用框架的情況下,類中各個部分的初始化或執行順序,后來我在開發中使用了Spring,發現初始化順序與之前的稍有不同,特別是其初始化以xml配置文檔作為驅動,xml中先定義生么類就試圖優先實例化這個類,搞得我有點糾結。現在來細細測試研究一下。
這次采用的測試代碼與之前的類似:有三個主線類B、C和D,其中D繼承C,C繼承B,這三個類中均包含static塊、普通初始化塊和無參的構造方法;有兩個輔助類E和F,B中包含E類和F類的成員變量,F類成員變量是static類型,E類的成員變量是普通類型;程序運行入口在A.java中。為了符合Spring的開發思路,增加了兩個接口IE和IF,E和F分別實現這兩個接口。
正文
IE.java
1 package chloe.spring; 2 3 public interface IE 4 { 5 public void funcOfE(); 6 }
IF.java
1 package chloe.spring; 2 3 public interface IF 4 { 5 public void funcOfF(); 6 }
E.java
1 package chloe.spring; 2 3 public class E implements IE 4 { 5 E() 6 { 7 System.out.println("執行E的構造函數"); 8 } 9 public void funcOfE() 10 { 11 System.out.println("執行E的函數"); 12 } 13 }
F.java
1 package chloe.spring; 2 3 public class F implements IF 4 { 5 F() 6 { 7 System.out.println("執行F的構造函數"); 8 } 9 public void funcOfF() 10 { 11 System.out.println("執行F的函數"); 12 } 13 }
B.java
1 package chloe.spring; 2 3 public class B 4 { 5 protected IE e;//實現的類和實例由Spring容器注入 6 protected static IF f;//f的實現的類和實例由Spring容器注入 7 protected String sb;//初始值由Spring容器依據配置文件給出 8 static 9 { 10 System.out.println("執行B類的static塊(B包含E類的成員變量,包含靜態F類成員變量)"); 11 //f.funcOfF();屬性f的注入在構造函數之后,更在此static初始化之后,所以這里不能調用f的函數,調用的話會報初始化異常 12 } 13 { 14 System.out.println("執行B實例的普通初始化塊"); 15 } 16 B() 17 { 18 System.out.println("執行B類的構造函數(B包含E類的成員變量,包含靜態F類成員變量)"); 19 //e.funcOfE();屬性e的注入在此構造函數之后,所以這里不能調用f的函數,調用的話會報初始化異常 20 } 21 22 public String getSb() 23 { 24 return sb; 25 } 26 public void setSb(String sb1) 27 { 28 this.sb=sb1; 29 System.out.println("已給B的成員變量sb賦初值:"+sb1); 30 } 31 32 public void setE(IE e1) 33 { 34 this.e = e1; 35 System.out.println("已將E類實例注入B的引用成員變量e"); 36 } 37 38 public void setF(IF f1)//此方法不能加static修飾符,否則Spring注入時報NotWritablePropertyException 39 { 40 f = f1; 41 System.out.println("已將F類實例注入B的static引用成員變量f"); 42 } 43 }
C.java
1 package chloe.spring; 2 3 public class C extends B 4 { 5 static 6 { 7 System.out.println("執行C的static塊(C繼承B)"); 8 } 9 { 10 System.out.println("執行C的普通初始化塊"); 11 } 12 C() 13 { 14 System.out.println("執行C的構造函數(C繼承B)"); 15 } 16 }
D.java
1 package chloe.spring; 2 3 public class D extends C 4 { 5 6 protected static String sd;//由Spring容器依據配置文件賦初始值 7 8 static 9 { 10 System.out.println("執行D的static塊(D繼承C)"); 11 12 } 13 { 14 System.out.println("執行D實例的普通初始化塊"); 15 } 16 protected String sd1;//由Spring容器依據配置文件賦初始值 17 D() 18 { 19 System.out.println("執行D的構造函數(D繼承C);父類B的實例成員變量sb的值為:"+sb+";本類D的static成員變量sd的值為:"+sd+";本類D的實例成員變量sd1的值是:"+sd1); 20 } 21 22 public void methodOfD() 23 { 24 System.out.println("運行D中的方法methodOfD"); 25 } 26 27 public void setSd(String sdtmp) 28 { 29 sd=sdtmp; 30 System.out.println("已初始化D的static成員變量sd"); 31 } 32 33 public void setSd1(String sd1tmp) 34 { 35 sd1=sd1tmp; 36 System.out.println("已初始化D的實例成員變量sd1"); 37 38 } 39 }
A.java
1 package chloe.spring; 2 3 import org.springframework.context.ApplicationContext; 4 import org.springframework.context.support.ClassPathXmlApplicationContext; 5 6 public class A 7 { 8 public static void main(String[] args) 9 { 10 System.out.println("====運行A中的main函數,准備載入xml配置文件===="); 11 ApplicationContext appContext=new ClassPathXmlApplicationContext("applicationContext1.xml"); 12 System.out.println("====xml配置文件載入完畢,准備獲得bean D===="); 13 D d=(D)appContext.getBean("beand"); 14 System.out.println("====已經獲取bean D,准備運行D中的方法methodOfD===="); 15 d.methodOfD(); 16 17 } 18 }
配置文件applicationContext1.xml內容:
1 <?xml version="1.0" encoding="UTF-8"?><!--Spring框架需要使用的bean定義文件--> 2 <beans 3 xmlns="http://www.springframework.org/schema/beans" 4 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 5 xmlns:p="http://www.springframework.org/schema/p" 6 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"> 7 8 <bean id="beane" class="chloe.spring.E"></bean> 9 10 <bean id="beanf" class="chloe.spring.F"></bean> 11 12 <bean id ="beanb" class="chloe.spring.B"> 13 <property name="e" ref="beane"></property> <!--設置bean的成員變量的實現類--> 14 <property name="f" ref="beanf"></property> 15 <property name="sb" value="初始sb"></property> 16 </bean> 17 18 <bean id ="beanc" class="chloe.spring.C"></bean> 19 20 <bean id="beand" class="chloe.spring.D"> 21 <property name="sd1" value="初始sd1"></property> 22 <property name="sd" value="初始sd"></property> 23 </bean> 24 25 </beans>
運行A.java后輸出如下結果:
1 ====運行A中的main函數,准備載入xml配置文件==== 2 2012-10-28 19:06:40 org.springframework.context.support.AbstractApplicationContext prepareRefresh 3 信息: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@a01335: startup date [Sun Oct 28 19:06:40 CST 2012]; root of context hierarchy 4 2012-10-28 19:06:40 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions 5 信息: Loading XML bean definitions from class path resource [applicationContext1.xml] 6 2012-10-28 19:06:40 org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons 7 信息: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@1fc2fb: defining beans [beane,beanf,beanb,beanc,beand]; root of factory hierarchy 8 執行E的構造函數 9 執行F的構造函數 10 執行B類的static塊(B包含E類的成員變量,包含靜態F類成員變量) 11 執行B實例的普通初始化塊 12 執行B類的構造函數(B包含E類的成員變量,包含靜態F類成員變量) 13 已將E類實例注入B的引用成員變量e 14 已將F類實例注入B的static引用成員變量f 15 已給B的成員變量sb賦初值:初始sb 16 執行C的static塊(C繼承B) 17 執行B實例的普通初始化塊 18 執行B類的構造函數(B包含E類的成員變量,包含靜態F類成員變量) 19 執行C的普通初始化塊 20 執行C的構造函數(C繼承B) 21 執行D的static塊(D繼承C) 22 執行B實例的普通初始化塊 23 執行B類的構造函數(B包含E類的成員變量,包含靜態F類成員變量) 24 執行C的普通初始化塊 25 執行C的構造函數(C繼承B) 26 執行D實例的普通初始化塊 27 執行D的構造函數(D繼承C);父類B的實例成員變量sb的值為:null;本類D的static成員變量sd的值為:null;本類D的實例成員變量sd1的值是:null 28 已初始化D的實例成員變量sd1 29 已初始化D的static成員變量sd 30 ====xml配置文件載入完畢,准備獲得bean D==== 31 ====已經獲取bean D,准備運行D中的方法methodOfD==== 32 運行D中的方法methodOfD
由輸出結果可知,在main函數中執行ApplicationContext appContext=new ClassPathXmlApplicationContext("applicationContext1.xml")時,Spring便開始了對類和實例的初始化,初始化順序與xml配置文件中bean的定義順序一致,且一個類的構造函數運行時機總是早於其成員變量的初始化時機,不管成員變量是否是static類型,這一點與不用Spring框架的中的順序不同。此時如果把xml文件中B的定義的放在E的前面,則在執行完B的構造函數后,注入其屬性之前實例化E和F,這時的輸出結果為:
1 ====運行A中的main函數,准備載入xml配置文件==== 2 2012-10-28 20:06:56 org.springframework.context.support.AbstractApplicationContext prepareRefresh 3 信息: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@a01335: startup date [Sun Oct 28 20:06:56 CST 2012]; root of context hierarchy 4 2012-10-28 20:06:56 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions 5 信息: Loading XML bean definitions from class path resource [applicationContext1.xml] 6 2012-10-28 20:06:56 org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons 7 信息: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@1fc2fb: defining beans [beanb,beane,beanf,beanc,beand]; root of factory hierarchy 8 執行B類的static塊(B包含E類的成員變量,包含靜態F類成員變量) 9 執行B實例的普通初始化塊 10 執行B類的構造函數(B包含E類的成員變量,包含靜態F類成員變量) 11 執行E的構造函數 12 執行F的構造函數 13 已將E類實例注入B的引用成員變量e 14 已將F類實例注入B的static引用成員變量f 15 已給B的成員變量sb賦初值:初始sb 16 執行C的static塊(C繼承B) 17 執行B實例的普通初始化塊 18 執行B類的構造函數(B包含E類的成員變量,包含靜態F類成員變量) 19 執行C的普通初始化塊 20 執行C的構造函數(C繼承B) 21 執行D的static塊(D繼承C) 22 執行B實例的普通初始化塊 23 執行B類的構造函數(B包含E類的成員變量,包含靜態F類成員變量) 24 執行C的普通初始化塊 25 執行C的構造函數(C繼承B) 26 執行D實例的普通初始化塊 27 執行D的構造函數(D繼承C);父類B的實例成員變量sb的值為:null;本類D的static成員變量sd的值為:null;本類D的實例成員變量sd1的值是:null 28 已初始化D的實例成員變量sd1 29 已初始化D的static成員變量sd 30 ====xml配置文件載入完畢,准備獲得bean D==== 31 ====已經獲取bean D,准備運行D中的方法methodOfD==== 32 運行D中的方法methodOfD
我們來繼續分析更改xml配置文件前的輸出結果,即第一個輸出結果。由第10到12行以及第13到15行可知,無繼承關系的類B的初始化順序為:static初始化塊 --> 普通初始化塊 --> 構造函數 --> 成員變量實例化和初始化 。另外由第16到20行以及第21到27行可知,如果某個類繼承了父類,而父類static塊之前已執行,則初始化順序為:子類static初始化塊-->父類普通初始化塊-->父類構造函數,如果父類還有父類,則先運行父類的父類的初始化塊,這是一個遞歸的過程。
如果初始化子類時,父類的還未初始化,即父類的static塊還沒有執行過,那順序應該是怎樣的呢?我們將xml配置文件中D類的定義移到B和C的定義之前,再運行,得到如下輸出:
1 ====運行A中的main函數,准備載入xml配置文件==== 2 2012-10-28 20:55:39 org.springframework.context.support.AbstractApplicationContext prepareRefresh 3 信息: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@a01335: startup date [Sun Oct 28 20:55:39 CST 2012]; root of context hierarchy 4 2012-10-28 20:55:40 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions 5 信息: Loading XML bean definitions from class path resource [applicationContext1.xml] 6 2012-10-28 20:55:40 org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons 7 信息: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@1fc2fb: defining beans [beane,beanf,beand,beanb,beanc]; root of factory hierarchy 8 執行E的構造函數 9 執行F的構造函數 10 執行B類的static塊(B包含E類的成員變量,包含靜態F類成員變量) 11 執行C的static塊(C繼承B) 12 執行D的static塊(D繼承C) 13 執行B實例的普通初始化塊 14 執行B類的構造函數(B包含E類的成員變量,包含靜態F類成員變量) 15 執行C的普通初始化塊 16 執行C的構造函數(C繼承B) 17 執行D實例的普通初始化塊 18 執行D的構造函數(D繼承C);父類B的實例成員變量sb的值為:null;本類D的static成員變量sd的值為:null;本類D的實例成員變量sd1的值是:null 19 已初始化D的實例成員變量sd1 20 已初始化D的static成員變量sd 21 執行B實例的普通初始化塊 22 執行B類的構造函數(B包含E類的成員變量,包含靜態F類成員變量) 23 已將E類實例注入B的引用成員變量e 24 已將F類實例注入B的static引用成員變量f 25 已給B的成員變量sb賦初值:初始sb 26 執行B實例的普通初始化塊 27 執行B類的構造函數(B包含E類的成員變量,包含靜態F類成員變量) 28 執行C的普通初始化塊 29 執行C的構造函數(C繼承B) 30 ====xml配置文件載入完畢,准備獲得bean D==== 31 ====已經獲取bean D,准備運行D中的方法methodOfD==== 32 運行D中的方法methodOfD
可以看出,實例化E和F之后,准備初始化D,而此時D的父類C還未初始化和實例化(被Spring容器載入),於是要先初始化C,同樣,C的父類B也沒有初始化,於是先初始化B,這樣就出現了第10到12行的結果。另外static初始化塊的執行要早於普通初始化塊和構造函數,因此執行完D的static塊后才執行其父類的普通初始化塊和構造函數。另外還可以看出,如果父類之前已經實例化(執行過普通初始化塊了構造函數),之后又有該父類的子類需要初始化,則該父類的普通初始化塊和構造函數又要執行一遍,但該父類的static塊不會再執行,我想這是由於父類的構造函數也是子類構造函數的一部分,只是在子類的構造函數中省略了,實際創建子類的實例時,也要運行父類的構造函數才能完整地實例化子類。
由輸出結果的最后三行可知,運行D d=(D)appContext.getBean("beand")時,並不會再次初始化和實例化D,好像只是將變量d指向了Spring容器中已經存在的D實例。所以我推測,使用Spring時,在配置文件中定義的類都是在運行new ClassPathXmlApplicationContext("applicationContext1.xml")時一起初始化和實例化的,並且完成了實例之間的連接,之后getBean只是引用這些已經存在的實例,這樣就比較節約內存。因為傳統的new方法每調用一次就會在內存中新建一個實例,這樣同一類型的實例會有很多個副本。
總結
在Spring框架下,類的初始化順序優先級如下:
1. 按照xml配置文件中類的定義順序加載類並創建類的實例。
2. 假設當前要加載X類,則先運行X的static塊。如果此時X的父類Y還沒有加載,則先查找配置文件來加載Y,運行Y的static塊,這樣一直遞歸下去。當X的所有先驅類的static塊運行完畢后,再類似遞歸地實例化X的先驅類(運行先驅類的普通初始化塊和構造函數),完成所有先驅類的實例化后才運行X的普通初始化塊和構造函數。
3. 執行完X的構造函數之后,開始根據xml配置文件給X的成員變量賦初值。如果X的引用型成員變量所屬的類還未加載,則先查找配置文件來加載(初始化、實例化)該成員變量所屬的類,加載完畢后將其注入給X的成員變量,完成成員變量的初始化。
在前一篇文章(http://www.cnblogs.com/zhouqing/archive/2012/10/26/2741916.html)中,如果在定義成員變量的同時對該成員變量實例化,則可以在構造函數中調用成員變量實例的函數,但是使用了Spring后,如果成員變量交給Spring容器實例化,則在構造函數中不能調用該成員變量的實例方法,因為在構造函數完成之前,成員變量還沒有實例化,結果就會報空指針異常。在使用Spring時,到底將哪些類交給Spring容器管理,哪些類在代碼中管理,不能一概而論,要根據功能需要來考慮。另外如果多次運行new ClassPathXmlApplicationContext("applicationContext1.xml"),則會再次重新載入和實例化一遍,這樣很木有必要,而且容易導致數據不一致,因此一般將new ClassPathXmlApplicationContext的結果作為某個類的static全局變量,隨時被其他的類使用,而包含這個全局變量的類就不要托管給Spring了喲~