上一章讲到refreshBeanFactory做了两件事情,一件是创建容器,一件是加载BeanDefinition,即loadBeanDefinitions()。加载BeanDefinition包含两个过程。本章讲第一点。
- 加载bean配置文件
- 解析bean配置文件
bean配置文件整体加载过程
加载bean配置文件的过程可以分为三个:
- 初始化XmlBeanDefinitionReader
- 解析资源文件路径
- 加载文件内容
loadBeanDefinitions()为抽象方法,由AbstractXmlApplicationContext实现。具体代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException { XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
beanDefinitionReader.setEnvironment(this.getEnvironment()); beanDefinitionReader.setResourceLoader(this); beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
initBeanDefinitionReader(beanDefinitionReader); loadBeanDefinitions(beanDefinitionReader); }
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); } }
|
真正实现加载BeanDefinition是由loadBeanDefinitions()来处理,这里getConfigResources()默认返回null,所以会走到第二个reader.loadBeanDefinitions()。第二个loadBeanDefinitions()在最后也会将configLocations转换成Resource对象,然后会调用到一个loadBeanDefinitions()。源码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException { ResourceLoader resourceLoader = getResourceLoader(); if (resourceLoader == null) { throw new BeanDefinitionStoreException( "Cannot import bean definitions from location [" + location + "]: no ResourceLoader available"); }
if (resourceLoader instanceof ResourcePatternResolver) { try { Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location); int loadCount = loadBeanDefinitions(resources); if (actualResources != null) { for (Resource resource : resources) { actualResources.add(resource); } } if (logger.isDebugEnabled()) { logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]"); } return loadCount; } catch (IOException ex) { throw new BeanDefinitionStoreException( "Could not resolve bean definition resource pattern [" + location + "]", ex); } } else { Resource resource = resourceLoader.getResource(location); int loadCount = loadBeanDefinitions(resource); if (actualResources != null) { actualResources.add(resource); } if (logger.isDebugEnabled()) { logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]"); } return loadCount; } }
|
小结一下。经过一系列的重载、委托,最后都会走到XmlBeanDefinitionReader.loadBeanDefinitions(Resource)。这也就是真正的加载方法,会在第三小节说明。而走到这个方法,所需要的Resource对象,则是通过resourceLoader.getResources()而来的。
解析资源文件路径
这里需要重提的是,在初始化XmlBeanDefinitionReader的时候,设置resourceLoader,传入的this,也就是ClassPathXmlApplicationContext对象。这里调用resourceLoader.getResources()则是父类(DefaultResourceLoader)所实现的方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| public Resource getResource(String location) { Assert.notNull(location, "Location must not be null");
for (ProtocolResolver protocolResolver : this.protocolResolvers) { Resource resource = protocolResolver.resolve(location, this); if (resource != null) { return resource; } }
if (location.startsWith("/")) { return getResourceByPath(location); } else if (location.startsWith(CLASSPATH_URL_PREFIX)) { return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader()); } else { try { URL url = new URL(location); return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url)); } catch (MalformedURLException ex) { return getResourceByPath(location); } } }
protected Resource getResourceByPath(String path) { return new ClassPathContextResource(path, getClassLoader()); }
|
这里主要区分location是不是为url方法传入。如果是直接new一个UrlResource对象,否则会走到getResourceByPath(),而该方法为protected,允许子类重写,例如FileSystemXmlApplicationContext就重写了该方法,处理文件路径的location。如下
1 2 3 4 5 6 7
| @Override protected Resource getResourceByPath(String path) { if (path.startsWith("/")) { path = path.substring(1); } return new FileSystemResource(path); }
|
加载文件内容
回到第一节留下的坑,XmlBeanDefinitionReader.loadBeanDefinitions(Resource)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException { return loadBeanDefinitions(new EncodedResource(resource)); } 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()); } ... 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(); } } }
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException { try { Document doc = doLoadDocument(inputSource, resource); return registerBeanDefinitions(doc, resource); } catch ....{ .... } }
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception { return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler, getValidationModeForResource(resource), isNamespaceAware()); }
|
到这里为止,又分为两条路,一条就是加载Document对象,一条就是继续真正解析。前者是DefaultDocumentLoader调用jdk的JAXP将xml转换为所需要的Document对象,写过xml的应该很了解,这一块代码就不深入分析了。且看后者:registerBeanDefinitions()
1 2 3 4 5 6 7 8 9
| 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; }
|
总结一下,从最开始的loadBeanDefinitions()方法说起
- 初始化XmlBeanDefinitionReader。
- 由XmlBeanDefinitionReader调用loadBeanDefinitions(),该方法分两步:解析文件路径、加载文件内容。
- 解析文件路径,分别处理传入的location方式,封装成所需的Resource对象。
- 加载文件内容,又分为两步。通过jdk的JAXP将xml转换成文档对象、将该文档对象进行解析。
第4点中的第二个步骤:registerBeanDefinitions()下章单独描述。