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">
* <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.
*/
@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">
* <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 {
@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">
* <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";
@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">
* <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 = "_";
@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中配置的每个自定义标签,都相当于在容器中注册一个 特殊功能实例