EDITORIAL words
Background and resources:
Cao worker said Spring Boot source (5) - how to read from the bean properties file
Cao worker said Spring Boot source (6) - Spring bean from how to resolve the xml file
Project code address Mind Mapping address
Engineering Schematic:
Overview
We see this title, I do not know my heart did not have an answer? We Think again, xml file has everything it?
Such a thought, spring xml file, the contents really a lot, estimated that many elements you have not configured, especially in the past two years out new programmers, estimates are Tucao, and now are not annotated yet, who also with xml? But in fact, whether it is xml, or notes, all configuration information, but different manifestations of it, I read the first few students say, should know that we use json, properties file written configuration bean.
Therefore, the specific form is not important, xml and notes only the two most common expression fills us this example to explain it to xml.
xml in, but it is still very structured, the various elements, all in accordance with the namespace share obviously white, I have listed the following tables:
namespace | element |
---|---|
useful | constant、property-path、list、set、map、properties |
context | property-placeholder、property-override、annotation-config、component-scan、load-time-weaver、spring-configured、mbean-export、mbean-server |
beans | import、bean、alias |
task | annotation-driven、scheduler、scheduled-tasks、executor |
cache | advice、annotation-driven |
aop | config、scoped-proxy、aspectj-autoproxy |
We see it, spring is actually support for xml is the most comprehensive, and some notes, xml basically have. Spent six years as a farmer's yard, I found a lot of elements I have not configured, not to mention familiar with its inherent principle of. But then, we must not forget that today's title, so many elements, do not do anything in common? spring parse these elements, in the end is how to achieve it, not to mention how these elements take effect, read things always need to keep the place up it, then, is how to store it?
We will select some elements to explain. We say this, first explain xml analytical methods used in spring; then start from the util namespace, pick a constant element of this in-depth explanation.
xml analytical methods employed in the spring
Last lecture, we talked about, is how to resolve xml spring elements, I think of a way from the spring source in today, it used to parse xml skeleton code to extract a bit, so the following is the basic, such as for the following xml file, we intend to traverse it again:
test-xml-read.xml:
<?xml version="1.0" encoding="UTF-8"?>
<f:table xmlns:f="http://www.w3school.com.cn/furniture"
xmlns:t="http://www.w3school.com.cn/t">
<f:name>African Coffee Table</f:name>
<f:width>80</f:width>
<f:length>120</f:length>
<t:abc></t:abc>
</f:table>
Well, spring code in the skeleton, probably as follows:
package org.springframework.bootstrap.sample;
import lombok.extern.slf4j.Slf4j;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.*;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
@Slf4j
public class XmlSimpleUse {
public static void main(String[] args) {
//读取xml文件
URL url = Thread.currentThread().getContextClassLoader()
.getResource("test-xml-read.xml");
InputStream inputStream = url.openStream();
//将流转变为InputSource,在后续xml解析使用
InputSource inputSource = new InputSource(inputStream);
DocumentBuilderFactory factory = createDocumentBuilderFactory();
DocumentBuilder docBuilder = factory.newDocumentBuilder();
// 可选,设置实体解析器,其实就是:你可以自定义去哪里加载xsd/dtd文件
docBuilder.setEntityResolver(new EntityResolver() {
@Override
public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
return null;
}
});
// 设置回调处理器,当解析出现错误时,(比如xsd里指定了不能出现a元素,然后xml里出现了a元素)
docBuilder.setErrorHandler(null);
//解析xml文件,获取到Document,代表了整个文件
Document document = docBuilder.parse(inputSource);
// 获取根元素
Element root = document.getDocumentElement();
log.info("root is {}",root);
//获取根元素下的每个child元素
NodeList nodeList = root.getChildNodes();
for (int i = 0; i < nodeList.getLength(); i++) {
Node node = nodeList.item(i);
if (node instanceof Element) {
Element ele = (Element) node;
log.info("ele:{}",ele);
}
}
}
protected static DocumentBuilderFactory createDocumentBuilderFactory() {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setValidating(true);
// Enforce namespace aware for XSD...
factory.setNamespaceAware(true);
return factory;
}
}
Output is as follows:
21:38:19.638 [main] INFO o.s.bootstrap.sample.XmlSimpleUse - root is [f:table: null]
21:38:19.653 [main] INFO o.s.bootstrap.sample.XmlSimpleUse - ele:[f:name: null]
21:38:19.653 [main] INFO o.s.bootstrap.sample.XmlSimpleUse - ele:[f:width: null]
21:38:19.653 [main] INFO o.s.bootstrap.sample.XmlSimpleUse - ele:[f:length: null]
21:38:19.654 [main] INFO o.s.bootstrap.sample.XmlSimpleUse - ele:[t:abc: null]
We can see the above demo code, without reliance on any type of spring, substantially reducing the spring substantially when parsing process xml, in the spring extra details, there are two:
Custom entityResolver
docBuilder.setEntityResolver, this part, we are above the default implementation.
大家看我们前面的xml,有一定了解的同学可能知道,前面定义了两个namespace,语法一般是下面这样的:
xmlns:namespace-prefix="namespaceURI"
所以,我们这边的两个namespace,前缀分别是f、t,内容分别是:
http://www.w3school.com.cn/furniture、http://www.w3school.com.cn/t
但是,我们一般xml文件是有格式要求的,比如spring里,比如
那,这个约束是在哪里呢?在namespaceURI
对应的dtd/xsd等文件中。
像上面截图这样,就是:
这一句,定义一个命名空间
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
// 下面这个,你要当成key/value来理解,key就是:http://www.springframework.org/schema/context,
//value,就是对应的xsd文件
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"
有了上面的基础知识,再来说那个接口:
public interface EntityResolver {
// 一般传入的systemId即为后边这样的:http://www.springframework.org/schema/context/spring-context.xsd
public abstract InputSource resolveEntity (String publicId,
String systemId)
throws SAXException, IOException;
}
这个接口呢,就是让我们自定义一个方法,来解析外部xml实体,一般传入的参数如下:
即,publicId为null,systemId为xsd的uri,这个uri一般是可以通过网络获取的,比如:
http://www.springframework.org/schema/context/spring-context.xsd
但是,spring是自定义了自己的entityResolver
,实现类为:org.springframework.beans.factory.xml.ResourceEntityResolver
。
这个类,会在本地寻找对应的xsd文件,主要逻辑就是去查找classpath下的META-INF/spring.schemas
,我们可以看看spring-beans包内的该文件:
spring为什么要自定义EntityResolver呢,spring为啥要在本地找呢,原因是:
如果不自定义,jdk的dom解析类,就会直接使用http://www.springframework.org/schema/context/spring-context.xsd
这个东西,去作为URL,建立socket网络连接来获取。而部分环境,比如生产环境,基本是外网隔离的,你这时候是没办法去下载这个xsd文件的,岂不是就没法校验xml文件的语法、格式了吗?
所以,spring要将这个外部的xsd引用,转为在classpath下的查找。
自定义元素解析逻辑
这部分,大家再看下之前的骨架代码:
Document document = docBuilder.parse(inputSource);
Element root = document.getDocumentElement();
log.info("root is {}",root);
NodeList nodeList = root.getChildNodes();
for (int i = 0; i < nodeList.getLength(); i++) {
Node node = nodeList.item(i);
//遍历每个元素,我们这里只是简单输出
if (node instanceof Element) {
Element ele = (Element) node;
log.info("ele:{}",ele);
}
}
骨架代码里,遍历每个元素,在spring里,遍历到每个ele时,要去判断对应的namespace,如果是默认的,交给xxx处理;如果不是默认的,要根据namespace找到对应的namespacehandler,具体大家可以看看上一节:
曹工说Spring Boot源码(6)-- Spring怎么从xml文件里解析bean的
我们在前面说了,本讲只先挑一个元素来讲解,即
util-constant元素详解
用法
<?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:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
<util:constant id="chin.age" static-field=
"java.sql.Connection.TRANSACTION_SERIALIZABLE"/>
</beans>
以上,即定义了一个常量bean,这个bean的值,就是static-field
中指定的,这里是java.sql.Connection#TRANSACTION_SERIALIZABLE
,值为8。
/**
* A constant indicating that
* dirty reads, non-repeatable reads and phantom reads are prevented.
* This level includes the prohibitions in
* <code>TRANSACTION_REPEATABLE_READ</code> and further prohibits the
* situation where one transaction reads all rows that satisfy
* a <code>WHERE</code> condition, a second transaction inserts a row that
* satisfies that <code>WHERE</code> condition, and the first transaction
* rereads for the same condition, retrieving the additional
* "phantom" row in the second read.
*/
int TRANSACTION_SERIALIZABLE = 8;
我们的测试代码如下:
package org.springframework.utilnamespace;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.util.MyFastJson;
import java.util.List;
import java.util.Map;
@Slf4j
public class TestConstant {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"classpath:util-namespace-test-constant.xml"},false);
context.refresh();
Map<String, Object> map = context.getDefaultListableBeanFactory().getAllSingletonObjectMap();
log.info("singletons:{}", JSONObject.toJSONString(map));
List<BeanDefinition> list =
context.getBeanFactory().getBeanDefinitionList();
MyFastJson.printJsonStringForBeanDefinitionList(list);
// Object bean = context.getBean("chin.age");
// System.out.println("bean:" + bean);
}
}
注意,这里,我们没有调用getBean
等方法,我们只是简单地,采用json输出了其bean definition
,输出如下:
{
"abstract":false,
"autowireCandidate":true,
"autowireMode":0,
// bean的class,好像是个factory,不是个int啊,我们那个bean,按理说,是int类型的
"beanClass":"org.springframework.beans.factory.config.FieldRetrievingFactoryBean",
"beanClassName":"org.springframework.beans.factory.config.FieldRetrievingFactoryBean",
"constructorArgumentValues":{
"argumentCount":0,
"empty":true,
"genericArgumentValues":[],
"indexedArgumentValues":{}
},
"dependencyCheck":0,
"enforceDestroyMethod":true,
"enforceInitMethod":true,
"lazyInit":false,
"lenientConstructorResolution":true,
"methodOverrides":{
"empty":true,
"overrides":[]
},
"nonPublicAccessAllowed":true,
"primary":false,
"propertyValues":{
"converted":false,
"empty":false,
// 这个是我们给这个常量bean设置的值,被放在了property
"propertyValueList":[
{
"converted":false,
"name":"staticField",
"optional":false,
"value":"java.sql.Connection.TRANSACTION_SERIALIZABLE"
}
]
},
"prototype":false,
"qualifiers":[],
"resolvedAutowireMode":0,
"role":0,
"scope":"",
"singleton":true,
"synthetic":false
}
我们放开前面注释的两行代码:
Object bean = context.getBean("chin.age");
System.out.println("bean:" + bean);
output:
bean:8
有些同学估计有点蒙了,不要慌,这里简单说下结论,因为
org.springframework.beans.factory.config.FieldRetrievingFactoryBean
。
当我们去getBean的时候,spring发现其为factory bean,就会调用这个工厂bean的工厂方法,去生产。
所以,这里呢,bean是谁?是那个工厂org.springframework.beans.factory.config.FieldRetrievingFactoryBean
,而不是这个工厂的产品:数字8。
具体的解析过程
根据namespaceUri获得对应的namespaceHandler
从之前的骨架入手,spring里也有类似的代码:
DefaultBeanDefinitionDocumentReader#parseBeanDefinitions
// 这个方法,里面可以看到通过root得到了children,然后对children遍历
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 {
// 这里,如果是<util:constant>,因为不是默认命名空间,所以走这里。
delegate.parseCustomElement(ele);
}
}
}
}
else {
delegate.parseCustomElement(root);
}
}
进入BeanDefinitionParserDelegate#parseCustomElement(org.w3c.dom.Element)
:
public BeanDefinition parseCustomElement(Element ele) {
return parseCustomElement(ele, null);
}
public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
String namespaceUri = getNamespaceURI(ele);
//这里,通过namespaceUri,查找对应的handler,我们这里,会得到:org.springframework.beans.factory.xml.UtilNamespaceHandler
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
if (handler == null) {
error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
return null;
}
// 调用org.springframework.beans.factory.xml.UtilNamespaceHandler,解析constant元素
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}
NamespaceHandler概览
好了,我们看看这个UtilNamespaceHandler
:
public class UtilNamespaceHandler extends NamespaceHandlerSupport {
public void init() {
registerBeanDefinitionParser("constant", new ConstantBeanDefinitionParser());
registerBeanDefinitionParser("property-path", new PropertyPathBeanDefinitionParser());
registerBeanDefinitionParser("list", new ListBeanDefinitionParser());
registerBeanDefinitionParser("set", new SetBeanDefinitionParser());
registerBeanDefinitionParser("map", new MapBeanDefinitionParser());
registerBeanDefinitionParser("properties", new PropertiesBeanDefinitionParser());
}
private static class ConstantBeanDefinitionParser extends AbstractSimpleBeanDefinitionParser {
@Override
protected Class getBeanClass(Element element) {
return FieldRetrievingFactoryBean.class;
}
@Override
protected String resolveId(Element element, AbstractBeanDefinition definition, ParserContext parserContext) {
String id = super.resolveId(element, definition, parserContext);
if (!StringUtils.hasText(id)) {
id = element.getAttribute("static-field");
}
return id;
}
}
...省略
}
这里,很明显,UtilNamespaceHandler
定义了每个元素,该由什么类来处理。
namespaceHandler如何找到指定元素的parser
我们还是接着前面的代码handler.parse(Element element, ParserContext parserContext)
往下看,是不是这样吧:
父类 NamespaceHandlerSupport#parse
public BeanDefinition parse(Element element, ParserContext parserContext) {
//这里,继续调用了本类的另一个方法来获取Parser
return findParserForElement(element, parserContext).parse(element, parserContext);
}
// 这里就是上面调用的方法,来获取Parser
private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
String localName = parserContext.getDelegate().getLocalName(element);
// 这里的parser是一个map,map的元素就是来自于子类的init方法
BeanDefinitionParser parser = this.parsers.get(localName);
if (parser == null) {
parserContext.getReaderContext().fatal(
"Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
}
return parser;
}
这两个方法是在父类中实现的,整体来说,NamespaceHandlerSupport
中维护了一个map,里面保存本namespace下,具体的元素及其对应的parser。
/**
* Stores the {@link BeanDefinitionParser} implementations keyed by the
* local name of the {@link Element Elements} they handle.
*/
private final Map<String, BeanDefinitionParser> parsers =
new HashMap<String, BeanDefinitionParser>();
我们看看,这个parser是什么时候存了东西进去的吧?通过find usage,发现如下方法会进行put操作:
NamespaceHandlerSupport#registerBeanDefinitionParser
protected final void registerBeanDefinitionParser(String elementName, BeanDefinitionParser parser) {
this.parsers.put(elementName, parser);
}
这个方法还比较熟悉,因为在前面出现过了:
public class UtilNamespaceHandler extends NamespaceHandlerSupport {
private static final String SCOPE_ATTRIBUTE = "scope";
// 这个init方法,就是在之前通过namespaceUri来获取对应的handler时,初始化的
@override
public void init() {
registerBeanDefinitionParser("constant", new ConstantBeanDefinitionParser());
registerBeanDefinitionParser("property-path", new PropertyPathBeanDefinitionParser());
registerBeanDefinitionParser("list", new ListBeanDefinitionParser());
registerBeanDefinitionParser("set", new SetBeanDefinitionParser());
registerBeanDefinitionParser("map", new MapBeanDefinitionParser());
registerBeanDefinitionParser("properties", new PropertiesBeanDefinitionParser());
}
parser实现类概述
通过上一节的讲解,我们知道了UtilNamespaceHandler下的元素,及其对应的Parser。我们看看其类图(图小,可在单独tab查看):
我们先通过其实现的接口,来了解其核心功能:
package org.springframework.beans.factory.xml;
import org.w3c.dom.Element;
import org.springframework.beans.factory.config.BeanDefinition;
/**
* 该接口主要被DefaultBeanDefinitionDocumentReader使用,来处理顶级的,非默认命名空间下的的顶级元素(直接在<beans></beans>下)
* Interface used by the {@link DefaultBeanDefinitionDocumentReader} to handle custom,
* top-level (directly under {@code <beans/>}) tags.
*
* 实现类可以自由地通过该元素中的元数据,来转换为任意多个BeanDefinition。(比如<context:component-scan></>)
* <p>Implementations are free to turn the metadata in the custom tag into as many
* {@link BeanDefinition BeanDefinitions} as required.
*
* Dom解析器,通过元素所在的命名空间,找到对应的NamespaceHandler,再从NamespaceHandler中找到对应的BeanDefinitionParser
* <p>The parser locates a {@link BeanDefinitionParser} from the associated
* {@link NamespaceHandler} for the namespace in which the custom tag resides.
*
* @author Rob Harrop
* @since 2.0
* @see NamespaceHandler
* @see AbstractBeanDefinitionParser
*/
public interface BeanDefinitionParser {
/**
* 解析指定的element,注册其返回的BeanDefinition到BeanDefinitionRegistry
* (使用参数ParserContext#getRegistry()得到BeanDefinitionRegistry)
*/
BeanDefinition parse(Element element, ParserContext parserContext);
}
大家发现接口的意义了吗,虽然前面的类,很复杂,但我们通过接口,可以马上知道其核心功能。在这里,就是解析元素,获得BeanDefinition
。
其实,到这里,基本可以回答,标题所提出的问题了,Spring解析xml,得到了什么?
从这个接口,可以知道,得到了BeanDefinition
!
UtilNamespaceHandler.ConstantBeanDefinitionParser
我们具体看看
AbstractBeanDefinitionParser#parse
public final BeanDefinition parse(Element element, ParserContext parserContext) {
// 这个方法就是个骨架,用了模板方法设计模式,
// 1:调用另一个方法,获取BeanDefinition
AbstractBeanDefinition definition = parseInternal(element, parserContext);
if (definition != null && !parserContext.isNested()) {
// 2:获得id
String id = resolveId(element, definition, parserContext);
// 3:获得别名
String name = element.getAttribute(NAME_ATTRIBUTE);
String[] aliases = StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray(name));
// 4:将beanDefinition的id、别名、definition等放进一个holder类
BeanDefinitionHolder holder = new BeanDefinitionHolder(definition, id, aliases);
// 5:通过parserContext,得到BeanDefinitionRegistry,注册本bean进去
registerBeanDefinition(holder, parserContext.getRegistry());
if (shouldFireEvents()) {
BeanComponentDefinition componentDefinition = new BeanComponentDefinition(holder);
postProcessComponentDefinition(componentDefinition);
parserContext.registerComponent(componentDefinition);
}
}
return definition;
}
我们上面,第一步的注释那里,说用了模板设计模式,因为这个parseInternal是个抽象方法:
protected abstract AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext);
具体的实现,还在子类,鉴于这个类的层次有点深,我们再看看类图:
这个parseInternal
就在`AbstractSingleBeanDefinitionParser
:
// 这个方法也足够简单,就是构造一个BeanDefinition,用了builder设计模式。
protected final AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
String parentName = getParentName(element);
// ConstantBeanDefinitionParser覆盖了这个方法
Class<?> beanClass = getBeanClass(element);
if (beanClass != null) {
builder.getRawBeanDefinition().setBeanClass(beanClass);
}
else {
String beanClassName = getBeanClassName(element);
if (beanClassName != null) {
builder.getRawBeanDefinition().setBeanClassName(beanClassName);
}
}
builder.getRawBeanDefinition().setSource(parserContext.extractSource(element));
if (parserContext.isDefaultLazyInit()) {
// Default-lazy-init applies to custom bean definitions as well.
builder.setLazyInit(true);
}
// 子类.AbstractSimpleBeanDefinitionParser重写了这个方法
doParse(element, parserContext, builder);
return builder.getBeanDefinition();
}
这里的getBeanClass
,在ConstantBeanDefinitionParser被重写了,返回了一个工厂类class:
private static class ConstantBeanDefinitionParser extends AbstractSimpleBeanDefinitionParser {
@Override
protected Class getBeanClass(Element element) {
return FieldRetrievingFactoryBean.class;
}
...
}
而doParse
,也在ConstantBeanDefinitionParser
的父类中AbstractSimpleBeanDefinitionParser
进行了重写:
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
NamedNodeMap attributes = element.getAttributes();
// 这里,获取element下的属性
for (int x = 0; x < attributes.getLength(); x++) {
Attr attribute = (Attr) attributes.item(x);
if (isEligibleAttribute(attribute, parserContext)) {
String propertyName = extractPropertyName(attribute.getLocalName());
// 通过attribute.getValue()获取属性值,propertyName为属性名,加入到BeanDefinition
builder.addPropertyValue(propertyName, attribute.getValue());
}
}
postProcess(builder, element);
}
回头看看我们的xml:
<util:constant id="chin.age" static-field=
"java.sql.Connection.TRANSACTION_SERIALIZABLE"/>
再看看我们debug时,此时的beanDefinition:
这里,获取了beanDefinition后,就是进入到本小节开始的地方,去进行beanDefinition的注册了。
总的来说,
public class FieldRetrievingFactoryBean
implements FactoryBean<Object>, BeanNameAware, BeanClassLoaderAware, InitializingBean
这就是一个工厂bean。工厂bean在后续怎么被使用的,留待下一篇。
总结
本篇源码在:
https://gitee.com/ckl111/spring-boot-first-version-learn/tree/master/all-demo-in-spring-learning/spring-xml-demo/src/main/java/org/springframework/utilnamespace中的TestConstant.java
xml的解析demo在:
我发现,一个东西,自己看懂可能还行,相对容易点,但是要把这个东西写出来,却是一个大工程。。。看似简单的元素解析,你要把它讲清楚,还真的要点篇幅,哈哈,所以,这也是为什么本篇比较长的原因。
总的来说,再次回答标题,spring到底得到了什么,得到了beanDefinition,本篇里,只得到了一个beanDefinition,还是工厂类型的;后面,我们会看到其他多种多样的元素解析方式。
ok,就到这里,如果大家觉得有帮助,记得点赞。