当前位置: 首页 > article >正文

Spring 中的 BeanDefinitionParserDelegate 和 NamespaceHandler

一、BeanDefinitionParserDelegate

Spring在解析xml文件的时候,在遇到<bean>标签的时候,我们会使用BeanDefinitionParserDelegate对象类解析<bean>标签的内容,包括<bean>标签的多个属性,例如 id name class init-method destory-method等,还包括各种子标签 例如 <properties> <constructor>

org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader#doRegisterBeanDefinitions


	/**
	 * Register each bean definition within the given root {@code <beans/>} element.
	 */
	@SuppressWarnings("deprecation")  // for Environment.acceptsProfiles(String...)
	protected void doRegisterBeanDefinitions(Element root) {
		// Any nested <beans> elements will cause recursion in this method. In
		// order to propagate and preserve <beans> default-* attributes correctly,
		// keep track of the current (parent) delegate, which may be null. Create
		// the new (child) delegate with a reference to the parent for fallback purposes,
		// then ultimately reset this.delegate back to its original (parent) reference.
		// this behavior emulates a stack of delegates without actually necessitating one.
		BeanDefinitionParserDelegate parent = this.delegate;
		this.delegate = createDelegate(getReaderContext(), root, parent);

		if (this.delegate.isDefaultNamespace(root)) {
			String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
			if (StringUtils.hasText(profileSpec)) {
				String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
						profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
				// We cannot use Profiles.of(...) since profile expressions are not supported
				// in XML config. See SPR-12458 for details.
				if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
					if (logger.isDebugEnabled()) {
						logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec +
								"] not matching: " + getReaderContext().getResource());
					}
					return;
				}
			}
		}

		preProcessXml(root);
		parseBeanDefinitions(root, this.delegate);
		postProcessXml(root);

		this.delegate = parent;
	}

 可以看到这里 this.delegate 是一个 BeanDefinitionParserDelegate 对象

 接下来的几层调用中,传递的都是 this.delegate 这个BeanDefinitionParserDelegate 对象

org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader#parseBeanDefinitions

/**
 * Parse the elements at the root level in the document:
 * "import", "alias", "bean".
 * @param root the DOM root element of the document
 */
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
    //如果 root 的 namespaceUri为 http://www.springframework.org/schema/beans
	if (delegate.isDefaultNamespace(root)) {

		NodeList nl = root.getChildNodes();
		for (int i = 0; i < nl.getLength(); i++) {
			Node node = nl.item(i);
			if (node instanceof Element) {
				Element ele = (Element) node;
  //如果 ele 的 namespaceUri为http://www.springframework.org/schema/beans              
				if (delegate.isDefaultNamespace(ele)) {
					parseDefaultElement(ele, delegate);
				}else {
  //如果 ele 的 namespaceUri 不是 http://www.springframework.org/schema/beans
					delegate.parseCustomElement(ele);
				}
			}
		}
	}
	else {
    //如果 root 的 namespaceUri不是 http://www.springframework.org/schema/beans
		delegate.parseCustomElement(root);
	}
}

从上面的逻辑可以看出,

1.如果root和子标签的 namespaceUri 为 http://www.springframework.org/schema/beans 的时候,就用 DefaultBeanDefinitionDocumentReader.parseDefaultElement(ele, delegate); 进行解析

2.如果root和子标签的 namespaceUri 不是 http://www.springframework.org/schema/beans 的时候,就用 delegate.parseCustomElement(ele); 进行解析
 

org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader#parseDefaultElement

private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
	if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
		importBeanDefinitionResource(ele);
	}
	else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
		processAliasRegistration(ele);
	}
	else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
		processBeanDefinition(ele, delegate);
	}
	else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
		// recurse
		doRegisterBeanDefinitions(ele);
	}
}

 DefaultBeanDefinitionDocumentReader.parseDefaultElement 方法只处理 "import", "alias", "bean","beans" 这几个标签,说明只有这几个标签的 namespaceUri 为 http://www.springframework.org/schema/beans 处理这几个标签用的方法分别是
DefaultBeanDefinitionDocumentReader.importBeanDefinitionResource(ele);
DefaultBeanDefinitionDocumentReader.processAliasRegistration(ele);
DefaultBeanDefinitionDocumentReader.processBeanDefinition(ele, delegate);
DefaultBeanDefinitionDocumentReader.doRegisterBeanDefinitions(ele);

org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parseCustomElement(org.w3c.dom.Element)

/**
 * Parse a custom element (outside of the default namespace).
 * @param ele the element to parse
 * @return the resulting bean definition
 */
@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)

/**
 * Parse a custom element (outside of the default namespace).
 * @param ele the element to parse
 * @param containingBd the containing bean definition (if any)
 * @return the resulting bean definition
 */
@Nullable
public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
	String namespaceUri = getNamespaceURI(ele);
	if (namespaceUri == null) {
		return null;
	}
    // 根据Element的 namespaceUri 获取一个NamespaceHandler,来处理Element
	NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
	if (handler == null) {
		error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
		return null;
	}
    // 用获取到的 NamespaceHandler 解析 Element
	return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}

 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) {
    // 获取当前类路径下所有的 NamespaceHandler 的映射
    // key 为 namespaceUri,
    // value 有两种情况 如果是第一次获取该 namespaceUri 对应的NamespaceHandler
    // 那么value就是NamespaceHandler具体实现类的完全限定名,是一个字符串,用于后面实例化
    // 如果不是第一次获取该 namespaceUri 对应的NamespaceHandler,value就是NamespaceHandler
    // 具体实现类的实例对象,因为第一次实例化以后,会用实例替换掉刚开始加载的实现类完全限定名
    // ,所以就可以后面获取到的就是实例
	Map<String, Object> handlerMappings = getHandlerMappings();
    // 根据当前Element的namespaceUri获取能处理当前Element的NamespaceHandler实现类
	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 = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
            // 注册该NamespaceHandler 实现类,能用到的所有标签解析器
			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);
		}
	}
}

 org.springframework.beans.factory.xml.DefaultNamespaceHandlerResolver#getHandlerMappings

/**
 * Load the specified NamespaceHandler mappings lazily.
 */
private Map<String, Object> getHandlerMappings() {
	Map<String, Object> handlerMappings = this.handlerMappings;
	// 第一次 handlerMappings 为空,需要加载
	if (handlerMappings == null) {
		synchronized (this) {
			handlerMappings = this.handlerMappings;
			if (handlerMappings == null) {
				if (logger.isTraceEnabled()) {
					logger.trace("Loading NamespaceHandler mappings from [" + this.handlerMappingsLocation + "]");
				}
				try {
					// 加载类路径下所有的 NamespaceHandler 映射
					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;
}

 /** * 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";

我们看看这个目录下到底是什么样的

只有spring-beans.jar , spring-context.jar , spring-aop.jar这几个包下有spring.handlers,其他的jar包下没有发现spring.handlers这个文件,我们再来测试一下,上面加载NamespaceHandler的方法加载的是不是全部这些handler。

public class PropertiesLoaderUtilsTest {

    @Test
    public void loadAllProperties() throws Exception{
        Properties properties = PropertiesLoaderUtils.loadAllProperties("META-INF/spring.handlers");
        System.out.println(JSON.toJSONString(properties , SerializerFeature.PrettyFormat));
    }

}

运行结果:
{
	"http://www.springframework.org/schema/aop":"org.springframework.aop.config.AopNamespaceHandler",
	"http://www.springframework.org/schema/cache":"org.springframework.cache.config.CacheNamespaceHandler",
	"http://www.springframework.org/schema/task":"org.springframework.scheduling.config.TaskNamespaceHandler",
	"http://www.springframework.org/schema/p":"org.springframework.beans.factory.xml.SimplePropertyNamespaceHandler",
	"http://www.springframework.org/schema/util":"org.springframework.beans.factory.xml.UtilNamespaceHandler",
	"http://www.springframework.org/schema/lang":"org.springframework.scripting.config.LangNamespaceHandler",
	"http://www.springframework.org/schema/c":"org.springframework.beans.factory.xml.SimpleConstructorNamespaceHandler",
	"http://www.springframework.org/schema/context":"org.springframework.context.config.ContextNamespaceHandler",
	"http://www.springframework.org/schema/jee":"org.springframework.ejb.config.JeeNamespaceHandler"
}

 可以看出确实包含了所有的handler

二、NamespaceHandler

由以上的介绍得知:
1.当标签的 namespaceUri ==http://www.springframework.org/schema/beans 的时候,就使用org.springframework.beans.factory.xml.BeanDefinitionParserDelegate进行解析,主要有<beans> <bean> <import> <alias>这几个标签

2.当标签的 namespaceUri !=http://www.springframework.org/schema/beans 的时候,就通过org.springframework.beans.factory.xml.DefaultNamespaceHandlerResolver.resolve(String namespaceUri),来获取一个能解析标签的NamespaceHandler实现类来解析该标签,以下是每个NamespaceHandler 能解析哪些标签

/**
 * {@link org.springframework.beans.factory.xml.NamespaceHandler}
 * for the '{@code jee}' namespace.
 * @author Rob Harrop
 * @since 2.0
 */
public class JeeNamespaceHandler extends NamespaceHandlerSupport {
	@Override
	public void init() {
		registerBeanDefinitionParser("jndi-lookup", new JndiLookupBeanDefinitionParser());
		registerBeanDefinitionParser("local-slsb", new LocalStatelessSessionBeanDefinitionParser());
		registerBeanDefinitionParser("remote-slsb", new RemoteStatelessSessionBeanDefinitionParser());
	}
}

/**
 * {@code NamespaceHandler} for the {@code aop} namespace.
 *
 * <p>Provides a {@link org.springframework.beans.factory.xml.BeanDefinitionParser} for the
 * {@code <aop:config>} tag. A {@code config} tag can include nested
 * {@code pointcut}, {@code advisor} and {@code aspect} tags.
 *
 * <p>The {@code pointcut} tag allows for creation of named
 * {@link AspectJExpressionPointcut} beans using a simple syntax:
 * <pre class="code">
 * &lt;aop:pointcut id=&quot;getNameCalls&quot; expression=&quot;execution(* *..ITestBean.getName(..))&quot;/&gt;
 * </pre>
 *
 * <p>Using the {@code advisor} tag you can configure an {@link org.springframework.aop.Advisor}
 * and have it applied to all relevant beans in you {@link org.springframework.beans.factory.BeanFactory}
 * automatically. The {@code advisor} tag supports both in-line and referenced
 * {@link org.springframework.aop.Pointcut Pointcuts}:
 *
 * <pre class="code">
 * &lt;aop:advisor id=&quot;getAgeAdvisor&quot;
 *     pointcut=&quot;execution(* *..ITestBean.getAge(..))&quot;
 *     advice-ref=&quot;getAgeCounter&quot;/&gt;
 *
 * &lt;aop:advisor id=&quot;getNameAdvisor&quot;
 *     pointcut-ref=&quot;getNameCalls&quot;
 *     advice-ref=&quot;getNameCounter&quot;/&gt;</pre>
 *
 * @author Rob Harrop
 * @author Adrian Colyer
 * @author Juergen Hoeller
 * @since 2.0
 */
public class AopNamespaceHandler extends NamespaceHandlerSupport {
	/**
	 * Register the {@link BeanDefinitionParser BeanDefinitionParsers} for the
	 * '{@code config}', '{@code spring-configured}', '{@code aspectj-autoproxy}'
	 * and '{@code scoped-proxy}' tags.
	 */
	@Override
	public void init() {
		// In 2.0 XSD as well as in 2.5+ XSDs
		registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());
		registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());
		registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());

		// Only in 2.0 XSD: moved to context namespace in 2.5+
		registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
	}

}
/**
 * {@code NamespaceHandler} that supports the wiring of
 * objects backed by dynamic languages such as Groovy, JRuby and
 * BeanShell. The following is an example (from the reference
 * documentation) that details the wiring of a Groovy backed bean:
 *
 * <pre class="code">
 * &lt;lang:groovy id="messenger"
 *     refresh-check-delay="5000"
 *     script-source="classpath:Messenger.groovy"&gt;
 * &lt;lang:property name="message" value="I Can Do The Frug"/&gt;
 * &lt;/lang:groovy&gt;
 * </pre>
 *
 * @author Rob Harrop
 * @author Juergen Hoeller
 * @author Mark Fisher
 * @since 2.0
 */
public class LangNamespaceHandler extends NamespaceHandlerSupport {

	@Override
	public void init() {
		registerScriptBeanDefinitionParser("groovy", "org.springframework.scripting.groovy.GroovyScriptFactory");
		registerScriptBeanDefinitionParser("bsh", "org.springframework.scripting.bsh.BshScriptFactory");
		registerScriptBeanDefinitionParser("std", "org.springframework.scripting.support.StandardScriptFactory");
		registerBeanDefinitionParser("defaults", new ScriptingDefaultsParser());
	}

	private void registerScriptBeanDefinitionParser(String key, String scriptFactoryClassName) {
		registerBeanDefinitionParser(key, new ScriptBeanDefinitionParser(scriptFactoryClassName));
	}

}
/**
 * {@link org.springframework.beans.factory.xml.NamespaceHandler}
 * for the '{@code context}' namespace.
 *
 * @author Mark Fisher
 * @author Juergen Hoeller
 * @since 2.5
 */
public class ContextNamespaceHandler extends NamespaceHandlerSupport {

	@Override
	public void init() {
		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());
	}

}
/**
 * {@link NamespaceHandler} for the {@code util} namespace.
 *
 * @author Rob Harrop
 * @author Juergen Hoeller
 * @since 2.0
 */
public class UtilNamespaceHandler extends NamespaceHandlerSupport {

	private static final String SCOPE_ATTRIBUTE = "scope";


	@Override
	public void init() {
		registerBeanDefinitionParser("constant", new ConstantBeanDefinitionParser());
		registerBeanDefinitionParser("property-path", new PropertyPathBeanDefinitionParser());
		registerBeanDefinitionParser("list", new ListBeanDefinitionParser());
		registerBeanDefinitionParser("set", new SetBeanDefinitionParser());
		registerBeanDefinitionParser("map", new MapBeanDefinitionParser());
		registerBeanDefinitionParser("properties", new PropertiesBeanDefinitionParser());
	}
}
/**
 * {@code NamespaceHandler} for the 'task' namespace.
 *
 * @author Mark Fisher
 * @since 3.0
 */
public class TaskNamespaceHandler extends NamespaceHandlerSupport {

	@Override
	public void init() {
		this.registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenBeanDefinitionParser());
		this.registerBeanDefinitionParser("executor", new ExecutorBeanDefinitionParser());
		this.registerBeanDefinitionParser("scheduled-tasks", new ScheduledTasksBeanDefinitionParser());
		this.registerBeanDefinitionParser("scheduler", new SchedulerBeanDefinitionParser());
	}

}
/**
 * {@code NamespaceHandler} allowing for the configuration of declarative
 * cache management using either XML or using annotations.
 *
 * <p>This namespace handler is the central piece of functionality in the
 * Spring cache management facilities.
 *
 * @author Costin Leau
 * @since 3.1
 */
public class CacheNamespaceHandler extends NamespaceHandlerSupport {

	@Override
	public void init() {
		registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenCacheBeanDefinitionParser());
		registerBeanDefinitionParser("advice", new CacheAdviceParser());
	}

}

以上是Spring自己已经实现好的多个NamespaceHandler

这里还需要提一嘴以下两个Handler,是用来扩展自定义标签解析的

/**
 * Simple {@code NamespaceHandler} implementation that maps custom attributes
 * directly through to bean properties. An important point to note is that this
 * {@code NamespaceHandler} does not have a corresponding schema since there
 * is no way to know in advance all possible attribute names.
 *
 * <p>An example of the usage of this {@code NamespaceHandler} is shown below:
 *
 * <pre class="code">
 * &lt;bean id=&quot;rob&quot; class=&quot;..TestBean&quot; p:name=&quot;Rob Harrop&quot; p:spouse-ref=&quot;sally&quot;/&gt;</pre>
 *
 * Here the '{@code p:name}' corresponds directly to the '{@code name}'
 * property on class '{@code TestBean}'. The '{@code p:spouse-ref}'
 * attributes corresponds to the '{@code spouse}' property and, rather
 * than being the concrete value, it contains the name of the bean that will
 * be injected into that property.
 *
 * @author Rob Harrop
 * @author Juergen Hoeller
 * @since 2.0
 */
public class SimplePropertyNamespaceHandler implements NamespaceHandler {

	private static final String REF_SUFFIX = "-ref";


	@Override
	public void init() {
	}

	@Override
	@Nullable
	public BeanDefinition parse(Element element, ParserContext parserContext) {
		parserContext.getReaderContext().error(
				"Class [" + getClass().getName() + "] does not support custom elements.", element);
		return null;
	}

	@Override
	public BeanDefinitionHolder decorate(Node node, BeanDefinitionHolder definition, ParserContext parserContext) {
		if (node instanceof Attr) {
			Attr attr = (Attr) node;
			String propertyName = parserContext.getDelegate().getLocalName(attr);
			String propertyValue = attr.getValue();
			MutablePropertyValues pvs = definition.getBeanDefinition().getPropertyValues();
			if (pvs.contains(propertyName)) {
				parserContext.getReaderContext().error("Property '" + propertyName + "' is already defined using " +
						"both <property> and inline syntax. Only one approach may be used per property.", attr);
			}
			if (propertyName.endsWith(REF_SUFFIX)) {
				propertyName = propertyName.substring(0, propertyName.length() - REF_SUFFIX.length());
				pvs.add(Conventions.attributeNameToPropertyName(propertyName), new RuntimeBeanReference(propertyValue));
			}
			else {
				pvs.add(Conventions.attributeNameToPropertyName(propertyName), propertyValue);
			}
		}
		return definition;
	}

}
/**
 * Simple {@code NamespaceHandler} implementation that maps custom
 * attributes directly through to bean properties. An important point to note is
 * that this {@code NamespaceHandler} does not have a corresponding schema
 * since there is no way to know in advance all possible attribute names.
 *
 * <p>An example of the usage of this {@code NamespaceHandler} is shown below:
 *
 * <pre class="code">
 * &lt;bean id=&quot;author&quot; class=&quot;..TestBean&quot; c:name=&quot;Enescu&quot; c:work-ref=&quot;compositions&quot;/&gt;
 * </pre>
 *
 * Here the '{@code c:name}' corresponds directly to the '{@code name}
 * ' argument declared on the constructor of class '{@code TestBean}'. The
 * '{@code c:work-ref}' attributes corresponds to the '{@code work}'
 * argument and, rather than being the concrete value, it contains the name of
 * the bean that will be considered as a parameter.
 *
 * <b>Note</b>: This implementation supports only named parameters - there is no
 * support for indexes or types. Further more, the names are used as hints by
 * the container which, by default, does type introspection.
 *
 * @author Costin Leau
 * @since 3.1
 * @see SimplePropertyNamespaceHandler
 */
public class SimpleConstructorNamespaceHandler implements NamespaceHandler {

	private static final String REF_SUFFIX = "-ref";

	private static final String DELIMITER_PREFIX = "_";


	@Override
	public void init() {
	}

	@Override
	@Nullable
	public BeanDefinition parse(Element element, ParserContext parserContext) {
		parserContext.getReaderContext().error(
				"Class [" + getClass().getName() + "] does not support custom elements.", element);
		return null;
	}

	@Override
	public BeanDefinitionHolder decorate(Node node, BeanDefinitionHolder definition, ParserContext parserContext) {
		if (node instanceof Attr) {
			Attr attr = (Attr) node;
			String argName = StringUtils.trimWhitespace(parserContext.getDelegate().getLocalName(attr));
			String argValue = StringUtils.trimWhitespace(attr.getValue());

			ConstructorArgumentValues cvs = definition.getBeanDefinition().getConstructorArgumentValues();
			boolean ref = false;

			// handle -ref arguments
			if (argName.endsWith(REF_SUFFIX)) {
				ref = true;
				argName = argName.substring(0, argName.length() - REF_SUFFIX.length());
			}

			ValueHolder valueHolder = new ValueHolder(ref ? new RuntimeBeanReference(argValue) : argValue);
			valueHolder.setSource(parserContext.getReaderContext().extractSource(attr));

			// handle "escaped"/"_" arguments
			if (argName.startsWith(DELIMITER_PREFIX)) {
				String arg = argName.substring(1).trim();

				// fast default check
				if (!StringUtils.hasText(arg)) {
					cvs.addGenericArgumentValue(valueHolder);
				}
				// assume an index otherwise
				else {
					int index = -1;
					try {
						index = Integer.parseInt(arg);
					}
					catch (NumberFormatException ex) {
						parserContext.getReaderContext().error(
								"Constructor argument '" + argName + "' specifies an invalid integer", attr);
					}
					if (index < 0) {
						parserContext.getReaderContext().error(
								"Constructor argument '" + argName + "' specifies a negative index", attr);
					}

					if (cvs.hasIndexedArgumentValue(index)) {
						parserContext.getReaderContext().error(
								"Constructor argument '" + argName + "' with index "+ index+" already defined using <constructor-arg>." +
								" Only one approach may be used per argument.", attr);
					}

					cvs.addIndexedArgumentValue(index, valueHolder);
				}
			}
			// no escaping -> ctr name
			else {
				String name = Conventions.attributeNameToPropertyName(argName);
				if (containsArgWithName(name, cvs)) {
					parserContext.getReaderContext().error(
							"Constructor argument '" + argName + "' already defined using <constructor-arg>." +
							" Only one approach may be used per argument.", attr);
				}
				valueHolder.setName(Conventions.attributeNameToPropertyName(argName));
				cvs.addGenericArgumentValue(valueHolder);
			}
		}
		return definition;
	}

	private boolean containsArgWithName(String name, ConstructorArgumentValues cvs) {
		return (checkName(name, cvs.getGenericArgumentValues()) ||
				checkName(name, cvs.getIndexedArgumentValues().values()));
	}

	private boolean checkName(String name, Collection<ValueHolder> values) {
		for (ValueHolder holder : values) {
			if (name.equals(holder.getName())) {
				return true;
			}
		}
		return false;
	}

}

实例:

SimpleConstructorNamespaceHandler 主要用于处理自定义命名空间中的标签,这些标签通常用于简化构造函数注入。通过使用自定义命名空间,你可以更简洁地配置 Bean 的构造函数参数,而不需要编写冗长的 XML 代码。


使用场景
        1.简化构造函数注入:
        当你需要频繁地配置具有多个构造函数参数的 Bean 时,使用自定义命名空间可以简化配置,提高可读性和维护性。
        2.扩展 Spring 配置:
        通过自定义命名空间,你可以扩展 Spring 的配置能力,添加特定于项目的配置元素。

假设你有一个自定义命名空间 constructor,用于简化构造函数注入。以下是实现和使用 SimpleConstructorNamespaceHandler 的步骤:
 

1. 定义命名空间和模式
首先,定义自定义命名空间和对应的 XSD 文件。
namespace-uri: http://example.com/schema/constructor
XSD 文件: constructor.xsd    放在resource目录下

<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
            xmlns:constructor="http://example.com/schema/constructor"
            targetNamespace="http://example.com/schema/constructor"
            elementFormDefault="qualified">

    <xsd:element name="bean">
        <xsd:complexType>
            <xsd:sequence>
                <xsd:element name="arg" minOccurs="0" maxOccurs="unbounded">
                    <xsd:complexType>
                        <xsd:attribute name="type" type="xsd:string" use="required"/>
                        <xsd:attribute name="value" type="xsd:string" use="optional"/>
                        <xsd:attribute name="ref" type="xsd:string" use="optional"/>
                    </xsd:complexType>
                </xsd:element>
            </xsd:sequence>
            <xsd:attribute name="class" type="xsd:string" use="required"/>
            <xsd:attribute name="id" type="xsd:string" use="optional"/>
        </xsd:complexType>
    </xsd:element>
</xsd:schema>

2. 实现 SimpleConstructorNamespaceHandler
创建一个类 SimpleConstructorNamespaceHandler,实现 NamespaceHandlerSupport 接口,并注册解析器。

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

public class SimpleConstructorNamespaceHandler extends NamespaceHandlerSupport {

    @Override
    public void init() {
        registerBeanDefinitionParser("bean", new ConstructorBeanDefinitionParser());
    }
}

3. 实现 BeanDefinitionParser
创建一个类 ConstructorBeanDefinitionParser,实现 BeanDefinitionParser 接口,解析自定义标签并生成 BeanDefinition。

public class ConstructorBeanDefinitionParser implements BeanDefinitionParser {

    @Override
    public BeanDefinition parse(Element element, ParserContext parserContext) {
        String className = element.getAttribute("class");
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(className);

        getChildElements(element , builder);

        parserContext.getRegistry().registerBeanDefinition(element.getAttribute("id"), builder.getBeanDefinition());
        return builder.getBeanDefinition();
    }

    private void getChildElements(Element parent , BeanDefinitionBuilder builder) {

        NodeList nodes = parent.getChildNodes();

        for (int i = 0; i < nodes.getLength(); i++) {
            Node node = nodes.item(i);
            if (node instanceof Element && ((Element) node).getTagName().equals("constructor:arg")) {
                Element ele = ((Element) node);
                String type = ele.getAttribute("type");
                String value = ele.getAttribute("value");
                String ref = ele.getAttribute("ref");

                if (value != null) {
                    if ("int".equals(type)) {
                        builder.addConstructorArgValue(Integer.parseInt(value));
                    } else if ("java.lang.String".equals(type)) {
                        builder.addConstructorArgValue(value);
                    } else if (ref != null) {
                        builder.addConstructorArgReference(ref);
                    } else {
                        throw new IllegalArgumentException("Either 'value' or 'ref' must be specified for constructor argument of type " + type);
                    }

                }
            }

        }
    }

}

4. 注册命名空间处理器
在 META-INF/spring.handlers 文件中注册命名空间处理器,这样就会被Properties properties = PropertiesLoaderUtils.loadAllProperties("META-INF/spring.handlers"); 加载到

http\://example.com/schema/constructor=com.example.SimpleConstructorNamespaceHandler

 在META-INF/ 下添加 spring.schemas

http\://example.com/schema/constructor/constructor.xsd=constructor.xsd

 

5. 配置 Spring 应用上下文
在 Spring 的 XML 配置文件中使用自定义命名空间。

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:constructor="http://example.com/schema/constructor"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://example.com/schema/constructor
                           http://example.com/schema/constructor/constructor.xsd">

    <constructor:bean id="myBean" class="com.example.MyBean">
        <constructor:arg type="java.lang.String" value="Hello World"/>
        <constructor:arg type="int" value="123"/>
        <constructor:arg type="java.util.Date" ref="dateBean"/>
    </constructor:bean>

    <bean id="dateBean" class="java.util.Date"/>
</beans>

6. 示例类
假设你有一个 MyBean 类,需要通过构造函数注入参数。

package com.example;

import java.util.Date;

public class MyBean {

    private String message;
    private int number;
    private Date date;

    public MyBean(String message, int number, Date date) {
        this.message = message;
        this.number = number;
        this.date = date;
    }

    public void printInfo() {
        System.out.println("Message: " + message);
        System.out.println("Number: " + number);
        System.out.println("Date: " + date);
    }
}

创建一个主类来测试配置。

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

public class Main {

    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        MyBean myBean = context.getBean("myBean", MyBean.class);
        myBean.printInfo();
    }
}

运行结果:
Message: Hello World
Number: 123
Date: Sat Nov 16 21:38:47 CST 2024

三、NamespaceHandler 如何 解析标签内容

由上面的内容可知,通过 namespaceUri 可以获取到一个对应的NamespaceHandler实现类,那么

NamespaceHandler实现类的实现类又是怎么解析标签内容呢。

 org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parseCustomElement(org.w3c.dom.Element, org.springframework.beans.factory.config.BeanDefinition)

/**
 * Parse a custom element (outside of the default namespace).
 * @param ele the element to parse
 * @param containingBd the containing bean definition (if any)
 * @return the resulting bean definition
 */
@Nullable
public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
	String namespaceUri = getNamespaceURI(ele);
	if (namespaceUri == null) {
		return null;
	}
    // 根据Element的 namespaceUri 获取一个NamespaceHandler,来处理Element
	NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
	if (handler == null) {
		error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
		return null;
	}
    // 用获取到的 NamespaceHandler 解析 Element
	return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}

由以上代码可知,获取到NamespaceHandler实例之后,会调用

org.springframework.beans.factory.xml.NamespaceHandler#parse 方法进行解析,该方法在子类org.springframework.beans.factory.xml.NamespaceHandlerSupport 进行了实现


/**
 * Support class for implementing custom {@link NamespaceHandler NamespaceHandlers}.
 * Parsing and decorating of individual {@link Node Nodes} is done via {@link BeanDefinitionParser}
 * and {@link BeanDefinitionDecorator} strategy interfaces, respectively.
 *
 * <p>Provides the {@link #registerBeanDefinitionParser} and {@link #registerBeanDefinitionDecorator}
 * methods for registering a {@link BeanDefinitionParser} or {@link BeanDefinitionDecorator}
 * to handle a specific element.
 *
 * @author Rob Harrop
 * @author Juergen Hoeller
 * @since 2.0
 * @see #registerBeanDefinitionParser(String, BeanDefinitionParser)
 * @see #registerBeanDefinitionDecorator(String, BeanDefinitionDecorator)
 */
public abstract class NamespaceHandlerSupport implements NamespaceHandler {

	/**
	 * Stores the {@link BeanDefinitionParser} implementations keyed by the
	 * local name of the {@link Element Elements} they handle.
	 * NamespaceHandlerSupport 的子类实例化之后,调用init()方法的时候,就会把该
	 * 实现类能处理的 标签 和 对应的标签解析器 注册在这里,这样在调用handler.parse()
	 * 的时候,就可以根据标签名获取到对应的 标签解析器,对标签的内容进行解析
	 */
	private final Map<String, BeanDefinitionParser> parsers = new HashMap<>();

	/**
	 * 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<>();

	/**
	 * 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<>();


	/**
	 * 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);
	}

	/**
	 * 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);
        // 创建 NamespaceHandler 实例,调用init()方法的时候,会将该实现类能处理标签和
        // 标签解析器注册在this.parsers,所以这里就可以根据 标签名 获取到标签解析器
		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
	@Nullable
	public BeanDefinitionHolder decorate(
			Node node, BeanDefinitionHolder definition, ParserContext parserContext) {

		BeanDefinitionDecorator decorator = findDecoratorForNode(node, parserContext);
		return (decorator != null ? decorator.decorate(node, definition, parserContext) : null);
	}

	/**
	 * 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}.
	 */
	@Nullable
	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);
	}

}

四、BeanDefinitionParser
 

由上面可以知道,BeanDefinitionParser 的多个最终实现类解析的都是除了<beans> <bean> <alias> <imoprt> 之外的标签,但是这些标签大多没有class这个属性,那么把这些标签解析为BeanDefination之后,没有class,如何实例化呢,接下来我们就以标签
<context:property-placeholder location="jdbc.properties"/> 为例看看如何实例化
<context:property-placeholder location="jdbc.properties"/>  是用PropertyPlaceholderBeanDefinitionParser 这个解析器来解析的


我们从解析入口开始看
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 实例以后,调用parser.parse(element, parserContext)

org.springframework.beans.factory.xml.BeanDefinitionParser#parse(Element element, ParserContext parserContext) 具体实现在  org.springframework.beans.factory.xml.AbstractBeanDefinitionParser#parse(​​​​​​​Element element, ParserContext parserContext)

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

org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser#parseInternal(Element element, ParserContext parserContext)
​​​​​​​

/**
 * 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();
}

Class<?> beanClass = getBeanClass(element);
    if (beanClass != null) {
        builder.getRawBeanDefinition().setBeanClass(beanClass);
    }else {
        String beanClassName = getBeanClassName(element);
        if (beanClassName != null) {
            builder.getRawBeanDefinition().setBeanClassName(beanClassName);
        }
    }

先通过 Class<?> beanClass = getBeanClass(element);  AbstractSingleBeanDefinitionParser.getBeanClass(Element element);默认实现是返回null,如果子类重写以后,返回一个Class对象,那就使用返回的这个 Class,如果子类没有重写,那么返回null,那就再通过 String beanClassName = getBeanClassName(element); 从elememt中获取class属性的值。
org.springframework.context.config.PropertyPlaceholderBeanDefinitionParser#getBeanClass(Element element)

@Override
@SuppressWarnings("deprecation")
protected Class<?> getBeanClass(Element element) {
	// As of Spring 3.1, the default value of system-properties-mode has changed from
	// 'FALLBACK' to 'ENVIRONMENT'. This latter value indicates that resolution of
	// placeholders against system properties is a function of the Environment and
	// its current set of PropertySources.
	if (SYSTEM_PROPERTIES_MODE_DEFAULT.equals(element.getAttribute(SYSTEM_PROPERTIES_MODE_ATTRIBUTE))) {
		return PropertySourcesPlaceholderConfigurer.class;
	}

	// The user has explicitly specified a value for system-properties-mode: revert to
	// PropertyPlaceholderConfigurer to ensure backward compatibility with 3.0 and earlier.
	// This is deprecated; to be removed along with PropertyPlaceholderConfigurer itself.
	return org.springframework.beans.factory.config.PropertyPlaceholderConfigurer.class;
}

 可以看到实现类PropertyPlaceholderBeanDefinitionParser返回了PropertySourcesPlaceholderConfigurer.class,所以当我们在 xml 中配置了 <context:property-placeholder location="jdbc.properties"/> 标签以后,最终会在容器中注册一个PropertySourcesPlaceholderConfigurer实例,该实例会加载 location="jdbc.properties" 中的配置,当其他组件需要使用location="jdbc.properties"中的配置的时候,就可以通过这个PropertySourcesPlaceholderConfigurer实例进行获取

由以上可以猜想,我们在xml中配置的每个自定义标签,都相当于在容器中注册一个 特殊功能实例


http://www.kler.cn/a/401238.html

相关文章:

  • if 语句 和 case 语句
  • [代码随想录Day16打卡] 找树左下角的值 路径总和 从中序与后序遍历序列构造二叉树
  • M|告白(2024)
  • Unity 编辑器下 Android 平台 Addressable 加载模型粉红色,类似材质丢失
  • 汽车与摩托车分类数据集
  • 百度智能云 VectorDB 优势数量 TOP 1
  • rk3568, can(3)-----canfd与can2.0
  • 3D Streaming 在线互动展示系统:NVIDIA RTX 4090 加速实时渲染行业数字化转型
  • Django学习笔记十五:Django和Flask有什么区别?
  • React教程第四节 组件的三大属性之state
  • 菜鸟驿站二维码/一维码 取件识别功能
  • MongoDB自定义顺序排序
  • 身份证号码校验
  • 【python爬虫之 funboost 分布式函数调度框架】
  • sql server查看当前正在执行的sql
  • 理解DOM:前端开发的基础
  • LLM2CLIP:通过大型语言模型扩展 CLIP 的能力边界
  • 机器学习评价标准
  • Postman之newman
  • 使用Python和OpenCV连接并处理IP摄像头视频流
  • 点云(网格)PCA及其存在的问题
  • 「三」体验HarmonyOS端云一体化开发模板——使用DevEco Studio直接创建端云一体化工程
  • 计算机视觉:赋予机器“看”的能力
  • element表单校验
  • 高级java每日一道面试题-2024年11月12日-框架篇[SpringBoot篇]-SpringBoot中的监视器是什么?
  • 【mongo8社区办】mongosh MongoServerSelectionError 超时