Spring IoC自定义标签解析

hresh 343 0

Spring IoC自定义标签解析

概述

本文接着 Spring IoC之存储对象BeanDefinition 一文继续学习,在学习自定义标签的知识时,首先我们先了解一下自定义标签的实现,欢迎阅读:Spring自定义标签的实现

自定义标签

parseBeanDefinitions()方法中有这么一段代码:

if (delegate.isDefaultNamespace(ele)) {
    this.parseDefaultElement(ele, delegate);
} else {
    delegate.parseCustomElement(ele);
}

如果传入的标签不是默认标签,则调用 parseCustomElement(ele)方法,该方法定义如下:

public BeanDefinition parseCustomElement(Element ele) {
    return this.parseCustomElement(ele, (BeanDefinition)null);
}

@Nullable
public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
    // 获取 namespaceUri
    String namespaceUri = this.getNamespaceURI(ele);
    if (namespaceUri == null) {
        return null;
    } else {
        // 根据 namespaceUri 获取相应的 Handler
        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 {
            // 调用自定义的 Handler 处理
            return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
        }
    }
}

处理过程分为三步:

  1. 获取标签的命名空间
  2. 读取自定义标签处理器
  3. 标签解析

获取标签的命名空间

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

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

在代码调试过程中可以看到此处的数据,如下图所示:

Spring IoC自定义标签解析

读取自定义标签处理器

根据 namespaceUri 获取 Handler,这个映射关系我们在 Spring.handlers 中已经定义了,所以只需要找到该类,然后初始化返回,最后调用该 Handler 对象的 parse() 方法处理,该方法我们也提供了实现。所以上面的核心就在于怎么找到该 Handler 类。调用方法为

this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri)

首先 this.readerContext.getNamespaceHandlerResolver()返回了一个 NamespaceHandlerResolver 对象,该对象在 registerBeanDefinitions 中的 createReaderContext()方法设置的,其中涉及到以下方法:

public XmlReaderContext createReaderContext(Resource resource) {
    return new XmlReaderContext(resource, this.problemReporter, this.eventListener, this.sourceExtractor, this, this.getNamespaceHandlerResolver());
}

XmlReaderContext 构造函数中最后一个参数就是 NamespaceHandlerResolver 对象,该对象由 getNamespaceHandlerResolver() 提供,如下:

public NamespaceHandlerResolver getNamespaceHandlerResolver() {
    if (this.namespaceHandlerResolver == null) {
        this.namespaceHandlerResolver = createDefaultNamespaceHandlerResolver();
    }
    return this.namespaceHandlerResolver;
}

protected NamespaceHandlerResolver createDefaultNamespaceHandlerResolver() {
    ClassLoader cl = (getResourceLoader() != null ? getResourceLoader().getClassLoader() : getBeanClassLoader());
    return new DefaultNamespaceHandlerResolver(cl);
}

所以 getNamespaceHandlerResolver().resolve(namespaceUri) 调用的就是 DefaultNamespaceHandlerResolver 的 resolve()。如下:

public NamespaceHandler resolve(String namespaceUri) {
    // 获取handlerMapping对象,其键为当前的命名空间url,
    // 值为当前命名空间的处理逻辑类对象,或者为处理逻辑类的包含全路径的类名
    Map<String, Object> handlerMappings = this.getHandlerMappings();
    // 查看是否存在当前url的处理类逻辑,没有则返回null
    Object handlerOrClassName = handlerMappings.get(namespaceUri);
    if (handlerOrClassName == null) {
        return null;
    } else if (handlerOrClassName instanceof NamespaceHandler) {
        // 如果存在当前url对应的处理类对象,则直接返回该处理对象
        return (NamespaceHandler)handlerOrClassName;
    } else {
        // 如果当前url对应的处理逻辑还是一个没初始化的全路径类名,则通过反射对其进行初始化
        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)BeanUtils.instantiateClass(handlerClass);
                // 调用 init() 方法
                namespaceHandler.init();
                // 记录在缓存
                handlerMappings.put(namespaceUri, namespaceHandler);
                return namespaceHandler;
            }
        } catch (ClassNotFoundException var7) {
            throw new FatalBeanException("Could not find NamespaceHandler class [" + className + "] for namespace [" + namespaceUri + "]", var7);
        } catch (LinkageError var8) {
            throw new FatalBeanException("Unresolvable class definition for NamespaceHandler class [" + className + "] for namespace [" + namespaceUri + "]", var8);
        }
    }
}

首先调用 getHandlerMappings() 获取所有配置文件中的映射关系 handlerMappings ,该关系为 <命名空间,类路径>,然后根据命名空间 namespaceUri 从映射关系中获取相应的信息,如果为空或者已经初始化了就直接返回,否则根据反射对其进行初始化,同时调用其 init() 方法,最后将该 Handler 对象缓存。 init() 方法主要是将自定义标签解析器进行注册,如我测试代码中的 init()

public class MyNamespaceHandler extends NamespaceHandlerSupport {
    @Override
    public void init() {
//        registerBeanDefinitionParser("car",new CarBeanDefinitionParser());
        registerBeanDefinitionParser("xxx",new CarParser(Car.class));
    }
}

当得到自定义命名空间处理后会马上执行 namespaceHandler.init() 来进行自定义 BeanDefinitionParser的注册,在这里,你可以注册多个标签解析器。init()中的 registerBeanDefinitionParser 方法 其实就是将映射关系放在一个 Map 结构的 parsers 对象中:private final Map parsers

标签解析

得到了解析器和分析的元素后,Spring 就可以将解析工作委托给自定义解析器去解析了,对于标签的解析使用的是:NamespaceHandler.parse(ele, new ParserContext(this.readerContext, this, containingBd))方法,进入到方法体内:

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

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

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

获取 localName,在上面的例子中就是 : xxx,然后从 Map 实例 parsers 中获取 CarParser 实例对象。返回 BeanDefinitionParser 对象后,调用其 parse(),该方法在 CarParser 中实现:

Spring IoC自定义标签解析

我们在 CarParser 类中定义了两个方法如下:

public class CarParser implements BeanDefinitionParser {
    private Class<?> beanclass;

    public CarParser(Class<?> beanclass) {
        this.beanclass = beanclass;
    }

    @Override
    public BeanDefinition parse(Element element, ParserContext parserContext) {
        RootBeanDefinition beanDefinition = new RootBeanDefinition();
        beanDefinition.setBeanClass(beanclass);
        beanDefinition.setLazyInit(false);

        String brand = element.getAttribute("brand");
        String color = element.getAttribute("color");
        double price = Double.valueOf(element.getAttribute("price"));
        int maxSpeed = Integer.valueOf(element.getAttribute("speed"));

        beanDefinition.getPropertyValues().add("brand",brand);
        beanDefinition.getPropertyValues().add("color",color);
        beanDefinition.getPropertyValues().add("price",price);
        beanDefinition.getPropertyValues().add("maxSpeed",maxSpeed);
        BeanDefinitionRegistry beanDefinitionRegistry = parserContext.getRegistry();
//        beanDefinitionRegistry.registerBeanDefinition(beanclass.getName(),beanDefinition);//注册bean到BeanDefinitionRegistry中

        String id = element.getAttribute("id");
        beanDefinitionRegistry.registerBeanDefinition(id,beanDefinition);
        return beanDefinition;
    }

}

自定义的 parse()方法首先定义创建一个 RootBeanDefinition,setBeanClass()方法相当于标签中的 class 属性,通过 element 获取到各个属性填写的值,然后填充到 beanDefinition 的 propertyValues 属性中,用于之后 bean 实例的创建。id 属性还是用来标识标签,最后将 id 和 beanDefinition 一起注册到BeanDefinitionRegistry 中。

至此,自定义标签的解析过程已经分析完成了。其实整个过程还是较为简单:首先会加载 handlers 文件,将其中内容进行一个解析,形成 <namespaceUri,类路径> 这样的一个映射,然后根据获取的 namespaceUri 就可以得到相应的类路径,对其进行初始化等到相应的 Handler 对象,调用 parse() 方法,在该方法中根据标签的 localName 得到相应的 BeanDefinitionParser 实例对象,调用 parse() ,该方法定义在 BeanDefinitionParser 的实现类中。对于自定义的 Parser 类,首先需要与 bean 类相关联,这里我们采用的是设置个 beanclass 属性, 最为重要的是 doParse()方法,该方法将标签中填写的属性值,填充到一个新的 BeanDefinition 中,最后将其注册到 BeanDefinitionRegistry 中 。

发表评论 取消回复
表情 图片 链接 代码

分享