Spring4.3.x 浅析xml配置的解析过程(5)——解析自定义命名空间的标签

概述


在上一篇解析<bean>标签及其所有子标签我们详细探讨了如何使用<bean>标签来创建一个BeanDefintion对象。这一篇我们开始探讨一下spring如何处理其它命名空间的xml标签,比如spring扩展的http://www.springframework.org/schema/contexthttp://www.springframework.org/schema/phttp://www.springframework.org/schema/aop命名空间。

经过前面的探讨,我们知道XmlBeanDefinitionReader使用BeanDefinitionDocumentReader对象把Document对象中包含的配置信息转换成BeanDefinition对象并把它注册到BeanDefintionRegistry对象中。默认使用DefaultBeanDefinitionDocumentReader来操作Document对象。在DefaultBeanDefinitionDocumentReader的实现中,它的责任是遍历xml根节点下的子节点,并把处理bean标签和自定义命名空间的标签(比如aop:,context:,p:等)的细节委托给BeanDefinitionParserDelegate对象,BeanDefinitionParserDelegate才是真正解析配置文件的地方。

下面是DefaultBeanDefinitionDocumentReader对象使用BeanDefinitionParserDelegate对象来处理自定义命名空间的标签的入口,

    protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
        // 检查root节点的命名空间是否为默认命名空间
        // spring配置文件中默认的命名空间为"http://www.springframework.org/schema/beans"
        if (delegate.isDefaultNamespace(root)) {
            NodeList nl = root.getChildNodes();
            // 遍历root节点下的所有子节点
            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);
        }
    }

parseBeanDefinitions方法的主要事情是区分节点标签是否是默认命名空间的标签,以及遍历根节点下的子节点。在这里对于任何一个节点,如果是默认命名空间的则调用DefaultBeanDefinitionDocumentReader方法parseDefaultElement处理,否则调用BeanDefinitionParserDelegate 的parseCustomElement方法来处理。这篇文章的主题是探讨spring如何解析自定义命名空间的标签,因此我们的入口是parseCustomElement方法,如下是这个方法的源代码。

    public BeanDefinition parseCustomElement(Element ele) {
        return parseCustomElement(ele, null);
    }

parseCustomElement(Element ele)方法把处理节点的任务传递给parseCustomElement(Element ele, BeanDefinition containingBd)方法,这个方法的源码如下。

    public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
        // 获取命名空间的uri
        String namespaceUri = getNamespaceURI(ele);
        // 根据NamespaceHandlerResolver对象获取命名空间的处理器NamespaceHandler对象。
        NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
        if (handler == null) {
            error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
            return null;
        }
        // 调用NamespaceHandler的parse方法处理节点
        return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
    }

parseCustomElement(Element ele, BeanDefinition containingBd)首先通过节点的命名空间uri字符串获取命名空间处理器NamespaceHandler对象,然后调用处理器的parse方法处理节点。spring定义了很多命名空间和它们对于的处理器类,因此,我们在这里需要清楚的是如何获取NamespaceHandler对象。

获取命名空间处理器NamespaceHandler对象

下面是spring定义的NamespaceHandler接口的代码。

public interface NamespaceHandler {

    /**
     * DefaultBeanDefinitionDocumentReader实例化一个NamespaceHandler对象的时候会调用此方法
     */
    void init();

    /**
     * 解析指定的的Element对象,并返回一个BeanDefinition对象。
     */
    BeanDefinition parse(Element element, ParserContext parserContext);

    /**
     * 解析指定的节点,并装饰指定的BeanDefinitionHolder对象,最后返回一个已经装饰的BeanDefinitionHolder对象。
     * 这个方法在parse方法之后被调用
     */
    BeanDefinitionHolder decorate(Node source, BeanDefinitionHolder definition, ParserContext parserContext);

}

Spring定义了NamespaceHandlerResolver接口来获取命名空间处理器,这个接口的定义如下。

public interface NamespaceHandlerResolver {

    /**
     * 解析命名空间的URI字符串并返回一个对应的NamespaceHandler对象,如果没有找到,则返回null
     */
    NamespaceHandler resolve(String namespaceUri);

}

NamespaceHandlerResolver 接口只定义了一个方法,并且Spring给出了一个默认的实现,那就是DefaultNamespaceHandlerResolver类,我们来看看这个类的resolve(String namespaceUri)方法,源代码如下。

    @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);
                // 检查handlerClass 是否实现了NamespaceHandler接口
                if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) {
                    throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri +
                            "] does not implement the [" + NamespaceHandler.class.getName() + "] interface");
                }
                // 实例化NamespaceHandler对象
                NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
                // 初始化NamespaceHandler对象
                namespaceHandler.init();
                // 把NamespaceHandler对象缓存到handlerMappings对象中
                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);
            }
        }
    }

DefaultNamespaceHandlerResolver的resolve方法通过调用getHandlerMappings()方法获得NamespaceHandler对象与命名空间URI的映射表,并从这个映射表中取到并返回对应的NamespaceHandler对象。resolve方法的重点就在于NamespaceHandler对象映射表的获取,下面看看getHandlerMappings()方法的源代码。

    private Map<String, Object> getHandlerMappings() {
        if (this.handlerMappings == null) {
            synchronized (this) {
                if (this.handlerMappings == null) {
                    try {
                        // 这里的handlerMappingsLocation默认为"META-INF/spring.handlers"
                        Properties mappings =
                                PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);
                        if (logger.isDebugEnabled()) {
                            logger.debug("Loaded NamespaceHandler mappings: " + mappings);
                        }
                        // 创建一个线程安全的Map对象
                        Map<String, Object> handlerMappings = new ConcurrentHashMap<String, Object>(mappings.size());
                        // 把Properties对象中的key-value复制到handlerMappings中国
                        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;
    }

getHandlerMappings在第一次调用的时候会通过DefaultNamespaceHandlerResolver的handlerMappingsLocation属性值(默认为META-INF/spring.handlers)获取classes路径和所有jar包中有的相应资源,我们来看看PropertiesLoaderUtils是怎么使用 loadAllProperties(String resourceName, ClassLoader classLoader)加载资源的,代码如下。

    public static Properties loadAllProperties(String resourceName, ClassLoader classLoader) throws IOException {
        Assert.notNull(resourceName, "Resource name must not be null");
        ClassLoader classLoaderToUse = classLoader;
        if (classLoaderToUse == null) {
            // 获取默认的ClassLoader对象
            classLoaderToUse = ClassUtils.getDefaultClassLoader();
        }
        // 使用ClassLoader对象来加载class路径和所有jar包下所匹配的资源
        Enumeration<URL> urls = (classLoaderToUse != null ? classLoaderToUse.getResources(resourceName) :
                ClassLoader.getSystemResources(resourceName));
        Properties props = new Properties();
        while (urls.hasMoreElements()) {
            URL url = urls.nextElement();
            URLConnection con = url.openConnection();
            // 为JNLPCachedJarURLConnection启动缓存
            ResourceUtils.useCachesIfNecessary(con);

            InputStream is = con.getInputStream();
            try {
                if (resourceName.endsWith(".xml")) {
                    // 加载xml文件
                    props.loadFromXML(is);
                } else {
                    // 加载properties文件
                    props.load(is);
                }
            } finally {
                is.close();
            }
        }
        return props;
    }

loadAllProperties方法从classes路径下和所有jar包中获取所有匹配的资源,并把这些资源文件的内容都保存到同一个Properties对象中作为loadAllProperties方法的返回值。

Spring自定义的NamespaceHandler对象

我们看看Spring都创建了哪些spring.handlers文件及其内容。
a. 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

b. 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

c. spring-aop包下的

http\://www.springframework.org/schema/aop=org.springframework.aop.config.AopNamespaceHandler

d. spring-tx包下的

http\://www.springframework.org/schema/tx=org.springframework.transaction.config.TxNamespaceHandler

e. spring-jdbc包下的

http\://www.springframework.org/schema/jdbc=org.springframework.jdbc.config.JdbcNamespaceHandler

e. spring-webmvc包下的

http\://www.springframework.org/schema/mvc=org.springframework.web.servlet.config.MvcNamespaceHandler

spring.handlers文件中=号左边的是命名空间uri,=号右边的是命名空间处理器类的全名称。

我们看看p命名空间的处理器SimplePropertyNamespaceHandler类的源码,如下。

public class SimplePropertyNamespaceHandler implements NamespaceHandler {

    private static final String REF_SUFFIX = "-ref";


    @Override
    public void init() {
    }

    @Override
    public BeanDefinition parse(Element element, ParserContext parserContext) {
        parserContext.getReaderContext().error(
                "Class [" + getClass().getName() + "] does not support custom elements.", element);
        return null;
    }

    @Override
    public BeanDefinitionHolder decorate(Node node, BeanDefinitionHolder definition, ParserContext parserContext) {
        // p命名空间只处理属性
        if (node instanceof Attr) {
            Attr attr = (Attr) node;
            String propertyName = parserContext.getDelegate().getLocalName(attr);
            String propertyValue = attr.getValue();
            // 获取BeanDefinition的MutablePropertyValues对象
            MutablePropertyValues pvs = definition.getBeanDefinition().getPropertyValues();
            if (pvs.contains(propertyName)) {
                parserContext.getReaderContext().error("Property '" + propertyName + "' is already defined using " +
                        "both <property> and inline syntax. Only one approach may be used per property.", attr);
            }
            if (propertyName.endsWith(REF_SUFFIX)) {
                // 获取属性名称
                propertyName = propertyName.substring(0, propertyName.length() - REF_SUFFIX.length());
                // 设置为bean引用
                pvs.add(Conventions.attributeNameToPropertyName(propertyName), new RuntimeBeanReference(propertyValue));
            } else {
                pvs.add(Conventions.attributeNameToPropertyName(propertyName), propertyValue);
            }
        }
        return definition;
    }

}

再看看aop命名空间的处理器AopNamespaceHandler 类的源码,如下。

public class AopNamespaceHandler extends NamespaceHandlerSupport {

    @Override
    public void init() {
        // 注册config标签的的解析器
        registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());
        // 注册aspectj-autoproxy标签的解析器
        registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());
        // 注册scoped-proxy标签的装饰器
        registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());

        // 注册spring-configured标签的解析器
        registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
    }

}

上面两个例子是spring创建NamespaceHandler实现类的两种方式,第一种是直接实现NamespaceHandler接口提供的方法,这种方式适合于命名空间中标签只有一个或者解析标签和属性的过程很简单,比如p命名空间;第二种是继承抽象类NamespaceHandlerSupport 并实现init方法,在init方法中注册标签的解析器和装饰器以及属性的装饰器,spring中大多数命名空间处理器都使用这种方式。

NamespaceHandlerSupport向子类提供了三个方法分别用于注册标签的解析、标签的装饰器、属性的装饰器,如下。

    /**
     * 注册标签的解析器BeanDefinitionParser对象
     */
    protected final void registerBeanDefinitionParser(String elementName, BeanDefinitionParser parser) {
        this.parsers.put(elementName, parser);
    }

    /**
     * 注册标签的装饰器BeanDefinitionDecorator对象
     */
    protected final void registerBeanDefinitionDecorator(String elementName, BeanDefinitionDecorator dec) {
        this.decorators.put(elementName, dec);
    }

    /**
     * 注册属性的装饰器BeanDefinitionDecorator对象
     */
    protected final void registerBeanDefinitionDecoratorForAttribute(String attrName, BeanDefinitionDecorator dec) {
        this.attributeDecorators.put(attrName, dec);
    }

spring为解析标签自定义了一个BeanDefinitionParser接口,源码如下。

public interface BeanDefinitionParser {

    /**
     * 解析指定的节点元素,并返回一个已经注册到BeanDefinitionRegistry对象(BeanDefinition注册表)中的BeanDefinition对象。
     */
    BeanDefinition parse(Element element, ParserContext parserContext);

}

spring还为装饰标签和属性定义了一个BeanDefinitionDecorator接口,源码如下。

public interface BeanDefinitionDecorator {

    /**
     * 根据指定Node对象装饰BeanDefinitionHolder对象,这个Node对象可以是属性,即Attr对象,
     * 也可以是标签元素,即Element对象
     * 最后返回一个装饰好了的BeanDefinitionHolder 对象
     */
    BeanDefinitionHolder decorate(Node node, BeanDefinitionHolder definition, ParserContext parserContext);

}

看到这里,我们再来说说NamespaceHandler和BeanDefinitionParser 以及BeanDefinitionDecorator二者的关系。
a. 对NamespaceHandler来说,它们可有可无。不过对于spring框架本身来说,这样的设计提高了spring的可维护性、可扩展性和易读性。
b. 对BeanDefinitionParser 和BeanDefinitionDecorator来说,它们必须受NamespaceHandler的管控和调度,否则它们将一无是处。

总结


(1)spring通过NamespaceHandler对象来解析自定义标签和属性,同时也用这个对象来装饰BeanDefinitionHolder对象。

(2)DefaultNamespaceHandlerResolver用于管理NamespaceHandler实现类。因此可以从DefaultNamespaceHandlerResolver中根据命名空间uri获取到对应的NamespaceHandler对象。

(3)NamespaceHandler实现类需要在/META-INF/spring.handler文件中注册,否则不能被DefaultNamespaceHandlerResolver对象加载。

(4)命名空间中有多个标签需要解析和装饰,命名空间处理器类最好继承NamespaceHandlerSupport ,并为标签分别定义BeanDefinitionParser解析器或者BeanDefinitionDecorator装饰器。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值