加载流程

针对以下的例子:

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());
}
}

大致的流程如下:

  1. 设置配置文件位置
    1. 读取并且校验配置文件
    2. 将XML文件转为doc文档
    3. 注册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 {
//初始化BeanFactory
super(parentBeanFactory);
//加载resource文件
this.reader.loadBeanDefinitions(resource);
}

跟踪super(parentBeanFactory);跟到最后,是

1
2
3
4
5
6
7
//忽略给定接口的自动装配,如果A中包含了B,在你注入A的时候,会自动注入B,下面是取消自动注入的代码	
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){
//读取检验模式(dtd/xsd...)
int validationMode = getValidationModeForResource(resource);
//将文件流转为Document格式,树状结构
Document doc = this.documentLoader.loadDocument(
inputSource, getEntityResolver(), this.errorHandler, validationMode,isNamespaceAware());
return registerBeanDefinitions(doc, resource);
}

isNamespaceAware()返回XML解析器是否应该支持XML命名空间。

getEntityResolver()中,在初始化reader的时候,ResourceLoader不为空了,执行的是

1
2
//entityResolver开始为空,resourceLoader为默认的ClassLoader
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();
//Environment()从环境中取,与profile字段有关,如果xml中有profile的环境设置,会改变这个值
documentReader.setEnvironment(getEnvironment());
//原有的BeanDefinitionRegistry 仓库
int countBefore = getRegistry().getBeanDefinitionCount();
//加载doc
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;
}

在这段代码中,preProcessXmlpostProcessXml两个方法都是空的,给后来的程序进行扩展,貌似在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包中实现的,大概的流程如下: