博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
4.4 spring-自定义标签的解析
阅读量:5037 次
发布时间:2019-06-12

本文共 15267 字,大约阅读时间需要 50 分钟。

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文件描述内容.

 

注意:没有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文件:

jorcen
088794

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
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
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
handlerMappings = new ConcurrentHashMap
( 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) { // 获取元素名称,也就是实例中的:

 

而对于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://www.cnblogs.com/mjorcen/p/3650813.html

你可能感兴趣的文章
时间模块 && time datetime
查看>>
jquery自动生成二维码
查看>>
spring回滚数据
查看>>
新浪分享API应用的开发
查看>>
美国专利
查看>>
【JavaScript】Write和Writeln的区别
查看>>
百度编辑器图片在线流量返回url改动
查看>>
我对你的期望有点过了
查看>>
微信小程序wx:key以及wx:key=" *this"详解:
查看>>
下拉框比较符
查看>>
2.2.5 因子的使用
查看>>
css选择器
查看>>
photoplus
查看>>
Python 拓展之推导式
查看>>
[Leetcode] DP-- 474. Ones and Zeroes
查看>>
80X86寄存器详解<转载>
查看>>
c# aop讲解
查看>>
iterable与iterator
查看>>
返回顶部(动画)
查看>>
webpack+react+antd 单页面应用实例
查看>>