加载流程
针对以下的例子:
1 2 3 4 5 6 7 8
| public class AppTest { @Test public void test() { BeanFactory bf = new XmlBeanFactory( new ClassPathResource("spring-config.xml")); MyTestBean myTestBean = (MyTestBean) bf.getBean("myTestBean"); Assert.assertEquals("testName",myTestBean.getName()); } }
|
大致的流程如下:
- 设置配置文件位置
- 读取并且校验配置文件
- 将XML文件转为doc文档
- 注册doc中的Bean到
第一句话可以拆分为两句:
1 2
| Resource resource=new ClassPathResource("spring-config.xml"); BeanFactory bf = new XmlBeanFactory(resource);
|
Resource是用于从实际类型的底层资源(例如文件或类路径资源)中抽象的资源描述符的接口。ClassPathResource在加载时,会调用系统的加载classLoader类,加载依赖的class。
1 2 3 4 5 6 7 8 9
| public ClassPathResource(String path, 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()); }
|
拿到resource之后,进入XmlBeanFactory。构造器里有两个主要的方法
1 2 3 4 5 6
| public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException { super(parentBeanFactory); this.reader.loadBeanDefinitions(resource); }
|
跟踪super(parentBeanFactory)
;跟到最后,是
1 2 3 4 5 6 7
| public AbstractAutowireCapableBeanFactory() { super(); ignoreDependencyInterface(BeanNameAware.class); ignoreDependencyInterface(BeanFactoryAware.class); ignoreDependencyInterface(BeanClassLoaderAware.class); }
|
this.reader.loadBeanDefinitions(resource);
在调用这个方法的时候,会将resource
自动封装为EncodedResource
,在这之前,this.reader
已经初始化 了,这个初始化为后面的获取实体检验加载器,提供了ResourceLoader
。
1
| private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);
|
后面在XmlBeanDefinitionReader
加载代码为
1 2 3 4 5 6
| InputStream inputStream = encodedResource.getResource().getInputStream(); InputSource inputSource = new InputSource(inputStream); if (encodedResource.getEncoding() != null) { inputSource.setEncoding(encodedResource.getEncoding()); } return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
|
将文件转为输入流,InputSource不仅包含了InputSource中的内容和编码。继续向下
1 2 3 4 5 6 7 8
| int doLoadBeanDefinitions(InputSource inputSource, Resource resource){ int validationMode = getValidationModeForResource(resource); Document doc = this.documentLoader.loadDocument( inputSource, getEntityResolver(), this.errorHandler, validationMode,isNamespaceAware()); return registerBeanDefinitions(doc, resource); }
|
isNamespaceAware()
返回XML解析器是否应该支持XML命名空间。
在getEntityResolver()
中,在初始化reader
的时候,ResourceLoader
不为空了,执行的是
1 2
| this.entityResolver = new ResourceEntityResolver(resourceLoader);
|
ResourceEntityResolver类图如下:
ResourceEntityResolver不是spring提供的包,没有继续跟下去了。接着往下看:
1 2 3 4 5
| ... private DocumentLoader documentLoader = new DefaultDocumentLoader(); ... Document doc = this.documentLoader.loadDocument( inputSource, getEntityResolver(), this.errorHandler, validationMode, isNamespaceAware());
|
1 2 3 4 5 6 7 8 9 10
| public Document loadDocument(InputSource inputSource, EntityResolver entityResolver, ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware); if (logger.isDebugEnabled()) { logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]"); } DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler); return builder.parse(inputSource); }
|
具体的如何转换可以继续跟,留着以后看,这里应该还包含了从远程下载dtd,xsd约束文件的代码,这个地方先跳过去。回到XmlBeanDefinitionReader.java
中的registerBeanDefinitions(doc, resource)
中来,
1 2 3 4 5 6 7 8 9 10
| public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); documentReader.setEnvironment(getEnvironment()); int countBefore = getRegistry().getBeanDefinitionCount(); documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); return getRegistry().getBeanDefinitionCount() - countBefore; }
|
createReaderContext(resource)
方法获取在bean读取过程中传递的上下文。
registerBeanDefinitions(doc, createReaderContext(resource))
跟踪下去,一直到doRegisterBeanDefinitions
方法,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| protected void doRegisterBeanDefinitions(Element root) { String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE); if (StringUtils.hasText(profileSpec)) { String[] specifiedProfiles = StringUtils.tokenizeToStringArray( profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS); if (!getEnvironment().acceptsProfiles(specifiedProfiles)) { return; } } BeanDefinitionParserDelegate parent = this.delegate; this.delegate = createDelegate(this.readerContext, root, parent); preProcessXml(root); parseBeanDefinitions(root, this.delegate); postProcessXml(root); this.delegate = parent; }
|
在这段代码中,preProcessXml
和postProcessXml
两个方法都是空的,给后来的程序进行扩展,貌似在spring-context中,就有对其扩展的例子
一些补充
SAX application: JAVA 解析 XML 通常有两种方式:DOM 和SAX。DOM(文档对象模型)是W3C标准,提供了标准的解析方式,但其解析效率一直不尽如人意,这是因为DOM解析XML文档时,把所有内容一次性的装载入内存,并构建一个驻留在内存中的树状结构(节点树)。如果需要解析的XML文档过大,或者我们只对该文档中的一部分感兴趣,这样就会引起性能问题。
EntityResolver: 如果SAX应用程序实现自定义处理外部实体,则必须实现此接口。Sax 解析xml时需要查找对应的dtd定义。EntityResolver申明了一个如何查询dtd的方法。
对于不同的验证模式,spring使用了不同的解析器进行解析。 例如在DelegatingEntityResolver中,加载dtd类型的BeansDtdResolver。加载xsd类型的PluggableEntityResolver,两个的解析模式不一样。
在关于Document是怎么解析这个resolveEntity的具体流程,是在com.sun.org.apache包中实现的,大概的流程如下: