首先感谢 《Spring源码深度解析》郝佳,让我对spring源码有了更深的理解,本篇文章主要是对《Spring源码深度解析》解读的笔记以及自己对书本解读后的理解
4、获取XML的验证模式
a、XML 文件的验证的模式是保证了XML文件的正确性,比较常用的验证模式有两种:DTD和XSD
b、验证模式的获取
是spring是通过getValidationModeForResource方法来获取对应资源的验证模式
//得到验证模式
protected int getValidationModeForResource(Resource resource) {
int validationModeToUse = getValidationMode();
if (validationModeToUse != VALIDATION_AUTO) {
return validationModeToUse;
}
int detectedMode = detectValidationMode(resource);
if (detectedMode != VALIDATION_AUTO) {
return detectedMode;
}
return VALIDATION_XSD;
}
这个方法很简单,如果设置了验证模式则使用设定的验证模式(XmlBeanDefinitionReader中的setValidationMode方法设定),否则使用detectValidationMode方法来自用检测验证模式,在detectValidationMode方法中有委托给了专门的处理类XmlValidationModeDetector的detectValidationMode方法处理,源码如下:
public int detectValidationMode(InputStream inputStream) throws IOException
{
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
byte byte0;
try
{
boolean isDtdValidated = false;
String content;
while((content = reader.readLine()) != null)
{
content = consumeCommentTokens(content);
//如果读取的行是空或者是注释则略过
if(inComment || !StringUtils.hasText(content))
continue;
if(hasDoctype(content))
{
isDtdValidated = true;
break;
}
//读取到<开始符号,验证迷失一定会在开始符号之前
if(hasOpeningTag(content))
break;
}
byte0 = ((byte)(isDtdValidated ? 2 : 3));
}
catch(CharConversionException _ex)
{
reader.close();
return 1;
}
reader.close();
return byte0;
Exception exception;
exception;
reader.close();
throw exception;
}
5、获取Document
a、经过验证后就是进行Document加载了,调用的是DocumentLoader接口的实现类的DefaultDocumentLoader发loadDocument方法,源码如下
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((new StringBuilder()).append("Using JAXP provider [").append(factory.getClass().getName())
.append("]").toString());
//创建DocumentBuilder
DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
//解析inputSource得到Document对象
return builder.parse(inputSource);
}
通过SAX解析XML对象,这里说下entityResolver参数,这个参数是通过getEntityResolver得到的
b、EntityResolver 用法
官方文档解释EntityResolver 是:如果SAX应用程序需要实现自定义处理外部实体,ze必须实现此接口并使用setEntityResolver方法向SAX驱动器注册一个实例也就是说对于解析一个XML,SAX首先读取给XML文档声明,根据声明寻找相应的DTD,以便于文档验证,可是下载过程比较漫长,还可能以为网络原因出错。EntityResolver 的作用是项目本身就可以提供一个如何寻找DTD声明的方法,即由程序来实现寻找DTD声明的过程,比如我们可以吧DTD文件放在项目的某处,在实现是读取并返回给SAX即可
EntityResolver 的接口声明
public abstract InputSource resolveEntity (String publicId, String systemId)throws SAXException, IOException;
EntityResolver的实现类DelegatingEntityResolver
@Override
public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
if (systemId != null) {
if (systemId.endsWith(DTD_SUFFIX)) {
//如果是dtd从这里
return this.dtdResolver.resolveEntity(publicId, systemId);
}
else if (systemId.endsWith(XSD_SUFFIX)) {
//通过调用META-INF/Spring.schemas解析
return this.schemaResolver.resolveEntity(publicId, systemId);
}
}
return null;
}
不同的验证模式Spring使用不同的解析器解析
6、bean的注册
a、当把文件转换为Document对象后,接下来就是提取注册了,使用的是XmlBeanDefinitionReader类的registerBeanDefinitions方法,源码如下
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
//使用DefaultBeanDefinitionDocumentReader实例化BeanDefinitionDocumentReader
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
//将环境变量设置其中
documentReader.setEnvironment(this.getEnvironment());
//实例化BeanDefinitionDocumentReader 时候会将BeanDefinitionRegistry传入,默认使用继承自DefaultListtableBeanFactory的子类
//记录统计前BeanDefinition的加载个数
int countBefore = getRegistry().getBeanDefinitionCount();
//加载及注册Bean
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
//记录本次加载BeanDefinition 个数
return getRegistry().getBeanDefinitionCount() - countBefore;
}
其中doc参数是上面loadDocument加载转换出来的。在这个方法中很好的应用了面向对象中单一职责的原则,将逻辑处理委托给单一的类进行处理, 而这个逻辑处理类就是BeanDefinitionDocumentReader。BeanDefinitionDocumentReader 是一个接口,而实例化工作是在实现类DefaultBeanDefinitionDocumentReader的createBeanDefinitionDocumentReader()中完成的。进入DefaultBeanDefinitionDocumentReader后发现这个方法的重要之一就是提取root,以便于再次将root作为参数继续BeanDefinition的注册
//DefaultBeanDefinitionDocumentReaderd的方法源码
@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
logger.debug("Loading bean definitions");
Element root = doc.getDocumentElement();
doRegisterBeanDefinitions(root);
}
protected void doRegisterBeanDefinitions(Element root) {
//处理profile属性
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
if (StringUtils.hasText(profileSpec)) {
Assert.state(this.environment != null, "Environment must be set for evaluating profiles");
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
if (!this.environment.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;
}
对于上面的代码我们可以看出首先是对profile的处理然后是解析,在跟进preProcessXml(root)或者postProcessXml(root)发现代码是空的,为什么是这样呢?因为就像面向对象设计学中常说的一句话,一个类要么是面向对象继承设计要么是final修饰,DefaultBeanDefinitionDocumentReaderd 中并没有final修饰,所以是面向继承二设计的。如果继承自DefaultBeanDefinitionDocumentReaderd的子类在bean解析前后做一些处理的话那个需要重写这两个方法
b、profile属性的使用
有了这个属性我们就可以同时在配置文件中部署两套配置来适应于生产环境和开发环境,这样可以方便的进行切换开发、部署环境,最常用的就是更换不同的数据库
c、解析并注册BeanDefinition
处理完profile 后就可以进行XML的的读取了,跟踪代码进入parseBeanDefinitions();源码如下
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
//对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)) {
//对bean的处理
parseDefaultElement(ele, delegate);
}
else {
//对bean的处理
delegate.parseCustomElement(ele);
}
}
}
}
else {
delegate.parseCustomElement(root);
}
}
在Spring的XML配置里面有两大类Bean声明,一个默认的
<bean id="text" class="text.TextBean"/>
另一种就是
<tx:annotation-driven/>
两种方式的读取及解析差别是非常大的,如果采用Spring默认的配置,Spring当热知道怎么做,如果是自定义的,那个需要用户实现一些接口及配置了。 对于根节点或者子节点如果是默认命名空间的话采用parseDefaultElement方法进行解析,否则使用delegate.parseCustomElement方法对自定义命名进行解析。判断是否默认命名空间还是自定义命名空间的办法其实是使用node.getNamespaceURI()获取命名空间。并与Spring中固定的命名空间http://www.springframework.org/schema/beans进行比对。如果一致则是默认,否则认为是自定义的。对于标签的解析在下一节“默认标签的解析”中讨论
容器的基本实现流程总结(起点是XmlBeanFactory):
①:先将配置配置文件封装成Resource类
②:对封装好的Resource类使用EncodedResource类进行编码处理
③:在XmlBeanDefinitionReader调用loadBeanDefinitions方法进行加载在并注册bean,而在loadBeanDefinitions方法中实际调用的是doLoadBeanDefinitions方法(逻辑核心)
④:获取验证方式,并对Xml进行验证(getValidationModeForResource)
⑤:获取Document对象(doLoadDocument)
⑥:解析并注册bean(registerBeanDefinitions方法最终跟踪到parseBeanDefinitions方法)