Spring IOC - 自定义解析Bean

本次分享还是要重复上一次分享的内容(Spring IOC - BeanDefinition注册),着重 Bean 的注册过程。上次我们说到 Bean 通过解析后被封装为 BeanDefinitionHolder 中,最终存放在 beanDefinitionMap 中。那么对应自定义 schema 的 Bean 解析 Spring 是怎么做到的,他又是怎样被注册到容器中的呢,这个两个问题,是我们这次要分析的。

 

在进行 Spring 源码分析前,如果你还不了解如何配置自己的 schema 来定义 Bean 你可以在网上自己查询一下,或者可以看看这个分享(自定义 Schema 解析 Spring Bean),可能对你有些帮助。

 

下面我们正式开始:

 

我们了解到最终解析自定义 schema 来解析 Bean 定义时,最终在 BeanDefinitionParserDelegate 的parseCustomElement方法中处理。源代码如下:

public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
	// 获取需要解析的Bean定义命名空间
	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;
	}
	// 使用对应的命名空间处理器进行解析,得到 Bean 定义
	return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}

 

通过上面的代码,我们关心是下面几点

  • readerContext.getNamespaceHandlerResolver 得到真正实例是什么?
  • NamespaceHandlerResolver.resolve 怎么处理命名空间?
  • NamespaceHandler.parse 如何扩展实现的?

下面我们就上面几个问题进行解答:

首先从 readerContext 入手,看看这个实例是什么时间,什么地方,怎么构建的?

在“BeanDefinition注册”分享中,我们我们讲到了当 Spring 找到了资源所在的位置,那么下一步就是解析资源、注册 Bean 定义。其中注册 Bean 定义的入口在org.springframework.beans.factory.xml.XmlBeanDefinitionReader#registerBeanDefinitions中。源码如下:

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
	// 生成 BeanDefinitionDocumentReader
	BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
	documentReader.setEnvironment(this.getEnvironment());
	int countBefore = getRegistry().getBeanDefinitionCount();
	// 此处注册 bean定义,并且创建前,构建了 ReadContext,作为入参
	documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
	return getRegistry().getBeanDefinitionCount() - countBefore;
}
protected XmlReaderContext createReaderContext(Resource resource) {
	// 此处我们发现有创建 NamespaceHandlerResolver 的触发点
	if (this.namespaceHandlerResolver == null) {
		this.namespaceHandlerResolver = createDefaultNamespaceHandlerResolver();
	}
	// 创建 ReaderContext。其中 resource (资源文件)、problemReporter (问题记录)
	// eventListener(事件监听器)、sourceExtractor(资源提取器)、this(容器引用)、
	// namespaceHandlerResolver(命名空间处理器的判定器) 被传入其中。
	return new XmlReaderContext(resource, this.problemReporter, this.eventListener,
			this.sourceExtractor, this, this.namespaceHandlerResolver);
}

先把 ReaderContext 说明完成,说明 namespaceHanderResolver。

 

看看 DefaultBeanDefinitionDocumentReader 中的 registerBeanDefinition 方法,了解 ReadContext 的传递过程:

public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
	// 把在 XmlBeanDefinitionReader 中构建好的 readerContext 赋值给 DefaultBeanDefinitionDocumentReader
	this.readerContext = readerContext;

	logger.debug("Loading bean definitions");
	Element root = doc.getDocumentElement();
	
	// 执行真正的解析过程
	doRegisterBeanDefinitions(root);
}
protected void doRegisterBeanDefinitions(Element root) {
	String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
	if (StringUtils.hasText(profileSpec)) {
		Assert.state(this.environment != null, "environment property must not be null");
		String[] specifiedProfiles = StringUtils.tokenizeToStringArray(profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
		if (!this.environment.acceptsProfiles(specifiedProfiles)) {
			return;
		}
	}

	// any nested <beans> elements will cause recursion in this method. In
	// order to propagate and preserve <beans> default-* attributes correctly,
	// keep track of the current (parent) delegate, which may be null. Create
	// the new (child) delegate with a reference to the parent for fallback purposes,
	// then ultimately reset this.delegate back to its original (parent) reference.
	// this behavior emulates a stack of delegates without actually necessitating one.
	BeanDefinitionParserDelegate parent = this.delegate;
	// 把 readerContext 作为入参构建 Bean 解析用的 delegate
	this.delegate = createHelper(readerContext, root, parent);

	preProcessXml(root);
	parseBeanDefinitions(root, this.delegate);
	postProcessXml(root);

	this.delegate = parent;
}

protected BeanDefinitionParserDelegate createHelper(XmlReaderContext readerContext, Element root, BeanDefinitionParserDelegate parentDelegate) {
	// 通过 readerContext 来构建 BeanDefinitionParserDelegate,至此 readerContext 终于到了解析会使用的地方
	BeanDefinitionParserDelegate delegate = new BeanDefinitionParserDelegate(readerContext, environment);
	delegate.initDefaults(root, parentDelegate);
	return delegate;
}

 

ReaderContext 在解析 Bean 定义前创建,同时包含了解析需要的NamespaceHandlerResolver, 下面我们看看 NamespaceHandlerResolver 的创建过程:

 

org.springframework.beans.factory.xml.XmlBeanDefinitionReader#createDefaultNamespaceHandlerResolver

protected NamespaceHandlerResolver createDefaultNamespaceHandlerResolver() {
	// 创建默认的命名空间处理器的判决处理器
	return new DefaultNamespaceHandlerResolver(getResourceLoader().getClassLoader());
}

 

下面我们来解读 DefaultNamespaceHandlerResolver:

/**
 * 使用命名空间处理器判断的默认配置文件来构建命名空间处理器判定器。
 *  默认配置的存放路径是在类路径的“META-INF/spring.handlers” 中。
 * Create a new <code>DefaultNamespaceHandlerResolver</code> using the
 * default mapping file location.
 * @param classLoader the {@link ClassLoader} instance used to load mapping resources
 * (may be <code>null</code>, in which case the thread context ClassLoader will be used)
 * @see #DEFAULT_HANDLER_MAPPINGS_LOCATION
 */
public DefaultNamespaceHandlerResolver(ClassLoader classLoader) {
	this(classLoader, DEFAULT_HANDLER_MAPPINGS_LOCATION);
}

/**
 * Create a new <code>DefaultNamespaceHandlerResolver</code> using the
 * supplied mapping file location.
 * @param classLoader the {@link ClassLoader} instance used to load mapping resources
 * may be <code>null</code>, in which case the thread context ClassLoader will be used)
 * @param handlerMappingsLocation the mapping file 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;
}

 

下面我们找到 spring-bean-3.1.2.RELEASE.jar (我使用的是spring 3.1.2,并且每个包都是单独引入的)包中的META-INF/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

这个文件定义了命名空间对应的命名空间处理器。例如http\://www.springframework.org/schema/p对应的命名空间处理器是SimplePropertyNamespaceHandler。这个命名空间负责对默认属性赋值的处理。

具体的看一下源码,了解一下对命名空间配置的解析过程:

/**
 * 查找提供的命名空间标识(URI)对应的命名空间处理器
 * Locate the {@link NamespaceHandler} for the supplied namespace URI
 * from the configured mappings.
 * @param namespaceUri the relevant namespace URI
 * @return the located {@link NamespaceHandler}, or <code>null</code> if none found
 */
public NamespaceHandler resolve(String namespaceUri) {
	// 配置文件的解析过程,配置文件解析成为命名空间URI为KEY,命名空间处理器类为 VALUE 的
	// Map 集合。后面会分析具体的加载过程。
	Map<String, Object> handlerMappings = getHandlerMappings();
	Object handlerOrClassName = handlerMappings.get(namespaceUri);
	// 如果无法通过命名空间定位符获取到值,直接返回
	if (handlerOrClassName == null) {
		return null;
	}
	// 如果对应的KEY获取的值是命名空间处理器实例,返回这个命名空间处理器
	// 当经过反射实例化后,对应的值已经变成了类的实例,不是类命名了
	else if (handlerOrClassName instanceof NamespaceHandler) {
		return (NamespaceHandler) handlerOrClassName;
	}
	// 实例化后第一次解析配置,懒加载
	else {
		String className = (String) handlerOrClassName;
		try {
			// 判断对应的命名空间处理器类是否加载到JVM中,同时判断配置的类是否是命名空间处理器类
			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();
			// 把存放URI对应的值转换为对应的处理器实例
			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);
		}
	}
}

/**
 * 加载指定的命名空间匹配,懒加载
 * Load the specified NamespaceHandler mappings lazily.
 */
private Map<String, Object> getHandlerMappings() {
	if (this.handlerMappings == null) {
		synchronized (this) {
			if (this.handlerMappings == null) {
				try {
					// 读取所有的配置文件,默认配置文件的命名是 “META-INF/spring.handlers”
					// 也就是说所有类路径下面的 “META-INF/spring.handlers”都会被加载到配置文件中
					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>();
					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;
}

 

自定义 schema 解析器

 

一般情况我们自定义解析器都会继承于org.springframework.beans.factory.xml.NamespaceHandlerSupport,NamespaceHandlerSupport实现NamespaceHandler。NamespaceHandlerSupport实现是维护了一个 Xml Element 名称到BeanDefinitionParser映射和一个 xml Element 名称到BeanDefinitionDecorator的映射。前者负责负责解析工作,后者负责判断是否需要装饰,进行装饰替换。

BeanDefinitionParser 一般都会继承基础的org.springframework.beans.factory.xml.AbstractBeanDefinitionParser。AbstractBeanDefinitionParser实现了BeanDefinitionParser。如果你可以判断是单例 Bean 定义,可以继承org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser。Spring 提供了丰富的解析器,供选择使用。

 

至此,我们熟悉了整个自定义命名处理器的调用过程。并且通过上面的分析,我们应该了解到,如果我们需要扩展自己的 schema 来做 bean 定义时,我们需要配置自己独有的命名空间处理器,这个处理器配置在“META-INF/spring.handlers”即可。Spring 会查找类路径下面所有“META-INF/spring.handlers”,从而把我们个性化的配置加载其中。

 

我们知道解析 xml 文件时,我们需要通过 dtd 或 xsd 来验证配置文件的正确性。同时这些文件也为我们书写时提供标准,像我们常用的 eclipse 或者 idea 都能在书写的过程中验证我们 Bean 定义文件书写的正确性。那么spring 做了些什么工作,来支持解析 xml 时通过我们xsd文件来验证的呢?

 

Spring 解析 bean 配置文件是使用的 JDK 自带的 DOM 解析方式。这种方式的使用方法可以参考 JDK API 文档。其中 DOM 解析提供客户端可以指定指定URI或本地的 xsd 文件。下面是需要使用的 JDK API

DocumentBuilder.setEntityResolver(EntityResolver) : 指定使用 EntityResolver 解析要解析的 XML 文档中存在的实体。将其设置为 null 将会导致底层实现使用其自身的默认实现和行为。

 

关于 EntityResolver :下面是JDK的文档说明

用于解析实体的基本接口。

此模块(包括源代码和文档)在公共域中,同时没有担保。有关更多信息,请参阅http://www.saxproject.org

如果 SAX 应用程序需要实现自定义处理外部实体,则必须实现此接口并使用 setEntityResolver 方法向 SAX 驱动器注册一个实例。

然后 XML 阅读器将允许应用程序在包含外部实体之前截取任何外部实体(包括外部 DTD 子集和外部参数实体,如果有)。

许多 SAX 应用程序不需要实现此接口,但对于从数据库或其他特定的输入源中构建 XML 文档的应用程序,或者对于使用 URI 类型(而不是 URL )的应用程序,这特别有用。

 

下面的解析器将使用系统标识符 "http://www.myhost.com/today" 为应用程序提供用于实体的特定字符流:

import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;

public class MyResolver implements EntityResolver {
    public InputSource resolveEntity (String publicId, String systemId) {
        if (systemId.equals("http://www.myhost.com/today")) {
            // return a special input source
            MyReader reader = new MyReader();
            return new InputSource(reader);
        } else {
            // use the default behaviour
            return null;
        }
    }
}

 应用程序还可以使用此接口将系统标识符重定向到本地 URI,或者在目录中查找替换(可能使用公共标识符)。

 

这个接口中方法 resolveEntity API 文档解释:

InputSource resolveEntity(String publicId,String systemId)

                          throws SAXException,IOException允许应用程序解析外部实体。

解析器将在打开任何外部实体(顶级文档实体除外)前调用此方法。此类实体包括在 DTD 内引用的外部 DTD 子集和外部参数实体(无论哪种情形,仅在在解析器都读取外部参数实体时)和在文档元素内引用的外部通用实体(如果解析器读取外部通用实体)。应用程序可以请求解析器本身定位实体、使用另外的 URI或者使用应用程序提供的数据(作为字符或字节输入流)。

应用程序编写者可以使用此方法将外部系统标识符重定向到安全的和/或本地的 URI,以便在目录中查找公共标识符或从数据库或其他输入源(其中包括对话框)中读取实体。XML 和 SAX 都不为使用公共或系统 ID 解析资源指定首选策略。但是,SAX 指定了如何解释通过此方法返回的任何 InputSource,并指定如果未返回,则系统 ID 将被重新引用为 URL。

如果系统标识符是 URL,则 SAX 解析器必须在将它报告给应用程序之前完整解析它。

参数:

publicId - 被引用的外部实体的公共标识符,如果未提供,则为 null。

systemId - 被引用的外部实体的系统标识符。

返回:

一个描述新输入源的 InputSource 对象,或者返回 null,以请求解析器打开到系统标识符的常规 URI 连接。

抛出:

SAXException - 任何 SAX 异常,可能包装另外的异常。

IOException - 特定于 Java 的 IO 异常,可能是由于为 InputSource 创建新的 InputStream 或 Reader 所导致的。

 

通过上面 API 解释,我们了解到 Spring 只需要配置自己的 EntityResolver 就可以完成扩展,事实上 Spring 也就是这样做的。

在org.springframework.beans.factory.xml.XmlBeanDefinitionReader#doLoadBeanDefinitions中引用了 DocumentLoader  来执行 Bean 定义文件的读入,这个过程中指定了 EntityResovler。

protected EntityResolver getEntityResolver() {
	if (this.entityResolver == null) {
		// Determine default EntityResolver to use.
		ResourceLoader resourceLoader = getResourceLoader();
		if (resourceLoader != null) {
			this.entityResolver = new ResourceEntityResolver(resourceLoader);
		}
		else {
			this.entityResolver = new DelegatingEntityResolver(getBeanClassLoader());
		}
	}
	return this.entityResolver;
}

 

在前面资源装载的分享中提到过当需要加载xml资源的时候,提前设置好了实体处理器。

org.springframework.context.support.AbstractXmlApplicationContext#loadBeanDefinitions(org.springframework.beans.factory.support.DefaultListableBeanFactory)中有

beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));的处理操作。

 

ResourceEntityResolver是继承DelegatingEntityResolver的,他们核心目的就是读取xml 实体定义。前者的实现是当后者无法获取 xml 实体定义的时候,通过指定的 ResourceLoader 在通过配置的URI再查询一遍(可能是本地,也可能是联网货主其他等,取决配置),进行加载。

 

下面我们来分析一下 Spring 相关源码:

 

org.springframework.beans.factory.xml.ResourceEntityResolver#resolveEntity

 

public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
	// 调用超类实现,读入本地实体定义
	InputSource source = super.resolveEntity(publicId, systemId);
	// 如果读取不到本地实体定义
	if (source == null && systemId != null) {
		String resourcePath = null;
		try {
			// 尝试文件方式查询实体定义文件
			// 例如配置为 : file:/F:/experiment/schema/abc.xsd,通过解析后得到地址为 /abc.xsd
			String decodedSystemId = URLDecoder.decode(systemId);
			String givenUrl = new URL(decodedSystemId).toString();
			String systemRootUrl = new File("").toURL().toString();
			// Try relative to resource base if currently in system root.
			if (givenUrl.startsWith(systemRootUrl)) {
				resourcePath = givenUrl.substring(systemRootUrl.length());
			}
		}
		// 无法通过URL的方式获取,(可以通过URL获取的时候,他就自动联网获取了,但是如果你
		// 配置了自己资源方式,那么就会出异常),
		catch (Exception ex) {
			// Typically a MalformedURLException or AccessControlException.
			if (logger.isDebugEnabled()) {
				logger.debug("Could not resolve XML entity [" + systemId + "] against system root URL", ex);
			}
			// No URL (or no resolvable URL) -> try relative to resource base.
			// 无法通过URL方式解析,还原为原始的 systemId
			resourcePath = systemId;
		}
		if (resourcePath != null) {
			if (logger.isTraceEnabled()) {
				logger.trace("Trying to locate XML entity [" + systemId + "] as resource [" + resourcePath + "]");
			}
			// 通过 resourceLoader 来加载资源
			Resource resource = this.resourceLoader.getResource(resourcePath);
			source = new InputSource(resource.getInputStream());
			source.setPublicId(publicId);
			source.setSystemId(systemId);
			if (logger.isDebugEnabled()) {
				logger.debug("Found XML entity [" + systemId + "]: " + resource);
			}
		}
	}
	return source;

}

 

org.springframework.beans.factory.xml.DelegatingEntityResolver#resolveEntity

public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
	if (systemId != null) {
		// 如果是 dtd 实体
		if (systemId.endsWith(DTD_SUFFIX)) {
			return this.dtdResolver.resolveEntity(publicId, systemId);
		}
		// 如果是 xsd 实体
		else if (systemId.endsWith(XSD_SUFFIX)) {
			return this.schemaResolver.resolveEntity(publicId, systemId);
		}
	}
	return null;
}

 

重点关注一下 xsd 实体解决器PluggableSchemaResolver,他的处理方式类似上面的命名空间处理器,命名空间处理器配置文件默认是 META-INF/spring.handlers ,外部实体的配置文件默认是 META-INF/spring.schemas

 

下面我们找到 spring-bean-3.1.2.RELEASE.jar (我使用的是spring 3.1.2,并且每个包都是单独引入的)包中的 META-INF/spring. schemas,了解里面的配置内容:

 

http\://www.springframework.org/schema/beans/spring-beans-2.0.xsd=org/springframework/beans/factory/xml/spring-beans-2.0.xsd
http\://www.springframework.org/schema/beans/spring-beans-2.5.xsd=org/springframework/beans/factory/xml/spring-beans-2.5.xsd
http\://www.springframework.org/schema/beans/spring-beans-3.0.xsd=org/springframework/beans/factory/xml/spring-beans-3.0.xsd
http\://www.springframework.org/schema/beans/spring-beans-3.1.xsd=org/springframework/beans/factory/xml/spring-beans-3.1.xsd
http\://www.springframework.org/schema/beans/spring-beans.xsd=org/springframework/beans/factory/xml/spring-beans-3.1.xsd
http\://www.springframework.org/schema/tool/spring-tool-2.0.xsd=org/springframework/beans/factory/xml/spring-tool-2.0.xsd
http\://www.springframework.org/schema/tool/spring-tool-2.5.xsd=org/springframework/beans/factory/xml/spring-tool-2.5.xsd
http\://www.springframework.org/schema/tool/spring-tool-3.0.xsd=org/springframework/beans/factory/xml/spring-tool-3.0.xsd
http\://www.springframework.org/schema/tool/spring-tool-3.1.xsd=org/springframework/beans/factory/xml/spring-tool-3.1.xsd
http\://www.springframework.org/schema/tool/spring-tool.xsd=org/springframework/beans/factory/xml/spring-tool-3.1.xsd
http\://www.springframework.org/schema/util/spring-util-2.0.xsd=org/springframework/beans/factory/xml/spring-util-2.0.xsd
http\://www.springframework.org/schema/util/spring-util-2.5.xsd=org/springframework/beans/factory/xml/spring-util-2.5.xsd
http\://www.springframework.org/schema/util/spring-util-3.0.xsd=org/springframework/beans/factory/xml/spring-util-3.0.xsd
http\://www.springframework.org/schema/util/spring-util-3.1.xsd=org/springframework/beans/factory/xml/spring-util-3.1.xsd
http\://www.springframework.org/schema/util/spring-util.xsd=org/springframework/beans/factory/xml/spring-util-3.1.xsd

 

下面是处理过程

public InputSource resolveEntity(String publicId, String systemId) throws IOException {
	if (logger.isTraceEnabled()) {
		logger.trace("Trying to resolve XML entity with public id [" + publicId +
				"] and system id [" + systemId + "]");
	}

	if (systemId != null) {
		String resourceLocation = getSchemaMappings().get(systemId);
		if (resourceLocation != null) {
			Resource resource = new ClassPathResource(resourceLocation, this.classLoader);
			try {
				InputSource source = new InputSource(resource.getInputStream());
				source.setPublicId(publicId);
				source.setSystemId(systemId);
				if (logger.isDebugEnabled()) {
					logger.debug("Found XML schema [" + systemId + "] in classpath: " + resourceLocation);
				}
				return source;
			}
			catch (FileNotFoundException ex) {
				if (logger.isDebugEnabled()) {
					logger.debug("Couldn't find XML schema [" + systemId + "]: " + resource, ex);
				}
			}
		}
	}
	return null;
}

/**
 * Load the specified schema mappings lazily.
 */
private Map<String, String> getSchemaMappings() {
	if (this.schemaMappings == null) {
		synchronized (this) {
			if (this.schemaMappings == null) {
				if (logger.isDebugEnabled()) {
					logger.debug("Loading schema mappings from [" + this.schemaMappingsLocation + "]");
				}
				try {
					Properties mappings =
							PropertiesLoaderUtils.loadAllProperties(this.schemaMappingsLocation, this.classLoader);
					if (logger.isDebugEnabled()) {
						logger.debug("Loaded schema mappings: " + mappings);
					}
					Map<String, String> schemaMappings = new ConcurrentHashMap<String, String>();
					CollectionUtils.mergePropertiesIntoMap(mappings, schemaMappings);
					this.schemaMappings = schemaMappings;
				}
				catch (IOException ex) {
					throw new IllegalStateException(
							"Unable to load schema mappings from location [" + this.schemaMappingsLocation + "]", ex);
				}
			}
		}
	}
	return this.schemaMappings;

}

 

 至此上面的问比较好回答了。这个过程中我们是围绕两个问题作为线索来分析的,一个是解析 Bean 定义文件的正确性验证(schema对应的xsd或者dtd的文件解析方式),一个是自定义的命名空间解析器(命名空间配置处理器的映射配置)。

猜你喜欢

转载自bodu-li.iteye.com/blog/2148144
今日推荐