spring源码-自定义标签-4

  一、自定义标签,自定义标签在使用上面相对来说非常常见了,这个也算是spring对于容器的拓展。通过自定义标签的方式可以创造出很多新的配置方式,并且交给容器直接管理,不需要人工太多的关注。这也是spring对于配置拓展的一个很重要的方式。

  二、自定义标签的几个步骤:1、创建可扫描的标签和对应的解析类  2、读取页面元素解析 3、加入容器管理

  三、涉及到的常用类:BeanDefinitionParser、NamespaceHandlerSupport;文件:spring.handlers、spring.schemas、*.xsd

  四、实现过程:

  1)需要实现的类:

  

  pojo:

public class User{

    private String id;
    private String name;
    private String age;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAge() {
        return age;
    }

    public void setAge(String age) {
        this.age = age;
    }

}

  UserBeanDefinitionParser:

public class UserBeanDefinitionParser implements BeanDefinitionParser {

    //解析xml
    public BeanDefinition parse(Element element, ParserContext parserContext) {
        //通过读取xml的元素来实现具体配置
        String id = element.getAttribute("id");
        String name = element.getAttribute("name");
        String age = element.getAttribute("age");
        //注册到spring容器
        BeanDefinitionRegistry registry = parserContext.getRegistry();
        BeanDefinition beanDefinition = null;
        try {
            //这里的RootBeanDefinition为我们常用的注册形式
            beanDefinition = new RootBeanDefinition(User.class);
            beanDefinition.getPropertyValues().add("id", id);
            beanDefinition.getPropertyValues().add("name", name);
            beanDefinition.getPropertyValues().add("age", age);
            //注册
            registry.registerBeanDefinition(id, beanDefinition);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return beanDefinition;
    }
}

  UserNameSpaceHandler:

//用于提供namespace的扫描
public class UserNameSpaceHandler extends NamespaceHandlerSupport {

    //初始化
    public void init() {
        //注册解析手段
        registerBeanDefinitionParser("user",new UserBeanDefinitionParser());
    }
}

  2)因为来了,这个没有啥关联啊。那么久需要用到具体的配置来做关联(spring.handlers、spring.schemas、*.xsd)

  

  备注:这个自己默认配置在resources里面就可以。目录必须为META-INF,默认两个文件:spring.handlers、spring.schemas。标签:*.xsd

  spring.handlers:默认处理的NamespaceHandlerSupport

http\://www.pinnet.com/schema/user=com.pinnet.customLabel.UserNameSpaceHandler

  spring.schemas:用于使用的标签格式以及验证

http\://www.pinnet.com/schema/user.xsd=META-INF/user.xsd

  user.xsd:这里写了一个简单的例子(标签的编写这里不做介绍,可以自己查询schema的官网

<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema
        xmlns="http://www.pinnet.com/schema/user"
        xmlns:xsd="http://www.w3.org/2001/XMLSchema"
        xmlns:beans="http://www.springframework.org/schema/beans"
        targetNamespace="http://www.pinnet.com/schema/user"
        elementFormDefault="qualified">
    <xsd:import namespace="http://www.springframework.org/schema/beans"/>
    <xsd:element name="user">
        <xsd:complexType>
            <xsd:complexContent>
                <xsd:extension base="beans:identifiedType">
                    <xsd:attribute name="name" type="xsd:string"/>
                    <xsd:attribute name="age" type="xsd:string"/>
                </xsd:extension>
            </xsd:complexContent>
        </xsd:complexType>
    </xsd:element>
</xsd:schema>

  3)好了上面的基本准备工作ok了。具体就是容器做的处理了,我们来关注源码的实现部分。

  spring-bean.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:pinnet="http://www.pinnet.com/schema/user" xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.pinnet.com/schema/user http://www.pinnet.com/schema/user.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
    <pinnet:user id="user1" name="name1" age="age1"/>
    <pinnet:user id="user2" name="name2" age="age2"/>
    <pinnet:user id="user3" name="name3" age="age3"/>
</beans>

  注意:有下划线的部分,这里就是引用过后,然后需要进行的配置。xmlns:pinnet中的pinnet可以自己随便取

  测试:

public class Test {

    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
        User user1 = (User) context.getBean("user1");
        User user2 = (User) context.getBean("user2");
        User user3 = (User) context.getBean("user3");
        System.out.println(user1);
        System.out.println(user2);
        System.out.println(user3);
    }
}

  4)好了重点来了,源码部分。

  这里从自定义标签开始讲起:其他部分可以参考:spring源码-bean之初始化-1到7)部分

  parseCustomElement

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
        //判断根节点是否是默认的(也就是spring提供的beans)
        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)) {
                        this.parseDefaultElement(ele, delegate);
                    } else {
                        //这里就是自定义的配置方式
                        delegate.parseCustomElement(ele);
                    }
                }
            }
        } else {
            //如果根节点不是自定义的那就自己自定义处理
            delegate.parseCustomElement(root);
        }
    }
public BeanDefinition parseCustomElement(Element ele) {
        //解析配置
        return this.parseCustomElement(ele, (BeanDefinition)null);
    }

    public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
        //获取Namespace的uri
        String namespaceUri = this.getNamespaceURI(ele);
        //然后获取NamespaceHandler的实现类NamespaceHandlerSupport的对应实现
        NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
        if (handler == null) {
            this.error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
            return null;
        } else {
            //处理、解析
            return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
        }
    }

  resolve:

public NamespaceHandler resolve(String namespaceUri) {
        //获取所有NamespaceHandler的NamespaceHandlerSupport实现类(过程不详解了)
        Map<String, Object> handlerMappings = this.getHandlerMappings();
        //获取具体的NamespaceHandlerSupport实现类
        Object handlerOrClassName = handlerMappings.get(namespaceUri);
        if (handlerOrClassName == null) {
            return null;
        } else if (handlerOrClassName instanceof NamespaceHandler) {
            return (NamespaceHandler)handlerOrClassName;
        } else {
            //我们默认第一次通过spring.handlers,一般都是String类型的
            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");
                } else {
                    //获取提前加入容器的NamespaceHandler
                    NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
                    //调用初始化这里的初始化查看前面的调用
                    namespaceHandler.init();
                    //加入缓存
                    handlerMappings.put(namespaceUri, namespaceHandler);
                    return namespaceHandler;
                }
            } catch (ClassNotFoundException var7) {
                throw new FatalBeanException("NamespaceHandler class [" + className + "] for namespace [" + namespaceUri + "] not found", var7);
            } catch (LinkageError var8) {
                throw new FatalBeanException("Invalid NamespaceHandler class [" + className + "] for namespace [" + namespaceUri + "]: problem with handler class file or dependent class", var8);
            }
        }
    }

  parse

public BeanDefinition parse(Element element, ParserContext parserContext) {
        //发现解析的BeanDefinitionParser,并调用实现类的parse方法
        return this.findParserForElement(element, parserContext).parse(element, parserContext);
    }

    private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
        //获取本地的localName,在NamespaceHandlerSupport中进行了init,所以会直接获取到UserNameSpaceHandler
        String localName = parserContext.getDelegate().getLocalName(element);
        BeanDefinitionParser parser = (BeanDefinitionParser)this.parsers.get(localName);
        if (parser == null) {
            parserContext.getReaderContext().fatal("Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
        }

        return parser;
    }

  五:上面基本的实现过程就是这样子了,包含了源码的实现逻辑。这里需要关注的点在于匹配的过程。其他都是自己手动完成的。

  六、用处:相对于我们人的解析过程,自定义标签的解析的过程并不是很复杂,更多需要自己手动去完成解析。自定义标签的好处在于,可以很大程度的减少代码冗余的情况。通过同一套流程,开发者只需要关注配置就可以了,而不需要关注具体的解析过程和实现逻辑。另外可以更加方便的与spring容器进行深度的整合!

  

原文地址:https://www.cnblogs.com/ll409546297/p/10108555.html

时间: 2024-09-29 09:39:24

spring源码-自定义标签-4的相关文章

spring源码阅读(二) Bean加载之自定义标签加载

紧接着上一篇关于spring默认标签加载,这一篇来看下自定义标签的加载 继续从 DefaultBeanDefinitionDocumentReader来看 protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) { if (delegate.isDefaultNamespace(root)) { NodeList nl = root.getChildNodes(); for(

spring源码剖析(二)Spring默认标签解析及注册实现

在使用spring的时候,我也经常会使用到bean标签,beans标签,import标签,aop标签等. 下面主要为读者介绍spring的默认的自带标签的解析流程. 验证模式(DTD&XSD) dtd基本已被淘汰,现在spring的验证模式基本都是采用xsd文件作为xml文档的验证模式,通过xsd文件可以检查该xml是否符合规范,是否有效.在使用xsd文件对xml文档进行校验的时候,除了要名称空间外(xmlns="http://www.springframework.org/schema

spring源码分析

spring源码剖析(九)springMVC源码剖析 springMVC 相信大伙都用过,但是spring框架对于你请求的一个url 到你看到的返回结果,期间做了哪些出来呢,文件上传的封装?controller的寻找?过滤器的调用?AOP的调用?视图的解析?页面的跳转?  这些过程具体是怎么实现的,下面我们一一来向大家介绍springMVC的架构.... 2017-02-26 14:02 阅读(248) 评论(0) spring源码剖析(八)spring整合mybatis原理 MyBatis相信

spring源码剖析(六)AOP实现原理剖析

Spring的AOP实现原理,酝酿了一些日子,写博客之前信心不是很足,所以重新阅读了一边AOP的实现核心代码,而且又从网上找了一些Spring Aop剖析的例子,但是发现挂羊头买狗肉的太多,标题高大上,内容却大部分都是比较浅显的一些介绍,可能也是由于比较少人阅读这部分的核心代码逻辑把,然后写这部分介绍的人估计也是少之又少,不过说实话,Spring Aop的核心原理实现介绍确实不太好写,里面涉及的类之间的调用还是蛮多的,关系图画的太细的画也很难画,而且最重要的一点就是,如果对AOP的概念以及spr

Spring源码学习笔记(5)

Spring源码学习笔记(五) 前言-- 最近花了些时间看了<Spring源码深度解析>这本书,算是入门了Spring的源码吧.打算写下系列文章,回忆一下书的内容,总结代码的运行流程.推荐那些和我一样没接触过SSH框架源码又想学习的,阅读郝佳编著的<Spring源码深度解析>这本书,会是个很好的入门 写下一句话,开篇不尴尬  ----  上篇文章中梳理到 Spring 加载资源文件后开始解析 Bean, 现在我们从两个解析函数 parseDefaultElement() 和 par

【Spring源码分析】配置文件读取流程

前言 Spring配置文件读取流程本来是和http://www.cnblogs.com/xrq730/p/6285358.html一文放在一起的,这两天在看Spring自定义标签的时候,感觉对Spring配置文件读取流程还是研究得不够,因此将Spring配置文件读取流程部分从之前的文章拆出来单独成为一文. 为了看一下Spring配置文件加载流程,先定义一个bean.xml: 1 <?xml version="1.0" encoding="UTF-8"?>

spring源码阅读(2)-- 容器启动之加载BeanDefinition

在<spring源码阅读(1)-- 容器启动之资源定位>一文中,阅读了spring是怎么根据用户指定的配置加载资源,当加载完资源,接下来便是把从资源中加载BeanDefinition. BeanDefinition作为spring其中一个组件,spring是这样描述BeanDefinition的:BeanDefinition描述了一个bean实例,它具有属性值,构造函数参数值以及具体实现提供的更多信息.个人的理解是BeanDefinition保存了一个bean实例的所有元数据,下面列举一些常用

Spring源码深度解析pdf

下载地址:网盘下载 <Spring源码深度解析>从核心实现和企业应用两个方面,由浅入深.由易到难地对Spring源码展开了系统的讲解,包括Spring的设计理念和整体架构.容器的基本实现.默认标签的解析.自定义标签的解析.bean的加载.容器的功能扩展.AOP.数据库连接JDBC.整合MyBatis.事务.SpringMVC.远程服务.Spring消息服务等内容. <Spring源码深度解析>不仅介绍了使用Spring框架开发项目必须掌握的核心概念,还指导读者如何使用Spring框

Spring源码系列 — BeanDefinition扩展点

前言 前文介绍了Spring Bean的生命周期,也算是XML IOC系列的完结.但是Spring的博大精深,还有很多盲点需要摸索.整合前面的系列文章,从Resource到BeanDefinition,再到容器扩展点,最后到Bean创键,这个过程中无处不存在Spring预留的扩展口. 本篇文章介绍Spring的另一种扩展点:BeanDefinition扩展点,该扩展点是为处理BeanDefinition而设计.本文主要从以下几点分析: BeanDefinition扩展点的几种方式 BeanDef