• 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容器进行深度的整合!

      

  • 相关阅读:
    leetcode 第二题Add Two Numbers java
    二叉树中的那些常见的面试题(转)
    运行的指令
    Python常见经典 python中if __name__ == '__main__': 的解析
    软件测试基本概念
    JAVA Android王牌教程
    17个新手常见Python运行时错误
    QTP
    链表有关的常见面试题
    Robot Framework and Ride
  • 原文地址:https://www.cnblogs.com/ll409546297/p/10108555.html
Copyright © 2020-2023  润新知