Spring是如何解析自定义标签

456 阅读12分钟

前言

在上一篇 一文带你深入理解Spring中的xml解析过程与BeanDefinition的注册 中介绍了xml的解析配置和默认标签的解析过程。对应下图的左侧部分,本篇将完成右侧部分自定义标签的解析过程分析,并尝试实现一个我们自定义的标签。

解析自定义标签

命名空间处理器Handler

我们直接定位到parseCustomElement()方法所在的位置

  # BeanDefinitionParserDelegate

  public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
      // 获取命名空间URI
      String namespaceUri = getNamespaceURI(ele);
      if (namespaceUri == null) {
          return null;
      }
      // 获取该URI对应的Handler
      // resolve方法会通过反射实例化一个NamespaceHandler, 返回该实例, 同时将其放到一个Map中缓存起来, 在下次用到的时候直接从该缓存中获取
      // 这里的readerContext对象, 是在XmlBeanDefinitionReader类的registerBeanDefinitions()方法中创建的
      NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
      if (handler == null) {
          error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
          return null;
      }
      // 委托给 Parser解析
      return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
  }

上面的方法完成了三件事:

  • 获取命名空间Uri
  • 根据Uri获取对应的命名空间处理器Handler,并缓存:NamespaceHandler
  • 通过处理器对自定义标签,进行解析处理

下面来看第二步中的resolve(namespaceUri)这个方法

# DefaultNamespaceHandlerResolver
public NamespaceHandler resolve(String namespaceUri) {
    // 获取一个handlerMapping
    // map形如:http://www.springframework.org/schema/context -> org.springframework.context.config.ContextNamespaceHandler
    // 对于context这个自定义的标签来说, 一共注册了9个处理器handler, 每个处理器在init()方法中又注册了一个或多个处理不同标签元素的解析器parser
    Map<String, Object> handlerMappings = getHandlerMappings();

    // 通过namespaceUri获取到一个处理器handler对象, 实际上第一次拿到的只是写在配置文件中的字符串, 因此下面需要通过反射进行实例化
    Object handlerOrClassName = handlerMappings.get(namespaceUri);
    if (handlerOrClassName == null) {
        return null;
    } else if (handlerOrClassName instanceof NamespaceHandler) {
        // 如果handlerOrClassName是一个NamespaceHandler对象,则直接返回 - 拿到对应的handler了
        // 说明不是第一次了
        return (NamespaceHandler) handlerOrClassName;
    } else {
        // 如果handlerOrClassName不是NamespaceHandler对象,则是String对象
        String className = (String) handlerOrClassName;
        try {
            // 通过String获取到一个Class对象,那么这个String对象肯定是一个类的全限定名啦
            Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);
            if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) {
                throw new FatalBeanException("// 省略");
            }
            // 直接通过反射, 调用的无参构造器, 构造一个实例
            NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
            // 初始化, 注册一个或多个与该自定义标签对应BeanDefinitionParser, 将其存入到HashMap中: —> this.parsers.put(elementName, parser);
            // init中的方法如:registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser())
            // 接下来的步骤会根据自定义标签再去获取相对应的解析器
            namespaceHandler.init();
            // 把新创建的namespaceHandler, 再存储到handlerMappings中
            handlerMappings.put(namespaceUri, namespaceHandler);
            return namespaceHandler;
        } catch (Exception ex) {
            // 省略
        }
    }
}

该方法其实做了两件事:

  • 实例化一个处理器NamespaceHandler,并进行缓存相关的优化处理
  • 调用namespaceHandler.init(),其实该方法是为NamespaceHandler注册了一系列的解析器。自定义的xml的标签属性等,就是通过这些解析器完成解析的。 我们来逐行分析这段函数的细节:
    第一行调用了 getHandlerMappings() 方法得到了一个Map,我们来跟进这个方法:
# DefaultNamespaceHandlerResolver
private Map<String, Object> getHandlerMappings() {					
    Map<String, Object> handlerMappings = this.handlerMappings;							
    if (handlerMappings == null) {										
        synchronized (this) {											
            handlerMappings = this.handlerMappings;								
            if (handlerMappings == null) {							
                try {
                    // 默认一共加载了三个Spring源码模块的META-INF/spring.handlers文件里指定的处理器hanlder, 总共是9个
                    Properties mappings = PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);
                    handlerMappings = new ConcurrentHashMap<>(mappings.size());
                    CollectionUtils.mergePropertiesIntoMap(mappings, handlerMappings);
                    this.handlerMappings = handlerMappings;
                }
                catch (IOException ex) {
                    // 省略异常
                }
            }
        }
    }
    return handlerMappings;
}

我们来分析上面的代码:第一行中,将属性handlerMappings赋值给方法的局部变量,其值实际为空,所以进入try逻辑处理块。

重点来分析PropertiesLoaderUtils的loadAllProperties这个静态方法:

  • 我们先看第一个参数handlerMappingsLocation, 该参数是如何赋值进来的呢?

在前面简单提到过readerContext的初始化时机,在XmlBeanDefinitionReader.registerBeanDefinitions(...)方法中,调用了documentReader.registerBeanDefinitions(doc, createReaderContext(resource))方法,其中的createReaderContext方法,最后调用了DefaultNamespaceHandlerResolver的构造参数,并给了默认值 "META-INF/spring.handlers"

loadAllProperties代码如下,该方法是一个加载Properties的工具方法,属性文件的位置已经通过参数赋值进去,即上面所说的"META-INF/spring.handlers"

public static Properties loadAllProperties(String resourceName, @Nullable ClassLoader classLoader) throws IOException {
		Assert.notNull(resourceName, "Resource name must not be null");
		ClassLoader classLoaderToUse = classLoader;
		if (classLoaderToUse == null) {
			classLoaderToUse = ClassUtils.getDefaultClassLoader();
		}

		// 默认spring中一个有三处, 分别是:spring-context有5项, spring-aop有1项, spring-beans有3项
		// 因此一共加载了9相, 并在调用的地方, 放在了map中
		Enumeration<URL> urls = (classLoaderToUse != null ? classLoaderToUse.getResources(resourceName) :
				ClassLoader.getSystemResources(resourceName));
		Properties props = new Properties();
		while (urls.hasMoreElements()) {
			URL url = urls.nextElement();
			URLConnection con = url.openConnection();
			ResourceUtils.useCachesIfNecessary(con);
			try (InputStream is = con.getInputStream()) {
				if (resourceName.endsWith(XML_FILE_EXTENSION)) {
					props.loadFromXML(is);
				}
				else {
					props.load(is);
				}
			}
		}
		return props;
	}

至此,getHandlerMappings() 方法就分析完了,该方法返回了在resources目录下的"META-INF/spring.handlers"文件中定义的NamespaceHandler。默认一共是9个。
这里返回的只是一个全类名的字符串,下面,还需要进行实例创建和缓存。回来继续来看DefaultNamespaceHandlerResolver。

	# DefaultNamespaceHandlerResolver
	public NamespaceHandler resolve(String namespaceUri) {
		// 获取一个handlerMapping
		// map形如:http://www.springframework.org/schema/context -> org.springframework.context.config.ContextNamespaceHandler
		// 对于context这个自定义的标签来说, 一共注册了9个处理器handler, 每个处理器在init()方法中又注册了一个或多个处理不同标签元素的处理器parser
		Map<String, Object> handlerMappings = getHandlerMappings();

		// 通过namespaceUri获取到一个处理器handler对象, 实际上此时拿到的只是写在配置文件中的字符串, 因此下面需要通过反射进行实例化
		Object handlerOrClassName = handlerMappings.get(namespaceUri);
		if (handlerOrClassName == null) {
			return null;
		}
		else if (handlerOrClassName instanceof NamespaceHandler) {
			// 如果handlerOrClassName是一个NamespaceHandler对象,则直接返回 - 拿到对应的handler了
			return (NamespaceHandler) handlerOrClassName;
		}
		else {
			// 如果handlerOrClassName不是NamespaceHandler对象,则是String对象
			String className = (String) handlerOrClassName;
			try {
				// 通过String获取到一个Class对象,那么这个String对象肯定是一个类的全限定名
				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);
				// 初始化, 注册一个或多个与该自定义标签对应BeanDefinitionParser, 将其存入到HashMap中: —> this.parsers.put(elementName, parser);
				// init中的方法如:registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser())
				// 接下来的步骤会根据自定义标签再去获取相对应的解析器
				namespaceHandler.init();
				// 把新创建的namespaceHandler, 再存储到handlerMappings中
				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);
			}
		}
	}

上面的代码, 在namespaceHandler.init()方法逻辑之前, 主要完成了将从配置文件中获取到的Handler实例化成对象,同时放入缓存,方便下次获取。
再来看init()方法,该方法源自NamespaceHandler接口,如下:

public interface NamespaceHandler {

    /**
     * 初始化解析器
     */
    void init();

    /**
     * 解析自定义的标签元素
     */
    BeanDefinition parse(Element element, ParserContext parserContext);

    BeanDefinitionHolder decorate(Node source, BeanDefinitionHolder definition, ParserContext parserContext);
}

spring 还为我们提供了 NamespaceHandlerSupport 类,该类抽象实现了NamespaceHandler的方法,一般自定义的Handler继承自该抽象类即可。源码如下。

public abstract class NamespaceHandlerSupport implements NamespaceHandler {

	/**
	 * 用来缓存解析器, key为自定义标签的前缀, 如<component:scan>中的 "component"
	 */
	private final Map<String, BeanDefinitionParser> parsers = new HashMap<>();

	@Override
	public BeanDefinition parse(Element element, ParserContext parserContext) {
		// 先获取到element这个自定义标签对应的namespaceURI, 再从map中根据这个key获取相应的解析器
		// 这个map是在实例化NamespaceHandler之后, 在调用它的init()方法的时候, 里面调用了多个registerBeanDefinitionParser方法, 将其存入到map中的
		BeanDefinitionParser parser = findParserForElement(element, parserContext);
		// 再委托给获取到的解析器parser去解析这个自定义标签
		return (parser != null ? parser.parse(element, parserContext) : null);
	}

	/**
	 * 返回对应的自定义标签的解析器
	 */
	private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
		//去掉标签前面的前缀, 如:context:component-scan ——> component-scan
		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;
	}

	@Override
	public BeanDefinitionHolder decorate(
			Node node, BeanDefinitionHolder definition, ParserContext parserContext) {
		BeanDefinitionDecorator decorator = findDecoratorForNode(node, parserContext);
		return (decorator != null ? decorator.decorate(node, definition, parserContext) : null);
	}

	// 省略其他方法和属性

}


我们以component这个自定义标签为例,来看下具体的解析过程: 先给出测试代码:
xml配置文件:

<context:component-scan base-package="com.context.basic.custom_xml_element.basic"/>

Java代码:

com.context.basic
public class App {
    public static void main( String[] args ) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:custom_xml_element.xml");
        Person person = (Person) applicationContext.getBean("person");
        System.out.println(person);
    }
}

package com.context.basic;
@Component
public class Person {
    public Person() {
        System.out.println("Person init...");
    }
}

基于上面这段代码,继续跟踪DefaultNamespaceHandlerResolver.resolve方法中的调用:namespaceHandler.init();跟踪进入,源码如下:

public class ContextNamespaceHandler extends NamespaceHandlerSupport {

    @Override
    public void init() {
        // 第一个参数分别对应于context下的不同的标签, 第二个实例对象, 用来解析与之对应的标签元素
        // 如 <context:property-placeholder/>、<context:annotation-config/>	等
        // 其实这里就是一行代码, 将标签对应的解析器, 放到一个map中, 定义如下:Map<String, BeanDefinitionParser> parsers
        // 当后面解析某个自定义的标签元素时. 直接通过key从这个map中获取对应的解析器进行解析即可
        registerBeanDefinitionParser("property-placeholder", new PropertyPlaceholderBeanDefinitionParser());
        registerBeanDefinitionParser("property-override", new PropertyOverrideBeanDefinitionParser());
        registerBeanDefinitionParser("annotation-config", new AnnotationConfigBeanDefinitionParser());
        registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser());
        registerBeanDefinitionParser("load-time-weaver", new LoadTimeWeaverBeanDefinitionParser());
        registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
        registerBeanDefinitionParser("mbean-export", new MBeanExportBeanDefinitionParser());
        registerBeanDefinitionParser("mbean-server", new MBeanServerBeanDefinitionParser());
    }
}

这段代码,注册了context命名空间下,不同的标签元素的解析。
这里先分析下NamespaceHandler这个类,该类在上面见到过,它是命名空间处理器的跟接口, 定义的规范如下:

public interface NamespaceHandler {

    /**
     * 初始化解析器,这个方法将会在NamespaceHandler实例化之后,使用之前调用
     */
    void init();

    /**
     * 完成元素标签的解析工作,并注册成BeanDefinition中,以便beanFactory完成实例等工作
     * 真正的解析工作是在自定义的元素解析器中完成的,这里只是从注册的解析器中,获取到对应的属性解析器, 来完成解析工作的
     */
    @Nullable
    BeanDefinition parse(Element element, ParserContext parserContext);

    /**
     * 对BeanDefinition进行装饰
     */
    @Nullable
    BeanDefinitionHolder decorate(Node source, BeanDefinitionHolder definition, ParserContext parserContext);
}

NamespaceHandlerde的抽象实现类:NamespaceHandlerSupport,我们重点来看解析和初始化相关的源码,装饰部分不贴出来了。

public abstract class NamespaceHandlerSupport implements NamespaceHandler {

    // 用来存储某个命名空间的所有xml标签属性的解析器对
    private final Map<String, BeanDefinitionParser> parsers = new HashMap<>();

    @Override
    @Nullable
    public BeanDefinition parse(Element element, ParserContext parserContext) {
        // 先获取到element这个自定义标签对应的namespaceURI, 再从map中根据这个key获取相应的解析器
        // 这个map是在实例化NamespaceHandler之后, 在调用它的init()方法的时候, 里面调用了多个registerBeanDefinitionParser方法, 将其存入到map中的
        BeanDefinitionParser parser = findParserForElement(element, parserContext);
        // 再委托给获取到的解析器parser去解析这个自定义标签
        return (parser != null ? parser.parse(element, parserContext) : null);
    }

    /**
     * 返回对应的自定义标签的解析器
     */
    @Nullable
    private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
        //去掉标签前面的前缀, 如:context:component-scan ——> component-scan
        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;
    }
	
    protected final void registerBeanDefinitionParser(String elementName, BeanDefinitionParser parser) {
        this.parsers.put(elementName, parser);
    }
}

通过上面给出的NamespaceHandlerSupport源码,可以总结主要完成两件事(装饰部分,本文不说明):

  • 注册Parser解析器, 并缓存到parsers中获取的相匹配的解析器, 完成解析, 并封装成变量中。由于该操作是通过其中的registerBeanDefinitionParser完成的,所有在我们自定义标签时候, 需要调用该方法(在自定义的NamespaceHandler的init方法中调用, 文末会给出一个自定义标签的示例)。
  • 通过parsers中获取的相匹配的解析器, 完成解析, 并封装成BeanDefinition对象返回。

属性解析器Parser

最后我们再来分析元素标签的属性解析器BeanDefinitionParser
在自定义handler的init方法中注册了自定义的属性解析器,下面来分析该接口

public interface BeanDefinitionParser {
    @Nullable
    BeanDefinition parse(Element element, ParserContext parserContext);
}

该接口只定义了parse这一个方法,该方法完成将xml中的元素属性,封装为BeanDefinition的功能。 来看下该接口的抽象实现类:

public abstract class AbstractBeanDefinitionParser implements BeanDefinitionParser {

    public static final String ID_ATTRIBUTE = "id";

    public static final String NAME_ATTRIBUTE = "name";

    @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));
                    }
                }

                // 将Bean的定义信息、id、别名包装到holder类中
                BeanDefinitionHolder holder = new BeanDefinitionHolder(definition, id, aliases);
                // 进行BeanDefinition的注册工作
                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;
    }

    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;
        }
    }


    protected void registerBeanDefinition(BeanDefinitionHolder definition, BeanDefinitionRegistry registry) {
        BeanDefinitionReaderUtils.registerBeanDefinition(definition, registry);
    }

    @Nullable
    protected abstract AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext);

    protected boolean shouldGenerateId() {
            return false;
    }

    protected boolean shouldGenerateIdAsFallback() {
            return false;
    }

    protected boolean shouldParseNameAsAliases() {
            return true;
    }

    protected boolean shouldFireEvents() {
            return true;
    }

    protected void postProcessComponentDefinition(BeanComponentDefinition componentDefinition) {
    }

}

注意里面的模板方法parseInternal,该方法留给子类实现的。在下面我们自定义一个标签的属性解析器的时候,使用的AbstractSingleBeanDefinitionParser,就继承了AbstractBeanDefinitionParser。

自定义标签解析总结

  1. 获取META-INF/spring.handlers文件定义的所有handler。
  2. 优先通过缓存获取, 获取不到, 则根据第一步找的进行反射创建, 并缓存。
  3. 调用handler的init方法, 在init中调用其父类NamespaceHandlerSupport中的。registerBeanDefinitionParser方法,将自定义的标签元素解析器注册到map中。
  4. 完成上述工作之后, 准备解析。 实际上,这就是Spring中处理自定义标签的核心流程。后续的parse这里先不详细说明,大概流程就是使用前面初始化好的handler和parsers, 根据不同的属性从map中获取不同的parser,来完成解析工作。

额外的多说一句,context还可以使用过滤器,context中默认有自己的过滤器,也可以自定义规则,有两种include-filter和exclude-filter,符合过滤器规则的,才会被实例。

如:关闭默认过滤器的情况下,只有在base-package包下的Service才会被注入到spring容器中。 具体的源码在ComponentScanBeanDefinitionParser.configureScanner(..)获取扫描器的时候会调用parseTypeFilters(),实现该功能,有兴趣可以自己跟踪。

<context:component-scan base-package="com.custom_xml_element.filter" use-default-filters="false">
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/>
</context:component-scan>

实现一个自定义的spring标签元素

根据上面的源码分析,我们大致知道,需要准备几样东西,

  • META-INF/spring.handlers中指定命名空间处理器的全路径、及对应的实现类,这里该类继承自NamespaceHandlerSupport
  • 自定义标签元素的解析器,这里继承自AbstractSingleBeanDefinitionParser抽象类即可
  • 定义好相关的xml约束文件

1. Handler类:

package com.mystrace.selftag;

import org.springframework.beans.factory.xml.NamespaceHandlerSupport;

public class FiverNamespaceHandler extends NamespaceHandlerSupport {
    @Override
    public void init() {
            // 对应于配置文件中的后半部分:<fiver:mybean>
            registerBeanDefinitionParser("mybean", new FiverSingleBeanDefinitionParser());
    }
}
  1. parser类:
package com.mystrace.selftag;

import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;

public class FiverSingleBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {

    @Override
    protected Class<?> getBeanClass(Element element) {
        return User.class;
    }

    @Override
    protected void doParse(Element element, BeanDefinitionBuilder builder) {
        String name = element.getAttribute("name");
        String password = element.getAttribute("password");

        if (StringUtils.hasText(name)) {
                builder.addPropertyValue("name", name);
        }

        if (StringUtils.hasText(password)) {
                builder.addPropertyValue("password", password);
        }
    }
}
  1. 普通类
package com.mystrace.selftag;

public class User {

    private String name;
    private String password;

    public String getName() {
            return name;
    }

    public void setName(String name) {
            this.name = name;
    }

    public String getPassword() {
            return password;
    }

    public void setPassword(String password) {
            this.password = password;
    }
}
  1. 测试类
package com.mystrace.selftag;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class AppSelfTag {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:self_tag.xml");
        User user = (User) context.getBean("user");
        System.out.println("name = " + user.getName() + ", password = " + user.getPassword());
    }
}

  1. spring配置文件self_tag.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans
    xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:fiver="http://www.fiver.com/schema/user"  #要和下面的spring.handlers中的key一致
    xsi:schemaLocation="
    http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.fiver.com/schema/user
    http://www.fiver.com/schema/user.xsd">  #要和下面的user.xsd文件名称一致,且域名一致

    <fiver:mybean id="user" name="fiver" password="123456"/>

</beans>

  1. 在resources/META-INF目录下创建文件spring.handlers 内容如下:
http\://www.fiver.com/schema/user=com.mystrace.selftag.FiverNamespaceHandler

其中左侧的key,要和spring配置文件中beans节点中的xmlns:fiver对应的值一致,右边的值即为Handler的全类名

  1. 在user.xsd(xml定义文件)
<schema xmlns="http://www.w3.org/2001/XMLSchema"
    targetNamespace="http://www.fiver.com/schema/user" #注意这里定义了的命名空间
    xmlns:tns="http://www.fiver.com/schema/user"
    elementFormDefault="qualified">
        <element name="mybean">
            <complexType>
                <attribute name ="id" type = "string"/>
                <attribute name ="name" type = "string"/>
                <attribute name ="password" type = "string"/>
            </complexType>
	</element>
</schema>

至此,Spring中关于默认标签和自定义标签的解析已经梳理完毕。

默认标签和自定义标签的解析总结

我们以一张图来总结相关的源码解析过程,希望对读者有所帮助。

obtainFreshBeanFactory().jpg