接下来分析BeanFactory bf = new XmlBeanFactory(new ClassPathResource("MyBean.xml")); 这行代码的实现细节。
通过XmlBeanFactory初始化时序图来看下上面代码具体是如何执行的。
首先调用ClassPathResource的构造函数将XML配置文件封装成一个Resource对象,然后根据Resource对象加载并注册BeanDefinition,最终得到了一个XmlBeanFactory对象。
从上面我们了解到,读取配置文件是通过ClassPathResource来完成的。那么ClassPathResource具体是如何读取配置文件的呢?
Spring通过Resource接口提供了读取配置文件的方法,而ClassPathResource实现了Resource接口。从ClassPathResource层次结构图中可以看到:
如何查看类的层次结构图? 传送门:eclipse-查看继承层次结构图/继承实现层次结构图
Resource接口抽象了所有Spring内部使用的底层侧缘:File、URL、ClassPath等等。对于不同的资源文件都有对应的实现:FileSystemResource、ClassPathResource、UrlResource、InputStreamResource、ByteArrayResource等等。相关结构层次图如图所示。
new ClassPathResource("MyBean.xml")到底做了哪些操作呢?
ClassPathResource.java代码片段
public ClassPathResource(String path) { this(path, (ClassLoader) null); } public ClassPathResource(String path, @Nullable ClassLoader classLoader) { Assert.notNull(path, "Path must not be null"); String pathToUse = StringUtils.cleanPath(path); if (pathToUse.startsWith("/")) { pathToUse = pathToUse.substring(1); } this.path = pathToUse; this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader()); }从代码中可以看到,程序创建了一个ClassPathResource对象,并将ClassPathResource对象的path属性指向了处理后的路径名。
了解了如何将配置文件封装为Resource类型的实例后,我们继续了解XmlBeanFactory的初始化。
看下XmlBeanFactory的构造方法
XmlBeanFactory.java代码片段
private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this); public XmlBeanFactory(Resource resource) throws BeansException { this(resource, null); } public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException { super(parentBeanFactory); this.reader.loadBeanDefinitions(resource); }跟踪到父类AbstractAutowireCapableBeanFactory查看super(parentBeanFactory)
AbstractAutowireCapableBeanFactory代码片段
/** * Create a new AbstractAutowireCapableBeanFactory. */ public AbstractAutowireCapableBeanFactory() { super(); ignoreDependencyInterface(BeanNameAware.class); ignoreDependencyInterface(BeanFactoryAware.class); ignoreDependencyInterface(BeanClassLoaderAware.class); }ignoreDependencyInterface方法的主要功能是忽略给定接口的自动装配功能。 这时相信大家都能看出来,this.reader.loadBeanDefinitions(resource);才是资源加载的真正实现。跟踪到XmlBeanDefinitionReader中看看具体实现。
XmlBeanDefinitionReader代码片段
/** * Load bean definitions from the specified XML file. */ @Override public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException { return loadBeanDefinitions(new EncodedResource(resource)); } /** * Load bean definitions from the specified XML file. */ 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<>(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(); } } }通过上面的代码,我们知道对Resource实例的处理过程如下:
封装资源文件。new EncodedResource(resource):使用EncodedResource对Resource实例进行封装。获取InputStream 。InputStream inputStream = encodedResource.getResource().getInputStream();。构造InputSource。InputSource inputSource = new InputSource(inputStream);。调用doLoadBeanDefinitions(inputSource, encodedResource.getResource());继续处理资源。到现在还没处理资源文件ㄒoㄒ~~
继续往下看看doLoadBeanDefinitions(inputSource, encodedResource.getResource());到底做了哪些操作。
/** * Actually load bean definitions from the specified XML file. ... */ protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException { try { Document doc = doLoadDocument(inputSource, resource); return registerBeanDefinitions(doc, resource); } catch (BeanDefinitionStoreException ex) { ... }看到这个方法的第一行注释,我就知道终于要真正加载XML文件了^_^。
阅读方法后,我们知道,这个方法做了这么几件事:
加载Xml文件,并封装成Document 实例。Document doc = doLoadDocument(inputSource, resource);根据Document 实例注册BeanDefinitions。return registerBeanDefinitions(doc, resource);这几个步骤支撑着整个Spring容器的实现基础,尤其是根据Document 实例注册BeanDefinitions,非常复杂。以后会详细讲解。
Spring IoC容器的初始化过程可以总结为以下几个步骤:
获取Resouce实例。加载XML配置文件,封装成Resouce实例。这时Resouce实例中已经有了配置文件的路径等信息。获取Document实例。通过Resource,读取XML配置文件,封装成Document实例。这时Document实例中已经有了配置文件中的标签。获取并注册BeanDefinition实例。解析Document实例中的标签,最终获得BeanDefinition实例。这时BeanDefinition实例中已经有了bean的id、name、alias、class等信息。注册过程把BeanDefinition向IOC容器进行注册,相当于将bean的name作为key,BeanDefinition作为value,放入一个map中。下一篇文章会先讲解如何获取Document实例。