接上篇文章,到了具体解析的时候了,具体的分为两种情况,一种是默认命名空间的标签<bean>;另一种是自定义命名空间的标签比如<context:xxx>,<tx:xxx>等。先看下默认的命名空间的标签解析。
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);
}
}
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,alias,bean,beans.一个一个分析
对import标签的解析再方法importBeanDefinitionResource中
protected void importBeanDefinitionResource(Element ele) {
//获取resource属性
String location = ele.getAttribute(RESOURCE_ATTRIBUTE);
//为空报错
if (!StringUtils.hasText(location)) {
getReaderContext().error("Resource location must not be empty", ele);
return;
}
// Resolve system properties: e.g. "${user.dir}"
//如果存在占位符从系统属性中取到,这说到过很多次了
location = getReaderContext().getEnvironment().resolveRequiredPlaceholders(location);
Set<Resource> actualResources = new LinkedHashSet<Resource>(4);
// Discover whether the location is an absolute or relative URI
boolean absoluteLocation = false;
try {
//判断是不是绝对路径,带前缀的classpath:和classpath*:,file:http://解析为绝对路径
//其他为相对路径,瞎写报错
absoluteLocation = ResourcePatternUtils.isUrl(location) || ResourceUtils.toURI(location).isAbsolute();
}
catch (URISyntaxException ex) {
// cannot convert to an URI, considering the location relative
// unless it is the well-known Spring prefix "classpath*:"
}
// Absolute or relative?
//绝对路径
if (absoluteLocation) {
try {
//再次根据资源去解析
int importCount = getReaderContext().getReader().loadBeanDefinitions(location, actualResources);
if (logger.isDebugEnabled()) {
logger.debug("Imported " + importCount + " bean definitions from URL location [" + location + "]");
}
}
catch (BeanDefinitionStoreException ex) {
getReaderContext().error(
"Failed to import bean definitions from URL location [" + location + "]", ele, ex);
}
}
else {
// No URL -> considering resource location as relative to the current file.
//相对路径先去找到绝对路径再去解析
try {
int importCount;
Resource relativeResource = getReaderContext().getResource().createRelative(location);
//比如这里相对路径写错,文件不存在
if (relativeResource.exists()) {
importCount = getReaderContext().getReader().loadBeanDefinitions(relativeResource);
actualResources.add(relativeResource);
}
else {
String baseLocation = getReaderContext().getResource().getURL().toString();
importCount = getReaderContext().getReader().loadBeanDefinitions(
StringUtils.applyRelativePath(baseLocation, location), actualResources);
}
if (logger.isDebugEnabled()) {
logger.debug("Imported " + importCount + " bean definitions from relative location [" + location + "]");
}
}
catch (IOException ex) {
getReaderContext().error("Failed to resolve current resource location", ele, ex);
}
catch (BeanDefinitionStoreException ex) {
getReaderContext().error("Failed to import bean definitions from relative location [" + location + "]",
ele, ex);
}
}
Resource[] actResArray = actualResources.toArray(new Resource[actualResources.size()]);
getReaderContext().fireImportProcessed(location, actResArray, extractSource(ele));
}
虽然代码不少但是基本逻辑很简单,就是找到resource的属性,找到其对应的绝对路径,然后再次进行解析。看了代码是不是很熟悉,我们一般是怎么用的?
绝对路径这样写:
<import resource="classpath:springmvc.xml"/>
<import resource="classpath*:springmvc.xml"/>
<import resource="file:C:\guangju\workspace2\springlearn\src\main\resources\springmvc.xml"/>
相对路径这么写(相对当期文件):
比如说你的当前文件再Context目录下,你要引用的文件再根目录下,就这么写
<import resource="../springmvc.xml"/>
如果当前文件和引用文件在同一目录下就这么写
<import resource="springmvc.xml"/>
<import resource="/springmvc.xml"/>
然后继续看alias的解析方法processAliasRegistration
这个标签是怎么用的呢,主要是给注册的bean注册别名,默认情况下一个bean的id就可以索引到该bean但是如果不想用id,或者在不同的场景下用不同的名称就需要用标签<alias>
具体这么用
<bean id="dao" class="com.pactera.aop.DaoImpl" ></bean>
<alias name="dao" alias="dao1"/>
这样这个bean就有个两个别名,用dao,dao1都可以得到此bean.看代码。
protected void processAliasRegistration(Element ele) {
//获取name属性
String name = ele.getAttribute(NAME_ATTRIBUTE);
//获取别名
String alias = ele.getAttribute(ALIAS_ATTRIBUTE);
boolean valid = true;
if (!StringUtils.hasText(name)) {
getReaderContext().error("Name must not be empty", ele);
valid = false;
}
if (!StringUtils.hasText(alias)) {
getReaderContext().error("Alias must not be empty", ele);
valid = false;
}
if (valid) {
try {
//注册
getReaderContext().getRegistry().registerAlias(name, alias);
}
catch (Exception ex) {
getReaderContext().error("Failed to register alias '" + alias +
"' for bean with name '" + name + "'", ele, ex);
}
getReaderContext().fireAliasRegistered(name, alias, extractSource(ele));
}
}
注册代码registerAlias
public void registerAlias(String name, String alias) {
Assert.hasText(name, "'name' must not be empty");
Assert.hasText(alias, "'alias' must not be empty");
//当前的别名和name相同,在别名中移除此别名
if (alias.equals(name)) {
this.aliasMap.remove(alias);
}
else {
//别名在aliasMap已经存在直接返回
String registeredName = this.aliasMap.get(alias);
if (registeredName != null) {
if (registeredName.equals(name)) {
// An existing alias - no need to re-register
return;
}
if (!allowAliasOverriding()) {
throw new IllegalStateException("Cannot register alias '" + alias + "' for name '" +
name + "': It is already registered for name '" + registeredName + "'.");
}
}
//如果是循环引用报错
checkForAliasCircle(name, alias);
//别名和beanname放入map
this.aliasMap.put(alias, name);
}
}
典型的循环引用
<bean id="dao" class="com.pactera.aop.DaoImpl" name="dao1"></bean>
<alias name="dao1" alias="dao"/>