spring5/springboot2源码学习 -- xml文件的解析

概念

在较早的spring版本中,xml是配置spring唯一的方式。在如今的spring5.x版本已经spring boot2.x版本中,xml已经不再是唯一的配置手段了,甚至已经不再是推荐的手段。

但是,作为spring元老级的功能,xml配置的方式在可预见的时间内还是不会被淘汰的。所以学习一下spring中读取xml配置文件的方法也还是不错的。

用法

演示一个十分基础的用法,作为讲解原理的起点。

public class XmlBeanDefinitionTest {
  @Test
  public void test(){
    //创建一个实现了BeanDefinitionRegistry的BeanFactory实现
    //DefaultListableBeanFactory也是绝大多数场景下,BeanFactory的具体实现
    DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
    //XmlBeanDefinitionReader创建,从名字可以看出来 这个类是用来从xml文件中读取配置的
    XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
    //具体解析xml文件中的配置,并注册到BeanDefinitionRegistry中去
    reader.loadBeanDefinitions(new ClassPathResource("xmlBeanDefinition.xml"));
  }
}

实现

进入reader.loadBeanDefinitions(new ClassPathResource(“xmlBeanDefinition.xml”))

可以发现主要的逻辑在XmlBeanDefinitionReader.doLoadBeanDefinitions()方法中

//省略异常捕获代码
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException {
  //将xml文件转化为Document对象
  Document doc = doLoadDocument(inputSource, resource);//@1
  //解析配置
  int count = registerBeanDefinitions(doc, resource);//@2
  if (logger.isDebugEnabled()) {
    logger.debug("Loaded " + count + " bean definitions from " + resource);
  }
  return count;
}

1.将xml文件转换为Document对象

对应上面@1处的方法

protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
  return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
                                          getValidationModeForResource(resource), isNamespaceAware());
}

EntityResolver是啥

可以当做是xml文件转换为对象的一个验证模板,在spring 中有如下实现:

  • BeansDtdResolver:spring中以dtd验证模式来加载xml中的中的配置。会从当前路径下找spring-beans.dtd文件

  • PluggableSchemaResolver:spring中以scheme模式(xsd)加载配置。这个是可插入式的,spring中用的最多的是标签。这个EntityResolver应用了SPI的手法,在使用时,会先到META-INF/spring.schemas路径下读取所有的配置项,每个配置项就是类似这种:

    http\://www.springframework.org/schema/beans/spring-beans.xsd=org/springframework/beans/factory/xml/spring-beans.xsd
    

    将一个xsd的url映射到本地的类路径下

  • DelegatingEntityResolver:内部聚合了一个dtdResolver和一个schemaResolver,分别用来处理dtd模式和xsd模式的xml文件

  • ResourceEntityResolver:继承自DelegatingEntityResolver,首先用DelegatingEntityResolver去读取验证文件,如果没有读取到,则在当前路径下找

validationMode又是啥

validationMode就是上述的dtd或者xsd,是xml验证的不同模式。在spring中,使用XmlValidationModeDetector来获取验证模式,检测方法就是判断验证文件是否包含DOCTYPE,如果包含就是dtd,否则就是xsd

DocumentLoader.loadDocument()方法

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

spring中也是通过SAX解析XML文档,项创建一个DocumentBuilderFactory,在创建出DocumentBuilder,然后去解析InputSource的文件,返回一个Document对象。具体知识都是xml的,跟spring无关,这里就不再继续介绍了

2.关键:将Document文档转化为BeanDefinition,并注册到BeanDefinitionRegistry中去

对应上面的@2方法

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
  //BeanDefinitionDocumentReader用于具体解析Document
  BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
  int countBefore = getRegistry().getBeanDefinitionCount();
  //解析并注册BeanDefinition
  documentReader.registerBeanDefinitions(doc, createReaderContext(resource));//@2.1
  return getRegistry().getBeanDefinitionCount() - countBefore;
}

先看看这个createReaderContext()都干了啥

public XmlReaderContext createReaderContext(Resource resource) {
   return new XmlReaderContext(resource, this.problemReporter, this.eventListener,
         this.sourceExtractor, this, getNamespaceHandlerResolver());
}
这个NamespaceHandlerResolver又是啥
public interface NamespaceHandlerResolver {
  //将一个标签的解析映射到一个NamespaceHandler
	NamespaceHandler resolve(String namespaceUri);
}

作用就是根据一个标签找到一个具体的NamespaceHandler,由这个NamespaceHandler去具体解析标签。其默认实现DefaultNamespaceHandlerResolver也使用了SPI机制,会去classpath低下找META-INF/spring.handlers文件。比如大dubbo的jar中就有这个文件:

http\://dubbo.apache.org/schema/dubbo=com.alibaba.dubbo.config.spring.schema.DubboNamespaceHandler
http\://code.alibabatech.com/schema/dubbo=com.alibaba.dubbo.config.spring.schema.DubboNamespaceHandler

就是把标签交给DubboNamespaceHandler去解析

2.1.DocumentReader.registerBeanDefinitions()方法

进入@2.1处的方法,然后兜兜转转,找到具体的逻辑处理方法:DocumentReader.doRegisterBeanDefinition()方法

然后再找到更具体的处理方法😂,DocumentReader.parseBeanDefinitions()

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)) {
            //@2.1.1解析默认标签
						parseDefaultElement(ele, delegate);
					}
					else {
            //@2.1.2解析自定义标签
						delegate.parseCustomElement(ele);
					}
				}
			}
		}
		else {
			delegate.parseCustomElement(root);
		}
	}

转来转去,重要找到了,真的去处理xml里面各个标签的方法了。

解析默认标签

@4的方法用于解析默认标签。

主要的逻辑在DefaultBeanDefinitionDocumentReader.parseDefaultElement()方法中

private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
  //解析import标签
  if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
    importBeanDefinitionResource(ele);
  }
  //解析alias标签
  else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
    processAliasRegistration(ele);
  }
  //解析bean标签
  else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
    processBeanDefinition(ele, delegate);
  }
  //解析嵌套的beans标签
  else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
    // recurse
    doRegisterBeanDefinitions(ele);
  }
}
解析import标签

一般使用如:

用于导入别的配置文件。其解析非常简单:

  • 找到对应的文件
  • 调用前面的reader的loadBeanDefinitions方法
解析alias标签

类似这样

<bean id="userInfo" class="com.pk.study.spring.UserInfo"/>
<alias name="userInfo" alias="hahaha,aaa"/>

解析也很简单,就是调用AliasRegistry接口的registerAlias方法注册一下

解析bean标签

这个是最复杂的标签,有茫茫多的属性配置。所以我单独用一篇文章来分析😂

请见:xml文件的解析之bean标签的解析

2.1.2.解析自定义标签

如果还不清楚如果在spring中使用自定义的标签,可以看下这篇:https://blog.csdn.net/yuxiuzhiai/article/details/104175364

结语

在spring中,加载xml文件,着实是非常复杂了。。。

(水平有限,最近在看spring源码,分享学习过程,希望对各位有点微小的帮助。如有错误,请指正~)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1. 添加logback-spring.xml文件 在src/main/resources目录下创建一个名为logback-spring.xml的文件,将下面的代码复制并粘贴进去。 ``` <?xml version="1.0" encoding="utf-8" ?> <configuration> <!-- 确定打印的日志级别 --> <timestamp key="byDay" datePattern="yyyyMMdd" /> <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{35} -%msg%n</pattern> </encoder> </appender> <!-- 输出到本地文件 --> <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <file>${LOG_PATH}/springboot2.log</file> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>${LOG_PATH}/springboot2.%d{yyyy-MM-dd}.log</fileNamePattern> <maxHistory>30</maxHistory> </rollingPolicy> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{35} -%msg%n</pattern> </encoder> </appender> <!-- root级别日志处理器 --> <root level="info"> <appender-ref ref="CONSOLE" /> <appender-ref ref="FILE" /> </root> <!-- 配置包日级别日志处理器 --> <logger name="com.example" level="debug" additivity="false"> <appender-ref ref="CONSOLE" /> <appender-ref ref="FILE" /> </logger> </configuration> ``` 2. 配置文件参数 在application.properties或application.yml文件中添加以下参数: - logging.config:指定logback-spring.xml配置文件路径,如果不指定将使用默认的日志配置。 - logging.path:指定日志文件存放路径,必须是一个相对目录。例如:logging.path=log 相当于在项目根目录下创建了一个log文件夹,日志将输出到该文件夹中。如果没有设置该参数,则日志将输出到控制台。 ``` #以配置文件的方式加载日志框架,默认使用logback-spring.xml作为配置文件 logging.config=classpath:logback-spring.xml #指定日志存放的相对路径 logging.path=log ``` 3. 在代码中使用日志输出 在需要输出日志的类中使用注入方式加载日志,在方法中使用对应的日志级别输出日志。 ``` @Component public class TestController { private static final Logger logger = LoggerFactory.getLogger(TestController.class); @RequestMapping("/test") public String test() { logger.debug("这是一条debug日志"); logger.info("这是一条info日志"); logger.warn("这是一条warn日志"); logger.error("这是一条error日志"); return "test"; } } ``` 运行项目,打开控制台或者查看日志文件,可以看到日志已经输出成功。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值