一、案例
public class TestDemo {
public static void main(String[] args) {
// 将 xml 转换成 Resource
Resource resource = new ClassPathResource("spring-dev.xml");
// 这个类提供加载及注册 bean 的功能
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
// 这个类提供验证、解析 xml 的功能
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
// 验证及解析 xml、注册及获取 bean
reader.loadBeanDefinitions(resource);
// 获取bean
TestClass testClass = (TestClass)factory.getBean("testClass");
}
}
二、调用流程解析
1.将 XML
转化成 Resource
Spring 实现了对配置文件的封装,直接用 new ClassPathResource("spring-dev.xml")
即可
2.初始化 XmlBeanDefinitionReader
由 XmlBeanDefinitionReader
实现对资源文件(Resource
)进行读取和注册
3.验证及解析 xml、注册及获取 bean
调用流程如下:
- 1.
XmlBeanDefinitionReader#loadBeanDefinitions(Resource resource)
- 2.
XmlBeanDefinitionReader#loadBeanDefinitions(EncodedResource encodedResource)
- 3.
XmlBeanDefinitionReader#doLoadBeanDefinitions(InputSource inputSource, Resource resource)
- 4.1
XmlBeanDefinitionReader#doLoadDocument(InputSource inputSource, Resource resource)
- 4.1.1
XmlBeanDefinitionReader#loadDocument(InputSource inputSource, EntityResolver entityResolver, ErrorHandler errorHandler, int validationMode, boolean namespaceAware)
- 4.1.1
- 4.2
XmlBeanDefinitionReader#registerBeanDefinitions(Document doc, Resource resource)
- 4.2.1
DefaultBeanDefinitionDocumentReader#registerBeanDefinitions(Document doc, XmlReaderContext readerContext)
- 4.2.1.1
DefaultBeanDefinitionDocumentReader#doRegisterBeanDefinitions(Element root)
- 4.2.1.1.1
DefaultBeanDefinitionDocumentReader#parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate)
- 4.2.1.1.1
- 4.2.1.1
- 4.2.1
- 4.1
- 3.
- 2.
流程解析:
- XmlBeanDefinitionReader#loadBeanDefinitions(Resource resource)
- XmlBeanDefinitionReader#loadBeanDefinitions(EncodedResource encodedResource)
上面两个方法处理过程如下:
- 封装资源文件。当进入 XmlBeanDefinitionReader 后首先对参数 Resource 使用 EncodedResource 类进行封装。
- 获取输入流。从 Resource 中获取对应的 InputStream 并构造 InputSource。
- 通过构造的 InputSource 实例 和 Resource 实例继续调用函数 doLoadBeanDefinitions(inputSource, encodedResource.getResource())
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
Assert.notNull(encodedResource, "EncodedResource must not be null");
if (logger.isTraceEnabled()) {
logger.trace("Loading XML bean definitions from " + encodedResource);
}
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();
}
}
}
- XmlBeanDefinitionReader#doLoadBeanDefinitions(InputSource inputSource, Resource resource)
上面方法的处理过程如下:
- 获取对 XML 文件的验证模式
- 加载 XML 文件,并得到对应的 Document
- 根据返回的 Document 注册 Bean 信息
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
// 功能一:获取对XML文件的验证模式
// 功能二:加载 XML 文件,并得到对应的 Document
Document doc = doLoadDocument(inputSource, resource);
// 根据返回的 Document 注册 Bean 信息
int count = registerBeanDefinitions(doc, resource);
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + count + " bean definitions from " + resource);
}
return count;
}
.....
}
- XmlBeanDefinitionReader#doLoadDocument(InputSource inputSource, Resource resource)
- XmlBeanDefinitionReader#loadDocument(InputSource inputSource, EntityResolver entityResolver, ErrorHandler errorHandler, int validationMode, boolean namespaceAware)
利用SAX
解析XML
获取Document
- XmlBeanDefinitionReader#loadDocument(InputSource inputSource, EntityResolver entityResolver, ErrorHandler errorHandler, int validationMode, boolean namespaceAware)
/**
* 通过 SAX 解析 XML 文档
*
* @param entityResolver
*
* 如果 SAX 应用程序需要实现自定义处理外部实体,则必须实现此接口并使用 setEntityResolver() 方法向 SAX 驱动器
* 注册一个实例。也就是说,对于解析一个 XML ,SAX首先读取该 XML 文档上的声明,根据声明区寻找相应的DTD定义,以便于
* 对文档进行验证。默认的寻找规则即通过网络(实际上就是申明的 DTD 的 URI 地址)来下载相应的 DTD 声明,并进行认证。
* 下载的过程是一个漫长的过程,而且当网络中断或不可用时,这里会报错,就是因为相应的 DTD 声明没有被找到的原因。
* EntityResolver 的作用是项目本身可以提供一个如何寻找 DTD 声明的方法,即由程序来实现寻找 DTD 声明的过程,比
* 如我们将 DTD 文件放到项目中某处,在实现时直接将此文档读取并返回给 SAX 即可。这样避免了通过网络来寻找相应的声明。
*
* DTD文件对应的EntityResolver:BeansDtdResolver
* XSD文件对应的EntityResolver:PluggableSchemaResolver
*
* Load the {@link Document} at the supplied {@link InputSource} using the standard JAXP-configured
* XML parser.
*/
@Override
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
if (logger.isTraceEnabled()) {
logger.trace("Using JAXP provider [" + factory.getClass().getName() + "]");
}
DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
return builder.parse(inputSource);
}
- XmlBeanDefinitionReader#registerBeanDefinitions(Document doc, Resource resource)
- DefaultBeanDefinitionDocumentReader#registerBeanDefinitions(Document doc, XmlReaderContext readerContext)
- DefaultBeanDefinitionDocumentReader#doRegisterBeanDefinitions(Element root)
- DefaultBeanDefinitionDocumentReader#parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate)
- DefaultBeanDefinitionDocumentReader#doRegisterBeanDefinitions(Element root)
- DefaultBeanDefinitionDocumentReader#registerBeanDefinitions(Document doc, XmlReaderContext readerContext)
/**
* XmlBeanDefinitionReader#registerBeanDefinitions(Document doc, Resource resource)
* 根据返回的 Document 注册 Bean 信息
*/
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
// 使用 DefaultBeanDefinitionDocumentReader 实例化 BeanDefinitionDocumentReader
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
// 记录统计前 BeanDefinition 的加载个数
int countBefore = getRegistry().getBeanDefinitionCount();
// 加载及注册 Bean
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
// 记录本次加载的 BeanDefinition 个数
return getRegistry().getBeanDefinitionCount() - countBefore;
}
/**
* DefaultBeanDefinitionDocumentReader#registerBeanDefinitions(Document doc, XmlReaderContext readerContext)
* 这个方法的重要目的之一就是提取root,以便于再次将 root 作为参数继续 BeanDefinition 的注册
*/
@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
/**
* Element root = doc.getDocumentElement()
* 这个方法的重要目的之一就是提取root,以便于再次将 root 作为参数继续 BeanDefinition 的注册
*/
doRegisterBeanDefinitions(doc.getDocumentElement());
}
/**
* DefaultBeanDefinitionDocumentReader#doRegisterBeanDefinitions(Element root)
* Register each bean definition within the given root {@code <beans/>} element.
*/
@SuppressWarnings("deprecation") // for Environment.acceptsProfiles(String...)
protected void doRegisterBeanDefinitions(Element root) {
// Any nested <beans> elements will cause recursion in this method. In
// order to propagate and preserve <beans> default-* attributes correctly,
// keep track of the current (parent) delegate, which may be null. Create
// the new (child) delegate with a reference to the parent for fallback purposes,
// then ultimately reset this.delegate back to its original (parent) reference.
// this behavior emulates a stack of delegates without actually necessitating one.
// 专门处理解析
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createDelegate(getReaderContext(), root, parent);
if (this.delegate.isDefaultNamespace(root)) {
// 处理 profile 属性
/*
* 首先程序会获取 beans 节点是否定义了 profile 属性,若定义了则会需要到环境变量中去寻找。因为 profile 是
* 可以同时指定多个的,需要程序对其拆分,并解析每个 profile 是都符合环境变量中所定义的,不定义则不会浪费性能
* 去解析。
*/
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
if (StringUtils.hasText(profileSpec)) {
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
// We cannot use Profiles.of(...) since profile expressions are not supported
// in XML config. See SPR-12458 for details.
if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
if (logger.isDebugEnabled()) {
logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec +
"] not matching: " + getReaderContext().getResource());
}
return;
}
}
}
// 解析前处理,留给子类
preProcessXml(root);
parseBeanDefinitions(root, this.delegate);
// 解析前处理,留给子类
postProcessXml(root);
this.delegate = parent;
}
/**
* DefaultBeanDefinitionDocumentReader#parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate)
* 解析配置文件中的 默认标签 和 自定义标签
*/
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
// 对 beans 处理
/*
* Spring 的 XML 配置里有两大类 Bean 声明,一个是默认的,如:
* <bean id="test" class="test.TestBean" />
* 另一类是自定义的,如:
* <tx:annotation-driven />
* 两种方式的读取及解析差别是非常大的,如果采用 Spring 默认的配置,Spring 当然知道该怎么做,但是如果是自定义
* 的。那么就需要用户实现一些接口及配置了。对于根节点或者子节点如果是默认命名空间的话采用 parseDefaultElement()
* 方法进行解析,否则使用 delegate.parseCustomElement() 方法对自定义命名空间进行解析。而判断是否默认命名空间还是
* 自定义命名空间的办法其实是使用 node.getNamespaceURI() 获取命名空间,并与 Spring 中固定的命名空间
* http://www.springframework.org/schema/beans 进行对比。如果一致则认为是默认,否则就认为是自定义。
*/
if (delegate.isDefaultNamespace(root)) {
NodeList nl = root.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (node instanceof Element) {
Element ele = (Element) node;
if (delegate.isDefaultNamespace(ele)) {
parseDefaultElement(ele, delegate);
}
else {
delegate.parseCustomElement(ele);
}
}
}
}
else {
delegate.parseCustomElement(root);
}
}