2. 自定义标签解析
自定义标签的解析由BeanDefinitionParserDelegate委托类的parseCustomElement方法来完成。
// 源码位置:org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parseCustomElement(org.w3c.dom.Element)
@Nullable
public BeanDefinition parseCustomElement(Element ele) {
return parseCustomElement(ele, null);
}
// 源码位置:org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parseCustomElement(org.w3c.dom.Element, org.springframework.beans.factory.config.BeanDefinition)
@Nullable
public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
String namespaceUri = getNamespaceURI(ele);
if (namespaceUri == null) {
return null;
}
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));
}
2.1. 获取NamespaceHandler
NamespaceHandler是解析自定义命名空间的统一接口,各命名空间通过实现该接口来定义解析流程。因此需要根据命名空间找到对应的NamespaceHandler来解析。
在Spring XML默认标签配置解析注册Bean源码分析(四)——BeanDefinitionDocumentReader这篇文章里面,在创建XmlReaderContext的时候我们提到过一个参数getNamespaceHandlerResolver()
,它创建了一个new DefaultNamespaceHandlerResolver(cl)
对象放到了XmlReaderContext里面。从DefaultNamespaceHandlerResolver类名我们知道这是一个解析器,目的就是解析出我们所需要的NamespaceHandler。那么重点看一下这一行代码:
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
既然我们在创建ReaderContext的时候传入了DefaultNamespaceHandlerResolver,那么先通过this.readerContext.getNamespaceHandlerResolver()
拿到DefaultNamespaceHandlerResolver,然后在调用DefaultNamespaceHandlerResolver的resolve方法得到对应的NamespaceHandler类。
// 源码位置:org.springframework.beans.factory.xml.DefaultNamespaceHandlerResolver#resolve
/**
* 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) {
Map<String, Object> handlerMappings = getHandlerMappings();
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();
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();
得到命名空间与NamespaceHandler的对应关系,我们看一下getHandlerMappings()方法。
// 源码位置:org.springframework.beans.factory.xml.DefaultNamespaceHandlerResolver#getHandlerMappings
/**
* Load the specified NamespaceHandler mappings lazily.
*/
private Map<String, Object> getHandlerMappings() {
Map<String, Object> handlerMappings = this.handlerMappings;
if (handlerMappings == null) {
synchronized (this) {
handlerMappings = this.handlerMappings;
if (handlerMappings == null) {
if (logger.isTraceEnabled()) {
logger.trace("Loading NamespaceHandler mappings from [" + this.handlerMappingsLocation + "]");
}
try {
Properties mappings =
PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);
if (logger.isTraceEnabled()) {
logger.trace("Loaded NamespaceHandler mappings: " + mappings);
}
handlerMappings = new ConcurrentHashMap<>(mappings.size());
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;
}
很显然其对应关系是从Properties文件中读取的:
Map<String, Object> handlerMappings = this.handlerMappings;
Properties mappings = PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);
handlerMappings = new ConcurrentHashMap<>(mappings.size());
CollectionUtils.mergePropertiesIntoMap(mappings, handlerMappings);
看一下handlerMappingsLocation参数,跟踪一下会发现,它的默认值是public static final String DEFAULT_HANDLER_MAPPINGS_LOCATION = "META-INF/spring.handlers";
。因此,classpath路径下所有的META-INF/spring.handlers文件都会被加载。我们看一下我们自己定义的spring.handlers,其内容为:http\://www.yanglh.com/schema/user=com.yanglh.ioc.custom.MyNamespaceHandler
。我们回到resolve方法,Object handlerOrClassName = handlerMappings.get(namespaceUri);
得到了String类型的类名com.yanglh.ioc.custom.MyNamespaceHandler
,但resolve返回值是NamespaceHandler,所以还需要对com.yanglh.ioc.custom.MyNamespaceHandler
进行实例化,如下:
// className = com.yanglh.ioc.custom.MyNamespaceHandler
Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);
// 实例化com.yanglh.ioc.custom.MyNamespaceHandler,它实现了NamespaceHandler接口
NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
// 对com.yanglh.ioc.custom.MyNamespaceHandler对象初始化
namespaceHandler.init();
// 替换映射关系的Map,将String类型的NamespaceHandler类名,改为实际的NamespaceHandler对象
handlerMappings.put(namespaceUri, namespaceHandler);
return namespaceHandler;
对于namespaceHandler.init();
,我们还记得当时定义MyNamespaceHandler类时的init方法的内容吗?看一下:
public void init() {
registerBeanDefinitionParser("user",new UserBenDefinitionParser());
}
它注入了当自定标签为<自定义标签名称:user
时,其对应的解析类为UserBenDefinitionParser。registerBeanDefinitionParser方法会把标签和BenDefinitionParser类注册到一个Map里。
protected final void registerBeanDefinitionParser(String elementName, BeanDefinitionParser parser) {
this.parsers.put(elementName, parser);
}
我们可以认为,一个命名空间对应一个NamespaceHandler,一个NamespaceHandler里面可以有多个BeanDefinitionParser。
比如:<自定义标签名称:user
对应UserBenDefinitionParser解析器;<自定义标签名称:car
时我们再定义CarBenDefinitionParser解析器;我们看一下Spring自带的MvcNamespaceHandler,它是SpringMVC命名空间的NamespaceHandler,里面注入了<mvc:annotation-driven、<mvc:interceptors等一系列的BeanDefinitionParser解析器。
到这里,就拿到了NamespaceHandler对象。
2.2. 获取BeanDefinitionParser
handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
NamespaceHandler的parse方法用于解析并最终注册BeanDefinition。
ParserContext
BeanDefinition解析过程中传递相关配置及状态的上下文,里面保存了XmlReaderContext和BeanDefinitionParserDelegate对象。
由于我们自定义的MyNamespaceHandler并不是直接实现NamespaceHandler接口,而是通过继承NamespaceHandlerSupport类。所以handler.parse方法由父类NamespaceHandlerSupport来执行。
// 源码位置:org.springframework.beans.factory.xml.NamespaceHandlerSupport#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) {
BeanDefinitionParser parser = findParserForElement(element, parserContext);
return (parser != null ? parser.parse(element, parserContext) : null);
}
获取BeanDefinitionParser
// 源码位置:org.springframework.beans.factory.xml.NamespaceHandlerSupport#findParserForElement
/**
* Locates the {@link BeanDefinitionParser} from the register implementations using
* the local name of the supplied {@link Element}.
*/
@Nullable
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;
}
localName是获取的XML节点的本地名称,节点的本地名称是节点限定名的一部分,出现在冒号之后. 限定名通常当作特定XML文档命名空间的一部分。例如在限定名<myname:user中user是本地名,myname是前缀。
通过本地名称,可以从parsers这个Map中拿到对应的BeanDefinitionParser对象(还记得MyNamespaceHandler的init方法吗?)。
2.3. BeanDefinitionParser解析
在上一节我们获取到了节点本地名对应的BeanDefinitionParser对象,下面将通过该对象的parse(element, parserContext)方法完成解析工作。在这之前我们先看一下我们自定义的BeanDefinitionParser其继承关系,如下图:
parse(element, parserContext)方法调用的是AbstractBeanDefinitionParser类。
// 源码位置:org.springframework.beans.factory.xml.AbstractBeanDefinitionParser#parse
@Override
@Nullable
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);
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;
}
2.3.1. 创建生成AbstractBeanDefinition对象
AbstractBeanDefinition definition = parseInternal(element, parserContext);
// 源码位置:org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser#parseInternal
/**
* Creates a {@link BeanDefinitionBuilder} instance for the
* {@link #getBeanClass bean Class} and passes it to the
* {@link #doParse} strategy method.
* @param element the element that is to be parsed into a single BeanDefinition
* @param parserContext the object encapsulating the current state of the parsing process
* @return the BeanDefinition resulting from the parsing of the supplied {@link Element}
* @throws IllegalStateException if the bean {@link Class} returned from
* {@link #getBeanClass(org.w3c.dom.Element)} is {@code null}
* @see #doParse
*/
@Override
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);
}
doParse(element, parserContext, builder);
return builder.getBeanDefinition();
}
AbstractBeanDefinition对象由BeanDefinitionBuilder中获取得到,因此要想得到AbstractBeanDefinition首先需要创建BeanDefinitionBuilder对象,它是一个BeanDefinition的构建器,用于在实现Spring自定义命名空间处理程序时使用。它在创建的时候通过new GenericBeanDefinition()
创建了一个GenericBeanDefinition类。
然后doParse(element, parserContext, builder);
是我们自定义解析方式的程序。
// 源码位置:com.yanglh.ioc.custom.UserBenDefinitionParser#doParse
protected void doParse(Element element, BeanDefinitionBuilder bean) {
String userName = element.getAttribute("userName");
String email = element.getAttribute("email");
if(StringUtils.hasText(userName)){
bean.addPropertyValue("userName",userName);
}
if(StringUtils.hasText(email)){
bean.addPropertyValue("email",email);
}
}
上面就是我们自定义的解析程序,将属性和值赋值到BeanDefinitionBuilder类中BeanDefinition对象中。
做完以上步骤,我们也就得到AbstractBeanDefinition对象。
2.3.2. 注册BeanDefinition
BeanDefinitionHolder holder = new BeanDefinitionHolder(definition, id, aliases);
registerBeanDefinition(holder, parserContext.getRegistry());
到了这一步就和默认标签配置的流程一样了。先创建BeanDefinitionHolder对象持有BeanDefinition及相关信息,然后调用registerBeanDefinition方法。
// 源码位置:org.springframework.beans.factory.xml.AbstractBeanDefinitionParser#registerBeanDefinition
/**
* Register the supplied {@link BeanDefinitionHolder bean} with the supplied
* {@link BeanDefinitionRegistry registry}.
* <p>Subclasses can override this method to control whether or not the supplied
* {@link BeanDefinitionHolder bean} is actually even registered, or to
* register even more beans.
* <p>The default implementation registers the supplied {@link BeanDefinitionHolder bean}
* with the supplied {@link BeanDefinitionRegistry registry} only if the {@code isNested}
* parameter is {@code false}, because one typically does not want inner beans
* to be registered as top level beans.
* @param definition the bean definition to be registered
* @param registry the registry that the bean is to be registered with
* @see BeanDefinitionReaderUtils#registerBeanDefinition(BeanDefinitionHolder, BeanDefinitionRegistry)
*/
protected void registerBeanDefinition(BeanDefinitionHolder definition, BeanDefinitionRegistry registry) {
BeanDefinitionReaderUtils.registerBeanDefinition(definition, registry);
}
默认标签的BeanDefinition注册流程同样是由BeanDefinitionReaderUtils完成的,后面的流程请参考Spring XML默认标签配置解析注册Bean源码分析(六)——解析注册的完成这一篇的描述。
以上就是自定义标签的注册解析流程。