Spring IOC 加载过程(二 解析xml,注册bean)

上一篇介绍了Spring 是如何加载xml文件的,这一篇介绍如何解析xml。直接开始吧

/**
* 解析根节点  在DefaultBeanDefinitionDocumentReader这个类中
*/
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
	//这里拿到根节点 判断是否是默认命名空间的标签 即<beans> <bean> <import> <alias>
	if (delegate.isDefaultNamespace(root)) {
		/*
		* 获取子节点列表 
		* 这里注意一下这个方法 getChildNodes(),这个方法 看名字意思是获取子节点 
		* 其实 这里会获取到比子节点多的多的节点,这个方法会把回车 tab 空格 等 认为是一个节点,所以下面有一个if 判断
		* 只有真正的节点才能转换为Element
		* 
		*/
		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); //把根节点当作非默认标签解析
	}
}

下面介绍解析默认标签的方法(Element ele, BeanDefinitionParserDelegate delegate)

/**
* 在DefaultBeanDefinitionDocumentReader这个类中 解析默认标签节点元素
* delegate这个对象 我上一篇有介绍 是真正解析xml的工作类 
* 这个方法基本不用做过的介绍,就是根据标签分别去做解析,我们具体解释下面的四个方法
*/
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
	if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) { //<import>
		importBeanDefinitionResource(ele);
	}
	else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {//<alias>
		processAliasRegistration(ele);
	}
	else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {//<bean>
		processBeanDefinition(ele, delegate);
	}
	else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {<beans>
		// recurse
		doRegisterBeanDefinitions(ele);
	}
}

我们继续分析上面四个方法

/**
* 解析import 标签
*/
protected void importBeanDefinitionResource(Element ele) {
	String location = ele.getAttribute(RESOURCE_ATTRIBUTE); //这里首先获取location属性
	if (!StringUtils.hasText(location)) {//如果这个属性是空的,直接报错返回
		getReaderContext().error("Resource location must not be empty", ele);
		return;
	}
	// Resolve system properties: e.g. "${user.dir}" 
	//这里根据注释可以看出是在处理location属性中的${}字符 ,也可以从其源码中分析处理,这里由于不是重点就不分析源码了
	location = getReaderContext().getEnvironment().resolveRequiredPlaceholders(location);
	//new 一个set
	Set<Resource> actualResources = new LinkedHashSet<Resource>(4);
	// Discover whether the location is an absolute or relative URI
	// 这里判断location 属性是绝对路径还是相对路径
	boolean absoluteLocation = false;
	try {
		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));
}
/**
* 解析alias 标签
*/
protected void processAliasRegistration(Element ele) {
	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));
	}
}
//这是上面那个方法的实现,注册alias  我个人翻译一下 注册别名对象,就是把这个类的信息放到aliasMap这个容器中,就没了,没了,算是注册完成了。
public void registerAlias(String name, String alias) {
	Assert.hasText(name, "'name' must not be empty");
	Assert.hasText(alias, "'alias' must not be empty");
	if (alias.equals(name)) {
		this.aliasMap.remove(alias);
	}
	else {
		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);
		this.aliasMap.put(alias, name);
	}
}
/**
* 解析bean 标签
*/
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
	BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
	if (bdHolder != null) {
		bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
		try {
			// Register the final decorated instance. 注册bean 到 beanfactory
			BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
		}
		catch (BeanDefinitionStoreException ex) {
			getReaderContext().error("Failed to register bean definition with name '" +
					bdHolder.getBeanName() + "'", ele, ex);
		}
		// Send registration event.
		getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
	}
}
/**
* 这是上面方法的实现,可以理解为获取一个类的解释(我是看方法名字自译的),我觉得这个方法需要解释一下
*/
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) {
	String id = ele.getAttribute(ID_ATTRIBUTE);
	String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);

	List<String> aliases = new ArrayList<String>();
	if (StringUtils.hasLength(nameAttr)) {//这里是处理name属性,如果中间有逗号,分号就切分弄成数组放到list中
		String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
		aliases.addAll(Arrays.asList(nameArr));
	}

	String beanName = id;
	if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {//如果上面的list不是空的则把beanName 默认为第一个名称
		beanName = aliases.remove(0);
		if (logger.isDebugEnabled()) {
			logger.debug("No XML 'id' specified - using '" + beanName +
					"' as bean name and " + aliases + " as aliases");
		}
	}

	if (containingBean == null) {
		checkNameUniqueness(beanName, aliases, ele);
	}
	//调用同名方法(增加了beanName参数,处理一些属性,返回一个AbstractBeanDefinition 对象,这个对象是BeanDefinitionHolder 对象中的一个属性,这个对象才是真正的类的解析,可以自行查看它的属性,都是对类的描述)
	AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
	if (beanDefinition != null) {
		if (!StringUtils.hasText(beanName)) {//如果beanName 为空,则生成一个
			try {
				if (containingBean != null) {
					beanName = BeanDefinitionReaderUtils.generateBeanName(
							beanDefinition, this.readerContext.getRegistry(), true);
				}
				else {
					beanName = this.readerContext.generateBeanName(beanDefinition);
					// Register an alias for the plain bean class name, if still possible,
					// if the generator returned the class name plus a suffix.
					// This is expected for Spring 1.2/2.0 backwards compatibility.
					String beanClassName = beanDefinition.getBeanClassName();
					if (beanClassName != null &&
							beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&
							!this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
						aliases.add(beanClassName);
					}
				}
				if (logger.isDebugEnabled()) {
					logger.debug("Neither XML 'id' nor 'name' specified - " +
							"using generated bean name [" + beanName + "]");
				}
			}
			catch (Exception ex) {
				error(ex.getMessage(), ele);
				return null;
			}
		}
		String[] aliasesArray = StringUtils.toStringArray(aliases);
		return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
	}

	return null;
}
/**
*同名方法,其实上个方法的前半部分只是解析了id 和name属性,这里算是后半部分解析了其他属性,我们下面一一描述
*/
public AbstractBeanDefinition parseBeanDefinitionElement(
			Element ele, String beanName, BeanDefinition containingBean) {

		this.parseState.push(new BeanEntry(beanName));

		String className = null;
		if (ele.hasAttribute(CLASS_ATTRIBUTE)) {//解析class属性
			className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
		}

		try {
			String parent = null;
			if (ele.hasAttribute(PARENT_ATTRIBUTE)) {//解析parent属性
				parent = ele.getAttribute(PARENT_ATTRIBUTE);
			}
			AbstractBeanDefinition bd = createBeanDefinition(className, parent);//调用构造方法构造AbstractBeanDefinition 对象

			parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);//处理一些其他属性,具体不解释了,自己看一下 都能看明白,可以在配置文件中做相应的配置
			bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));//解析description
			
			parseMetaElements(ele, bd);//解析meta属性,定义初始数据
			parseLookupOverrideSubElements(ele, bd.getMethodOverrides());//解析lookup-method属性
			parseReplacedMethodSubElements(ele, bd.getMethodOverrides());//解析replaced-method属性,这几个都好不常用。。我没有用到过,也不太清楚具体怎么用,不过我查了一下,有好多例子可以参考,这个需要经验才能确认在什么情况下使用

			parseConstructorArgElements(ele, bd);//解析构造方法中的默认参数
			parsePropertyElements(ele, bd);//解析property属性,默认属性赋值
			parseQualifierElements(ele, bd);//接续qualifier属性

			bd.setResource(this.readerContext.getResource());
			bd.setSource(extractSource(ele));

			return bd;
		}
		catch (ClassNotFoundException ex) {
			error("Bean class [" + className + "] not found", ele, ex);
		}
		catch (NoClassDefFoundError err) {
			error("Class that bean class [" + className + "] depends on not found", ele, err);
		}
		catch (Throwable ex) {
			error("Unexpected failure during bean definition parsing", ele, ex);
		}
		finally {
			this.parseState.pop();
		}

		return null;
	}

这里有一句代码BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
根据字面意思是通过一个工具类注册对象的定义。看代码

//参数是一个BeanDefinitionHolder类,和 BeanDefinitionRegistry类,第一个参数是对象的定义,就是bean 第二个参数是beanFactory
public static void registerBeanDefinition(
		BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
		throws BeanDefinitionStoreException {

	// Register bean definition under primary name.
	String beanName = definitionHolder.getBeanName();
	registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());

	// Register aliases for bean name, if any.
	String[] aliases = definitionHolder.getAliases();
	if (aliases != null) {
		for (String alias : aliases) {
			registry.registerAlias(beanName, alias);
		}
	}
}

处理beans的方法 就不分析了,自己看吧(如果自己看一眼的话会发现,这里是有的哦

然后是解析自定义标签的方法 BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd)
自定义标签 可以理解为 不是上面四个默认标签以外的所有标签

/*
 * 这里还有一层封装
 */
public BeanDefinition parseCustomElement(Element ele) {
	return parseCustomElement(ele, null);
}

/*
 * 这里是解析自定义标签的实现
 * 
 */
public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
	//先获取元素的命名空间 [context:component-scan: null] 
	//这里用这个来做示例,因为我本地xml的第一个自定义标签是这个
	//这里获取到的是http://www.springframework.org/schema/context
	String namespaceUri = getNamespaceURI(ele);
	//然后根据命名空间获取处理器
	NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
	if (handler == null) {
		error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
		return null;
	}
	//处理该元素 这里就是每个处理器单独处理了,回头在捡着几个重要的介绍。
	return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}

这里解释一下NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);这句代码(记住这里哦,一会我们还要回到这里),本来应该在上一篇介绍的,上一篇忘了,这里补充一下
this.readerContext 这个对象是在创建delegate的时候初始化进来的
创建的方法this.delegate = createDelegate(getReaderContext(), root, parent)DefaultBeanDefinitionDocumentReader类中

protected BeanDefinitionParserDelegate createDelegate(
		XmlReaderContext readerContext, Element root, BeanDefinitionParserDelegate parentDelegate) {

	BeanDefinitionParserDelegate delegate = new BeanDefinitionParserDelegate(readerContext);
	delegate.initDefaults(root, parentDelegate);
	return delegate;
}
/**
* 类DefaultBeanDefinitionDocumentReader  documentReader
*/
protected final XmlReaderContext getReaderContext() {
	return this.readerContext;
}

上面方法中readerContext对象是获取的documentReader对象的,又要往上面追 documentReader中的registerBeanDefinitions(Document doc, XmlReaderContext readerContext)这个方法,可以看到把参数的readerContext赋值给了当前对象的readerContext属性

public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
	this.readerContext = readerContext;
	logger.debug("Loading bean definitions");
	Element root = doc.getDocumentElement();
	doRegisterBeanDefinitions(root);
}

继续往前面追代码,可以看到最终是来源于createReaderContext(resource)这个方法,这里resource这个对象不要有任何疑惑,它就是我们的Spring xml文件资源

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
	BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
	int countBefore = getRegistry().getBeanDefinitionCount();
	documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
	return getRegistry().getBeanDefinitionCount() - countBefore;
}

然后看createReaderContext(resource)这个方法

/**
* 进来看 其实这里也没啥,就是new了一个对象返回了,其实我们要看的是`getNamespaceHandlerResolver()`这个方法
*
public XmlReaderContext createReaderContext(Resource resource) {
	return new XmlReaderContext(resource, this.problemReporter, this.eventListener,
			this.sourceExtractor, this, getNamespaceHandlerResolver());
}

。。。哇,这里进来又是一层,最后返回类一个DefaultNamespaceHandlerResolver类的对象,看到这里readContext这个对象就完了,暂时不多解释,回到刚才获取命名空间的那里继续

public NamespaceHandlerResolver getNamespaceHandlerResolver() {
	if (this.namespaceHandlerResolver == null) {
		this.namespaceHandlerResolver = createDefaultNamespaceHandlerResolver();
	}
	return this.namespaceHandlerResolver;
}

/**
*上面那个方法的实现
*/
protected NamespaceHandlerResolver createDefaultNamespaceHandlerResolver() {
	return new DefaultNamespaceHandlerResolver(getResourceLoader().getClassLoader());
}

现在回到NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);这里来啦,应该还能跟上面接上吧
现在 我们就知道 this.readerContext.getNamespaceHandlerResolver()这句代码是得到了个啥东西了吧
没错就是DefaultNamespaceHandlerResolver这个类的实例对象
然后这个对象里的NamespaceHandler resolve(String namespaceUri)这个方法获取到了命名空间处理器,这里还得岔开解释DefaultNamespaceHandlerResolver这个类的构造方法,我直接把源码都贴出来吧,加上了少量的注释,不多解释了,有不懂的可以在下面评论或者私信我,我觉得应该都能看懂。
这里需要注意的一个点 toString 方法中有调用getHandlerMappings()方法 所以 在idea中加断点看的时候 会发现map中已经有值了, 是因为idea会另外启动一个线程去调用toString 方法,不加断点就没有(这个真的是。。。)

/*
 * Copyright 2002-2012 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.beans.factory.xml;

import java.io.IOException;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.beans.BeanUtils;
import org.springframework.beans.FatalBeanException;
import org.springframework.core.io.support.PropertiesLoaderUtils;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;

/**
 * Default implementation of the {@link NamespaceHandlerResolver} interface.
 * Resolves namespace URIs to implementation classes based on the mappings
 * contained in mapping file.
 *
 * <p>By default, this implementation looks for the mapping file at
 * {@code META-INF/spring.handlers}, but this can be changed using the
 * {@link #DefaultNamespaceHandlerResolver(ClassLoader, String)} constructor.
 *
 * @author Rob Harrop
 * @author Juergen Hoeller
 * @since 2.0
 * @see NamespaceHandler
 * @see DefaultBeanDefinitionDocumentReader
 */
public class DefaultNamespaceHandlerResolver implements NamespaceHandlerResolver {

	//一个常量,默认配置文件的地址,这里有好多这个配置文件
	public static final String DEFAULT_HANDLER_MAPPINGS_LOCATION = "META-INF/spring.handlers";
	//一个日志对象
	protected final Log logger = LogFactory.getLog(getClass());
	//一个类加载器
	private final ClassLoader classLoader;
	//一个字符串变量 其实是配文件路径
	private final String handlerMappingsLocation;
	//一个实时刷新的容器,用来存放处理器和命名空间的对象关系
	private volatile Map<String, Object> handlerMappings;
	//无参构造函数
	public DefaultNamespaceHandlerResolver() {
		this(null, DEFAULT_HANDLER_MAPPINGS_LOCATION);
	}

	//带一个类加载器构造函数(也是我们上面使用的构造器)
	public DefaultNamespaceHandlerResolver(ClassLoader classLoader) {
		this(classLoader, DEFAULT_HANDLER_MAPPINGS_LOCATION);
	}

	//带一个类加载器,带一个配置文件地址的构造函数
	public DefaultNamespaceHandlerResolver(ClassLoader classLoader, String handlerMappingsLocation) {
		Assert.notNull(handlerMappingsLocation, "Handler mappings location must not be null");
		this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
		this.handlerMappingsLocation = handlerMappingsLocation;
	}
	//获取命名空间处理器
	@Override
	public NamespaceHandler resolve(String namespaceUri) {
		Map<String, Object> handlerMappings = getHandlerMappings();
		Object handlerOrClassName = handlerMappings.get(namespaceUri);
		if (handlerOrClassName == null) {
			return null;
		}
		else if (handlerOrClassName instanceof NamespaceHandler) {
			return (NamespaceHandler) handlerOrClassName;
		}
		else {
			String className = (String) handlerOrClassName;
			try {
				Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);
				if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) {
					throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri +
							"] does not implement the [" + NamespaceHandler.class.getName() + "] interface");
				}
				NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
				namespaceHandler.init();
				handlerMappings.put(namespaceUri, namespaceHandler);
				return namespaceHandler;
			}
			catch (ClassNotFoundException ex) {
				throw new FatalBeanException("NamespaceHandler class [" + className + "] for namespace [" +
						namespaceUri + "] not found", ex);
			}
			catch (LinkageError err) {
				throw new FatalBeanException("Invalid NamespaceHandler class [" + className + "] for namespace [" +
						namespaceUri + "]: problem with handler class file or dependent class", err);
			}
		}
	}

	//加载处理器和命名空间的映射
	private Map<String, Object> getHandlerMappings() {
		if (this.handlerMappings == null) {
			synchronized (this) {
				if (this.handlerMappings == null) {
					try {
						Properties mappings =
								PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);
						if (logger.isDebugEnabled()) {
							logger.debug("Loaded NamespaceHandler mappings: " + mappings);
						}
						Map<String, Object> handlerMappings = new ConcurrentHashMap<String, Object>(mappings.size());
						CollectionUtils.mergePropertiesIntoMap(mappings, handlerMappings);
						this.handlerMappings = handlerMappings;
					}
					catch (IOException ex) {
						throw new IllegalStateException(
								"Unable to load NamespaceHandler mappings from location [" + this.handlerMappingsLocation + "]", ex);
					}
				}
			}
		}
		return this.handlerMappings;
	}


	@Override
	public String toString() {
		return "NamespaceHandlerResolver using mappings " + getHandlerMappings();
	}

}

这里是好多个spring.handlers中一个的内容

http\://www.springframework.org/schema/c=org.springframework.beans.factory.xml.SimpleConstructorNamespaceHandler
http\://www.springframework.org/schema/p=org.springframework.beans.factory.xml.SimplePropertyNamespaceHandler
http\://www.springframework.org/schema/util=org.springframework.beans.factory.xml.UtilNamespaceHandler

具体的自定义标签就要看具体的handler处理了。我后面会写几个常用的handler的使用,这篇就先到这里吧。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值