本文接着 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)); } } } 复制代码
处理过程分为三步:
获取标签的命名空间
标签的解析是从命名空间的提起开始的,无论是 区分 Spring 中默认标签和自定义标签 还是 区分自定义标签中不同标签的处理器 都是以标签所提供的命名空间为基础的,而至于如何提取对应元素的命名空间其实并不需要我们亲内去实现,在 org.w3c.dom.Node 中已经提供了方法供我们直接调用:
String namespaceUri = getNamespaceURI(ele); @Nullable public String getNamespaceURI(Node node) { return node.getNamespaceURI(); } 复制代码
在代码调试过程中可以看到此处的数据,如下图所示:
读取自定义标签处理器
根据 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 中实现:
我们在 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 就可以得到相应的类路径,对其进行初始化等到相应的 Handler 对象,调用 parse()
方法,在该方法中根据标签的 localName 得到相应的 BeanDefinitionParser 实例对象,调用 parse()
,该方法定义在 BeanDefinitionParser 的实现类中。对于自定义的 Parser 类,首先需要与 bean 类相关联,这里我们采用的是设置个 beanclass 属性, 最为重要的是 doParse()
方法,该方法将标签中填写的属性值,填充到一个新的 BeanDefinition 中,最后将其注册到 BeanDefinitionRegistry 中 。