Spring 源码分析--自定义标签的解析
Posted 否定之否定
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring 源码分析--自定义标签的解析相关的知识,希望对你有一定的参考价值。
本文分析自定义标签的解析:
一:BeanDefinitionParserDelegate 类
public BeanDefinition parseCustomElement(Element ele) { return parseCustomElement(ele, null); } public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) { String namespaceUri = getNamespaceURI(ele); 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)); }
通过前一篇文章介绍自定义标签的使用方法后,或多或少对自定义标签的实现过程有一个自己的想法。其实思路非常的简单,无非是根据对应的bean获取对应的命名空间,根据命名空间解析对应的处理器,然后根据用户自定义的处理器进行解析。
二:获取标签的命名空间
三:提取自定义标签处理器
有了命名空间,就可以进行NamespaceHandler的提取了,分析NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);在readerContext初始化的时候其属性namespaceHandlerResolver已经被初始化为DefaultNamespaceHandlerResolver的实例,所以,这里调用的resolve方法其实调用的是DefaultNamespaceHandlerResolver类中的方法。
/** * Default implementation of the {@link NamespaceHandlerResolver} interface. * Resolves namespace URIs to implementation classes based on the mappings * contained in mapping file. * * <p>By default, this implementation looks for the mapping file at * {@code META-INF/spring.handlers}, but this can be changed using the * {@link #DefaultNamespaceHandlerResolver(ClassLoader, String)} constructor. * * @author Rob Harrop * @author Juergen Hoeller * @since 2.0 * @see NamespaceHandler * @see DefaultBeanDefinitionDocumentReader */ public class DefaultNamespaceHandlerResolver implements NamespaceHandlerResolver { /** * The location to look for the mapping files. Can be present in multiple JAR files. */ public static final String DEFAULT_HANDLER_MAPPINGS_LOCATION = "META-INF/spring.handlers"; /** Logger available to subclasses */ protected final Log logger = LogFactory.getLog(getClass()); /** ClassLoader to use for NamespaceHandler classes */ private final ClassLoader classLoader; /** Resource location to search for */ private final String handlerMappingsLocation; /** Stores the mappings from namespace URI to NamespaceHandler class name / instance */ private volatile Map<String, Object> handlerMappings; /** * Create a new {@code DefaultNamespaceHandlerResolver} using the * default mapping file location. * <p>This constructor will result in the thread context ClassLoader being used * to load resources. * @see #DEFAULT_HANDLER_MAPPINGS_LOCATION */ public DefaultNamespaceHandlerResolver() { this(null, DEFAULT_HANDLER_MAPPINGS_LOCATION); } /** * Create a new {@code DefaultNamespaceHandlerResolver} using the * default mapping file location. * @param classLoader the {@link ClassLoader} instance used to load mapping resources * (may be {@code null}, in which case the thread context ClassLoader will be used) * @see #DEFAULT_HANDLER_MAPPINGS_LOCATION */ public DefaultNamespaceHandlerResolver(ClassLoader classLoader) { this(classLoader, DEFAULT_HANDLER_MAPPINGS_LOCATION); } /** * Create a new {@code DefaultNamespaceHandlerResolver} using the * supplied mapping file location. * @param classLoader the {@link ClassLoader} instance used to load mapping resources * may be {@code null}, in which case the thread context ClassLoader will be used) * @param handlerMappingsLocation the mapping file location */ public DefaultNamespaceHandlerResolver(ClassLoader classLoader, String handlerMappingsLocation) { Assert.notNull(handlerMappingsLocation, "Handler mappings location must not be null"); this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader()); this.handlerMappingsLocation = handlerMappingsLocation; } /** * 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 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("NamespaceHandler class [" + className + "] for namespace [" + namespaceUri + "] not found", ex); } catch (LinkageError err) { throw new FatalBeanException("Invalid NamespaceHandler class [" + className + "] for namespace [" + namespaceUri + "]: problem with handler class file or dependent class", err); } } } /** * Load the specified NamespaceHandler mappings lazily. */ private Map<String, Object> getHandlerMappings() { if (this.handlerMappings == null) { synchronized (this) { if (this.handlerMappings == null) { try {
//this.handlerMappingsLocation在构造函数中已经被初始化为:META-INF/Spring.handlers Properties mappings = PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader); if (logger.isDebugEnabled()) { logger.debug("Loaded NamespaceHandler mappings: " + mappings); } Map<String, Object> handlerMappings = new ConcurrentHashMap<String, Object>(mappings.size());
//将Properties格式文件合并到Map格式的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 this.handlerMappings; } @Override public String toString() { return "NamespaceHandlerResolver using mappings " + getHandlerMappings(); } }
分析:
(3.1)resolve方法
当得到自定义命名空间处理后马上执行namespaceHandler.init()来进行自定义BeanDefinitionParse的注册。在这里,可以注册多个标签解析器,上一篇文章中示例只有支持<myname:user的写法,当然也可以注册多个解析器,如<myname:A;<myname:B等,使得myname的命名空间中可以支持多种标签解析。
(3.2)getHandlerMappings方法
注册后,命名空间处理器就可以根据标签的不同来调用不同的解析器进行解析。那么,根据上面的函数与之前介绍过的例子,我们基本可以推断getHandlerMappings的主要功能就是读取Spring.handlers配置文件并将配置文件缓存在map中。
该方法借助工具类PropertiesLoaderUtils对属性handlerMappingsLoaction进行了配置文件的读取,handlerMappingsLocation被默认初始化为“META-INF/Spring.handlers”
四:标签解析
得到了解析器已经要分析的元素后,Spring就可以将解析工作问题委托给自定义解析器取解析了。在Spring中的代码为:
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
以之前提到的示例进行分析,此时的handler已经被实例化成为了我们自定义的MyNamespaceHandler了,而MyNamespaceHandler也已经完成了初始化工作,但是在我们实现的自定义命名空间处理器中并没有实现parse方法,所以推断,这个方法是父类中的实现,查看父类NamespaceHandlerSupport中的parse方法。
(4.1)NamespaceHandlerSupport 类
public abstract class NamespaceHandlerSupport implements NamespaceHandler { /** * Stores the {@link BeanDefinitionParser} implementations keyed by the * local name of the {@link Element Elements} they handle. */ private final Map<String, BeanDefinitionParser> parsers = new HashMap<String, BeanDefinitionParser>(); /** * Stores the {@link BeanDefinitionDecorator} implementations keyed by the * local name of the {@link Element Elements} they handle. */ private final Map<String, BeanDefinitionDecorator> decorators = new HashMap<String, BeanDefinitionDecorator>(); /** * Stores the {@link BeanDefinitionDecorator} implementations keyed by the local * name of the {@link Attr Attrs} they handle. */ private final Map<String, BeanDefinitionDecorator> attributeDecorators = new HashMap<String, BeanDefinitionDecorator>(); /** * Parses the supplied {@link Element} by delegating to the {@link BeanDefinitionParser} that is * registered for that {@link Element}. */ @Override public BeanDefinition parse(Element element, ParserContext parserContext) { return findParserForElement(element, parserContext).parse(element, parserContext); } /** * Locates the {@link BeanDefinitionParser} from the register implementations using * the local name of the supplied {@link Element}. */ private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) { //获取元素名称,也就是<myname:user 中的user,若在示例中,此时localName为user
String localName = parserContext.getDelegate().getLocalName(element);
//根据user找到对应的解析器,也就是在registerBeanDefinitionParse("user", new UserBeanDefinitionParser());注册的解析器 BeanDefinitionParser parser = this.parsers.get(localName); if (parser == null) { parserContext.getReaderContext().fatal( "Cannot locate BeanDefinitionParser for element [" + localName + "]", element); } return parser; } /** * Decorates the supplied {@link Node} by delegating to the {@link BeanDefinitionDecorator} that * is registered to handle that {@link Node}. */ @Override public BeanDefinitionHolder decorate( Node node, BeanDefinitionHolder definition, ParserContext parserContext) { return findDecoratorForNode(node, parserContext).decorate(node, definition, parserContext); } /** * Locates the {@link BeanDefinitionParser} from the register implementations using * the local name of the supplied {@link Node}. Supports both {@link Element Elements} * and {@link Attr Attrs}. */ private BeanDefinitionDecorator findDecoratorForNode(Node node, ParserContext parserContext) { BeanDefinitionDecorator decorator = null; String localName = parserContext.getDelegate().getLocalName(node); if (node instanceof Element) { decorator = this.decorators.get(localName); } else if (node instanceof Attr) { decorator = this.attributeDecorators.get(localName); } else { parserContext.getReaderContext().fatal( "Cannot decorate based on Nodes of type [" + node.getClass().getName() + "]", node); } if (decorator == null) { parserContext.getReaderContext().fatal("Cannot locate BeanDefinitionDecorator for " + (node instanceof Element ? "element" : "attribute") + " [" + localName + "]", node); } return decorator; } /** * Subclasses can call this to register the supplied {@link BeanDefinitionParser} to * handle the specified element. The element name is the local (non-namespace qualified) * name. */ protected final void registerBeanDefinitionParser(String elementName, BeanDefinitionParser parser) { this.parsers.put(elementName, parser); } /** * Subclasses can call this to register the supplied {@link BeanDefinitionDecorator} to * handle the specified element. The element name is the local (non-namespace qualified) * name. */ protected final void registerBeanDefinitionDecorator(String elementName, BeanDefinitionDecorator dec) { this.decorators.put(elementName, dec); } /** * Subclasses can call this to register the supplied {@link BeanDefinitionDecorator} to * handle the specified attribute. The attribute name is the local (non-namespace qualified) * name. */ protected final void registerBeanDefinitionDecoratorForAttribute(String attrName, BeanDefinitionDecorator dec) { this.attributeDecorators.put(attrName, dec); } }
解析过程中首先是寻找元素对应的解析器,进而调用解析器中的parse方法,那么结合示例来说,其实就是首先获取在MyNameSpaceHandler类中的init方法中注册的对应的UserBeanDefinitionParser实例,并调用其parse方法进行进一步的解析。
(4.2)parse方法的处理
示例中的UserBeanDefinitionParser类继承了AbstractSingleBeanDefinitionParser类,AbstractSingleBeanDefinitionParser继承了AbstractBeanDefinitionParser 类
(4.2.1)AbstractBeanDefinitionParser 类
public abstract class AbstractBeanDefinitionParser implements BeanDefinitionParser { /** 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 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) { parserContext.getReaderContext().error(ex.getMessage(), element); return null; } } return definition; } /** * Resolve the ID for the supplied {@link BeanDefinition}. * <p>When using {@link #shouldGenerateId generation}, a name is generated automatically. * Otherwise, the ID is extracted from the "id" attribute, potentially with a * {@link #shouldGenerateIdAsFallback() fallback} to a generated id. * @param element the element that the bean definition has been built from * @param definition the bean definition to be registered * @param parserContext the object encapsulating the current state of the parsing process; * provides access to a {@link org.springframework.beans.factory.support.BeanDefinitionRegistry} * @return the resolved id * @throws BeanDefinitionStoreException if no unique name could be generated * for the given bean definition */ protected String resolveId(Element element, AbstractBeanDefinition definition, ParserContext parserContext) throws BeanDefinitionStoreException { if (shouldGenerateId()) { return parserContext.getReaderContext().generateBeanName(definition); } else { String id = element.getAttribute(ID_ATTRIBUTE); if (!StringUtils.hasText(id) && shouldGenerateIdAsFallback()) { id = parserContext.getReaderContext().generateBeanName(definition); } return id; } } /** * 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); } /** * Central template method to actually parse the supplied {@link Element} * into one or more {@link BeanDefinition BeanDefinitions}. * @param element the element that is to be parsed into one or more {@link BeanDefinition BeanDefinitions} * @param parserContext the object encapsulating the current state of the parsing process; * provides access to a {@link org.springframework.beans.factory.support.BeanDefinitionRegistry} * @return the primary {@link BeanDefinition} resulting from the parsing of the supplied {@link Element} * @see #parse(org.w3c.dom.Element, ParserContext) * @see #postProcessComponentDefinition(org.springframework.beans.factory.parsing.BeanComponentDefinition) */ protected abstract AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext); /** * Should an ID be generated instead of read from the passed in {@link Element}? * <p>Disabled by default; subclasses can override this to enable ID generation. * Note that this flag is about <i>always</i> generating an ID; the parser * won\'t even check for an "id" attribute in this case. * @return whether the parser should always generate an id */ protected boolean shouldGenerateId() { return false; } /** * Should an ID be generated instead if the passed in {@link Element} does not * specify an "id" attribute explicitly? * <p>Disabled by default; subclasses can override this to enable ID generation * as fallback: The parser will first check for an "id" attribute in this case, * only falling back to a generated ID if no value was specified. * @return whether the parser should generate an id if no id was specified */ protected boolean shouldGenerateIdAsFallback() { return false; } /** * Determine whether the element\'s "name" attribute should get parsed as * bean definition aliases, i.e. alternative bean definition names. * <p>The default implementation returns {@code true}. * @return whether the parser should evaluate the "name" attribute as aliases * @since 4.1.5 */ protected boolean shouldParseNameAsAliases() { return true; } /** * Determine whether this parser is supposed to fire a * {@link org.springframework.beans.factory.parsing.BeanComponentDefinition} * event after parsing the bean definition. * <p>This implementation returns {@code true} by default; that is, * an event will be fired when a bean definition has been completely parsed. * Override this to return {@code false} in order to suppress the event. * @return {@code true} in order to fire a component registration event * after parsing the bean definition; {@code false} to suppress the event * @see #postProcessComponentDefinition * @see org.springframework.beans.factory.parsing.ReaderContext#fireComponentRegistered */ protected boolean shouldFireEvents() { return true; } /** * Hook method called after the primary parsing of a * {@link BeanComponentDefinition} but before the * {@link BeanComponentDefinition} has been registered with a * {@link org.springframework.beans.factory.support.BeanDefinitionRegistry}. * <p>Derived classes can override this method to supply any custom logic that * is to be executed after all the parsing is finished. * <p>The default implementation is a no-op. * @param componentDefinition the {@link BeanComponentDefinition} that is to be processed */ protected void postProcessComponentDefinition(BeanComponentDefinition componentDefinition) { } }
虽说是对自定义配置文件的解析,但是,我们可以看到,在这个函数中大部分的代码是用来处理将解析后的AbstractBeanDefinition转化为BeanDefinitionHolder并注册的功能,而真正去做解析的事情委托给了函数parseInternal,正是这个函数里调用了我们自定义的解析函数。
(4.2.2)AbstractSingleBeanDefinitionParser 类
在parseInternal中并不是直接调用自定义的doParse函数,而是进行了一系列的数据准备,包括对beanClass,scope,lazyInit等属性的准备。
public abstract class AbstractSingleBeanDefinitionParser extends AbstractBeanDefinitionParser { /** * 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)); if (parserContext.isNested()) { // Inner bean definition must receive same scope as containing bean. builder.setScope(parserContext.getContainingBeanDefinition().getScope()); } if (parserContext.isDefaultLazyInit()) { // Default-lazy-init applies to custom bean definitions as well. builder.setLazyInit(true); } doParse(element, parserContext, builder); return builder.getBeanDefinition(); } /** * Determine the name for the parent of the currently parsed bean, * in case of the current bean being defined as a child bean. * <p>The default implementation returns {@code null}, * indicating a root bean definition. * @param element the {@code Element} that is being parsed * @return the name of the parent bean for the currently parsed bean, * or {@code null} if none */ protected String getParentName(Element element) { return null; } /** * Determine the bean class corresponding to the supplied {@link Element}. * <p>Note that, for application classes, it is generally preferable to * override {@link #getBeanClassName} instead, in order to avoid a direct * dependence on the bean implementation class. The BeanDefinitionParser * and its NamespaceHandler can be used within an IDE plugin then, even * if the application classes are not available on the plugin\'s classpath. * @param element the {@code Element} that is being parsed * @return the {@link Class} of the bean that is being defined via parsing * the supplied {@code Element}, or {@code null} if none * @see #getBeanClassName */ protected Class<?> getBeanClass(Element element) { return null; } /** * Determine the bean class name corresponding to the supplied {@link Element}. * @param element the {@code Element} that is being parsed * @return the class name of the bean that is being defined via parsing * the supplied {@code Element}, or {@code null} if none * @see #getBeanClass */ protected String getBeanClassName(Element element) { return null; } /** * 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 * @paramSpring源码分析bean标签的解析及注册