序言
之前的已经分析过在不使用框架的情况下,类中各个部分的初始化或执行顺序,后来我在开发中使用了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了哟~