使用 Demo
配置文件
<?xml version="1.0" encoding="UTF-8"?> <!-- 注意 schema 位置,最后两行是我新增的自定义配置 --> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:myname="http://vip-augus.github.io/schema/product" xsi:schemaLocation="http://www.springframework.org/schema/beans http://vip-augus.github.io/schema/product http://vip-augus.github.io/schema/product.xsd"> <!-- 自定义标签使用 --> <myname:product id="product" productId="1" name="Apple" unit="台"/> </beans>
测试代码
public class ProductBootstrap { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("custom/custom-label.xml"); Product product = (Product) context.getBean("product"); // 输出 Product{, productId ='1', unit='台', name='Apple'} System.out.println(product.toString()); } }
小结
现在来回顾一下,Spring
遇到自定义标签是,加载自定义的大致流程:
•定位 spring.hanlders
和 spring.schemas
:在两个文件中找到对应的 handler
和 XSD
,默认位置在 resources
-> META-INF
。•Handler
注册 Parser
:扩展了 NamespaceHandlerSupport
的类,在初始化注册解析器•运行解析器 Parser
:扩展了 AbstractSingleBeanDefinitionParser
,通过重载方法进行属性解析,完成解析。
上面已经将自定义注解的使用讲了,接下来讲的是源码中如何对自定义标签进行解析。
自定义标签解析
在上一篇笔记中,讲了如何解析默认标签,Spring
判断一个标签不是默认标签的话,就会将这个标签解析交给自定义标签的解析方法
直接定位到解析自定义标签的方法吧:
org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parseCustomElement(org.w3c.dom.Element, org.springframework.beans.factory.config.BeanDefinition)
public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) { // 注释 3.8 ① 找到命名空间 String namespaceUri = getNamespaceURI(ele); // ② 根据命名空间找到对应的 NamespaceHandler NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri); // ③ 调用自定义的 NamespaceHandler 进行解析 return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd)); }
看着流程是不是觉得很熟悉,我们刚才在自定义标签使用时,定义的文件顺序是一样的,下面来讲下这三个方法,具体代码不会贴太多,主要记录一些关键方法和流程,详细代码和流程请下载我上传的工程~
① 获取标签的命名空间
public String getNamespaceURI(Node node) { return node.getNamespaceURI(); }
这个方法具体做的事情很简单,而且传参的类型 org.w3c.dom.Node
,已经提供了现成的方法,所以我们只需要调用即可。
② 根据命名空间找到对应的 NamespaceHandler
具体解析方法这这个类中:
org.springframework.beans.factory.xml.DefaultNamespaceHandlerResolver#resolve
public NamespaceHandler resolve(String namespaceUri) { // 注释 3.9 获取所有已经配置的 handler 映射 Map<String, Object> handlerMappings = getHandlerMappings(); // 从 map 中取出命名空间对应的 NamespaceHandler 的 className // 这个映射 map 值,没有的话,会进行实例化类,然后放入 map,等下次同样命名空间进来就能直接使用了 Object handlerOrClassName = handlerMappings.get(namespaceUri); if (handlerOrClassName == null) { return null; } else if (handlerOrClassName instanceof NamespaceHandler) { return (NamespaceHandler) handlerOrClassName; } else { String className = (String) handlerOrClassName; 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); // 调用 handler 的 init() 方法 namespaceHandler.init(); // 放入 handler 映射中 handlerMappings.put(namespaceUri, namespaceHandler); return namespaceHandler; } }
找对应的 NamespaceHandler
,关键方法在于 getHandlerMappings()
:
private Map<String, Object> getHandlerMappings() { Map<String, Object> handlerMappings = this.handlerMappings; // 如果没有缓存,进行缓存加载,公共变量,加锁进行操作,细节好评???? if (handlerMappings == null) { synchronized (this) { handlerMappings = this.handlerMappings; if (handlerMappings == null) { Properties mappings = PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader); handlerMappings = new ConcurrentHashMap<>(mappings.size()); CollectionUtils.mergePropertiesIntoMap(mappings, handlerMappings); this.handlerMappings = handlerMappings; } } } return handlerMappings; }
所以我们能看到,找 Handler
时,使用的策略是延迟加载,在 map
缓存中找到了直接返回,没找到对应的 Handler
,将处理器实例化,执行 init()
方法,接着将 Handler
放入 map
缓存中,等待下一个使用。
③ 调用自定义的 NamespaceHandler 进行解析
回忆一下,我们在自定义标签解析的时候,是没有重载 parse()
方法,所以定位进去,看到实际调用方法是这两行:
org.springframework.beans.factory.xml.NamespaceHandlerSupport#parse
public BeanDefinition parse(Element element, ParserContext parserContext) { // 寻找解析器并进行解析操作 BeanDefinitionParser parser = findParserForElement(element, parserContext); // 真正解析调用调用的方法 return (parser != null ? parser.parse(element, parserContext) : null); }
第一步获取解析器,就是我们之前在 init()
方法中,注册到 Spring
容器的解析器。
第二步才是解析器进行解析的方法,我们的解析器扩展的是 AbstractSingleBeanDefinitionParser
,所以实际是调用了我们解析器父类的父类 AbstractBeanDefinitionParser
的 parse
方法:
org.springframework.beans.factory.xml.AbstractBeanDefinitionParser#parse
public final BeanDefinition parse(Element element, ParserContext parserContext) { // 注释 3.10 实际自定义标签解析器调用的方法,在 parseInternal 方法中,调用了我们重载的方法 AbstractBeanDefinition definition = parseInternal(element, parserContext); ... return definition; }
解析关键方法
org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser#parseInternal
protected final AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) { BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(); String parentName = getParentName(element); if (parentName != null) { builder.getRawBeanDefinition().setParentName(parentName); } Class<?> beanClass = getBeanClass(element); if (beanClass != null) { builder.getRawBeanDefinition().setBeanClass(beanClass); } else { 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); } // 注释 3.11 在这里调用了我们写的解析方法 doParse(element, parserContext, builder); return builder.getBeanDefinition(); }
这里我要倒着讲,在第二步解析时,不是直接调用了自定义的 doParse
方法,而是进行了一系列的数据准备,包括了 beanClass
、 class
、 lazyInit
等属性的准备。
第一步解析,在我省略的代码中,是将第二步解析后的结果进行包装,从 AbstractBeanDefinition
转换成 BeanDefinitionHolder
,然后进行注册。转换和注册流程在第一篇笔记已经介绍过了,不再赘述。
到这里为止,我们自定义标签的解析就完成了~
总结
在我们自定义标签时,是不是感觉使用起来很简单,只需定义几个文件,然后在自定义解析器中写上业务处理逻辑,然后就能使用。
在我们分析完整个解析流程,就能看到,Spring
在背后默默帮我们完成了很多事情,类似默认标签解析过程,根据命名空间找到对应的处理器,然后再找到解析器,在解析器里面调用我们个性化的处理逻辑。
这两篇文章填了默认标签和自定义标签解析的坑,也完整的介绍了 Spring
将 bean
从配置中加载到内存中的全过程,下一篇开始分析解析类的加载~
由于个人技术有限,如果有理解不到位或者错误的地方,请留下评论,我会根据朋友们的建议进行修正
spring-analysis-note 码云 Gitee 地址[19]
spring-analysis-note Github 地址[20]