zoukankan      html  css  js  c++  java
  • Spring IoC 自定义标签解析

    前言

    本系列全部基于 Spring 5.2.2.BUILD-SNAPSHOT 版本。因为 Spring 整个体系太过于庞大,所以只会进行关键部分的源码解析。

    本篇文章主要介绍 Spring IoC 容器怎么解析自定义标签的。

    正文

    在分析自定义标签怎么解析之前,我们先看如何自定义一个标签以及让其能被 Spring 解析并加载成 bean

    自定义标签

    编写 XML Schema 文件:定义 XML 结构

    首先编写一个 users.xsd 文件,其中定义了 User 类型的属性(该类在Spring IoC BeanDefinition 的加载和注册一文中定义过);内容如下:

    <xsd:schema xmlns="http://ioc.leisurexi.com/schema/users"
                xmlns:xsd="http://www.w3.org/2001/XMLSchema"
                targetNamespace="http://ioc.leisurexi.com/schema/users">
    
        <xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
    
        <!-- 定义 User 类型(复杂类型) -->
        <xsd:complexType name="User">
            <xsd:attribute name="id" type="xsd:long" use="required"/>
            <xsd:attribute name="name" type="xsd:string" use="required"/>
    	</xsd:complexType>
    
        <!-- 定义 user 元素 -->
        <xsd:element name="user" type="User"/>
    
    </xsd:schema>
    

    注意:上面 XML 中的头部的地址,我是根据自己的包名来修改的,你需要在 resource 目录下创建对应的路径并把 xsd 文件放在该目录下。

    编写 user-context.xml 文件,并在其中使用我们刚才的自定义标签;内容如下:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:users="http://ioc.leisurexi.com/schema/users"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
                               http://www.springframework.org/schema/beans/spring-beans.xsd
                               http://ioc.leisurexi.com/schema/users
                               http://ioc.leisurexi.com/schema/users.xsd">
    
        <users:user id="1" name="leisurexi"/>
    
    </beans>
    

    自定义 BeanDefinitionParser 实现:XML 元素与 BeanDefinition 解析

    接下来我们定义一个类来继承 AbstractSingleBeanDefinitionParser 重写标签的解析方法,如下:

    public class UserBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
    
        @Override
        protected Class<?> getBeanClass(Element element) {
            return User.class;
        }
    
        @Override
        protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
            setPropertyValue("id", element, builder);
            setPropertyValue("name", element, builder);
        }
    
        private void setPropertyValue(String attributeName, Element element, BeanDefinitionBuilder builder) {
            String attributeValue = element.getAttribute(attributeName);
            if (StringUtils.hasText(attributeValue)) {
                builder.addPropertyValue(attributeName, attributeValue);
            }
        }
    
    }
    

    自定义 NamespaceHandler 实现:命名空间绑定

    然后我们实现 NamespaceHandlerSupport 去注册元素对应的解析类,如下:

    public class UserNamespaceHandler extends NamespaceHandlerSupport {
    
        @Override
        public void init() {
            // 将 "user" 元素注册对应的 BeanDefinitionParser 实现
            registerBeanDefinitionParser("user", new UserBeanDefinitionParser());
        }
    
    }
    

    实现类写好了,但还没完事;还需要在 META-INF 目录下创建一个 spring.handlers 文件,写上命名空间和解析类的映射关系,这是 Spring 中的约定,文件名和目录得一样;内容如下:

    http://ioc.leisurexi.com/schema/users=com.leisurexi.ioc.metadata.UserNamespaceHandler
    

    注册 XML 扩展:命名空间与 XML Schema 映射

    META-INF 创建一个 spring.schemas 文件,这也是约定,文件名和目录得一样;内容如下:

    http://ioc.leisurexi.com/schema/users.xsd=com/leisurexi/ioc/metadata/users.xsd
    

    接着编写测试类,看是否可以得到我们定义的 bean,代码如下:

    public static void main(String[] args) {
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
        reader.loadBeanDefinitions("META-INF/user-context.xml");
        User user = beanFactory.getBean(User.class);
        System.out.println(user);
    }
    

    最后我把目录结构贴出来:

    注意:com.leisurexi.ioc.metadata 是层级目录,最好目录一个接一个往下创建。

    代码解析

    BeanDefinitionParserDelegate#parseCustomElement

    public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
        // 获取自定义标签的命名空间
        String namespaceUri = getNamespaceURI(ele); 
        if (namespaceUri == null) {
            return null;
        } 
        // 获取自定义标签的处理器,见下文详解
        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));
    }
    

    上面代码中的 namespaceUri 就是我们在 XML 中定义的 http://ioc.leisurexi.com/schema/users

    DefaultNamespaceHandlerResolver#resolve

    public NamespaceHandler resolve(String namespaceUri) {
        // 命名空间和其处理类的映射
        Map<String, Object> handlerMappings = getHandlerMappings();
        // 根据命名空间获取处理类的全类名或其实例
        Object handlerOrClassName = handlerMappings.get(namespaceUri);
        if (handlerOrClassName == null) {
            return null;
        }
        // 如果已经是NamespaceHandler类型,代表已经实例化过,直接返回
        else if (handlerOrClassName instanceof NamespaceHandler) {
            return (NamespaceHandler) handlerOrClassName;
        }
        // 没有对handlerOrClassName实例化,进行实例化,并将实例缓存进handlerMappings
        else {
            String className = (String) handlerOrClassName;
            try {
                // 根据全类名获取对应的Class对象
                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");
                }
                // 实例化对应的Class对象
                NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
                // 执行init()方法,也就是在这里会注册元素的BeanDefinitionParser实现
                namespaceHandler.init();
                // 将实例化后的处理类,放入handlerMappings,下次就不用再次创建了
                handlerMappings.put(namespaceUri, namespaceHandler);
                return namespaceHandler;
            }
            // 省略异常处理...
        }
    }
    

    上面方法中的 getHandlerMappings() 方法返回的就是 spring.handlers 文件中的内容;key 为命名空间,value 为全类名。如下所示:

    NamespaceHandlerSupport#parse

    public BeanDefinition parse(Element element, ParserContext parserContext) {
        // 获取BeanDefinition解析器
        BeanDefinitionParser parser = findParserForElement(element, parserContext);
        // 进行标签的解析,见下文详解
        return (parser != null ? parser.parse(element, parserContext) : null);
    }
    
    private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
        // 获取标签元素,比如我们自定义的 <users:user id="1" name="leisurexi"/>,这里获取的就是user
        String localName = parserContext.getDelegate().getLocalName(element);
        // 根据名称获取解析器
        BeanDefinitionParser parser = this.parsers.get(localName);
        if (parser == null) {
            parserContext.getReaderContext().fatal("Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
        }
        return parser;
    }
    

    上面方法中的 parsers 中的内容,就是上面 UserNamespaceHandler 类中重写 init() 方法中注册的解析器。

    AbstractBeanDefinitionParser#parse

    public final BeanDefinition parse(Element element, ParserContext parserContext) {
        // 获取调用自定义解析方法后的BeanDefinition,见下文详解
        AbstractBeanDefinition definition = parseInternal(element, parserContext);
        if (definition != null && !parserContext.isNested()) {
            try { 
                // 获取id属性,如果有的话,没有的话就生成一个默认的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 = null;
                if (shouldParseNameAsAliases()) { 
                    // 获取name属性
                    String name = element.getAttribute(NAME_ATTRIBUTE);
                    if (StringUtils.hasLength(name)) { 
                        // 如果name属性不为空,根据逗号分割成数组作为别名
                        aliases = StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray(name));
                    }
                } 
                // 用definition、id和aliases构建成BeanDefinitionHolder
                BeanDefinitionHolder holder = new BeanDefinitionHolder(definition, id, aliases);
                // 注册BeanDefinition
                registerBeanDefinition(holder, parserContext.getRegistry()); 
                if (shouldFireEvents()) { 
                    // 发送bean注册完成事件
                    BeanComponentDefinition componentDefinition = new BeanComponentDefinition(holder);
                    postProcessComponentDefinition(componentDefinition);
                    parserContext.registerComponent(componentDefinition); 
                }
            }
            catch (BeanDefinitionStoreException ex) {
                String msg = ex.getMessage();
                parserContext.getReaderContext().error((msg != null ? msg : ex.toString()), element);
                return null;
            }
        }
        return definition;
    }
    

    上面方法中的 resolveId() 方法会获取标签中的 id 属性,如果没有的话会调用 BeanDefinitionReaderUtils.generateBeanName() 来生成一个,该方法和 registerBeanDefinition() 方法在Spring IoC 默认标签解析一文中有介绍。

    AbstractSingleBeanDefinitionParser#parseInternal

    protected final AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
        // 获取父bean名称
        String parentName = getParentName(element); 
        if (parentName != null) {
            builder.getRawBeanDefinition().setParentName(parentName);
        } 
        // 获取bean的类型,这里就会调用UserBeanDefinitionParser重写的getBeanClass()方法
        Class<?> beanClass = getBeanClass(element);
        if (beanClass != null) { 
            // 设置进BeanDefinition中
            builder.getRawBeanDefinition().setBeanClass(beanClass);
        } 
        // 如果没有重写getBeanClass()方法,就调用getBeanClassName()方法
        else {
            String beanClassName = getBeanClassName(element);
            if (beanClassName != null) {
                builder.getRawBeanDefinition().setBeanClassName(beanClassName);
            }
        }
        builder.getRawBeanDefinition().setSource(parserContext.extractSource(element));
        // 获取当前BeanDefinition的外层BeanDefinition
        BeanDefinition containingBd = parserContext.getContainingBeanDefinition(); 
        if (containingBd != null) {
            // 内嵌bean必需和外层bean有相同的作用域
            builder.setScope(containingBd.getScope()); 
        }
        if (parserContext.isDefaultLazyInit()) {
            builder.setLazyInit(true);
        }
        // 调用子类重写的doParse()方法
        doParse(element, parserContext, builder);
        return builder.getBeanDefinition();
    }
    

    上面方法中 doParse() 方法就会调用我们自定义的 UserBeanDefinitionParser 类中的 doParse() 方法。

    总结

    本文主要介绍了 Spring 对 XML 文件中自定义标签的解析,我们可以重新梳理一下思路:

    1. 根据命名空间获取对应的 NamespaceHandler
    2. 根据元素名称获取对应的 BeanDefinitionParser
    3. 调用重写的 doParse() 方法来构建 BeanDefinition
    4. 注册 BeanDefinition 以及发送 bean 注册完成事件。

    最后,我模仿 Spring 写了一个精简版,代码会持续更新。地址:https://github.com/leisurexi/tiny-spring

    参考

  • 相关阅读:
    Java——class对象
    Java——HashSet和TreeSet的区别
    Java——普通数据类型转化为String
    Java——Iterator迭代器
    Java——ArrayList和LinkedList的区别(转)
    Java——警告消除
    Object类——toString
    Java——动态调用类中方法
    SharePoint使用jsom查询当前用户信息
    SharePoint列表模板(.stp)
  • 原文地址:https://www.cnblogs.com/leisurexi/p/12931056.html
Copyright © 2011-2022 走看看