一、目的
管理bean的工作与业务代码分离。管理这些bean需要一些基础的属性信息(如pojo的属性值)和定义信息(如是否单例、作用域),这些信息可以使用spring注解的方式定义,也可以使用spring标签在xml配置文件中编写。目前spring注解的方式较为主流,xml配置的方式是spring初期大部分的选择,也是较为基础部分,本文探讨的是后者。读取xml文件的目的,就是为了把这些bean的信息提取出来,供后面解析bean使用。
二、功能演示
- Person类
package com.kaka.spring.beans;
public class Person {
private Integer age;
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
- xml配置文件:applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="person" class="com.kaka.spring.beans.Person">
<property name="age" value="22"/>
</bean>
</beans>
- 程序运行,这样person中的age属性就是22了
public static void main(String[] args) {
Resource classPathResource = new ClassPathResource("applicationContext.xml");
BeanFactory xmlBeanFactory = new XmlBeanFactory(classPathResource);
Person person = (Person) xmlBeanFactory.getBean("person");
System.out.println(person);
}
三、主要相关类
- org.springframework.beans.factory.xml.XmlBeanFactory:程序的入口
- org.springframework.beans.factory.xml.XmlBeanDefinitionReader:加载Bean
- org.springframework.beans.factory.xml.DefaultDocumentLoader:加载xml配置文件
- org.springframework.core.io.ClassPathResource:对类路径下文件资源的表示
三、源码分析
- 使用ClassPathResource读取xml文件
Resource classPathResource = new ClassPathResource("applicationContext.xml");
这就是一个读取文件的简单操作,在spring中文件放在不同的地方就是一种不同的资源,进而都被抽象成一个Resource对象。我们这个applicationContext.xml文件是放在类路径下的,就可以使用ClassPathResource来读取。
- XmlBeanFactory初始化
public XmlBeanFactory(Resource resource) throws BeansException {
this(resource, null);
}
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
super(parentBeanFactory);
// 上来就是加载bean的操作啊
this.reader.loadBeanDefinitions(resource);
}
这个reader就是XmlBeanDefinitionReader对象,继续看reader类中的方法
- XmlBeanDefinitionReader去实现loadBeanDefinition的操作
@Override
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
// 把Resource包装成编码资源EncodedResource
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());
}
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 {
// 从encodedResource获取输入流
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
// 把inputStream包装成org.xml.sax.InputSource
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
// 一般spring中xx()方法是校验参数、准备数据的,真正干活儿的就是doXx()方法;继续分析这个方法
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的
Document doc = doLoadDocument(inputSource, resource);
// 注册bean
return registerBeanDefinitions(doc, resource);
}
// 省略catch的异常
...
}
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
// 这里调用了DefaultDocumentLoader的loadDocument的方法
return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
getValidationModeForResource(resource), isNamespaceAware());
}
- XmlBeanDefinitionReader把加载xml文件的操作,交由DefaultDocumentLoader处理
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
// 1.创建解析器工厂
DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
if (logger.isDebugEnabled()) {
logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]");
}
// 2.创建解析器对象
DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
// 3.获取Document
return builder.parse(inputSource);
}
protected DocumentBuilderFactory createDocumentBuilderFactory(int validationMode, boolean namespaceAware)
throws ParserConfigurationException {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(namespaceAware);
// 设置验证方式
if (validationMode != XmlValidationModeDetector.VALIDATION_NONE) {
factory.setValidating(true);
if (validationMode == XmlValidationModeDetector.VALIDATION_XSD) {
// Enforce namespace aware for XSD...
factory.setNamespaceAware(true);
try {
factory.setAttribute(SCHEMA_LANGUAGE_ATTRIBUTE, XSD_SCHEMA_LANGUAGE);
}
catch (IllegalArgumentException ex) {
ParserConfigurationException pcex = new ParserConfigurationException(
"Unable to validate using XSD: Your JAXP provider [" + factory +
"] does not support XML Schema. Are you running on Java 1.4 with Apache Crimson? " +
"Upgrade to Apache Xerces (or Java 1.5) for full XSD support.");
pcex.initCause(ex);
throw pcex;
}
}
}
return factory;
}
protected DocumentBuilder createDocumentBuilder(DocumentBuilderFactory factory,
@Nullable EntityResolver entityResolver, @Nullable ErrorHandler errorHandler)
throws ParserConfigurationException {
// 从解析器工厂中创建解析器
DocumentBuilder docBuilder = factory.newDocumentBuilder();
if (entityResolver != null) {
docBuilder.setEntityResolver(entityResolver);
}
if (errorHandler != null) {
docBuilder.setErrorHandler(errorHandler);
}
return docBuilder;
}
四、关键点总结
spring先使用Resource对象封装xml资源,然后把Resource传给XmlBeanFactory的构造方法;XmlBeanFactory交由XmlBeanDefinitionReader实现加载bean的操作,XmlBeanDefinitionReader在加载Bean的第一步先使用XmlBeanDefinitionReader去loadDocument进而返回xml对应的Document。
五、借鉴
- org.springframework.beans.factory.support.AbstractBeanDefinitionReader
类中用到的日志对象,可以使用final关键字声明,此类打印的日志就不会被改变。
protected final Log logger = LogFactory.getLog(getClass());
- org.springframework.beans.factory.xml.XmlBeanDefinitionReader#loadBeanDefinitions(org.springframework.core.io.support.EncodedResource)
打日志的时候使用级别控制
if (logger.isDebugEnabled()) {
logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]");
}
- org.springframework.beans.factory.xml.XmlBeanDefinitionReader#loadBeanDefinitions(org.springframework.core.io.support.EncodedResource)
在方法第一步判断必要参数
Assert.notNull(encodedResource, "EncodedResource must not be null");