美文网首页
spring容器标签解析之自定义标签

spring容器标签解析之自定义标签

作者: 会上树的程序猿 | 来源:发表于2019-06-23 10:07 被阅读0次

    前面我们知道了spring是如何解析bean标签的子标签如 meta、lookup-method、replaced-method、constructor-arg、property以及qualifier等,我们都是一一做了简答的解读,还有一种自定义类型的标签,接下来我们来看spring对自定义标签的解析过程,首先我们回到解析默认标签的方法的起始入口:DefaultBeanDefinitionDocumentReader#processBeanDefinition这里是解析beanDefinition的入口,其中包括我们自定义标签的解析过程

    • 首先是构建一个BeanDefinitionHolder的实例,在该实例不为null时去解析自定义标签
    BeanDefinitionHolder为 name 和 alias 的 BeanDefinition 对象
    BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
    若不为null
    if (bdHolder != null) {
    对自定义标签解析(因为我们可能对该标签有自己定义的属性)
    bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
    

    在上述的代码中我们发现真正解析自定义标签的入口是:

    delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
    

    找到方法入口了,我们接下来看真正的解析的过程:

     public BeanDefinitionHolder decorateBeanDefinitionIfRequired(Element ele, BeanDefinitionHolder definitionHolder) {
        return decorateBeanDefinitionIfRequired(ele, definitionHolder, null);
    }
    
    public BeanDefinitionHolder decorateBeanDefinitionIfRequired(
            Element ele, BeanDefinitionHolder definitionHolder, @Nullable BeanDefinition containingBd) {
    
        BeanDefinitionHolder finalDefinition = definitionHolder;
    
        // Decorate based on custom attributes first.
        //获取所有的属性
        NamedNodeMap attributes = ele.getAttributes();
        //遍历所有的属性,并寻找是否可以装饰的属性
        for (int i = 0; i < attributes.getLength(); i++) {
            Node node = attributes.item(i);
            finalDefinition = decorateIfRequired(node, finalDefinition, containingBd);
        }
    
        // Decorate based on custom nested elements.
        //获取所有的子元素
        NodeList children = ele.getChildNodes();
        //遍历所有的子元素,并寻找可以装饰的子元素
        for (int i = 0; i < children.getLength(); i++) {
            Node node = children.item(i);
            if (node.getNodeType() == Node.ELEMENT_NODE) {
                finalDefinition = decorateIfRequired(node, finalDefinition, containingBd);
            }
        }
        return finalDefinition;
    }
    

    这就是解析的过程,简单的看一下过程:

    • 首先是对于BeanDefinitionHolder的初始化过程
    • 首先获取元素的所有的属性
    BeanDefinitionHolder finalDefinition = definitionHolder;
    
    • 接着遍历属性,并判断是否有可以装饰的属性
    • 如果有,通过其decorateIfRequired()进行装饰
    装饰属性的过程
    public BeanDefinitionHolder decorateIfRequired(
            Node node, BeanDefinitionHolder originalDef, @Nullable BeanDefinition containingBd) {
        //获取自定义的命名空间
        String namespaceUri = getNamespaceURI(node);
        //对于默认标签的默认标签进行过滤
        if (namespaceUri != null && !isDefaultNamespace(namespaceUri)) {
            //根据命名空间去获取对应的处理器
            NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
            if (handler != null) {
                //进行装饰
                BeanDefinitionHolder decorated =
                        handler.decorate(node, originalDef, new ParserContext(this.readerContext, this, containingBd));
                if (decorated != null) {
                    return decorated;
                }
            }
            else if (namespaceUri.startsWith("http://www.springframework.org/")) {
                error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", node);
            }
            else {
                // A custom namespace, not to be handled by Spring - maybe "xml:...".
                if (logger.isDebugEnabled()) {
                    logger.debug("No Spring NamespaceHandler found for XML schema namespace [" + namespaceUri + "]");
                }
            }
        }
        return originalDef;
    }
    

    这是对属性装饰的详细过程,先说完所有的说完后我们接着更详细的看

    • 接着获取所有的子元素
    NodeList children = ele.getChildNodes();
    
    • 遍历所有的子元素,装饰可以装饰的子元素

    装饰子元素的过程跟装饰属性的过程是一致的,接下来我们详细的介绍下自定义标签的解析过程,我们知道我们所有的操作都是从配置到资源的封装再到转换成Document的过程,随之而来的是提取document里的各种元素的过程,并展开了对提取到的元素进行解析并装饰,由于spring中的标签有默认和自定义两种,spring对其分别解析过程,首先我们看一下自定义标签的使用过程:

    • 首先需要创建一个拓展的组件.
    • 接着是定义一个xsd文件描述组件的内容.
    • 接着是创建一个实现AbstractSingleBeanDefinitionParser 接口的类,用来解析xsd文件的定义和组件的定义.
    • 定义一个Handler继承NamespaceHandlerSupport 抽象类 ,其目的是将组件注册到spring的容器中.
    • 编写 spring.handlers和spring.schemas文件

    接下来我们按照上述的步骤来实现自定义标签的过程

    (1) 首先创建一个实体类

    public class User {
    private String name;
    private  String email;
    

    (2) 定义xsd组件

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

    (3) 创建一个实现AbstractSingleBeanDefinitionParser的类

    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 {
    
    //Element所对应的class类
    protected Class getBeanClass(Element element){
    
        return User.class;
    }
    
    //从element中解析并获取对应的元素
    protected void doParse(Element element, BeanDefinitionBuilder bd){
    
        String name = element.getAttribute("name");
        String email = element.getAttribute("email");
    
        //将获取到的元素添加到BeanDefinitionBuilder中
        if (StringUtils.hasText(name)){
    
            bd.addPropertyValue("name",name);
        }
        if (StringUtils.hasText(email)){
    
            bd.addPropertyValue("email",email);
        }
    }
    
    }
    

    (4) 创建一个继承于NamespaceHandlerSupport的Handler

    import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
    
    public class UserNamespaceHandler extends NamespaceHandlerSupport {
    public void init() {
        registerBeanDefinitionParser("user",new UserBeanDefinitionParser());
    }
    

    (5) 对于 Spring.handler和Spring.schemas的编写,注意的是这两个文件一般默认放在resource/META-INT文件下,可以通过Spring扩展或修改源码的方法来改路径

    • Spring.handlers
    http\://www.jianshu.com/schema/user=com.sgcc.bean.UserNamespaceHandler
    
    • Spring.schemas
    http\://www.jianshu.com/schema/user.xsd=user.xsd
    
    • 编写配置文件
    <?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.jianshu.com/schema/user"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.jianshu.com/schema/user http://www.jianshu.com/schema/user.xsd ">
    
    <myTag:user id="user" email="12233445566@qq.com" userName="chenssy"/>
    
    </beans>
    
    • 测试类
    public static void main(String[] args){
    ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
    User user = (User) context.getBean("user");
    System.out.println(user.getUserName() + "----" + user.getEmail());
    
    • 最后的运行结果如下:
    微信截图_20190622175826.png

    上面就是我们通过实现自定义标签简单的用法,我们在开发中常用的自定义的标签是<tx:annotation-driven>,接着我们来看解析的过程:

    public BeanDefinition parseCustomElement(Element ele) {
        return parseCustomElement(ele, null);
    }
    
    
    /**
     *
     * @param ele
     * @param containingBd 父类的beanDefinition,如果是顶层父类beanDefinition将设置为null
     * @return
     */
    @Nullable
    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;
        }
        //解析过程
        return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
    }
    

    上述就是解析自定义标签的过程,这里简单的总结下解析的每一步:

    • 首先是对命名空间的获取
    BeanDefinitionParserDelegate.java
    //获取命名空间的URL: http://www.springframework.org/schema/beans
    @Nullable
    public String getNamespaceURI(Node node) {
        return node.getNamespaceURI();
    }
    
    • 通过我们拿到的命名空间来构建NamespaceHandler实例
    XmlReaderContext.java
    <1>.获取一个NamespaceHandlerResolver的解析器
    /**
     * Return the namespace resolver.
     * @see XmlBeanDefinitionReader#setNamespaceHandlerResolver
     */
    public final NamespaceHandlerResolver getNamespaceHandlerResolver() {
        return this.namespaceHandlerResolver;
    }
    
    DefaultNamespaceHandlerResolver.java
    <2>.通过调用resolve方法构建一个NamespaceHandler实例
    /**
     * Locate the {@link NamespaceHandler} for the supplied namespace URI
     * from the configured mappings.
     * @param namespaceUri the relevant namespace URI
     * @return the located {@link NamespaceHandler}, or {@code null} if none found
     */
    @Override
    @Nullable
    public NamespaceHandler resolve(String namespaceUri) {
        //获取所有的已经配置了的handler映射器
        Map<String, Object> handlerMappings = getHandlerMappings();
        //根据我们的命名空间找到对应的处理器的名字
        Object handlerOrClassName = handlerMappings.get(namespaceUri);
        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);
                if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) {
                    throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri +
                            "] does not implement the [" + NamespaceHandler.class.getName() + "] interface");
                }
                //实例化NamespaceHandler类
                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);
            }
        }
    }
    

    上述构建NamespaceHandler的过程中,可以看到通过getHandlerMappings方法来读取Spring.handlers配置文件同时缓存到map集合中.

    /** Stores the mappings from namespace URI to NamespaceHandler class name / instance. */
    @Nullable
    private volatile Map<String, Object> handlerMappings;
    
    
    /**
     * Load the specified NamespaceHandler mappings lazily.
     */
    private Map<String, Object> getHandlerMappings() {
        Map<String, Object> handlerMappings = this.handlerMappings;
        //如果为null,说明没有被缓存,那么可以缓存了
        if (handlerMappings == null) {
            //
            synchronized (this) {
                handlerMappings = this.handlerMappings;
                if (handlerMappings == null) {
                    if (logger.isTraceEnabled()) {
                        logger.trace("Loading NamespaceHandler mappings from [" + this.handlerMappingsLocation + "]");
                    }
                    try {
                        //加载handlerMappingsLocation
                        Properties mappings =
                                PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);
                        if (logger.isTraceEnabled()) {
                            logger.trace("Loaded NamespaceHandler mappings: " + mappings);
                        }
                        handlerMappings = new ConcurrentHashMap<>(mappings.size());
                        //将mappings合并到handlerMappings
                        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;
    }
    
    • 解析过程
    BeanDefinitionParserDelegate.java
    handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
    
    ParserContext.java
    <1>.首先是获取一个ParserContext实例
    //BeanDefinition如果此时为顶级父类,默认为null
    public ParserContext(XmlReaderContext readerContext, BeanDefinitionParserDelegate delegate,
            @Nullable BeanDefinition containingBeanDefinition) {
    
        this.readerContext = readerContext;
        this.delegate = delegate;
        this.containingBeanDefinition = containingBeanDefinition;
    }
    <2>.通过parse方法解析
    
    /**
     * Parses the supplied {@link Element} by delegating to the {@link BeanDefinitionParser} that is
     * registered for that {@link Element}.
     */
    @Override
    @Nullable
    public BeanDefinition parse(Element element, ParserContext parserContext) {
        //通过element和parserContext来构建BeanDefinitionParser实例
        BeanDefinitionParser parser = findParserForElement(element, parserContext);
        //1.如果parser不为null,调用parse方法解析,并返回结果
        //为null时直接返回null
        return (parser != null ? parser.parse(element, parserContext) : null);
    }
    
    NamespaceHandlerSupport.java
    <3>.通过调用findParserForElement方法获取BeanDefinitionParser解析器实例
    //获取beanDefinitionParser解析器实例
    private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
        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;
    }
    <4>.如果构建的BeanDefinitionParser解析器不为null,才是真正的解析过程
    
    /** Constant for the "id" attribute. */
    public static final String ID_ATTRIBUTE = "id";
    
    /** Constant for the "name" attribute. */
    public static final String NAME_ATTRIBUTE = "name";
    
    
    @Override
    @Nullable
    public final BeanDefinition parse(Element element, ParserContext parserContext) {
        //获取一个beanDefinition实例
        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;
                //
                if (shouldParseNameAsAliases()) {
                    //获取name属性
                    String name = element.getAttribute(NAME_ATTRIBUTE);
    
                    if (StringUtils.hasLength(name)) {
                        aliases = StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray(name));
                    }
                }
                //将AbstractBeanDefinition转化为BeanDefinitionHolder实例
                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;
    }
    

    在上述的解析过程中,主要做了对AbstractBeanDefinition的转化为beanDefinitionHolder和注册的过程,实际上spring将真正解析自定义标签的任务交给了AbstractSingleBeanDefinitionParser#parseInternal()方法,接下来我们看看该方法:

    protected final AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
        //构建BeanDefinitionBuilder实例
        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);
        }
        //beanClass为null
        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) {
            //封装Scope属性
            // 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.
            //设置LazyInit属性
            builder.setLazyInit(true);
        }
        //调用子类的doParse进行解析
        doParse(element, parserContext, builder);
        return builder.getBeanDefinition();
    }
    

    简单的看一下parseInternal的解析过程:

    • 首先是构建一个BeanDefinitionBuilder构造器实例
    /**
     * Create a new {@code BeanDefinitionBuilder} used to construct a {@link GenericBeanDefinition}.
     */
    public static BeanDefinitionBuilder genericBeanDefinition() {
        return new BeanDefinitionBuilder(new GenericBeanDefinition());
    }
    
    • 如果当前元素有parent元素,首先获取它的父元素
      protected String getParentName(Element element) {
        return null;
    }
    

    可以发现默认是没有实现的

    • 其次是获取自定义标签的class
    protected Class<?> getBeanClass(Element element) {
        return null;
    

    可以看到的是,没有实现默认允许让子类去实现它

    • 接着是对属性的封装
    • 调用子类的doParse方法进行解析
    /**
     * Parse the supplied {@link Element} and populate the supplied
     * {@link BeanDefinitionBuilder} as required.
     * <p>The default implementation delegates to the {@code doParse}
     * version without ParserContext argument.
     * @param element the XML element being parsed
     * @param parserContext the object encapsulating the current state of the parsing process
     * @param builder used to define the {@code BeanDefinition}
     * @see #doParse(Element, BeanDefinitionBuilder)
     */
    protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
        doParse(element, builder);
    }
    
    /**
     * Parse the supplied {@link Element} and populate the supplied
     * {@link BeanDefinitionBuilder} as required.
     * <p>The default implementation does nothing.
     * @param element the XML element being parsed
     * @param builder used to define the {@code BeanDefinition}
     */
    protected void doParse(Element element, BeanDefinitionBuilder builder) {
    }
    

    实际上该方法是可以允许子类去实现解析的逻辑,到这里关于自定义标签的解析全部说完,简单的来个小结

    • 首先是对自定义标签命名空间(namespaceUri)的获取
    • 其次是加载默认文件的source/META-INF下的spring.handlers并放入缓存中,以<namespaceUri,类路径>方式存放.
    • 接着通过获取到的namespaceUri通过反射的方式来获取NamespaceHandler实例
    • 然后调用NamespaceHandler#init方法初始化,实际上是注册对应的解析器和要获取的实例如:
    public class UserNamespaceHandler extends NamespaceHandlerSupport {
    public void init() {
        registerBeanDefinitionParser("user",new UserBeanDefinitionParser());
    }
    

    上述代码是我们自己实现的过程,底层是将我们的user实体和自定义解析器进行了相关的注册操作.

    • 接着是调用NamespaceHandler#parse方法进行自定义标签的解析操作
    • 在解析前首先通过方法findParserForElement方法来进行解析器的获取操作
    • 其次是调用namespaceHandlerSupport#parser进行解析,主要是针对于AbstractBeanDefinition的转化和注册做了处理,然后是将真正的解析操作给了parseInternal方法来完成
    • 在parseInternal 方法中进行了一系列的属性设置操作如beanClass scope LazyInit等.
    • 最后调用子类的doParse方法进行解析的操作,我们也看到了是空实现,允许子类去做自己的解析逻辑处理

    关于自定义标签的解析的过程这里就简单的说完了,接下来我们来了解spring是如何对beanDefinition的注册的过程......

    相关文章

      网友评论

          本文标题:spring容器标签解析之自定义标签

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