本文开始,将开始spring 源码解读系列。该系列将会分析下spring的核心功能 IOC/DI,AOP,Transaction,MVC 的实现。通过分析系列源码,理清楚spring的运行流程,不至于在工作中遇到相关问题无从下手,同时学习spring中优秀的设计方式,对自己也是颇有裨益的。而本文作为spring源码的入门篇,着重介绍spring的基础容器的实现。本文大致分为以下几个板块:
- 容器
1.1 bean 容器的基本用法
1.2 bean容器的种类及区别- XmlBeanFactory 源码解读
2.1 加载配置文件
2.2 验证配置文件
2.2.1 DTD
2.2.2 XSD
2.3 将InputSource解析为标准xml Document
2.4 解析配置文件- XmlBeanFactory 过期替代方法
总结
1. 容器
容器是指用以容纳物料并以壳体为主的基本装置。生活中杯子,收纳箱都是容器。在java的世界里就是用来存放各种数据的装置-集合,而在spring里,则是存放各种bean的装置-BeanFactory。
1.1 bean 容器的基本用法
我们先来回顾一下spring容器的基本用法,尽管大家都知道。
- 定义一个bean
- 在配置文件配置该bean
- 初始化bean容器并获取bean
public class Person {
private String name;
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
'}';
}
}
<bean id="user" class="example.Person">
<constructor-arg name="name" value="hello person"/>
</bean>
public static void main(String[] args) {
BeanFactory beanFactory=new XmlBeanFactory(new ClassPathResource("spring-config.xml"));
System.out.println(beanFactory.getBean("user"));
}
1.2 bean容器的种类及区别
spring中最常用的bean容器为BeanFactory及ApplicationContext。同样都是bean的容器,两者有什么区别呢?
ApplicationContext和BeanFactory都提供基础的Bean容器的功能
-
ApplicationContext除了包含BeanFactory所有功能外,还包含了更多的扩展功能
。如Bean后置处理器,消息国际化,事件广播等。我们熟悉的Aop,事物也是基于ApplicationContentext实现的。
2. XmlBeanFactory 源码解读
BeanFactory作为容器的基础,定义了容器的基础行为。而XmlBeanFactory作为BeanFactory的常用实现之一,除了实现了BeanFactory的方法之外,还提供了从Xml解析配置文件,装载Bean的方法。试想如果让我们自己实现一个Bean的容器,我们会怎么去实现呢。编写一个指定规则的配置文件,加载这个配置文件,解析配置文件并存储。其实spring也做了类似的功能,只不过要比我们设想的要复杂。下面我们按照spring XmlBeanFacotry的初始化步骤来读下源码,大致分为以下步骤:
加载配置文件得到输入流
验证配置文件
读取配置文件得到Dom
解析Dom得到Bean抽象定义
-
存储解析后的数据
大致时序图如下(图丑凑合看吧( ╯□╰ )):
image.png
2.1 加载配置文件
我们是通过new XmlBeanFactory的方式构造XmlBeanFactory,跟踪其构造方法
/**
* 根据Resource实例化XmlBeanFactory
* @param resource
* @throws BeansException
*/
public XmlBeanFactory(Resource resource) throws BeansException {
this(resource, null);
}
/**
* 根据Resource和BeanFactory实例化XmlBeanFactory
* @param resource
* @param parentBeanFactory
* @throws BeansException
*/
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
super(parentBeanFactory);
this.reader.loadBeanDefinitions(resource);
}
主要做了两件事
1.调用父类的构造方法,设置parentBeanFactory。一般情况下parentBeanFactory为空
2.用XmlBeanDefinitionReader加载bean配置。其中XmlBeanDefinitionReader是XmlBeanFactory类是私有变量
private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);
我们继续跟踪步骤2
2.1 根据Resource 构造 EncodedResource
@Override
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
//根据Resource 构造 EncodedResource
//默认情况下encoding和charset都为空
return loadBeanDefinitions(new EncodedResource(resource));
}
2.2 将EncodedResource放入线程副本,防止循环加载
//将EncodedResource放入线程副本,防止循环加载
Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
if (currentResources == null) {
currentResources = new HashSet<EncodedResource>(4);
this.resourcesCurrentlyBeingLoaded.set(currentResources);
}
if (!currentResources.add(encodedResource)) {
throw new BeanDefinitionStoreException(
"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
}
2.3 从resource里获取输入流
try {
//从resource里获取输入流
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
//包装流
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
//根据inputSource和Resource 来读取配置文件并解析
//inputSource用来解析xml得到Dom(用jaxp解析xml的标准输入)
//encodedResource.getResource()用来获取xml的验证模式
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
finally {
inputStream.close();
}
}
再来看下如何根据resource获取流的,此处以上面基础用法部分的ClassPathResource为例
@Override
public InputStream getInputStream() throws IOException {
InputStream is;
if (this.clazz != null) {
is = this.clazz.getResourceAsStream(this.path);
}
else if (this.classLoader != null) {
is = this.classLoader.getResourceAsStream(this.path);
}
else {
is = ClassLoader.getSystemResourceAsStream(this.path);
}
if (is == null) {
throw new FileNotFoundException(getDescription() + " cannot be opened because it does not exist");
}
return is;
}
所以从上面可以看出,ClassPathResource获取InputStream是调用的Class.getResourceAsStream或者ClassLoader.getResourceAsStream或者ClassLoader.getSystemResourceAsStream方法获得输入流。
2.2 验证配置文件
spring的基础是基于配置文件。所有的定义又都是从配置文件来的,那么加载配置文件的时候就有必要验证配置文件的正确性。那么spring是如何验证xml的呢?xml验证一般有两种模式DTD和XSD
2.2.1 DTD
DTD即 Documnet Type Definition,文档定义语言。使用非XML文件编写,不可扩展,不支持命名空间,仅支持有限的数据类型。使用DTD,要在spring的配置文件中做如下配置:
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN"
"http://www.springframework.org/dtd/spring-beans-2.0.dtd">
2.2.2 XSD
XSD即XML Schemas Definition。XSD中描述了xml文档的结构,可以用某个xsd验证xml是否符合规范。XSD相比DTD而言,基于XML编写,支持扩展,支持命名空间,支持更多的数据类型。要在spring中使用xsd验证,需要做如下配置:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
其中要配置xmlns和xsi:schemaLocation。且schemaLocation中xmlns要和xsd文件的地址成对出现。另外 ,不配置xsd文件的版本号的原因是,spring会加载jar包中缓存的默认版本号,防止网络闪断造成的下载xsd失败。
2.2.3 spring验证配置文件
验证配置文件发生在利用InputSource得到Document的过程中,在这个过程中,先获取配置文件的验证模式,然后再加载配置文件得到Document。如果配置文件不通过,这个过程就会抛异常。
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
//加载配置文件得到Document
Document doc = doLoadDocument(inputSource, resource);
return registerBeanDefinitions(doc, resource);
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (SAXParseException ex) {
throw new XmlBeanDefinitionStoreException(resource.getDescription(),
"Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex);
}
1. 获取配置文件的验证模式,基于XSD验证还是基于DTD验证
。源码如下
protected int getValidationModeForResource(Resource resource) {
int validationModeToUse = getValidationMode();
if (validationModeToUse != VALIDATION_AUTO) {
return validationModeToUse;
}
int detectedMode = detectValidationMode(resource);
if (detectedMode != VALIDATION_AUTO) {
return detectedMode;
}
// Hmm, we didn't get a clear indication... Let's assume XSD,
// since apparently no DTD declaration has been found up until
// detection stopped (before finding the document's root tag).
return VALIDATION_XSD;
}
public int detectValidationMode(InputStream inputStream) throws IOException {
// Peek into the file to look for DOCTYPE.
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
try {
boolean isDtdValidated = false;
String content;
while ((content = reader.readLine()) != null) {
content = consumeCommentTokens(content);
if (this.inComment || !StringUtils.hasText(content)) {
continue;
}
if (hasDoctype(content)) {
isDtdValidated = true;
break;
}
if (hasOpeningTag(content)) {
// End of meaningful data...
break;
}
}
return (isDtdValidated ? VALIDATION_DTD : VALIDATION_XSD);
}
catch (CharConversionException ex) {
// Choked on some character encoding...
// Leave the decision up to the caller.
return VALIDATION_AUTO;
}
finally {
reader.close();
}
}
获取验证模式的依据是标签中是否有"DOCTYPE"出现
private boolean hasDoctype(String content) {
return content.contains(DOCTYPE);
}
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
getValidationModeForResource(resource), isNamespaceAware());
}
2. 将InputSource 解析为 Document对象
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
getValidationModeForResource(resource), isNamespaceAware());
}
设置验证模式的关键在于,构建DocumentBuilderFactory的属性设置
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) {
// 启用命名空间.
factory.setNamespaceAware(true);
try {
//设置xsd验证所需属性
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;
}
同时还要设置DocumentBuilder的ErrorHandler,方便异常抛出
protected DocumentBuilder createDocumentBuilder(
DocumentBuilderFactory factory, EntityResolver entityResolver, ErrorHandler errorHandler)
throws ParserConfigurationException {
DocumentBuilder docBuilder = factory.newDocumentBuilder();
if (entityResolver != null) {
docBuilder.setEntityResolver(entityResolver);
}
if (errorHandler != null) {
docBuilder.setErrorHandler(errorHandler);
}
return docBuilder;
}
其中ErrorHandler为类内部变量
private ErrorHandler errorHandler = new SimpleSaxErrorHandler(logger);
至此已完成了xsd校验的设置
2.3 将InputSource解析为标准xml Document
其实上面我们已经贴出了将InputSource解析为xml的源码。值得注意的是,spring解析xml 使用的是 jaxp,并未使用dom4j来解析xml,或许是spring>本文开始,将开始spring 源码解读系列。该系列将会分析下spring的核心功能 IC/DI,AOP,Transaction,MVC 的实现。通过分析系列源码,理清楚spring的运行流程,不至于在工作中遇到相关问题无从下手,同时学习spring中优秀的设计方式,对自己也是颇有裨益的。而本文作为spring源码的入门篇,着重介绍spring的基础容器的实现。本文大致分为以下几个板块:
- 容器
1.1 bean 容器的基本用法
1.2 bean容器的种类及区别- XmlBeanFactory 源码解读
2.1 加载配置文件
2.2 验证配置文件
2.2.1 DTD
2.2.2 XSD
2.3 读取配置文件得到Document
2.4 解析配置文件
2.5 存储解析后的数据- XmlBeanFactory 过期替代方法
1. 容器
容器,顾名思义容纳物体的器皿。生活中杯子,收纳箱都是容器。在java的世界里就是用来存放各种数据的器皿也就是集合,而在spring里,则是存放各种bean的器皿-BeanFactory。
1.1 bean 容器的基本用法
我们先来回顾一下spring容器的基本用法,尽管大家都知道。
step1:定义一个bean
step2:在配置文件配置该bean
step3:初始化bean容器并获取bean
public class Person {
private String name;
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
'}';
}
}
<bean id="user" class="example.Person">
<constructor-arg name="name" value="hello person"/>
</bean>
public static void main(String[] args) {
BeanFactory beanFactory=new XmlBeanFactory(new ClassPathResource("spring-config.xml"));
System.out.println(beanFactory.getBean("user"));
}
1.2 bean容器的种类及区别
spring中最常用的bean容器为BeanFactory及ApplicationContext。同样都是bean的容器,两者有什么区别呢?
ApplicationContext和BeanFactory都提供基础的Bean容器的功能
-
ApplicationContext除了包含BeanFactory所有功能外,还包含了更多的扩展功能
。如Bean后置处理器,消息国际化,事件广播等。我们熟悉的Aop,事物也是基于ApplicationContentext实现的。
2. XmlBeanFactory 源码解读
BeanFactory作为容器的基础,定义了容器的基础行为。而XmlBeanFactory作为BeanFactory的常用实现之一,除了实现了BeanFactory的方法之外,还提供了从Xml解析配置文件,装载Bean的方法。试想如果让我们自己实现一个Bean的容器,我们会怎么去实现呢。编写一个指定规则的配置文件,加载这个配置文件,解析配置文件并存储。其实spring也做了类似的功能,只不过要比我们设想的要复杂。下面我们按照spring XmlBeanFacotry的初始化步骤来读下源码,大致分为以下步骤:
加载配置文件得到输入流
验证配置文件
读取配置文件得到Dom
解析Dom得到Bean抽象定义
-
存储解析后的数据
大致时序图如下:
image.png
2.1 加载配置文件
我们是通过new XmlBeanFactory的方式构造XmlBeanFactory,跟踪其构造方法
/**
* 根据Resource实例化XmlBeanFactory
* @param resource
* @throws BeansException
*/
public XmlBeanFactory(Resource resource) throws BeansException {
this(resource, null);
}
/**
* 根据Resource和BeanFactory实例化XmlBeanFactory
* @param resource
* @param parentBeanFactory
* @throws BeansException
*/
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
super(parentBeanFactory);
this.reader.loadBeanDefinitions(resource);
}
主要做了两件事
1.调用父类的构造方法,设置parentBeanFactory。一般情况下parentBeanFactory为空
2.用XmlBeanDefinitionReader加载bean配置。其中XmlBeanDefinitionReader是XmlBeanFactory类是私有变量
private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);
我们继续跟踪步骤2
2.1 根据Resource 构造 EncodedResource
@Override
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
//根据Resource 构造 EncodedResource
//默认情况下encoding和charset都为空
return loadBeanDefinitions(new EncodedResource(resource));
}
2.2 将EncodedResource放入线程副本,防止循环加载
//将EncodedResource放入线程副本,防止循环加载
Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
if (currentResources == null) {
currentResources = new HashSet<EncodedResource>(4);
this.resourcesCurrentlyBeingLoaded.set(currentResources);
}
if (!currentResources.add(encodedResource)) {
throw new BeanDefinitionStoreException(
"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
}
2.3 从resource里获取输入流
try {
//从resource里获取输入流
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
//包装流
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
//根据inputSource和Resource 来读取配置文件并解析
//inputSource用来解析xml得到Dom(用jaxp解析xml的标准输入)
//encodedResource.getResource()用来获取xml的验证模式
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
finally {
inputStream.close();
}
}
再来看下如何根据resource获取流的,此处以上面基础用法部分的ClassPathResource为例
@Override
public InputStream getInputStream() throws IOException {
InputStream is;
if (this.clazz != null) {
is = this.clazz.getResourceAsStream(this.path);
}
else if (this.classLoader != null) {
is = this.classLoader.getResourceAsStream(this.path);
}
else {
is = ClassLoader.getSystemResourceAsStream(this.path);
}
if (is == null) {
throw new FileNotFoundException(getDescription() + " cannot be opened because it does not exist");
}
return is;
}
所以从上面可以看出,ClassPathResource获取InputStream是调用的Class.getResourceAsStream或者ClassLoader.getResourceAsStream或者ClassLoader.getSystemResourceAsStream方法获得输入流。
2.2 验证配置文件
spring的基础是基于配置文件。所有的定义又都是从配置文件来的,那么加载配置文件的时候就有必要验证配置文件的正确性。那么spring是如何验证xml的呢?xml验证一般有两种模式DTD和XSD
2.2.1 DTD
DTD即 Documnet Type Definition,文档定义语言。使用非XML文件编写,不可扩展,不支持命名空间,仅支持有限的数据类型。使用DTD,要在spring的配置文件中做如下配置:
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN"
"http://www.springframework.org/dtd/spring-beans-2.0.dtd">
2.2.2 XSD
XSD即XML Schemas Definition。XSD中描述了xml文档的结构,可以用某个xsd验证xml是否符合规范。XSD相比DTD而言,基于XML编写,支持扩展,支持命名空间,支持更多的数据类型。要在spring中使用xsd验证,需要做如下配置:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
其中要配置xmlns和xsi:schemaLocation。且schemaLocation中xmlns要和xsd文件的地址成对出现。另外 ,不配置xsd文件的版本号的原因是,spring会加载jar包中缓存的默认版本号,防止网络闪断造成的下载xsd失败。
2.2.3 spring验证配置文件
验证配置文件发生在利用InputSource得到Document的过程中,在这个过程中,先获取配置文件的验证模式,然后再加载配置文件得到Document。如果配置文件不通过,这个过程就会抛异常。
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
//加载配置文件得到Document
Document doc = doLoadDocument(inputSource, resource);
return registerBeanDefinitions(doc, resource);
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (SAXParseException ex) {
throw new XmlBeanDefinitionStoreException(resource.getDescription(),
"Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex);
}
1. 获取配置文件的验证模式,基于XSD验证还是基于DTD验证
。源码如下
protected int getValidationModeForResource(Resource resource) {
int validationModeToUse = getValidationMode();
if (validationModeToUse != VALIDATION_AUTO) {
return validationModeToUse;
}
int detectedMode = detectValidationMode(resource);
if (detectedMode != VALIDATION_AUTO) {
return detectedMode;
}
// Hmm, we didn't get a clear indication... Let's assume XSD,
// since apparently no DTD declaration has been found up until
// detection stopped (before finding the document's root tag).
return VALIDATION_XSD;
}
public int detectValidationMode(InputStream inputStream) throws IOException {
// Peek into the file to look for DOCTYPE.
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
try {
boolean isDtdValidated = false;
String content;
while ((content = reader.readLine()) != null) {
content = consumeCommentTokens(content);
if (this.inComment || !StringUtils.hasText(content)) {
continue;
}
if (hasDoctype(content)) {
isDtdValidated = true;
break;
}
if (hasOpeningTag(content)) {
// End of meaningful data...
break;
}
}
return (isDtdValidated ? VALIDATION_DTD : VALIDATION_XSD);
}
catch (CharConversionException ex) {
// Choked on some character encoding...
// Leave the decision up to the caller.
return VALIDATION_AUTO;
}
finally {
reader.close();
}
}
获取验证模式的依据是标签中是否有"DOCTYPE"出现
private boolean hasDoctype(String content) {
return content.contains(DOCTYPE);
}
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
getValidationModeForResource(resource), isNamespaceAware());
}
2. 将InputSource 解析为 Document对象
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
getValidationModeForResource(resource), isNamespaceAware());
}
设置验证模式的关键在于,构建DocumentBuilderFactory的属性设置
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) {
// 启用命名空间.
factory.setNamespaceAware(true);
try {
//设置xsd验证所需属性
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;
}
同时还要设置DocumentBuilder的ErrorHandler,方便异常抛出
protected DocumentBuilder createDocumentBuilder(
DocumentBuilderFactory factory, EntityResolver entityResolver, ErrorHandler errorHandler)
throws ParserConfigurationException {
DocumentBuilder docBuilder = factory.newDocumentBuilder();
if (entityResolver != null) {
docBuilder.setEntityResolver(entityResolver);
}
if (errorHandler != null) {
docBuilder.setErrorHandler(errorHandler);
}
return docBuilder;
}
其中ErrorHandler为类内部变量
private ErrorHandler errorHandler = new SimpleSaxErrorHandler(logger);
至此已完成了xsd校验的设置
2.3 将InputSource解析为标准xml Document
其实上面我们已经贴出了将InputSource解析为xml的源码。值得注意的是,spring解析xml 使用的是 jaxp,并未使用dom4j来解析xml,之所以不和Hibernate一样采用dom4j加载xml文件,可能是觉得加载少量的静态配置文件,jaxp性能足矣。大致经历了以下步骤
构建DocumentBuilderFactory-设置验证模式和命名空间
构建DocumentBuilder-设置错误处理器
解析配置文件得到 Document
@Override
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
//构建DocumentBuilderFactory-设置验证模式和命名空间
DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
if (logger.isDebugEnabled()) {
logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]");
}
//构建DocumentBuilder-设置错误处理器
DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
//解析配置文件
return builder.parse(inputSource);
}
2.4 配置文件解析
得到Document 之后,就可以开始配置文件的解析了。对配置文件的解析的过程,实际上是对配置文件各个标签进行解析转换为BeanDefinitionHolder
的过程。解析的过程又分为对默认标签(namespace 为 http://www.springframework.org/schema/beans)和自定义标签(除了默认标签之外的标签)
的解析。在分析两种解析的规则之前,我们先看下解析标签之前的执行逻辑
1.registerBeanDefinitions
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
Document doc = doLoadDocument(inputSource, resource);
return registerBeanDefinitions(doc, resource);
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (SAXParseException ex) {
throw new XmlBeanDefinitionStoreException(resource.getDescription(),
"Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex);
}
catch (SAXException ex) {
throw new XmlBeanDefinitionStoreException(resource.getDescription(),
"XML document from " + resource + " is invalid", ex);
}
catch (ParserConfigurationException ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"Parser configuration exception parsing XML from " + resource, ex);
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"IOException parsing XML document from " + resource, ex);
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"Unexpected exception parsing XML document from " + resource, ex);
}
}
2.创建BeanDefinitionDocumentReader,并调用其registerBeanDefinitions方法解析注册BeanDefinition
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;
}
3.得到Document的根节点,并循环解析
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
logger.debug("Loading bean definitions");
Element root = doc.getDocumentElement();
doRegisterBeanDefinitions(root);
}
4.解析前后的预处理(部分方法空实现,留给子类覆盖)
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)) {
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
if (StringUtils.hasText(profileSpec)) {
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
if (logger.isInfoEnabled()) {
logger.info("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;
}
5.进入解析方法,根据不同的标签类型执行不同的解析逻辑
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
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 {
//解析自定义标签 如mvc tx 等
delegate.parseCustomElement(ele);
}
}
}
}
else {
delegate.parseCustomElement(root);
}
}
2.4.1 默认标签的解析
spring 会把 namespace 为 http://www.springframework.org/schema/beans的都作为默认标签来解析。判断依据的代码如下:
public boolean isDefaultNamespace(String namespaceUri) {
return (!StringUtils.hasLength(namespaceUri) || BEANS_NAMESPACE_URI.equals(namespaceUri));
}
其中BEANS_NAMESPACE_URI=http://www.springframework.org/schema/beans
默认标签又分为import,alias,bean,beans
四类标签的解析(根据nodeName 判断
)。
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
importBeanDefinitionResource(ele);
}
else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
processAliasRegistration(ele);
}
else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
processBeanDefinition(ele, delegate);
}
else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
// recurse
doRegisterBeanDefinitions(ele);
}
}
我们以bean标签的解析为例,分析下解析的流程。
2.4.1.1 bean 标签的解析
bean标签的解析分为如下两个主要步骤
1. 将bean标签解析为BeanDefinitionHolder
2. 注册BeanDefinitionHolder
2.4.1.1.1 将bean标签解析为BeanDefinitionHolder
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
//解析bean 标签的各种属性,转换为BeanDefinitionHolder
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
// Register the final decorated instance.
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
}
catch (BeanDefinitionStoreException ex) {
getReaderContext().error("Failed to register bean definition with name '" +
bdHolder.getBeanName() + "'", ele, ex);
}
// Send registration event.
getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
}
可以看到核心解析代码是在BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
跟进该方法,如下
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele) {
return parseBeanDefinitionElement(ele, null);
}
获取id
获取name
处理多个name
当不存在id时且name存在时,将首个name作为beanName
检查name和id的唯一性
解析其它attribute
beanName为空时(id,name都为空),生成默认beanName
包装成BeanDefinitionHolder并返回
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) {
//获取id
String id = ele.getAttribute(ID_ATTRIBUTE);
//获取name
String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
//处理多个name
List<String> aliases = new ArrayList<String>();
if (StringUtils.hasLength(nameAttr)) {
String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
aliases.addAll(Arrays.asList(nameArr));
}
String beanName = id;
//当不存在id时且name存在时,将首个name作为beanName
if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
beanName = aliases.remove(0);
if (logger.isDebugEnabled()) {
logger.debug("No XML 'id' specified - using '" + beanName +
"' as bean name and " + aliases + " as aliases");
}
}
//检查name和id的唯一性
if (containingBean == null) {
checkNameUniqueness(beanName, aliases, ele);
}
//解析其它attribute
AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
//自动生成beanName
if (beanDefinition != null) {
if (!StringUtils.hasText(beanName)) {
try {
if (containingBean != null) {
beanName = BeanDefinitionReaderUtils.generateBeanName(
beanDefinition, this.readerContext.getRegistry(), true);
}
else {
beanName = this.readerContext.generateBeanName(beanDefinition);
// Register an alias for the plain bean class name, if still possible,
// if the generator returned the class name plus a suffix.
// This is expected for Spring 1.2/2.0 backwards compatibility.
String beanClassName = beanDefinition.getBeanClassName();
if (beanClassName != null &&
beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&
!this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
aliases.add(beanClassName);
}
}
if (logger.isDebugEnabled()) {
logger.debug("Neither XML 'id' nor 'name' specified - " +
"using generated bean name [" + beanName + "]");
}
}
catch (Exception ex) {
error(ex.getMessage(), ele);
return null;
}
}
String[] aliasesArray = StringUtils.toStringArray(aliases);
//包装成BeanDefinitionHolder并返回
return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
}
return null;
}
其中beanName的生成规则为className+"#"+num,其中num从-1开始自增。
到此为止,我们只看到了name 和id属性的解析,那么其它属性是如何解析的呢?
从AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);大致流程如下
解析class属性
解析parent属性
创建用来记录属性的AbstractBeanDefinition
硬编码解析bean的各种属性
解析description
解析meta标签
解析lookup-method
解析replaced-method
解析constructor-arg
解析property标签
解析qualifier
public AbstractBeanDefinition parseBeanDefinitionElement(
Element ele, String beanName, BeanDefinition containingBean) {
this.parseState.push(new BeanEntry(beanName));
String className = null;
//解析class属性
if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
}
try {
String parent = null;
//解析parent属性
if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
parent = ele.getAttribute(PARENT_ATTRIBUTE);
}
//创建用来记录属性的AbstractBeanDefinition
AbstractBeanDefinition bd = createBeanDefinition(className, parent);
//硬编码解析bean的各种属性
parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
//解析description
bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));
//解析meta标签
parseMetaElements(ele, bd);
//解析lookup-method
parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
//解析replaced-method
parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
//解析constructor-arg
parseConstructorArgElements(ele, bd);
//解析property标签
parsePropertyElements(ele, bd);
//解析qualifier
parseQualifierElements(ele, bd);
bd.setResource(this.readerContext.getResource());
bd.setSource(extractSource(ele));
return bd;
}
catch (ClassNotFoundException ex) {
error("Bean class [" + className + "] not found", ele, ex);
}
catch (NoClassDefFoundError err) {
error("Class that bean class [" + className + "] depends on not found", ele, err);
}
catch (Throwable ex) {
error("Unexpected failure during bean definition parsing", ele, ex);
}
finally {
this.parseState.pop();
}
return null;
}
大致的思路都是提取node的属性,然后做封装,我们以parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);为例来看下提取并封装的过程
public AbstractBeanDefinition parseBeanDefinitionAttributes(Element ele, String beanName,
BeanDefinition containingBean, AbstractBeanDefinition bd) {
if (ele.hasAttribute(SINGLETON_ATTRIBUTE)) {
error("Old 1.x 'singleton' attribute in use - upgrade to 'scope' declaration", ele);
}
else if (ele.hasAttribute(SCOPE_ATTRIBUTE)) {
bd.setScope(ele.getAttribute(SCOPE_ATTRIBUTE));
}
else if (containingBean != null) {
// Take default from containing bean in case of an inner bean definition.
bd.setScope(containingBean.getScope());
}
if (ele.hasAttribute(ABSTRACT_ATTRIBUTE)) {
bd.setAbstract(TRUE_VALUE.equals(ele.getAttribute(ABSTRACT_ATTRIBUTE)));
}
String lazyInit = ele.getAttribute(LAZY_INIT_ATTRIBUTE);
if (DEFAULT_VALUE.equals(lazyInit)) {
lazyInit = this.defaults.getLazyInit();
}
bd.setLazyInit(TRUE_VALUE.equals(lazyInit));
String autowire = ele.getAttribute(AUTOWIRE_ATTRIBUTE);
bd.setAutowireMode(getAutowireMode(autowire));
String dependencyCheck = ele.getAttribute(DEPENDENCY_CHECK_ATTRIBUTE);
bd.setDependencyCheck(getDependencyCheck(dependencyCheck));
if (ele.hasAttribute(DEPENDS_ON_ATTRIBUTE)) {
String dependsOn = ele.getAttribute(DEPENDS_ON_ATTRIBUTE);
bd.setDependsOn(StringUtils.tokenizeToStringArray(dependsOn, MULTI_VALUE_ATTRIBUTE_DELIMITERS));
}
String autowireCandidate = ele.getAttribute(AUTOWIRE_CANDIDATE_ATTRIBUTE);
if ("".equals(autowireCandidate) || DEFAULT_VALUE.equals(autowireCandidate)) {
String candidatePattern = this.defaults.getAutowireCandidates();
if (candidatePattern != null) {
String[] patterns = StringUtils.commaDelimitedListToStringArray(candidatePattern);
bd.setAutowireCandidate(PatternMatchUtils.simpleMatch(patterns, beanName));
}
}
else {
bd.setAutowireCandidate(TRUE_VALUE.equals(autowireCandidate));
}
if (ele.hasAttribute(PRIMARY_ATTRIBUTE)) {
bd.setPrimary(TRUE_VALUE.equals(ele.getAttribute(PRIMARY_ATTRIBUTE)));
}
if (ele.hasAttribute(INIT_METHOD_ATTRIBUTE)) {
String initMethodName = ele.getAttribute(INIT_METHOD_ATTRIBUTE);
if (!"".equals(initMethodName)) {
bd.setInitMethodName(initMethodName);
}
}
else {
if (this.defaults.getInitMethod() != null) {
bd.setInitMethodName(this.defaults.getInitMethod());
bd.setEnforceInitMethod(false);
}
}
if (ele.hasAttribute(DESTROY_METHOD_ATTRIBUTE)) {
String destroyMethodName = ele.getAttribute(DESTROY_METHOD_ATTRIBUTE);
bd.setDestroyMethodName(destroyMethodName);
}
else {
if (this.defaults.getDestroyMethod() != null) {
bd.setDestroyMethodName(this.defaults.getDestroyMethod());
bd.setEnforceDestroyMethod(false);
}
}
if (ele.hasAttribute(FACTORY_METHOD_ATTRIBUTE)) {
bd.setFactoryMethodName(ele.getAttribute(FACTORY_METHOD_ATTRIBUTE));
}
if (ele.hasAttribute(FACTORY_BEAN_ATTRIBUTE)) {
bd.setFactoryBeanName(ele.getAttribute(FACTORY_BEAN_ATTRIBUTE));
}
return bd;
}
可以看到,基本上都是提取attribute,然后设置到AbstractBeanDefinition的各个对应属性中,也就是说AbstractBeanDefinition的属性和我们在xml中配置的属性是一一对应的
。
下图为AbstractBeanDefinition的属性截图,详情请自行查看
2.4.1.1.2 注册BeanDefinitionHolder
解析完标签并包装后,剩下的工作就是beanDefinition的保存,即注册BeanDefinitionHolder。
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
// Register the final decorated instance.
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
}
catch (BeanDefinitionStoreException ex) {
getReaderContext().error("Failed to register bean definition with name '" +
bdHolder.getBeanName() + "'", ele, ex);
}
// Send registration event.
getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
}
public static void registerBeanDefinition(
BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
throws BeanDefinitionStoreException {
// Register bean definition under primary name.
String beanName = definitionHolder.getBeanName();
registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
// Register aliases for bean name, if any.
String[] aliases = definitionHolder.getAliases();
if (aliases != null) {
for (String alias : aliases) {
registry.registerAlias(beanName, alias);
}
}
}
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException {
Assert.hasText(beanName, "Bean name must not be empty");
Assert.notNull(beanDefinition, "BeanDefinition must not be null");
if (beanDefinition instanceof AbstractBeanDefinition) {
try {
((AbstractBeanDefinition) beanDefinition).validate();
}
catch (BeanDefinitionValidationException ex) {
throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
"Validation of bean definition failed", ex);
}
}
BeanDefinition oldBeanDefinition;
oldBeanDefinition = this.beanDefinitionMap.get(beanName);
if (oldBeanDefinition != null) {
if (!isAllowBeanDefinitionOverriding()) {
throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
"Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName +
"': There is already [" + oldBeanDefinition + "] bound.");
}
else if (oldBeanDefinition.getRole() < beanDefinition.getRole()) {
// e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
if (this.logger.isWarnEnabled()) {
this.logger.warn("Overriding user-defined bean definition for bean '" + beanName +
"' with a framework-generated bean definition: replacing [" +
oldBeanDefinition + "] with [" + beanDefinition + "]");
}
}
else if (!beanDefinition.equals(oldBeanDefinition)) {
if (this.logger.isInfoEnabled()) {
this.logger.info("Overriding bean definition for bean '" + beanName +
"' with a different definition: replacing [" + oldBeanDefinition +
"] with [" + beanDefinition + "]");
}
}
else {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Overriding bean definition for bean '" + beanName +
"' with an equivalent definition: replacing [" + oldBeanDefinition +
"] with [" + beanDefinition + "]");
}
}
this.beanDefinitionMap.put(beanName, beanDefinition);
}
else {
if (hasBeanCreationStarted()) {
// Cannot modify startup-time collection elements anymore (for stable iteration)
synchronized (this.beanDefinitionMap) {
this.beanDefinitionMap.put(beanName, beanDefinition);
List<String> updatedDefinitions = new ArrayList<String>(this.beanDefinitionNames.size() + 1);
updatedDefinitions.addAll(this.beanDefinitionNames);
updatedDefinitions.add(beanName);
this.beanDefinitionNames = updatedDefinitions;
if (this.manualSingletonNames.contains(beanName)) {
Set<String> updatedSingletons = new LinkedHashSet<String>(this.manualSingletonNames);
updatedSingletons.remove(beanName);
this.manualSingletonNames = updatedSingletons;
}
}
}
else {
// Still in startup registration phase
this.beanDefinitionMap.put(beanName, beanDefinition);
this.beanDefinitionNames.add(beanName);
this.manualSingletonNames.remove(beanName);
}
this.frozenBeanDefinitionNames = null;
}
if (oldBeanDefinition != null || containsSingleton(beanName)) {
resetBeanDefinition(beanName);
}
}
注册的关键在于将beanDefinition 放入map中,其中key为beanName,value为beanDefinition。
2.4.2 自定义标签的解析
自定义标签的解析关键有如下三个步骤:
1. 获取nameSpaceUri
2.根据nameSpaceUri获得相应的NamespaceHandler
3.调用NamespaceHandler.parse方法得到相应的BeanDefinition
2.4.2.1 获取namespaceUri
这步就是获取Element的NameSpaceUrI属性
2.4.2.2 获取NamespaceHandler
获取NamespaceHandler又分为如下步骤
- 获取namespaceHandlerResolver
- 通过namespaceHandlerResolver获取NamespaceHandler
2.4.2.2.1 获取namespaceHandlerResolver
获取XmlReaderContext的namespaceHandlerResolver属性,该属性是在解析配置文件创建XmlReaderContext时设置的默认值,代码如下
public XmlReaderContext createReaderContext(Resource resource) {
return new XmlReaderContext(resource, this.problemReporter, this.eventListener,
this.sourceExtractor, this, getNamespaceHandlerResolver());
}
public NamespaceHandlerResolver getNamespaceHandlerResolver() {
if (this.namespaceHandlerResolver == null) {
this.namespaceHandlerResolver = createDefaultNamespaceHandlerResolver();
}
return this.namespaceHandlerResolver;
}
protected NamespaceHandlerResolver createDefaultNamespaceHandlerResolver() {
return new DefaultNamespaceHandlerResolver(getResourceLoader().getClassLoader());
}
2.4.2.2.2 获取NamespaceHandler
获取NamespaceHandler时会做如下操作:
1. 首次加载的时候,读取"META-INF/spring.handlers"下的文件,初始化handlerMappings 其中key 为NameSpaceUrI,value为NamespaceHandler className
2. 取NameSpaceUrI相应的NamespaceHandler className,并通过反射的方式实例化NamespaceHandler
3. 调用NamespaceHandler的init方法,初始化NamespaceHandler(此时会注册BeanDefinitionParser)
4. 初始化NamespaceHandler完成后覆盖缓存,并返回(最后handlerMappings key为namespaceUri,value为初始化后的NamespaceHandler)
以spring-aop-4.3.18.RELEASE.jar 看下spring.handlers
http\://www.springframework.org/schema/aop=org.springframework.aop.config.AopNamespaceHandler
初始化NameSpaceHandler的部分代码如下:
public NamespaceHandler resolve(String namespaceUri) {
Map<String, Object> handlerMappings = getHandlerMappings();
Object handlerOrClassName = handlerMappings.get(namespaceUri);
if (handlerOrClassName == null) {
return null;
}
else if (handlerOrClassName instanceof NamespaceHandler) {
return (NamespaceHandler) handlerOrClassName;
}
else {
String className = (String) handlerOrClassName;
try {
Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);
if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) {
throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri +
"] does not implement the [" + NamespaceHandler.class.getName() + "] interface");
}
NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
namespaceHandler.init();
handlerMappings.put(namespaceUri, namespaceHandler);
return namespaceHandler;
}
catch (ClassNotFoundException ex) {
throw new FatalBeanException("NamespaceHandler class [" + className + "] for namespace [" +
namespaceUri + "] not found", ex);
}
catch (LinkageError err) {
throw new FatalBeanException("Invalid NamespaceHandler class [" + className + "] for namespace [" +
namespaceUri + "]: problem with handler class file or dependent class", err);
}
}
}
这样就完成了自定义标签解析器的自定义逻辑和通用逻辑的解耦,标签解析器只需要维护自身相关的业务逻辑即可,这也是spring框架中常用的优雅设计。
2.4.2.2.3 调用NamespaceHandler.parse获取BeanDefinition
调用NamespaceHandler.parse获取BeanDefinition时,又经过如下步骤
1. 获取Element的localName(如<aop:aspectj-autoproxy expose-proxy="true"/> 中的aspectj-autoproxy)
2. 根据localName获得BeanDefinitionParser(其中BeanDefinitionParser是在获取NamespaceHandler时,在init 方法内部调用registerBeanDefinitionParser 方法赋值的,放入map中,key为localName,value 为BeanDefinitionParser)
3. 调用BeanDefinitionParser的parse 方法获得BeanDefinition(这个返回的BeanDefinition,spring 并未像解析自定义标签那样去注册,因为一般在parse时已经完成了注册)
部分代码如下
public BeanDefinition parse(Element element, ParserContext parserContext) {
return findParserForElement(element, parserContext).parse(element, parserContext);
}
/**
* Locates the {@link BeanDefinitionParser} from the register implementations using
* the local name of the supplied {@link Element}.
*/
private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
String localName = parserContext.getDelegate().getLocalName(element);
BeanDefinitionParser parser = this.parsers.get(localName);
if (parser == null) {
parserContext.getReaderContext().fatal(
"Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
}
return parser;
}
自此,我们已经完成了整个bean的解析流程。最终的结果是Spring把BeanDefinition放到缓存map中,方便以后getBean时根据BeanDefinition的信息创建Bean。
3. XmlBeanFactory 过期替代方法
XmlBeanFactory 从spring 3.1开始被标记为过期方法。那么我们可以用什么方法来替代它呢?答案是DefaultListableBeanFactory。DefaultListableBeanFactory是spring注册加载bean的默认实现,XmlBeanFactory继承了DefaultListableBeanFactory,并定义了XmlBeanDefinitionReader实现了从xml文件读取。
old
BeanFactory beanFactory=new XmlBeanFactory(new ClassPathResource("spring-config.xml"));
System.out.println(beanFactory.getBean("um"));
}
replace
DefaultListableBeanFactory defaultListableBeanFactory=new DefaultListableBeanFactory();
XmlBeanDefinitionReader xmlBeanDefinitionReader=new XmlBeanDefinitionReader(defaultListableBeanFactory);
xmlBeanDefinitionReader.loadBeanDefinitions(new ClassPathResource("spring-config.xml"));
System.out.println(defaultListableBeanFactory.getBean("um"));
}
总结
通过分析XmlBeanFactory解析注册bean的过程中,我们发现spring的代码在执行的时候,并不是一步或者一个类就到位的,而是如抽丝剥茧一样,一层层调用,才得以见到其核心逻辑。
把一个复杂的逻辑,简化成一个个的逻辑单元,由这些个逻辑单元,搭建起整个方法的执行脉络。同时其可扩展的设计,充分体现了程序设计的开闭原则及单一职责原则
。在阅读源码的过程中,我们在深入原理的同时,更可以学习其优秀的设计模式。内外兼修,立于不败。
解析bean完成后,就可以创建并使用bean了。稍后,我们将继续解读bean创建的源码,看下bean创建的过程到底发生了什么。下一篇-spring源码系列-Bean的创建流程
参考文章
Spring 源码深度解析-郝佳
网友评论