ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

4.4 spring-自定义标签的解析

2019-09-15 11:00:23  阅读:201  来源: 互联网

标签:4.4 NamespaceHandler return String 自定义 spring element public


原文链接:https://my.oschina.net/u/1590001/blog/268181

1.0 自定义标签的解析.

  在之前的章节中,我们完成了对spring 默认标签的加载过程.那么现在我们将开始新的里程, spring 自定义标签的解析;

代码如下:

 1     /**
 2      * Parse the elements at the root level in the document: "import", "alias", "bean".
 3      * 
 4      * @param root the DOM root element of the document
 5      */
 6     protected void parseBeanDefinitions(Element root,
 7             BeanDefinitionParserDelegate delegate) {
 8         // 对Bean的处理
 9         if (delegate.isDefaultNamespace(root)) {
10             NodeList nl = root.getChildNodes();
11             for (int i = 0; i < nl.getLength(); i++) {
12                 Node node = nl.item(i);
13                 if (node instanceof Element) {
14                     Element ele = (Element) node;
15                     if (delegate.isDefaultNamespace(ele)) {
16                         // 对Bean的处理,如果是默认
17                         parseDefaultElement(ele, delegate);
18                     }
19                     else {
20                         // 对Bean的处理,如果是自定义
21                         delegate.parseCustomElement(ele);
22                     }
23                 }
24             }
25         }
26         else {
27             delegate.parseCustomElement(root);
28         }
29     }

  本章中,所有的内容都是围绕一句代码 delegate.parseCustomElement(ele); 开始的,

 1.1.0 首先我们看看自定义标签的使用.

  1.1.1 根据Spring提供的扩展Schema 的定义,扩展一个Spring 自定义的标签配置大致需要以下几个步骤,.

    1.  创建一个需要扩展的组件.

    2.  定义一个XSD文件.

    3.  创建一个文件.实现BeanDefinitionParser 接口, 用来解析XSD文件中的定义,和主键定义.

    4.  创建一个Handler 文件,扩展NameSpaceHandleSupport,目的是将组件注册到Spring容器中.

    5.  编写Spring.handlers 和 Spring.schemas 文件

 

1.1.1 自定义标签的使用过程.

 1. 首先我们创建一个普通的pojo

 1 package cn.c.bean;
 2 
 3 public class MJorcen {
 4     private String name;
 5     private String alias;
 6     private String code;
 7 
 8     public String getName() {
 9         return name;
10     }
11 
12     public void setName(String name) {
13         this.name = name;
14     }
15 
16     public String getAlias() {
17         return alias;
18     }
19 
20     public void setAlias(String alias) {
21         this.alias = alias;
22     }
23 
24     public String getCode() {
25         return code;
26     }
27 
28     public void setCode(String code) {
29         this.code = code;
30     }
31 
32 }

2.定义一个xsd文件描述内容.

 

<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    targetNamespace="http://www.example.org/MJorcen" xmlns:tns="http://www.example.org/MJorcen"
    elementFormDefault="qualified">
    <xsd:element name="mjorce">
        <xsd:complexType>
            <xsd:sequence>
                <xsd:element name="alias" type="xsd:string"></xsd:element>
                <xsd:element name="code" type="xsd:string"></xsd:element>
            </xsd:sequence>
            <xsd:attribute name="name " type="xsd:string"></xsd:attribute>
            <xsd:attribute name="id" type="xsd:string"></xsd:attribute>
        </xsd:complexType>
    </xsd:element>
</xsd:schema>

注意:没有ID不放行.Spring-4.0.0;

 

3.创建一个类,实现beanDefinitionParse接口,用来解析xsd文件中的定义和主键定义,一般我们选择的是继承,AbstractSimpleBeanDefinitionParser或者AbstractSingleBeanDefinitionParser来实现

package cn.c.parse;

import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractSimpleBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import cn.c.bean.MJorcen;

public class MJorcenNamespacesprase extends AbstractSimpleBeanDefinitionParser {

    @Override
    protected Class<?> getBeanClass(Element element) {
        return MJorcen.class;
    }

    @Override
    protected void doParse(Element element, ParserContext parserContext,
            BeanDefinitionBuilder builder) {
        String name = element.getAttribute("name");
        NodeList eles = element.getChildNodes();
        String alias = eles.item(0).getTextContent();
        String code = eles.item(1).getTextContent();
        builder.addPropertyValue("name", name);
        builder.addPropertyValue("alias", alias);
        builder.addPropertyValue("code", code);
    }

}

4.创建一个类,实现NamespaceHandlerSupport接口,目的是将组建注册到Spring 容器

 

package cn.c.parse;

import org.springframework.beans.factory.xml.NamespaceHandlerSupport;

public class MJorcenNamespacesHandlers extends NamespaceHandlerSupport {
    public static void main(String[] args) {
        System.out.println(MJorcenNamespacesHandlers.class);
    }

    public void init() {
        registerBeanDefinitionParser("mjorce", new MJorcenNamespacesprase());
    }
}

 

5.编写spring.handlers和spring.schema文件

spring.schema

MJorcen.xsd=cn\c\xml\MJorcen.xsd

spring.handlers

http\://www.example.org/MJorcen=cn.c.parse.MJorcenNamespacesHandlers

到此,自定义的配置就结束了,spring.schema,spring.handlers中去找对应的handler 和XSD文件,默认的位置是META-INF目录下,进而找到对应的handler以及解析元素的parser,进而完成整个自定义标签的解析,也就是说自定义与Spring中的默认标准不同在于,Spring将自定义标签解析的工作委托给了用户去实现.

 

6.创建xml文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:c="http://www.example.org/MJorcen"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd 
    http://www.example.org/MJorcen MJorcen.xsd">

    <c:mjorce id="mj" name="MichealJorcen">
        <c:alias>jorcen</c:alias>
        <c:code>088794</c:code>
    </c:mjorce>
</beans>

7.创建测试文件

package cn.c.main;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import cn.c.bean.MJorcen;

public class Main {
    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext(
                "cn/c/xml/applicationContxt.xml");
        MJorcen f = (MJorcen) ctx.getBean("MichealJorcen");
        System.out.println(f);
    }
}

 

2.0 下面,我们一起进入parseCustomElement方法,代码如下:

 

public BeanDefinition parseCustomElement(Element ele) {
        //containingBd为父类Bean ,顶层元素设置为null
        return parseCustomElement(ele, null);
    }

 

追踪下去如下:

 

/**
     * Parse the merge attribute of a collection element, if any.
     */
    public boolean parseMergeAttribute(Element collectionElement) {
        String value = collectionElement.getAttribute(MERGE_ATTRIBUTE);
        if (DEFAULT_VALUE.equals(value)) {
            value = this.defaults.getMerge();
        }
        return TRUE_VALUE.equals(value);
    }

    public BeanDefinition parseCustomElement(Element ele) {
        //containingBd为父类Bean ,顶层元素设置为null
        return parseCustomElement(ele, null);
    }
    //containingBd为父类Bean ,顶层元素设置为null
    public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
        //获取对应的命名空间
        String namespaceUri = getNamespaceURI(ele);
        //根据对应的命名空间找到对应的 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进行解析,[NamespaceHandlerSupport.java]
        return handler.parse(ele, new ParserContext(this.readerContext, this,
                containingBd));
    }

 

 

其实思路很简单,无非就是根据对应的bean获取对应的命名空间,然后根据命名空间找到对应的解析器,然后根据对应的解析器进行解析,说起来容易,做起来可没那么简单,让我们来看看具体实现,

 

2.1.获取命名空间

标签的解析是从命名空间开始,无论是Spring默认的还是自定义的,都是以命名空间为基础的.至于如何实现,在org.w3c.dom.Node 中已经提供好的方法:

 

public String getNamespaceURI(Node node) {
        return node.getNamespaceURI();
    }

 

2.2 提取自定义标签处理器.

 

 

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

 

当得到命名空间处理器后,马上执行 init() 来注册解析器,如:

  public void init() { registerBeanDefinitionParser("mjorce", new MJorcenNamespacesprase()); } 

 这里,你也可以注册多个解析器, 如<c:M,<c:N 等.使得c的命名空间中可以支持多种标签的解析.

  注册后,命名空间处理器就可以根据标签的不同来调用不同的解析器了.下面我们来看看getHandlerMappings方法

/**
     * 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());
                        //将Properties格式文件合并到Map格式的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;
    }

 

2.3 标签解析

 得到了解析器和需要分析的元素后,Spring就可以将解析工作委托给自定义的解析器了.代码如下:

// 调用NamespaceHandler 注册的parse进行解析
        return handler.parse(ele, new ParserContext(this.readerContext, this,
                containingBd));

 

解析个过程中,首先是寻找元素 对应的解析器,进而调用解析器中的parse方法. 

那么结合实例,其实就是首先获取在handler中的init方法中注册的对应的Parse 实例.并调用parse方法,但是我们实现的自定义命名空间解析器并没有parse方法,所以推断,这个方法是在父类实现的.

[NamespaceHandlerSupport.java]

@Override
    public BeanDefinition parse(Element element, ParserContext parserContext) {
        // 寻找解析器,并进行解析
        return findParserForElement(element, parserContext).parse(element, parserContext);
    }

[NamespaceHandlerSupport.java]

   解析过程中,首先是寻找元素对应的解析器,进而调用解析器的parse 方法.

那么结合实例,其实就是首先获取在handler中的init方法中注册的对应的Parse 实例.并调用parse方法

 

/**
     * Locates the {@link BeanDefinitionParser} from the register implementations using
     * the local name of the supplied {@link Element}.
     */
    private BeanDefinitionParser findParserForElement(Element element,
            ParserContext parserContext) {
        // 获取元素名称,也就是实例中的:<c:mjorcen 中的 mjorcen,
        String localName = parserContext.getDelegate().getLocalName(element);
        // 根据mjorcen找到对应的解析器,也就是:registerBeanDefinitionParser("mjorce", new MJorcenNamespacesprase());
        BeanDefinitionParser parser = this.parsers.get(localName);

        if (parser == null) {
            parserContext.getReaderContext().fatal(
                    "Cannot locate BeanDefinitionParser for element [" + localName + "]",
                    element);
        }
        return parser;
    }

 

而对于parse方法的处理:

[AbstractBeanDefinitionParser.java]

@Override
    public final BeanDefinition parse(Element element, ParserContext parserContext) {
        AbstractBeanDefinition definition = parseInternal(element, parserContext);
        if (definition != null && !parserContext.isNested()) {
            try {
                // 获取元素Id属性,如果没有,不放行
                String id = resolveId(element, definition, parserContext);
                if (!StringUtils.hasText(id)) {
                    parserContext.getReaderContext().error(
                            "Id is required for element '"
                                    + parserContext.getDelegate().getLocalName(element)
                                    + "' when used as a top-level tag", element);
                }
                String[] aliases = new String[0];
                String name = element.getAttribute(NAME_ATTRIBUTE);
                if (StringUtils.hasLength(name)) {
                    aliases = StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray(name));
                }
                // 将AbstractBeanDefinition 转化为 BeanDefinitionHandler
                BeanDefinitionHolder holder = new BeanDefinitionHolder(definition, id,
                        aliases);
                registerBeanDefinition(holder, parserContext.getRegistry());
                if (shouldFireEvents()) {
                    // 需要通知监听器则进行处理
                    BeanComponentDefinition componentDefinition = new BeanComponentDefinition(
                            holder);
                    postProcessComponentDefinition(componentDefinition);
                    parserContext.registerComponent(componentDefinition);
                }
            }
            catch (BeanDefinitionStoreException ex) {
                parserContext.getReaderContext().error(ex.getMessage(), element);
                return null;
            }
        }
        return definition;
    }

从上面可以看到,真正的解析是委托给了函数parseInternal

  在parseInternal 中.并不是直接调用自定义的doParse 方法,而是进行了一系列的数据准备,包括对beanClass,scope,lazyInit等属性的准备.

@Override
    protected final AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
        String parentName = getParentName(element);
        if (parentName != null) {
            builder.getRawBeanDefinition().setParentName(parentName);
        }
        // 获取自定义标签中的class,此时会调用自定义解析器,如 , getBeanClass方法.
        Class<?> beanClass = getBeanClass(element);
        if (beanClass != null) {
            builder.getRawBeanDefinition().setBeanClass(beanClass);
        }
        else {
            // 如果子类没有实现getBeanClass 则尝试检查子类是否重新getBeanClassName方法.
            String beanClassName = getBeanClassName(element);
            if (beanClassName != null) {
                builder.getRawBeanDefinition().setBeanClassName(beanClassName);
            }
        }
        builder.getRawBeanDefinition().setSource(parserContext.extractSource(element));
        if (parserContext.isNested()) {
            // Inner bean definition must receive same scope as containing bean.
            // 若存在父类,则使用父类的scope属性
            builder.setScope(parserContext.getContainingBeanDefinition().getScope());
        }
        if (parserContext.isDefaultLazyInit()) {
            // Default-lazy-init applies to custom bean definitions as well.
            //延迟加载
            builder.setLazyInit(true);
        }
        //调用子类的doParse方法.
        doParse(element, parserContext, builder);
        return builder.getBeanDefinition();
    }

到此为止,Spring的解析工作全部结束了.接下来的任务就是如何使用这些bean .

 

 

 

 

转载于:https://my.oschina.net/u/1590001/blog/268181

标签:4.4,NamespaceHandler,return,String,自定义,spring,element,public
来源: https://blog.csdn.net/cheshifei3571/article/details/100849195

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有