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/beansif (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/beansdelegate.parseCustomElement(ele);}}}}else {//如果 root 的 namespaceUri不是 http://www.springframework.org/schema/beansdelegate.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)) {// recursedoRegisterBeanDefinitions(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,来处理ElementNamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);if (handler == null) {error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);return null;}// 用获取到的 NamespaceHandler 解析 Elementreturn 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 {@Testpublic 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 {@Overridepublic 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">* <aop:pointcut id="getNameCalls" expression="execution(* *..ITestBean.getName(..))"/>* </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">* <aop:advisor id="getAgeAdvisor"* pointcut="execution(* *..ITestBean.getAge(..))"* advice-ref="getAgeCounter"/>** <aop:advisor id="getNameAdvisor"* pointcut-ref="getNameCalls"* advice-ref="getNameCounter"/></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.*/@Overridepublic void init() {// In 2.0 XSD as well as in 2.5+ XSDsregisterBeanDefinitionParser("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">* <lang:groovy id="messenger"* refresh-check-delay="5000"* script-source="classpath:Messenger.groovy">* <lang:property name="message" value="I Can Do The Frug"/>* </lang:groovy>* </pre>** @author Rob Harrop* @author Juergen Hoeller* @author Mark Fisher* @since 2.0*/
public class LangNamespaceHandler extends NamespaceHandlerSupport {@Overridepublic 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 {@Overridepublic 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";@Overridepublic 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 {@Overridepublic 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 {@Overridepublic 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">* <bean id="rob" class="..TestBean" p:name="Rob Harrop" p:spouse-ref="sally"/></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";@Overridepublic void init() {}@Override@Nullablepublic BeanDefinition parse(Element element, ParserContext parserContext) {parserContext.getReaderContext().error("Class [" + getClass().getName() + "] does not support custom elements.", element);return null;}@Overridepublic 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">* <bean id="author" class="..TestBean" c:name="Enescu" c:work-ref="compositions"/>* </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 = "_";@Overridepublic void init() {}@Override@Nullablepublic BeanDefinition parse(Element element, ParserContext parserContext) {parserContext.getReaderContext().error("Class [" + getClass().getName() + "] does not support custom elements.", element);return null;}@Overridepublic 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 argumentsif (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"/"_" argumentsif (argName.startsWith(DELIMITER_PREFIX)) {String arg = argName.substring(1).trim();// fast default checkif (!StringUtils.hasText(arg)) {cvs.addGenericArgumentValue(valueHolder);}// assume an index otherwiseelse {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 nameelse {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
<?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 {@Overridepublic void init() {registerBeanDefinitionParser("bean", new ConstructorBeanDefinitionParser());}
}
3. 实现 BeanDefinitionParser
创建一个类 ConstructorBeanDefinitionParser,实现 BeanDefinitionParser 接口,解析自定义标签并生成 BeanDefinition。
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.w3c.dom.Element;public class ConstructorBeanDefinitionParser implements BeanDefinitionParser {@Overridepublic BeanDefinition parse(Element element, ParserContext parserContext) {String className = element.getAttribute("class");BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(className);for (Element argElement : getChildElements(element, "arg")) {String type = argElement.getAttribute("type");String value = argElement.getAttribute("value");String ref = argElement.getAttribute("ref");if (value != null) {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);}}parserContext.getRegistry().registerBeanDefinition(element.getAttribute("id"), builder.getBeanDefinition());return builder.getBeanDefinition();}private List<Element> getChildElements(Element parent, String tagName) {NodeList nodes = parent.getElementsByTagName(tagName);List<Element> elements = new ArrayList<>();for (int i = 0; i < nodes.getLength(); i++) {Node node = nodes.item(i);if (node instanceof Element && node.getParentNode() == parent) {elements.add((Element) node);}}return elements;}
}
4. 注册命名空间处理器
在 META-INF/spring.handlers 文件中注册命名空间处理器。
http\://example.com/schema/constructor=com.example.SimpleConstructorNamespaceHandler
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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://example.com/schema/constructorhttp://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();}
}