分析调用栈
Spring 通过读取 xml 配置文件注册 bean ,通过工厂可以获取注册的 bean,示例代码:
XmlBeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
Object main = beanFactory.getBean("main");
System.out.println(main);
配置文件 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="main" class="com.pine.Main" />
</beans>
在上述 XmlBeanFactory 中有一个解析器,通过该解析器解析 xml 配置文件:
BeanDefinition 类型是 bean 标签在 JVM 中的存在形式
进入loadBeanDefinitions
方法中,重点是331
行开始的这一段:
try {
// 获取配置文件的输入流
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
// InputSource 用于 xml 解析,类似工具类
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
// 执行解析逻辑
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
finally {
inputStream.close();
}
}
在 Spring 源码中有一个惯例,就是 do… 方法一般是真正执行具体逻辑的方法
继续进入 doLoadBeanDefinitions 方法,可以看到是一个 try 后面很多 catch ,只需要关注 try 里的内容就可以了,catch里无非是一些异常处理。这个方法中需要关注的是 registerBeanDefinitions 方法,继续进入它的重载方法,再来到 doRegisterBeanDefinitions 方法。
这个方法中主要有两个部分,上面的 if 块和下面的 parseBeanDefinitions 方法。 if 块的作用是处理不同的 profile (多环境),parseBeanDefinitions 才是解析标签。
这个方法的注释及方法体如下:
/**
* Parse the elements at the root level in the document:
* "import", "alias", "bean".
* @param root the DOM root element of the document
*/
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 {
delegate.parseCustomElement(ele);
}
}
}
}
else {
delegate.parseCustomElement(root);
}
}
bean 标签是 Spring 中默认自带的元素,所以先看 parseDefaultElement 方法,再进入 processBeanDefinition 方法,继续来到 parseBeanDefinitionElement 方法。
分析解析方法
至此,终于来到了最终执行解析逻辑的方法!
1)首先是前两行:
// 解析 id 属性
String id = ele.getAttribute(ID_ATTRIBUTE);
// 解析 name 属性
String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
很浅显易懂,就是解析 id 和 name 这两个属性。
2)然后是处理别名:
// 处理别名
List<String> aliases = new ArrayList<>();
if (StringUtils.hasLength(nameAttr)) {
String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
aliases.addAll(Arrays.asList(nameArr));
}
3)接着兼容了 bean 标签中没有 id 的情况(这里也说明了为什么 bean 可以不指定 id):
String beanName = id;
if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
beanName = aliases.remove(0);
……
}
4)然后是当前方法的重载方法 parseBeanDefinitionElement,
4.1)这个方法中首先解析了 class 和 parent 属性:
String className = null;
if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
}
String parent = null;
if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
parent = ele.getAttribute(PARENT_ATTRIBUTE);
}
4.2)然后是 lookup-method、replaced-method 等子标签:
// 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);
5)最后是一个 if 块,这个 if 块主要是处理了 id , name 都为空的情况,会根据算法为当前 bean 生成一个名字,这里不细究。
这样,就搞清楚了 xml 配置文件中 bean 标签是如何解析的。
欢迎关注微信公众号co松柏
一起学习交流!