1. 回顾

前面我们详细了解了springxml配置文件中加载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命名空间,如contextaop等:

xmlns:context="http://www.springframework.org/schema/context"
  1. 这些命名空间在哪里定义的?
  2. 命名空间是如何解析的?

下面我们就深入研究一下。

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的整个过程如下:

  1. 如果节点是默认命名空间,使用默认解析方法,否则调用自定义解析方法;
  2. 循环根节点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元素使用自定义的解析器进行解析,

  1. 根据命名空间获取到一个NamespaceHandler实例:
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
  1. 调用handlerparse方法进行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 ...
    }
}

这里面有两个重要信息

  1. 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-beans
    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
    
  • spring-context
    http\://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.CacheNamespaceHandler
    
  • spring-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有三个接口,分别如下:

  1. 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());
    }
}
  1. 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>();

最终是根据ElementlocalName来查找BeanDefinitionParser的实现,然后调用实现的parser方法来进行BeanDefinition的解析。

  1. decorate

6. 总结

今天就写到这里,明天具体分析一下BeanDefinitionParser的常用的实现类。

Copyright © wychuan.com 2017 all right reserved,powered by Gitbook该文件修订时间: 2018-01-09 08:05:07

results matching ""

    No results matching ""