美文网首页
Spring源码解析(三)-解析自定义标签

Spring源码解析(三)-解析自定义标签

作者: 秋水畏寒 | 来源:发表于2020-05-02 20:53 被阅读0次

    Spring版本

    5.2.5.RELEASE

    参考

    《芋道源码》

    1. demo

    在《Spring源码解析-BeanDefinition注册》中,遗留了自定义标签的解析,那么本文接着补充该部分的内容。
    在解析源码之前,我们先写一个demo,实现自定义标签的功能,来熟悉并帮助理解源码

    1.1 项目结构

    项目结构

    1.2 user.xsd

    <?xml version="1.0" encoding="UTF-8"?>
    <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://www.cmsblogs.com/schema/user" targetNamespace="http://www.cmsblogs.com/schema/user" elementFormDefault="qualified">
    
        <xsd:element name="user">
            <xsd:complexType>
                <xsd:attribute name="id" type="xsd:string" />
                <xsd:attribute name="name" type="xsd:string" />
                <xsd:attribute name="email" type="xsd:string" />
            </xsd:complexType>
        </xsd:element>
    
    </xsd:schema>
    

    我们自定义标签的时候,需要有标签名,标签内还有对应的各种属性,该文件就是用来定义这些数据的,比如说:

    <xsd:element name="user">
    

    指定了自定义标签的标签名称,而:

    <xsd:attribute name="id" type="xsd:string" />
    

    则定义了标签内的属性名称及其数据类型

    1.3 User

    package com.kungyu.custom.element;
    
    /**
     * @author wengyongcheng
     * @since 2020/4/15 10:07 下午
     */
    public class User {
    
        private String id;
    
        private String name;
    
        private String email;
    
        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 getEmail() {
            return email;
        }
    
        public void setEmail(String email) {
            this.email = email;
        }
    }
    

    相当于一个bean

    1.4 UserDefinitionParser

    package com.kungyu.custom.element;
    
    import org.springframework.beans.factory.support.BeanDefinitionBuilder;
    import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
    import org.springframework.util.StringUtils;
    import org.w3c.dom.Element;
    
    /**
     * @author wengyongcheng
     * @since 2020/4/15 10:12 下午
     */
    public class UserDefinitionParser extends AbstractSingleBeanDefinitionParser {
        @Override
        protected Class<?> getBeanClass(Element element) {
            return User.class;
        }
    
        @Override
        protected void doParse(Element element, BeanDefinitionBuilder builder) {
            String id = element.getAttribute("id");
            String name = element.getAttribute("name");
            String email = element.getAttribute("email");
    
            if (StringUtils.hasText(id)) {
                builder.addPropertyValue("id", id);
            }
            if (StringUtils.hasText(name)) {
                builder.addPropertyValue("name", name);
            }
            if (StringUtils.hasText(email)) {
                builder.addPropertyValue("email", email);
            }
        }
    }
    
    

    在xml文件中使用自定义标签,需要一个解析类来解析自定义的标签

    1.5 UserNamespaceHandler

    package com.kungyu.custom.element;
    
    import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
    
    /**
     * @author wengyongcheng
     * @since 2020/4/15 10:21 下午
     */
    public class UserNamespaceHandler extends NamespaceHandlerSupport {
        @Override
        public void init() {
            registerBeanDefinitionParser("user", new UserDefinitionParser());
        }
    }
    

    注册自定义的解析器

    1.6 spring.handlers

    http\://www.cmsblogs.com/schema/user=com.kungyu.custom.element.UserNamespaceHandler
    

    spring解析自定义标签的时候,默认会去读取META-INF/spring.handlers文件来获取UserNamespaceHandler

    1.7 spring.schemas

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

    指定自定义标签的xsd文件

    1.8 spring.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:myTag="http://www.cmsblogs.com/schema/user"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.cmsblogs.com/schema/user http://www.cmsblogs.com/schema/user.xsd">
    
        <myTag:user id="user" email="12233445566@qq.com" name="chenssy"/>
    </beans>
    

    1.9 测试

    package com.kungyu.custom.element;
    
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    /**
     * @author wengyongcheng
     * @since 2020/4/15 10:27 下午
     */
    public class Test {
    
        public static void main(String[] args) {
            ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
            User user = (User) context.getBean("user");
            System.out.println(user.getName() + "----" + user.getEmail());
        }
    }
    
    

    结果:


    运行结果

    2. 源码解读

    2.1 BeanDefinitionParserDelegate#parseCustomElement

        @Nullable
        public BeanDefinition parseCustomElement(Element ele) {
            return parseCustomElement(ele, null);
        }
    

    调用重载方法:

        @Nullable
        public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
            // 获取命名空间uri,也就是xml文档开头处定义的ns,如
            // xmlns="http://www.springframework.org/schema/beans"
            String namespaceUri = getNamespaceURI(ele);
            if (namespaceUri == null) {
                return null;
            }
            // 通过namespaceUri获取到可以解析该元素的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;
            }
            // 执行解析
            return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
        }
    

    在上文的demo中,执行debug,可以看到获得到的namespaceUri为:


    获取namespaceUri

    该值是我们在user.xsd定义的xmlns属性的值

    2.1.1 resolve

    @Override
        @Nullable
        public NamespaceHandler resolve(String namespaceUri) {
            // 获取已配置好的handleMapping
            // handleMapping存在俩种情况:
            // 1、NamespaceHandler对象
            // 2、未初始化的NamespaceHandler类路径
            Map<String, Object> handlerMappings = getHandlerMappings();
            Object handlerOrClassName = handlerMappings.get(namespaceUri);
            // 如果为空,直接返回,handlerMappings中存放的都是已经配置好的,意味着要么已经初始化了,要么也至少有个类路径来提供初始化
            if (handlerOrClassName == null) {
                return null;
            }
            // 对应第一种情况,已经存在的NamespaceHandler对象,直接返回
            else if (handlerOrClassName instanceof NamespaceHandler) {
                return (NamespaceHandler) handlerOrClassName;
            }
            else {
                // 对应第二种情况,存放的是类路径
                String className = (String) handlerOrClassName;
                try {
                    // 通过类路径进行类加载
                    Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);
                    // 判断handlerClass是否继承自NamespaceHandler
                    // isAssignableFrom用于俩个class对象的判断,而instance of是实例与class的判断
                    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();
                    // 写入handlerMappings作为缓存
                    handlerMappings.put(namespaceUri, namespaceHandler);
                    return namespaceHandler;
                }
                catch (ClassNotFoundException ex) {
                    throw new FatalBeanException("Could not find NamespaceHandler class [" + className +
                            "] for namespace [" + namespaceUri + "]", ex);
                }
                catch (LinkageError err) {
                    throw new FatalBeanException("Unresolvable class definition for NamespaceHandler class [" +
                            className + "] for namespace [" + namespaceUri + "]", err);
                }
            }
        }
    

    可以看到,该方法的作用在于根据namespaceUri解析出对应的自定义NamespaceHandler,在demo中,NamespaceHandler为UserNamespaceHandler,具体一步步解析如下:

    2.1.1.1 getHandlerMappings

        private Map<String, Object> getHandlerMappings() {
            Map<String, Object> handlerMappings = this.handlerMappings;
            if (handlerMappings == null) {
                synchronized (this) {
                    handlerMappings = this.handlerMappings;
                    if (handlerMappings == null) {
                        if (logger.isTraceEnabled()) {
                            logger.trace("Loading NamespaceHandler mappings from [" + this.handlerMappingsLocation + "]");
                        }
                        try {
                            // handlerMappingsLocation默认路径:META-INF/spring.handlers
                            // 所以,自定义标签的时候,需要写一份spring.handlers文件
                            Properties mappings =
                                    PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);
                            if (logger.isTraceEnabled()) {
                                logger.trace("Loaded NamespaceHandler mappings: " + mappings);
                            }
                            handlerMappings = new ConcurrentHashMap<>(mappings.size());
                            CollectionUtils.mergePropertiesIntoMap(mappings, handlerMappings);
                            this.handlerMappings = handlerMappings;
                        }
                        catch (IOException ex) {
                            throw new IllegalStateException(
                                    "Unable to load NamespaceHandler mappings from location [" + this.handlerMappingsLocation + "]", ex);
                        }
                    }
                }
            }
            return handlerMappings;
        }
    

    使用了双重检查机制,获取handlerMappings缓存对象,该缓存对象为空的情况下,读取handlerMappingsLocation获取默认的handlers文件路径:

    META-INF/spring.handlers
    

    所以我们在前文的demo才需要指定一份spring.handlers文件,在getHandlerMappings方法中,将spring.handlers文件的内容解析到handlerMappings中,handlerMappings的value值可能是以下俩种中的一种:

    • 已经加载完毕的NamespaceHandler:对应resolve方法的else if分支
    • 未加载的NamespaceHandler的类路径:对应resolve方法的else分支

    明显,在我们的demo中,获取到的仅仅是UserNamespaceHandler的类路径。

    初始完毕handlerMappings之后,返回到resolve方法,此时根据namespaceUri从handleMappings中获取到了自定义的NamespaceHandler的类路径,再通过else分支的代码对自定义NamespaceHandler进行加载(也就是对自定义的UserNamespaceHandler进行了加载),并且调用init方法:

    namespaceHandler.init();
    

    而在demo中,自定义的UserNamespaceHandler的init方法如下:

        @Override
        public void init() {
            registerBeanDefinitionParser("user", new UserDefinitionParser());
        }
    

    该方法的功能在于注册解析器,在后文findParserForElement方法中,将会通过该注册信息获取到对应的自定义解析器

    需要注意的是,registerBeanDefinitionParser方法第一个参数值必须为自定义标签的名称,也就是xsd文件中element的name属性:<xsd:element name="user">,否则获取不到解析器

    回到parseCustomElement方法,获取到NamespaceHandler之后,调用parse方法进行解析

    2.1.2 NamespaceSupport#parse

        @Override
        @Nullable
        public BeanDefinition parse(Element element, ParserContext parserContext) {
            // 获取到解析器
            BeanDefinitionParser parser = findParserForElement(element, parserContext);
            return (parser != null ? parser.parse(element, parserContext) : null);
        }
    
        /**
         * Locates the {@link BeanDefinitionParser} from the register implementations using
         * the local name of the supplied {@link Element}.
         */
        @Nullable
        private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
            // 获取自定义元素名称,也就是自定义标签的时候重写的init方法里面的elementName的值
            String localName = parserContext.getDelegate().getLocalName(element);
            // 根据localName获取对应的自定义解析器
            BeanDefinitionParser parser = this.parsers.get(localName);
            if (parser == null) {
                parserContext.getReaderContext().fatal(
                        "Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
            }
            return parser;
        }
    

    findParserForElement方法在于通过传入的element获取到2.1.1.1中注册的自定义解析器UserDefinitionParser:


    获取UserDefinitionParser

    UserDefinitionParser继承了AbstractSingleBeanDefinitionParser,而AbstractSingleBeanDefinitionParser继承了AbstractBeanDefinitionParser,所以核心方法

    parser.parse(element, parserContext)
    

    会首先进入AbstractBeanDefinitionParser#parse

    2.2 AbstractBeanDefinitionParser#parse

        @Override
        @Nullable
        public final BeanDefinition parse(Element element, ParserContext parserContext) {
            // 进行解析,得到definition,此时的definition已经是解析的对象,包含了自定义元素的属性值等
            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 = null;
                    // 是否指定name属性作为别名
                    if (shouldParseNameAsAliases()) {
                        String name = element.getAttribute(NAME_ATTRIBUTE);
                        if (StringUtils.hasLength(name)) {
                            aliases = StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray(name));
                        }
                    }
                    // 注册别名
                    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) {
                    String msg = ex.getMessage();
                    parserContext.getReaderContext().error((msg != null ? msg : ex.toString()), element);
                    return null;
                }
            }
            return definition;
        }
    

    首先调用了parseInternal方法

    2.3 AbstractSingleBeanDefinitionParser#parseInternal

        @Override
        protected final AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
            BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
            String parentName = getParentName(element);
            if (parentName != null) {
                builder.getRawBeanDefinition().setParentName(parentName);
            }
            // 获取自定义的BeanClass,一般情况下是需要重写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));
            BeanDefinition containingBd = parserContext.getContainingBeanDefinition();
            if (containingBd != null) {
                // Inner bean definition must receive same scope as containing bean.
                builder.setScope(containingBd.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();
        }
    

    该方法核心点在于:

    • getBeanClass:指定自定义的bean对象类型
    • doParse:指定自定义标签解析逻辑
      所以,demo中自定义UserDefinitionParser的时候,我们重写了这俩个方法

    回过头继续看看parse方法,解析完自定义标签之后,对id、别名等进行了解析,其中resolveId解析逻辑如下:

        protected String resolveId(Element element, AbstractBeanDefinition definition, ParserContext parserContext)
                throws BeanDefinitionStoreException {
    
            // 用户指定自动生成id的情况下,直接自动生成id
            if (shouldGenerateId()) {
                return parserContext.getReaderContext().generateBeanName(definition);
            }
            else {
                // 否则,获取元素定义的id属性
                String id = element.getAttribute(ID_ATTRIBUTE);
                // 如果没有定义id属性,并且用户指定了缺省id属性情况下自动生成id
                if (!StringUtils.hasText(id) && shouldGenerateIdAsFallback()) {
                    id = parserContext.getReaderContext().generateBeanName(definition);
                }
                return id;
            }
        }
    

    其余逻辑较为简单,此处省略

    3 时序图

    时序图

    相关文章

      网友评论

          本文标题:Spring源码解析(三)-解析自定义标签

          本文链接:https://www.haomeiwen.com/subject/qeilwhtx.html