Spring我爱编程spring

Spring扩展点1-NameSpaceHandler

2018-03-06  本文已影响30人  一帅

Spring标签的黑魔法

如果你JAVAer,那么肯定对Spring的XML的各种标签的配置比较熟悉。我们先来复习一下,所谓温故知新吗。

  1. 当需要声明式事务支持的时候
<tx:annotation-driven/>  //就是这么easy
  1. 当需要Spring自动扫描Bean的时候
<context:component-scan base-package="xxx.xxx" />
  1. 当需要把数据库的配置单独放在一个properties中的时候
<context:property-placeholder location="classpath:/xxx.properties"/>

如果你是初学Spring的话,你很可能被这些稀奇古怪的标签搞晕了头。因为这种配置实在是太多了。而且很可能在之前是那么配置的,过一段时间之后就那么配置了。这里举一个将数据库配置放在properties中的例子。

<bean id="propertyPlaceholderConfigurer" class="org.springframework,beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="locations">
        <list>
            <value>jdbc.properties<value/>
        </list>
    </property>
</bean>
<context:property-placeholder location="classpath:/jdbc.properties"/>

你可能会有疑问,为什么新的版本里可以这么配,难道底层的实现变了吗?

这里先搁置该疑问,后面再一一解答。

其实Spring中还有好多这种带黑魔法的配置,虽然很黑科技,这里就不一一列举了。毕竟它是Spring!

其他开源框架和Spring整合的标签的黑魔法

如果说Spring中自带的黑魔法配置你不以为然,那么其他开源框架中的黑魔法配置又是怎么一回事呢?

先来看几个开源框架的Spring中的配置

1.mongodb的java客户端

    <mongo:mongo id="mongo" host="${mongo.host}" port="${mongo.port}">
      <mongo:options
         connections-per-host="${mongo.connectionsPerHost}"
         threads-allowed-to-block-for-connection-multiplier="${mongo.threadsAllowedToBlockForConnectionMultiplier}"
         write-fsync="${mongo.writeFsync}"/>
    </mongo:mongo>

2.携程开源的配置管理中心 点击这里

<apollo:config/>

3.当当开源的分布式任务调度框架elastic-job

    <job:dataflow id="icSyncBaseDataJob" class="xxx.xxxx" streaming-process="true" registry-center-ref="regCenter" cron="0 0 2 * * ?" sharding-total-count="3" overwrite="true">
        <job:listener class="xxx.xxxx" />
    </job:dataflow>

可以看到每一个开源框架都有自己独有的命名空间(NameSpace),有没有感觉很吊!

那么作为一个有理想有抱负的程序员,你肯定会想如果有一天我自己开源了一个框架,并且要和Spring做集成的时候,我自己是不是也可以自定义属于自己的Spring标签(命名空间)呢?

如何自定义Spring的标签

自定义标签的流程我这里就不详细些了,这里推荐下面几篇文章

自定义Spring标签的原理

看完上面的自定义标签的流程,你是不是有疑问,为什么这样做就可以定义自己的标签。

其实这都要归功于Spring在设计之初就一贯坚持的设计原则:开闭原则:对扩展开放(Open for extension),对修改关闭(Closed for modification)。所以你可以在不修改Spring源代码的情况下扩展Spring的功能。

下面我们我们就来看一下Spring对应XML标签的处理

public class DefaultBeanDefinitionDocumentReader{
        // 解析xml标签
    /**
     * Parse the elements at the root level in the document:
     * "import", "alias", "bean".
     * @param root the DOM root element of the document
     */
    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;
                   // Spring默认的bean配置
                    if (delegate.isDefaultNamespace(ele)) {
                        parseDefaultElement(ele, delegate);
                    }
                  // 用户自定义的bean配置
                    else {
                        delegate.parseCustomElement(ele);
                    }
                }
            }
        }
      // 用户自定义的bean配置
        else {
            delegate.parseCustomElement(root);
        }
    }
      //其他方法。。。。
}

上面的代码逻辑还是很清晰的,一种处理默认标签,如

<bean "id=test" class="com.Test">

另一类是自定义的。如下

<tx:annotation-driven/>

下面我们来看一下自定义标签的解析原理

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

    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));
    }
}

我们看到,其实自定义标签的处理流程很简单。

第一步很简单,我们跳过。我们来看一下第二步的实现。

public class DefaultNamespaceHandlerResolver implements NamespaceHandlerResolver {
    /**
     * 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} if none found
     */
    @Override
    public NamespaceHandler resolve(String namespaceUri) {
        // 获取所有已经配置的handler映射
        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方法
                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);
            }
        }
    }

        /**
     * Load the specified NamespaceHandler mappings lazily.
     */
    private Map<String, Object> getHandlerMappings() {
        // 如果没有缓存则开始进行缓存
        if (this.handlerMappings == null) {
            synchronized (this) {
                if (this.handlerMappings == null) {
                    try {
                        // this.handlerMappingsLocation在构造函数中初始化为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>(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;
    }
}

现在我们知道了为什么需要在META-INF/spring.handlers文件中那样配置了。因为在加载所有的处理器的配置的默认目录就是META-INF/spring.handlers

当获取到自定义的NameSpaceHandler之后就可以进行处理器初始化并解析了。实际上就是调用init方法。我们之前使用自定义NameSpaceHandler的时候就用到了。在init方法中注入解析器如下

public class AopNamespaceHandler extends NamespaceHandlerSupport {

    /**
     * Register the {@link BeanDefinitionParser BeanDefinitionParsers} for the
     * '{@code config}', '{@code spring-configured}', '{@code aspectj-autoproxy}'
     * and '{@code scoped-proxy}' tags.
     */
    @Override
    public void init() {
        // In 2.0 XSD as well as in 2.1 XSD.
        registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());
        registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());
        registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());
    }

}

到现在为止,我们算是真正知道了怎么自定义新的XML标签,并且实现一个自定义的NameSpaceHandler。而且我么也知道了为什么要这么做。所以总结起来就是一句话

源代码面前了无秘密

上一篇 下一篇

猜你喜欢

热点阅读