1. 回顾
前面我们详细了解了spring从xml配置文件中加载bean的实现过程。现在我们再次回顾一下,加深印象,spring默认从xml中加载数据、解析并注册bean的核心代码如下:
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
importBeanDefinitionResource(ele);
}
else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
processAliasRegistration(ele);
}
else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
processBeanDefinition(ele, delegate);
}
else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
// recurse
doRegisterBeanDefinitions(ele);
}
}
从上面代码可以看出,spring默认解析xml的Element的四种节点,分别是import节点、alias节点、bean节点及嵌套的beans节点,其他元素都是使用自定义解析器进行解析的。
现在我们想一下,我们在xml配置里常用的:
<context:component-scan base-package="samples.spring.*"/>
此配置是如何解析的?
2. spring xml命名空间
spring自定义了一些xml命名空间,如context、aop等:
xmlns:context="http://www.springframework.org/schema/context"
- 这些命名空间在哪里定义的?
- 命名空间是如何解析的?
下面我们就深入研究一下。
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);
}
}
如上所述,spring在取到xml根节点后,解析bean的整个过程如下:
- 如果节点是默认命名空间,使用默认解析方法,否则调用自定义解析方法;
- 循环根节点
beans的所有一级子节点,分别使用步骤一进行解析;
2.1. 默认命名空间
public static final String BEANS_NAMESPACE_URI = "http://www.springframework.org/schema/beans";
public boolean isDefaultNamespace(String namespaceUri) {
return (!StringUtils.hasLength(namespaceUri) || BEANS_NAMESPACE_URI.equals(namespaceUri));
}
命名空间为空或http://www.springframework.org/schema/beans都表示spring的默认命名空间。
2.2. 自定义命名空间
public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
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));
}
自定义xml元素使用自定义的解析器进行解析,
- 根据命名空间获取到一个
NamespaceHandler实例:
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
- 调用
handler的parse方法进行BeanDefinition的解析:
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
3. 命名空间获取到NamespaceHandler
NamespaceHandlerResolver接口专门用来处理命名空间和NamespaceHandler实例的映射:
4. NamespaceHandlerResolver
public interface NamespaceHandlerResolver {
/**
* Resolve the namespace URI and return the located {@link NamespaceHandler}
* implementation.
* @param namespaceUri the relevant namespace URI
* @return the located {@link NamespaceHandler} (may be {@code null})
*/
NamespaceHandler resolve(String namespaceUri);
}
分析自定义的namespace URI,并找到指定命名空间的NamespaceHandler的实现。
4.1. 实现原理
接口NamespaceHandlerResolver只有一个默认实现类DefaultNamespaceHandlerResolver,实现的代码如下:
@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);
NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
namespaceHandler.init();
handlerMappings.put(namespaceUri, namespaceHandler);
return namespaceHandler;
}
catch ...
}
}
这里面有两个重要信息
getHandlerMappings();方法用来获取一个map,保存的是namespace uri到NamespaceHandler的映射;
/** Stores the mappings from namespace URI to NamespaceHandler class name / instance */
private volatile Map<String, Object> handlerMappings;
此映射关系的加载可以看一下此方法:
private Map<String, Object> getHandlerMappings() {
if (this.handlerMappings == null) {
synchronized (this) {
if (this.handlerMappings == null) {
try {
Properties mappings =
PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);
Map<String, Object> handlerMappings = new ConcurrentHashMap<String, Object>(mappings.size());
CollectionUtils.mergePropertiesIntoMap(mappings, handlerMappings);
this.handlerMappings = handlerMappings;
}
catch (IOException ex) {
}
}
}
}
return this.handlerMappings;
}
可以看到,此映射是从this.handlerMappingsLocation配置中加载的,而此变量的初始化:
/**
* The location to look for the mapping files. Can be present in multiple JAR files.
*/
public static final String DEFAULT_HANDLER_MAPPINGS_LOCATION = "META-INF/spring.handlers";
public DefaultNamespaceHandlerResolver(ClassLoader classLoader) {
this(classLoader, DEFAULT_HANDLER_MAPPINGS_LOCATION);
}
由此终于找到,命名空间和NamespaceHandler的映射是从各个jar文件的META-INF/spring.handlers文件中加载。
spring-beanshttp\://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.UtilNamespaceHandlerspring-contexthttp\://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler http\://www.springframework.org/schema/jee=org.springframework.ejb.config.JeeNamespaceHandler http\://www.springframework.org/schema/lang=org.springframework.scripting.config.LangNamespaceHandler http\://www.springframework.org/schema/task=org.springframework.scheduling.config.TaskNamespaceHandler http\://www.springframework.org/schema/cache=org.springframework.cache.config.CacheNamespaceHandlerspring-aop
http\://www.springframework.org/schema/aop=org.springframework.aop.config.AopNamespaceHandler找到
NamespaceHandler的实现类之后,在此处调用了期init方法,init方法的具体作用,下面会具体展开介绍。
5. NamespaceHandler
public interface NamespaceHandler {
void init();
BeanDefinition parse(Element element, ParserContext parserContext);
BeanDefinitionHolder decorate(Node source, BeanDefinitionHolder definition, ParserContext parserContext);
}
NamespaceHandler有三个接口,分别如下:
init():被DefaultBeanDefinitionDocumentReader在调用自定义解析方法之前调用,主要用来定义xml元素节点与解析器之间的绑定关系;
我们先看一下最简单的实现类ContextNamespaceHandler:
public class ContextNamespaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
registerBeanDefinitionParser("property-placeholder", new PropertyPlaceholderBeanDefinitionParser());
registerBeanDefinitionParser("property-override", new PropertyOverrideBeanDefinitionParser());
registerBeanDefinitionParser("annotation-config", new AnnotationConfigBeanDefinitionParser());
registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser());
registerBeanDefinitionParser("load-time-weaver", new LoadTimeWeaverBeanDefinitionParser());
registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
registerBeanDefinitionParser("mbean-export", new MBeanExportBeanDefinitionParser());
registerBeanDefinitionParser("mbean-server", new MBeanServerBeanDefinitionParser());
}
}
parse:此方法负责解析自定义的Element,并负责把解析到的BeanDefinition注册到容器。
此方法的默认在NamespaceHandlerSupport中定义,NamespaceHandler的所有实现默认都继承此类,一般只会实现init方法,parse方法使用的是NamespaceHandlerSupport中实现:
@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
return findParserForElement(element, parserContext).parse(element, parserContext);
}
private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
String localName = parserContext.getDelegate().getLocalName(element);
BeanDefinitionParser parser = this.parsers.get(localName);
if (parser == null) {
parserContext.getReaderContext().fatal(
"Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
}
return parser;
}
从上述实现代码看出,此类中保存了element名称与BeanDefinitionParser之前的键值对,
/**
* Stores the {@link BeanDefinitionParser} implementations keyed by the
* local name of the {@link Element Elements} they handle.
*/
private final Map<String, BeanDefinitionParser> parsers =
new HashMap<String, BeanDefinitionParser>();
最终是根据Element的localName来查找BeanDefinitionParser的实现,然后调用实现的parser方法来进行BeanDefinition的解析。
decorate:
6. 总结
今天就写到这里,明天具体分析一下BeanDefinitionParser的常用的实现类。