上文Spring XML配置默认bean标签解析对bean标签进行的了解析,此文继续解析默认标签
这三个标签其实都是对bean功能的扩展,下面一一讲解。
先查看解析的开始位置:
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
if (delegate.nodeNameEquals(ele, "import")) {
this.importBeanDefinitionResource(ele);
} else if (delegate.nodeNameEquals(ele, "alias")) {
this.processAliasRegistration(ele);
} else if (delegate.nodeNameEquals(ele, "bean")) {
this.processBeanDefinition(ele, delegate);
} else if (delegate.nodeNameEquals(ele, "beans")) {
this.doRegisterBeanDefinitions(ele);
}
}
对bean 进行定义的时候,除了可以使用id 命名,为了提供多个别名,使用alias来指定,这些所有的名称都指向同一个bean。
<bean class="com.fans.User1" name="user1"/>
<alias name="user1" alias="user2,user3"/>
这里alias的name 属性指向真实bean标签的 beanName 或者别名,使用idea的时候会提示已注册的内容。
this.processAliasRegistration(ele);
进入方法:
protected void processAliasRegistration(Element ele) {
String name = ele.getAttribute("name");
String alias = ele.getAttribute("alias");
boolean valid = true;
if (!StringUtils.hasText(name)) {
this.getReaderContext().error("Name must not be empty", ele);
valid = false;
}
if (!StringUtils.hasText(alias)) {
this.getReaderContext().error("Alias must not be empty", ele);
valid = false;
}
if (valid) {
try {
this.getReaderContext().getRegistry().registerAlias(name, alias);
} catch (Exception var6) {
this.getReaderContext().error("Failed to register alias '" + alias + "' for bean with name '" + name + "'", ele, var6);
}
this.getReaderContext().fireAliasRegistered(name, alias, this.extractSource(ele));
}
}
以上说到监听器可以看到,都是通过 ReaderContext 读取文件上下文对象中的 ReaderEventListener 进行处理的,这里alias标签对应的是 ReaderEventListener ## aliasRegistered 方法,上文bean标签解析使用的是componentRegistered 方法。其他的标签也都是使用的这里的监听事件触发方法。
继续说注册的过程,this.getReaderContext() 获取XmlReadContext ,也就是XML读取上下文对象,
再获取 BeanDefinitionRegistry 注册对象。
然后通过 registerAlias 方法完成别名的注册,这里接口也有多种实现,通过断点调试发现是
SimpleAliasRegistry 实现类中的方法(其实GenericApplicationContext 实现类只是跳转,还是指向这里)。
public void registerAlias(String name, String alias) {
Assert.hasText(name, "'name' must not be empty");
Assert.hasText(alias, "'alias' must not be empty");
synchronized(this.aliasMap) {
if (alias.equals(name)) {
this.aliasMap.remove(alias);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Alias definition '" + alias + "' ignored since it points to same name");
}
} else {
String registeredName = (String)this.aliasMap.get(alias);
if (registeredName != null) {
if (registeredName.equals(name)) {
return;
}
if (!this.allowAliasOverriding()) {
throw new IllegalStateException("Cannot define alias '" + alias + "' for name '" + name + "': It is already registered for name '" + registeredName + "'.");
}
if (this.logger.isDebugEnabled()) {
this.logger.debug("Overriding alias '" + alias + "' definition for registered name '" + registeredName + "' with new target name '" + name + "'");
}
}
this.checkForAliasCircle(name, alias);
this.aliasMap.put(alias, name);
if (this.logger.isTraceEnabled()) {
this.logger.trace("Alias definition '" + alias + "' registered for name '" + name + "'");
}
}
}
}
这里也有几个步骤,不过和bean属性相比还是比较简单的:
通过以上就可以发现,其实和bean标签的别名操作完全相同。
具体可以查看bean标签解析。
import 就是为了处理大量的配置文件的编写,所以使用可分模块的方式。
<beans>
<import reource="consumerContext.xml">
<import reource="systemContext.xml">
beans>
this.importBeanDefinitionResource(ele);
进入方法查看:
protected void importBeanDefinitionResource(Element ele) {
// (1) 获取Resouce 的属性对应的路径
String location = ele.getAttribute("resource");
if (!StringUtils.hasText(location)) {
this.getReaderContext().error("Resource location must not be empty", ele);
} else {
// (2)解析文件中的特殊属性,例如${user.dir} 等
location = this.getReaderContext().getEnvironment().resolveRequiredPlaceholders(location);
Set<Resource> actualResources = new LinkedHashSet(4);
boolean absoluteLocation = false;
try {
// (3)判断是相对路径还是据对路径
absoluteLocation = ResourcePatternUtils.isUrl(location) || ResourceUtils.toURI(location).isAbsolute();
} catch (URISyntaxException var11) {
}
int importCount;
if (absoluteLocation) {
try {
// (4) 如果是绝对路径,进行递归调用使用另外一次的解析
importCount = this.getReaderContext().getReader().loadBeanDefinitions(location, actualResources);
if (this.logger.isTraceEnabled()) {
this.logger.trace("Imported " + importCount + " bean definitions from URL location [" + location + "]");
}
} catch (BeanDefinitionStoreException var10) {
this.getReaderContext().error("Failed to import bean definitions from URL location [" + location + "]", ele, var10);
}
} else {
try {
// (5) 如果是相对路径则先解析成绝对路径,然后进行解析,加载资源中的bean 对象
Resource relativeResource = this.getReaderContext().getResource().createRelative(location);
if (relativeResource.exists()) {
importCount = this.getReaderContext().getReader().loadBeanDefinitions(relativeResource);
actualResources.add(relativeResource);
} else {
String baseLocation = this.getReaderContext().getResource().getURL().toString();
importCount = this.getReaderContext().getReader().loadBeanDefinitions(StringUtils.applyRelativePath(baseLocation, location), actualResources);
}
if (this.logger.isTraceEnabled()) {
this.logger.trace("Imported " + importCount + " bean definitions from relative location [" + location + "]");
}
} catch (IOException var8) {
this.getReaderContext().error("Failed to resolve current resource location", ele, var8);
} catch (BeanDefinitionStoreException var9) {
this.getReaderContext().error("Failed to import bean definitions from relative location [" + location + "]", ele, var9);
}
}
Resource[] actResArray = (Resource[])actualResources.toArray(new Resource[0]);
this.getReaderContext().fireImportProcessed(location, actResArray, this.extractSource(ele));
}
}
查看上述代码可以得到:
(1)获取Resouce 的属性对应的路径
(2)解析文件中的特殊属性,例如${user.dir} 等
(3)判断是相对路径还是据对路径
(4)如果是绝对路径,进行递归调用使用另外一次的解析,加载资源中的bean 对象
(5)如果是相对路径则先解析成绝对路径,然后进行解析,加载资源中的bean 对象
(6)通知监听器,解析完成。这里也是使用
对于第(2)步骤特殊属性处理的位置,进入源码并使用断点查看,
this.getReaderContext().getEnvironment().resolveRequiredPlaceholders(location)
首先进入 PropertyResolver 实现类 AbstractEnvironment ,然后就会发现这只是一个代理。
进入 PropertyResolver 的另外一个实现类 AbstractPropertyResolver。
public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
if (this.strictHelper == null) {
this.strictHelper = this.createPlaceholderHelper(false);
}
return this.doResolvePlaceholders(text, this.strictHelper);
}
之后创建 PropertyPlaceholderHelper 协助解析,然后进入创建 PropertyPlaceholderHelper 对象的方法 replacePlaceholders ,再进入 parseStringValue 内部进行的处理:
解析import resouce内容其实可以理解,这个和解析单独的XML文件也是没有区别的,断点就可以查看进入了bean标签同样的解析方法。
我们使用beans标签的时候,发现其实包含了其他的bean,可以解析其实和 import 功能类似,并完成内部bean的解析注册。查看一下:
this.doRegisterBeanDefinitions(ele);
然后就会发现其实其实更干脆,这个和xml文件解析是相同的,也就是和外部的beans标签功能相同。
protected void doRegisterBeanDefinitions(Element root) {
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = this.createDelegate(this.getReaderContext(), root, parent);
if (this.delegate.isDefaultNamespace(root)) {
String profileSpec = root.getAttribute("profile");
if (StringUtils.hasText(profileSpec)) {
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(profileSpec, ",; ");
if (!this.getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec + "] not matching: " + this.getReaderContext().getResource());
}
return;
}
}
}
this.preProcessXml(root);
this.parseBeanDefinitions(root, this.delegate);
this.postProcessXml(root);
this.delegate = parent;
}
以上就是import、alias、beans 标签的解析。其实和开始说的一样,都是对bean标签解析功能的增强和扩展。
下面会讲下自定义标签的内容,希望每次查看源码都有收获,共勉!