先新建一个类
public class User {
private String userName;
private String email;
...省略setter、getter
}
新建一个BeanDefinitionParser
public class UserBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
@Override
protected Class getBeanClass(Element element) {
return User.class;
}
@Override
protected void doParse(Element element, BeanDefinitionBuilder builder) {
String name = element.getAttribute("userName");
String email = element.getAttribute("email");
if (StringUtils.hasText(name))
builder.addPropertyValue("userName", name);
if (StringUtils.hasText(email))
builder.addPropertyValue("email", email);
}
}
新建一个NameSpaceHandler
public class UserNameSpaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
registerBeanDefinitionParser("user",new UserBeanDefinitionParser());
}
}
在resources/META-INF目录下建一个spring-test.xsd文件(用于XML检验)
<schema xmlns="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.season.com/schema/user"
xmlns:tns="http://www.season.com/schema/user"
elementFormDefault="qualified">
<element name="user">
<complexType>
<attribute name="id" type="string"/>
<attribute name="userName" type="string"/>
<attribute name="email" type="string"/>
complexType>
element>
schema>
XML Schema可查阅 http://www.w3school.com.cn/schema/index.asp
在resources/META-INF目录下新建spring.handlers文件写上
http\://www.season.com/schema/user=com.season.meta.UserNameSpaceHandler
在resources/META-INF目录下新建spring.schemas文件写上
http://www.season.com/schema/user.xsd=META-INF/spring-test.xsd
在resources目录下建一个beans.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:myname="http://www.season.com/schema/user"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.season.com/schema/user
http://www.season.com/schema/user.xsd">
<myname:user id="user" email="[email protected]" userName="season"/>
beans>
测试:
@Test
public void testNameSpaceHandler(){
ApplicationContext actx = new ClassPathXmlApplicationContext("beans.xml");
User user = (User) actx.getBean("user");
System.out.println(user);
}
取XML配置的是XmlBeanDefinitionReader,它会先用SAX技术把XML配置文件读取成一个Document对象,然后利用BeanDefinitionDocumentReader对Document对象的节点进行解析,最后把bean封装成BeanDefinition存储在BeanRegistry中(参见:Spring IOC实现原理笔记(二) – 加载XML配置),如下:
XmlBeanDefinitionReader类
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
//实际创建的是DefaultBeanDefinitionDocumentReader类
BeanDefinitionDocumentReader documentReader = this.createBeanDefinitionDocumentReader();
int countBefore = this.getRegistry().getBeanDefinitionCount();
//【标记1】用BeanDefinitionDocumentReader对Document对象的节点进行解析
documentReader.registerBeanDefinitions(doc, this.createReaderContext(resource));
return this.getRegistry().getBeanDefinitionCount() - countBefore;
}
上面【标记1】出就是解析节点的开始,节点分默认节点和自定义节点两种情况。其中解析自定义节点的源码如下:
DefaultBeanDefinitionDocumentReader类
//解析自定义节点
public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
String namespaceUri = this.getNamespaceURI(ele);
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
if(handler == null) {
...
return null;
} else {
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}
}
上面代码中,首先会拿到节点的中的命名空间URI,(在上面的例子中是:http://www.season.com/schema/user),接着从NamespaceHandlerResolver中拿到对应该命名空间URI解析的NamespaceHandler,然后调用NamespaceHandler的parse进行解析。
关键代码是this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri),其中readerContext是上面【标记1】处的createReaderContext(resource)创建出来的,如下:
XmlBeanDefinitionReader类
public XmlReaderContext createReaderContext(Resource resource) {
return new XmlReaderContext(resource, this.problemReporter, this.eventListener, this.sourceExtractor,
this, this.getNamespaceHandlerResolver());
}
//上面调用了getNamespaceHandlerResolver方法
public NamespaceHandlerResolver getNamespaceHandlerResolver() {
if(this.namespaceHandlerResolver == null) {
this.namespaceHandlerResolver = this.createDefaultNamespaceHandlerResolver();
}
return this.namespaceHandlerResolver;
}
protected NamespaceHandlerResolver createDefaultNamespaceHandlerResolver() {
return new DefaultNamespaceHandlerResolver(this.getResourceLoader().getClassLoader());
}
上面可以看到getNamespaceHandlerResolver()获取到的是DefaultNamespaceHandlerResolver实例,所以接下来去看该类
public class DefaultNamespaceHandlerResolver implements NamespaceHandlerResolver {
...
//【标记2】
public DefaultNamespaceHandlerResolver(ClassLoader classLoader) {
this(classLoader, "META-INF/spring.handlers");
}
public NamespaceHandler resolve(String namespaceUri) {
Map handlerMappings = this.getHandlerMappings();
Object handlerOrClassName = handlerMappings.get(namespaceUri);
if(handlerOrClassName == null) {
return null;
} else if(handlerOrClassName instanceof NamespaceHandler) {
return (NamespaceHandler)handlerOrClassName;
} else {
String className = (String)handlerOrClassName;
...
Class handlerClass = ClassUtils.forName(className, this.classLoader);
...
//【标记3】
NamespaceHandler namespaceHandler = (NamespaceHandler)BeanUtils.instantiateClass(handlerClass);
//【标记4】
namespaceHandler.init();
handlerMappings.put(namespaceUri, namespaceHandler);
return namespaceHandler;
...
}
}
private Map getHandlerMappings() {
if(this.handlerMappings == null) {
synchronized(this) {
if(this.handlerMappings == null) {
try {
//【标记4】
Properties mappings = PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);
...
Map handlerMappings = new ConcurrentHashMap(mappings.size());
CollectionUtils.mergePropertiesIntoMap(mappings, handlerMappings);
this.handlerMappings = handlerMappings;
} catch...
}
}
}
return this.handlerMappings;
}
...
}
DefaultNamespaceHandlerResolver是会把所有NamespaceHandler缓存到handlerMappings(ConcurrentHashMap实例)里,resolve方法就是从handlerMappings获取相应的NamespaceHandler返回出去。
说说上面的标记的地方:
【标记2】:Spring默认的NamespaceHandler配置文件路径为META-INF/spring.handlers
【标记3】:未初始化的NamespaceHandler是通过反射实例出来的
【标记4】:实例化出NamespaceHandler后,第一件事是调用其init方法
【标记5】:加载META-INF/spring.handlers配置文件,并记录到handlerMappings。
Spring在解析XML的时候,解析到是自定义节点,就会找相对应的NameSpaceHandler去解析;而Spring默认加载写在META-INF/spring.handlers配置文件中配置的NameSpaceHandler,通过反射将其实例化
那NameSpaceHandler如何解析的呢?以上面的例子来分析。首先解析自定义节点的源码中,Spring拿到相应的NameSpaceHandler后,会调用器parse方法,而上面自定义的UserNameSpaceHandler没看到有parse方法,那就去父类,如下:
NamespaceHandlerSupport类
public abstract class NamespaceHandlerSupport implements NamespaceHandler {
public BeanDefinition parse(Element element, ParserContext parserContext) {
return this.findParserForElement(element, parserContext).parse(element, parserContext);
}
private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
//拿到标签名称(对应上面例子的 user)
String localName = parserContext.getDelegate().getLocalName(element);
BeanDefinitionParser parser = (BeanDefinitionParser)this.parsers.get(localName);
...
return parser;
}
protected final void registerBeanDefinitionParser(String elementName, BeanDefinitionParser parser) {
this.parsers.put(elementName, parser);
}
...
}
上面看到parse方法调用了findParserForElement拿到BeanDefinitionParser,再调BeanDefinitionParser的parse方法进行解析。要拿到BeanDefinitionParser看这句代码this.parsers.get(localName),其中parsers是一个Map,键存标签名称,值为相对应的BeanDefinitionParser。那我们自定义的BeanDefinitionParser(对应上面的UserBeanDefinitionParser)什么时候放进这个Map中呢?
回顾下【标记4】处代码,发现实例化出NamespaceHandler后,第一件事是调用其init方法,我们自定义的UserNameSpaceHandler在init方法中调用了registerBeanDefinitionParser方法,该方法在父类中,做的事情就是把我们传入的键值对存入parsers这个Map中,所以可以被找到并取出来进行解析节点的操作了。