关于java:spring源码解析IOC之自定义标签解析

1次阅读

共计 16098 个字符,预计需要花费 41 分钟才能阅读完成。

概述

之前咱们曾经介绍了 spring 中默认标签的解析,解析来咱们将剖析自定义标签的解析,咱们先回顾下自定义标签解析所应用的办法,如下图所示:

咱们看到自定义标签的解析是通过 BeanDefinitionParserDelegate.parseCustomElement(ele)进行的,解析来咱们进行详细分析。

自定义标签的应用

扩大 Spring 自定义标签配置个别须要以下几个步骤:

  1. 创立一个须要扩大的组件
  2. 定义一个 XSD 文件,用于形容组件内容
  3. 创立一个实现 AbstractSingleBeanDefinitionParser 接口的类,用来解析 XSD 文件中的定义和组件定义
  4. 创立一个 Handler,继承 NamespaceHandlerSupport,用于将组件注册到 Spring 容器
  5. 编写 Spring.handlers 和 Spring.schemas 文件

上面就依照下面的步骤来实现一个自定义标签组件。

创立组件

该组件就是一个一般的 JavaBean,没有任何特别之处。这里我创立了两个组件,为什么是两个,前面有用到

User.java

package dabin.spring01;

public class User {

    private String id;

    private String userName;

    private String email;public void setId(String id) {this.id = id;}public void setUserName(String userName) {this.userName = userName;}public void setEmail(String email) {this.email = email;}

    @Override
    public String toString() {final StringBuilder sb = new StringBuilder("{");
        sb.append("\"id\":\"")
                .append(id).append('\"');
        sb.append(",\"userName\":\"")
                .append(userName).append('\"');
        sb.append(",\"email\":\"")
                .append(email).append('\"');
        sb.append('}');
        return sb.toString();}
}

Phone.java

package dabin.spring01;

public class Phone {

    private String color;

    private int size;

    private String remark;


    public void setColor(String color) {this.color = color;}

    public void setSize(int size) {this.size = size;}

    public void setRemark(String remark) {this.remark = remark;}

    @Override
    public String toString() {final StringBuilder sb = new StringBuilder("{");
        sb.append("\"color\":\"")
                .append(color).append('\"');
        sb.append(",\"size\":")
                .append(size);
        sb.append(",\"remark\":\"")
                .append(remark).append('\"');
        sb.append('}');
        return sb.toString();}
}

定义 XSD 文件

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

    <xsd:element name="phone">
        <xsd:complexType>
            <xsd:attribute name="id" type="xsd:string" />
            <xsd:attribute name="color" type="xsd:string" />
            <xsd:attribute name="size" type="xsd:int" />
            <xsd:attribute name="remark" type="xsd:string" />
        </xsd:complexType>
    </xsd:element>

</xsd:schema>

在上述 XSD 文件中形容了一个新的 targetNamespace,并在这个空间里定义了一个 name 为 userphone的 element。user 外面有三个 attribute。次要是为了验证 Spring 配置文件中的自定义格局。再进一步解释,就是,Spring 地位文件中应用的 user 自定义标签中,属性只能是下面的三种,有其余的属性的话,就会报错。

Parser 类

定义一个 Parser 类,该类继承 AbstractSingleBeanDefinitionParser,并实现 getBeanClass()doParse() 两个办法。次要是用于解析 XSD 文件中的定义和组件定义。这里定义了两个 Parser 类,一个是解析 User 类,一个用来解析 Phone 类。

UserBeanDefinitionParser.java

package dabin.spring01;

import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;

public class UserBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
    @Override
    protected Class getBeanClass(Element ele){return User.class;}

    @Override
    protected void doParse(Element element, BeanDefinitionBuilder builder) {String id = element.getAttribute("id");
        String userName=element.getAttribute("userName");
        String email=element.getAttribute("email");
        if(StringUtils.hasText(id)){builder.addPropertyValue("id",id);
        }
        if(StringUtils.hasText(userName)){builder.addPropertyValue("userName", userName);
        }
        if(StringUtils.hasText(email)){builder.addPropertyValue("email", email);
        }

    }
}

PhoneBeanDefinitionParser.java

package dabin.spring01;

import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;

public class PhoneBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
    @Override
    protected Class getBeanClass(Element ele){return Phone.class;}

    @Override
    protected void doParse(Element element, BeanDefinitionBuilder builder) {String color = element.getAttribute("color");
        int size=Integer.parseInt(element.getAttribute("size"));
        String remark=element.getAttribute("remark");
        if(StringUtils.hasText(color)){builder.addPropertyValue("color",color);
        }
        if(StringUtils.hasText(String.valueOf(size))){builder.addPropertyValue("size", size);
        }
        if(StringUtils.hasText(remark)){builder.addPropertyValue("remark", remark);
        }

    }
}

Handler 类

定义 Handler 类,继承 NamespaceHandlerSupport , 次要目标是将下面定义的解析器 Parser 类 注册到 Spring 容器中。

package dabin.spring01;

import org.springframework.beans.factory.xml.NamespaceHandlerSupport;

public class MyNamespaceHandler extends NamespaceHandlerSupport {

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

        registerBeanDefinitionParser("phone",new PhoneBeanDefinitionParser());
    }

}

咱们看看 registerBeanDefinitionParser 办法做了什么

private final Map<String, BeanDefinitionParser> parsers = new HashMap<>();

protected final void registerBeanDefinitionParser(String elementName, BeanDefinitionParser parser) {this.parsers.put(elementName, parser);
}

就是将解析器 UserBeanDefinitionParser 和 PhoneBeanDefinitionParser 的实例放到全局的 Map 中,key 为 user 和 phone。

Spring.handlers 和 Spring.schemas

编写 Spring.handlers 和 Spring.schemas 文件,默认地位放在工程的 META-INF 文件夹下

Spring.handlers

http\://www.dabin.com/schema/user=dabin.spring01.MyNamespaceHandler

Spring.schemas

http\://www.dabin.com/schema/user.xsd=org/user.xsd

而 Spring 加载自定义的大抵流程是遇到自定义标签而后 就去 Spring.handlers 和 Spring.schemas 中去找对应的 handler 和 XSD,默认地位是 META-INF 下,进而有找到对应的 handler 以及解析元素的 Parser,从而实现了整个自定义元素的解析,也就是说 Spring 将向定义标签解析的工作委托给了 用户去实现。

创立测试配置文件

通过下面几个步骤,就能够应用自定义的标签了。在 xml 配置文件中应用如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:myTag="http://www.dabin.com/schema/user"
       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://www.dabin.com/schema/user http://www.dabin.com/schema/user.xsd">

    <bean id="myTestBean" class="dabin.spring01.MyTestBean"/>

    <myTag:user id="user" email="dabin@163.com" userName="dabin" />

    <myTag:phone id="iphone" color="black" size="128" remark="iphone XR"/>

</beans>

xmlns:myTag示意 myTag 的命名空间是 http://www.dabin.com/schema/user,在文章结尾的判断处 if (delegate.isDefaultNamespace(ele)) 必定会返回 false,将进入到自定义标签的解析

测试

import dabin.spring01.MyTestBean;
import dabin.spring01.Phone;
import dabin.spring01.User;
import org.junit.Test;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;

public class AppTest {
    @Test
    public void MyTestBeanTest() {BeanFactory bf = new XmlBeanFactory( new ClassPathResource("spring-config.xml"));
        //MyTestBean myTestBean01 = (MyTestBean) bf.getBean("myTestBean");
        User user = (User) bf.getBean("user");
        Phone iphone = (Phone) bf.getBean("iphone");

        System.out.println(user);
        System.out.println(iphone);
    }

}

输入后果:

("id":"user","userName":"dabin","email":"dabin@163. com”}
{"color":"black","size":128,"remark":"iphone XR"}

自定义标签的解析

理解了自定义标签的应用后,接下来咱们剖析下自定义标签的解析,自定义标签解析用的是办法:parseCustomElement(Element ele, @Nullable BeanDefinition containingBd),进入办法体:

public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
    // 获取 标签对应的命名空间
    String namespaceUri = getNamespaceURI(ele);
    if (namespaceUri == null) {return null;}

    // 依据 命名空间找到相应的 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;
    }

    // 调用自定义的 Handler 解决
    return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}

置信理解了自定义标签的应用办法后,或多或少会对向定义标签的实现过程有一个本人的想法。其实思路十分的简略,无非是依据对应的 bean 获取对应的命名空间,依据命名空间解析对应的处理器,而后依据用户自定义的处理器进行解析。

获取标签的命名空间

标签的解析是从命名空间的提起开始的,元论是 辨别 Spring 中默认标签和自定义标 还是 辨别自定义标签中不同标签的处理器 都是以标签所提供的命名空间为根底的,而至于如何提取对应元素的命名空间其实并不需要咱们亲内去实现,在 org.w3c.dom.Node 中曾经提供了办法供咱们间接调用:

String namespaceUri = getNamespaceURI(ele);
@Nullable
public String getNamespaceURI(Node node) {return node.getNamespaceURI();
}

这里咱们能够通过 DEBUG 看出myTag:user 自定义标签对应的 namespaceUri 是 http://www.dabin.com/schema/user

读取自定义标签处理器

依据 namespaceUri 获取 Handler,这个映射关系咱们在 Spring.handlers 中曾经定义了,所以只须要找到该类,而后初始化返回,最初调用该 Handler 对象的 parse() 办法解决,该办法咱们也提供了实现。所以下面的外围就在于怎么找到该 Handler 类。调用办法为:this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri)

public NamespaceHandler resolve(String namespaceUri) {
    // 获取所有曾经配置的 Handler 映射
    Map<String, Object> handlerMappings = getHandlerMappings();

    // 依据 namespaceUri 获取 handler 的信息:这里个别都是类门路
    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("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);
        }
    }
}

首先调用 getHandlerMappings() 获取所有配置文件中的映射关系 handlerMappings,就是咱们在 Spring.handlers 文件中配置 命名空间与命名空间处理器的映射关系,该关系为 < 命名空间, 类门路 >,而后依据命名空间 namespaceUri 从映射关系中获取相应的信息,如果为空或者曾经初始化了就间接返回,否则依据反射对其进行初始化,同时调用其 init()办法,最初将该 Handler 对象缓存。咱们再次回顾下示例中对于命名空间处理器的内容:

public class MyNamespaceHandler extends NamespaceHandlerSupport {

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

        registerBeanDefinitionParser("phone",new PhoneBeanDefinitionParser());
    }

}

当失去自定义命名空间解决后会马上执行 namespaceHandler.init()来进行自定义 BeanDefinitionParser 的注册,在这里,你能够注册多个标签解析器,如以后示例中 <myTag:user 标签就应用 new UserBeanDefinitionParser()解析器;<myTag:phone 就应用 new PhoneBeanDefinitionParser()解析器。

下面咱们曾经说过,init()中的 registerBeanDefinitionParser 办法 其实就是将映射关系放在一个 Map 构造的 parsers 对象中:private final Map<String, BeanDefinitionParser> parsers

标签解析

失去了解析器和剖析的元素后,Spring 就能够将解析工作委托给自定义解析器去解析了,对于标签的解析应用的是:NamespaceHandler.parse(ele, new ParserContext(this.readerContext, this, containingBd))办法,进入到办法体内:

public BeanDefinition parse(Element element, ParserContext parserContext) {BeanDefinitionParser parser = findParserForElement(element, parserContext);
    return (parser != null ? parser.parse(element, parserContext) : null);
}

调用 findParserForElement() 办法获取 BeanDefinitionParser 实例,其实就是获取在 init() 办法外面注册的实例对象。如下:

private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
    // 获取元素名称,也就是 <myTag:user 中的 user
    String localName = parserContext.getDelegate().getLocalName(element);
    // 依据 user 找到对应的解析器,也就是在
    //registerBeanDefinitionParser("user",new UserBeanDefinitionParser());
    // 中注册的解析器
    BeanDefinitionParser parser = this.parsers.get(localName);
    if (parser == null) {parserContext.getReaderContext().fatal("Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
    }
    return parser;
}

获取 localName,在下面的例子中就是:user,而后从 Map 实例 parsers 中获取 UserBeanDefinitionParser 实例对象。返回 BeanDefinitionParser 对象后,调用其 parse(),该办法在 AbstractBeanDefinitionParser 中实现:

咱们能够从 DEBUG 中看出,以后标签是 <myTag:user,对应的 localName 是 user,对应的自定义解析器是 UserBeanDefinitionParser,返回的是 UserBeanDefinitionParser 实例对象。接下来咱们看看 parser.parse (element, parserContext),该办法在 AbstractBeanDefinitionParser 中实现:

public final BeanDefinition parse(Element element, ParserContext parserContext) {AbstractBeanDefinition definition = parseInternal(element, parserContext);
    if (definition != null && !parserContext.isNested()) {
        try {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()) {String name = element.getAttribute(NAME_ATTRIBUTE);
                if (StringUtils.hasLength(name)) {aliases = StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray(name));
                }
            }
            BeanDefinitionHolder holder = new BeanDefinitionHolder(definition, id, aliases);
            // 将 AbstractBeanDefinition 转换为 BeanDefinitionHolder 并注册
            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;
}

尽管说是对自定义配置文件的解析,然而咱们能够看到在这个函数中大部分的代码用来解决将解析后的 AbstractBeanDefinition 转换为 BeanDefinitionHolder 并注册的性能,而真正去做解析的事件委托了给 parseInternal,真是这句代码调用了咱们的自定义解析函数。在 parseInternal 中,并不是间接调用自定义的 doParse 函数,而是进行了一些列的数据筹备,包含对 beanClass,scope,lazyInit 等属性的筹备。咱们进入到 AbstractSingleBeanDefinitionParser.parseInternal 办法中:

protected final AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
    // 创立一个 BeanDefinitionBuilder,外部实际上是创立一个 GenericBeanDefinition 的实例,用于存储自定义标签的元素
    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 {// beanClass 为 null,意味着子类并没有重写 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();}

public static BeanDefinitionBuilder genericBeanDefinition() {return new BeanDefinitionBuilder(new GenericBeanDefinition());
}

protected Class<?> getBeanClass(Element element) {return null;}

protected void doParse(Element element, BeanDefinitionBuilder builder) {}

在该办法中咱们次要关注两个办法:getBeanClass()doParse()。对于 getBeanClass() 办法,AbstractSingleBeanDefinitionParser 类并没有提供具体实现,而是间接返回 null,意味着它心愿子类可能重写该办法,当然如果没有重写该办法,这会去调用 getBeanClassName(),判断子类是否曾经重写了该办法。对于 doParse() 则是间接空实现。所以对于 parseInternal() 而言它总是期待它的子类可能实现 getBeanClass()doParse(),其中 doParse() 尤为重要,如果你不提供实现,怎么来解析自定义标签呢?最初将自定义的解析器:UserDefinitionParser 再次回观。

public class UserBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
    @Override
    protected Class getBeanClass(Element ele){return User.class;}

    @Override
    protected void doParse(Element element, BeanDefinitionBuilder builder) {String id = element.getAttribute("id");
        String userName=element.getAttribute("userName");
        String email=element.getAttribute("email");
        if(StringUtils.hasText(id)){builder.addPropertyValue("id",id);
        }
        if(StringUtils.hasText(userName)){builder.addPropertyValue("userName", userName);
        }
        if(StringUtils.hasText(email)){builder.addPropertyValue("email", email);
        }

    }
}

咱们看看 builder.addPropertyValue (“id”,id),实际上是将自定义标签中的属性解析,存入 BeanDefinitionBuilder 中的 beanDefinition 实例中

private final AbstractBeanDefinition beanDefinition;

public BeanDefinitionBuilder addPropertyValue(String name, @Nullable Object value) {this.beanDefinition.getPropertyValues().add(name, value);
    return this;
}

最初 将 AbstractBeanDefinition 转换为 BeanDefinitionHolder 并注册 registerBeanDefinition(holder, parserContext.getRegistry()); 这就和默认标签的注册是一样了。

至此,自定义标签的解析过程曾经剖析实现了。其实整个过程还是较为简单:首先会加载 handlers 文件,将其中内容进行一个解析,造成 <namespaceUri, 类门路 > 这样的一个映射,而后依据获取的 namespaceUri 就能够失去相应的类门路,对其进行初始化等到相应的 Handler 对象,调用 parse() 办法,在该办法中依据标签的 localName 失去相应的 BeanDefinitionParser 实例对象,调用 parse(),该办法定义在 AbstractBeanDefinitionParser 抽象类中,外围逻辑封装在其 parseInternal() 中,该办法返回一个 AbstractBeanDefinition 实例对象,其次要是在 AbstractSingleBeanDefinitionParser 中实现,对于自定义的 Parser 类,其须要实现 getBeanClass() 或者 getBeanClassName()doParse()。最初将 AbstractBeanDefinition 转换为 BeanDefinitionHolder 并注册。

正文完
 0