Spring源码学习之IOC(1)
IOC初始化
最近再看Spring技术内幕。发现里面写的挺不错,就是个人觉得有点乱。这里按照程序执行顺序重新整理一遍,方便理解。
再次声明:仅是个人意见!!!
Figure1
Figure2
Figure3
Figure4
Figure5
以FileSystemXmlApplicationContext为例,初始化的三个部分:
1. 配置文件资源(Resource interface)的定位
2. 配置文件资源的载入(载入Document对象并且解析成BeanDefinition的格式)
3. BeanDefinition在IOC容器中的注册(这一部分包括将BeanDefinition注册入容器)
代码1:
ClassPathResource res = new ClassPathResource(“beans.xml”);DefaultListableBeanFactory factory = new DefaultListableBeanFactory();XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);Reader.loadBeanDefinitions(res);
上面代码是一个简单的初始化一个IOC容器。其中DefaultListableBeanFactory是一个BeanFactory的实现类。其只是个IOC容器,而ApplicationContext是对上述进行了一个封装。
首先BeanDefinition是Xml中Bean标签的抽象,FileSystemXmlApplicationContext底层还是使用的DefaultListableBeanFactory。由Figure3可以看出AbstractApplicationContext实现了ResourceLoader,所以资源的定位这一部分是由AbstractApplicationContext完成的,而FileSystemXmlApplicationContext重写了getResourceByPath(String path)方法,所以具体定位是由FileSystemXmlApplicationContext完成的
第二部分这里使用了两个类DefaultBeanDefinitionDocumentReader和BeanDefinitionParserDelegate,前者是读取BeanDefinition的信息,后者是解析BeanDefinition的信息完成对Xml中Bean标签的抽象
由figure5可以看出DefaultListableBeanFactory继承了BeanDefinitionRegistry。所以第三部分BeanDefinition是由DefaultListableBeanFactory完成的。
1.启动初始化
在FileSystemXmlApplicationContext的构造方法FileSystemXmlApplicationContext(String[] configLocationions,boolean refresh,Application parent)中调用了方法refresh(),该方法定义了整个初始化IOC的流程,也是IOC初始化的入口.具体实现在其父类AbstractApplicationContext中实现。改方法中有句代码如ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();这句主要就是刷新(刷新的过程其实就是销毁所有的bean再关闭BeanFactory然后再新建)当前的bean facotry也就是IOC容器并且返回,当然如当前没有IOC那就新建一个返回。在obtainFreshBeanFactory()方法中调用refreshBeanFactory()方法,该方法是由其子类AbstractRefreshableApplicatioContext实现的。改方法中就会判断是否已经存在了IOC容器。判断完之后紧接着就是创建一个beanFactory代码如下:
代码2
DefaultListableBeanFactory beanFactory = createBeanFactory();beanFactory.setSerializationId(getId());customizeBeanFactory(beanFactory);loadBeanDefinitions(beanFactory);synchronized (this.beanFactoryMonitor) {this.beanFactory = beanFactory;}从这里可以看出其底层依旧是使用DefaultListableBeanFactory.从这里主要开始看loadBeanDefinitions这个方法了。这个loadBeanDefinitions(DefaultListableBeanFactory beanFactory)方法是个抽象方法具体实现需要在其子类AbstractXmlApplicationContext中查找代码如下:
代码3:
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException { XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory); beanDefinitionReader.setResourceLoader(this); beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this)); initBeanDefinitionReader(beanDefinitionReader); loadBeanDefinitions(beanDefinitionReader);}这段代码看起来和之前的代码一有相似之处了。但还有不同之处,由于代码1中我们直接定义了一个Resource换而言之就是我们手动的定位了Resource。而这里我们能获取Resource的唯一途径就在我们构造FileSystemXmlApplication的时候传入的configLocatio-ns这个文件路径数组。所以我们需要使用setResourceLoader(ResourceLoader resourceLoader)这个方法将一个Resource加载器给当前的BeanDefinition的读取器XmlBeanDefinitionReader,让它内部能够自动完成Resource的定位和创建。然后就是将这个XmlBeanDefinitionReader作为参数传递给loadBeanDefinitions的一个重载方法loadBeanDefinitions(XmlBeanDefinitionReader reader)中。在这个loadBeanDefinitions的方法中。从这里开始将进入配置文件资源Resource定位的部分了。
2.配置文件资源Resource定位
上面讲到进入重载方法loadBeanDefinitions(XmlBeanDefinitionReader reader)。在这个方法中代码如下:
代码4:
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {Resource[] configResources = getConfigResources();if (configResources != null) {reader.loadBeanDefinitions(configResources);}String[] configLocations = getConfigLocations();if (configLocations != null) {reader.loadBeanDefinitions(configLocations);}}从代码能够发现,它在这里不仅会检测配置文件路径configLocations还会检测已经定位好的Resource通过getConfigResources()方法。但是在该文件的源代码中的getConfigResources()方法中只有个return null的语句。所以if (configResources != null)始终不通过,换言之。这里只检测配置文件路径,这里可能是为了后续的拓展。至于getConfigLocations(),改方法继承自其父类AbstractRefreshableConfigApplicationContext中的。具体实现语句就是判断configLocations是否为null,如果不是null返回当前值,是null返回默认值。由于之前构造FileSystemXmlApplicationContext的时候已经将configLoactions传入进去了。所以这里直接进入if语句。调用loadBeanDef-initions(String[] locations)方法,该是继承自XmlBeanDefinitionReader父类AbstractBeanDefinitionReader中的。具体代码如下:
代码5:
public int loadBeanDefinitions(String[] locations) throws BeanDefinitionStoreException {Assert.notNull(locations, "Location array must not be null");int counter = 0;for (String location : locations) {counter += loadBeanDefinitions(location);}return counter;}代码6:
public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {ResourceLoader resourceLoader = getResourceLoader();if (resourceLoader == null) {throw new BeanDefinitionStoreException(…);}if (resourceLoader instanceof ResourcePatternResolver) {…}else {Resource resource = resourceLoader.getResource(location);int loadCount = loadBeanDefinitions(resource);if (actualResources != null) {actualResources.add(resource);}…return loadCount;}}这里首先会判断ResourceLoader是否为空。由于之前我们在AbstractXmlApplicationContext里填入了该值beanDefinitionReader.setResourceLoader(this)。所以继续执行下面的if。由于我们填入的ResourceLoader并不是ResourcePatternResolver类型所以进入else中。else中执行了Resource resource = resourceLoader.getRes-ource(location).由于之前在AbstractXmlApplicationContext里调用了beanDefinitionReader.setResourceLoader(this)。而AbstractXmlApplicationContext是DefaultResourceLoader的子类。DefaultResourceLoader又是ResouceLoader的实现类,所以这里应该调用的是DefaultResourceLoader的getResouce(String location)方法。具体代码如下:
代码7:
public Resource getResource(String location) {Assert.notNull(location, "Location must not be null");if (location.startsWith(CLASSPATH_URL_PREFIX)) {return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());}else {try {URL url = new URL(location);return new UrlResource(url);}catch (MalformedURLException ex) {return getResourceByPath(location);}}}由于这里并不是URL方式表示的配置文件路径。所以直接跳入cathc语句块。执行getResourceByPath(location)。由于这个getResource方法是其子类FileSystemXmlApplicationContext调用的,所以调用的是FileSystemXmlApplicationContext重写的getResourceByPath(location)并且返回一个Resource方法代码如下:
代码8:
protected Resource getResourceByPath(String path) {if (path != null && path.startsWith("/")) {path = path.substring(1);}return new FileSystemResource(path);}由于至此Resource的定位部分完成了。
3.BeanDefinition的加载和解析
之前已经介绍如何定位Resource.也就是已经将配置文件的IO封装成Resource并且返回了。让我们继续回到代码6中AbstractBeanDefinitionReader这个类。在获取了Resource之后继续执行了loadBeanDefinitions (Resource resource).这里的resource正是刚才获取的。执行的loadBeanDefinitions是BeanDefinitionReader的接口方法。具体的实现实在其实现类AbstractBeanDefinitionReader的子类中。当然loadBeanDefinitions(Resource resource)这个方法也不是直接使用resource去解析。而是将resource进一步封装成EncodeResource并将其作为参数传递给loadBeanDefinitions(EncodeResource encodeResource)这个重载的方法进行处理.主要代码如下:
代码9:
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {Assert.notNull(…);if (logger.isInfoEnabled()) {…}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(…);}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(…);}finally {currentResources.remove(encodedResource);if (currentResources.isEmpty()) {this.resourcesCurrentlyBeingLoaded.set(null);}}}如代码所示在这里会对Resource处理,将IO流从Resource中读取出来,然后封装成InputSource并将其传入doLoadBeanDefinitions。doLoadBeanDefinitions方法依旧是XmlBeanDefinitionReader中的方法.然后doLoadBeanDefinitions将根据inputSource产生出Document对象这个对象就是Xml文件的一个抽象。并且将这个Document传入XmlBeanDefinitionReader中的registerBeanDefinition方法中去代码如下
代码10:
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException {try {int validationMode = getValidationModeForResource(resource); Document doc = this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler, validationMode, isNamespaceAware());return registerBeanDefinitions(doc, resource); } … }配置文件加载完成,现在开始解析部分,将Xml形式解析成BeanDefinition这种形式。首先进入XmlBeanDefinitionReader这个的registerBeandDefinitions这个方法。这个方法内部会新建立一个BeanDefinitionDocumentReader这个对象。该对象是专门用于读取文档的一个API。然后再将上面所说的Document对象作为参数传递给BeanDefinitionDocumentReader对象的registerBeanDefinitions(Document doc, XmlReaderContext readerContext)方法中并且调用,该方法中将完成解析的过程代码如下:
代码11:
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { SPI.BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();int countBefore = getRegistry().getBeanDefinitionCount();documentReader.registerBeanDefinitions(doc, createReaderContext(resource));return getRegistry().getBeanDefinitionCount() - countBefore;}这里调用的registerBeanDefinitions这个方法是个接口方法,具体实现在其子类DefaultBeanDefinitionDocumentReader中实现。该子类实现的是仅仅是对Document的读取功能,具体解析的功能还需要一个类registerBeanDefinitions.所以在registerBeanDefinitions方法中会产生个registerBeanDefinitions类帮助解析。具体如何解析这里不做说明。自己也能看懂
至此,BeanDefinition的加载和解析也完成了。接下来就是如何注册了。
4.BeanDefinition在IOC容器中的注册
经过之前的分析已经能够了解到注册这部分的功能的实现是通过DefaultListableBeanFactory这个Class完成的。而实际上BeanDefinition的注册这一功能的实现是通过一个名为beanDefinitionMap的HashMap这样一个数据结构来维护的。那么具体是从说明地方开始调用和触发注册部分的功能的呢。通过Eclipse发现,如图所示

Figure6
当在DefaultBeanDefinitionDocumentReader调用processBeanDefinition的时候。当我们解析完BeanDefinition之后通过BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry())。这么一条语句调用的。最后会获取我们bean的Name和BeanDefinition对象然后作为参数传入registerBeanDefinition这个方法完成注册。注册代码如下:
代码12:
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException {Assert.hasText(beanName, "'beanName' must not be empty");Assert.notNull(beanDefinition, "BeanDefinition must not be null");if (beanDefinition instanceof AbstractBeanDefinition) {try {((AbstractBeanDefinition) beanDefinition).validate();}catch (BeanDefinitionValidationException ex) {throw new BeanDefinitionStoreException(…);}}synchronized (this.beanDefinitionMap) {Object oldBeanDefinition = this.beanDefinitionMap.get(beanName);if (oldBeanDefinition != null) {if (!this.allowBeanDefinitionOverriding) {throw new BeanDefinitionStoreException(…);}else {if (this.logger.isInfoEnabled()) {this.logger.info(…);}}}else {this.beanDefinitionNames.add(beanName);this.frozenBeanDefinitionNames = null;}this.beanDefinitionMap.put(beanName, beanDefinition);resetBeanDefinition(beanName);}}1 楼 milan_1982 2012-04-02 哥们,写的不错,
我也看了Spring技术内幕,书是不错,就是跳跃太大,很多代码省略了中间环节,直接最后一步,我一开始还以为自己眼花了。