目录
1.定义和xsd文件相对应的配置类,来承载配置
2.定义xsd文件
3.自定义与命名空间相对应的handler处理类
4.定义handler中注册的对应的BeanDefinitionParser,解析命名空间中的属性
5.定义spring.handlers和 spring.schemas文件。
6.spring-config.xml文件中引入自定义的people命名空间
7.编写测试类:
8 原理:
package com.chr.test.namespace;
/**
* @author chenhairong3
* @description 定义一个和xsd配置相对应的Java对象,来承载配置
* @date 2020/12/15
*/
public class People {
//id属性是必须的
private String id;
private String name;
private int age;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
id字段 不用在xsd中定义,默认就有此字段
package com.chr.test.namespace;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
/**
* @author chenhairong3
* @description 自定义的people命名空间的handler处理类
* @date 2020/12/15
*/
public class PeopleNamespaceHandler extends NamespaceHandlerSupport {
public PeopleNamespaceHandler() {
}
public void init() {
this.registerBeanDefinitionParser("config", new PeopleConfigBeanDefinitionParser());
}
}
package com.chr.test.namespace;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;
/**
* @author chenhairong3
* @description people命名空间下config元素对应的解析类
* @date 2020/12/15
*/
public class PeopleConfigBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
@Override
protected Class getBeanClass(Element element) {
return People.class;
}
@Override
protected void doParse(Element element, BeanDefinitionBuilder bean) {
String name = element.getAttribute("name");
String age = element.getAttribute("age");
String id = element.getAttribute("id");
if (StringUtils.hasText(id)) {
bean.addPropertyValue("id", id);
}
if (StringUtils.hasText(name)) {
bean.addPropertyValue("name", name);
}
if (StringUtils.hasText(age)) {
bean.addPropertyValue("age", Integer.valueOf(age));
}
}
}
spring.handlers 把命名空间url和handler处理类关联起来。
spring.schemas 把命名空间和对应的xsd文件关联起来。
spring.handlers文件如下:
http\://www.springframework.org/schema/people=com.chr.test.namespace.PeopleNamespaceHandler
spring.schemas 文件如下:
http\://www.springframework.org/schema/people=META-INF/people.xsd
http://www.springframework.org/schema/people
这里的路径我写的是我本地的绝对文件路径,我的电脑是windows系统的。使用时,请更换到自己xsd文件所对应的的目录。
file:///D://idea-workspace//test-spring//test-spring-configuration//src//main//resources//META-INF//people.xsd
/**
* @author chenhairong3
* @description
* @date 2020/12/15
*/
public class NamespaceTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("spring/spring-config.xml");
People people = (People) context.getBean("cutesource");
System.out.println("people = " + people.getId() +",name:"+people.getName()+",age:"+people.getAge());
}
}
输出:
people = cutesource,name:袁志俊,age:27
下来我们介绍一下Spring提供的NamespaceHandler的处理机制.
接下来我们先看下Spring提供的NamespaceHandlerSupport抽象类中的parse方法。
public abstract class NamespaceHandlerSupport implements NamespaceHandler {
private final Map parsers = new HashMap();
private final Map decorators = new HashMap();
private final Map attributeDecorators = new HashMap();
public NamespaceHandlerSupport() {
}
@Nullable
public BeanDefinition parse(Element element, ParserContext parserContext) {
BeanDefinitionParser parser = this.findParserForElement(element, parserContext);
return parser != null ? parser.parse(element, parserContext) : null;
}
//此次省略掉其他无关代码
}
parse方法中,会找到与命名空间相对应的BeanDefinitionParser,然后调用BeanDefinitionParser中的parse方法进行处理。
那么namespaceHandler在spring容器初始化的时候是怎么被调用的呢?
我们回到ClassPathXmlApplicationContext中的refresh 容器刷新方法:
public void refresh() throws BeansException, IllegalStateException {
synchronized(this.startupShutdownMonitor) {
this.prepareRefresh();
//在这里进行配置文件的加载,并把配置解析成BeanDefinition并放入map集合中
ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
this.prepareBeanFactory(beanFactory);
try {
this.postProcessBeanFactory(beanFactory);
this.invokeBeanFactoryPostProcessors(beanFactory);
this.registerBeanPostProcessors(beanFactory);
this.initMessageSource();
this.initApplicationEventMulticaster();
this.onRefresh();
this.registerListeners();
this.finishBeanFactoryInitialization(beanFactory);
this.finishRefresh();
} catch (BeansException var9) {
if (this.logger.isWarnEnabled()) {
this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
}
this.destroyBeans();
this.cancelRefresh(var9);
throw var9;
} finally {
this.resetCommonCaches();
}
}
}
我们继续进入这个方法:ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
//在这里完成BeanDefinition的加载,这是个抽象方法,最终会调用到子类AbstractRefreshableApplicationContext
this.refreshBeanFactory();
ConfigurableListableBeanFactory beanFactory = this.getBeanFactory();
if (this.logger.isDebugEnabled()) {
this.logger.debug("Bean factory for " + this.getDisplayName() + ": " + beanFactory);
}
return beanFactory;
}
//在这里完成BeanDefinition的加载,这是个抽象方法,最终会调用到子类AbstractRefreshableApplicationContext
this.refreshBeanFactory();
我们继续跟踪AbstractRefreshableApplicationContext类中的refreshBeanFactory()方法,
protected final void refreshBeanFactory() throws BeansException {
if (this.hasBeanFactory()) {
this.destroyBeans();
this.closeBeanFactory();
}
try {
DefaultListableBeanFactory beanFactory = this.createBeanFactory();
beanFactory.setSerializationId(this.getId());
this.customizeBeanFactory(beanFactory);
//在这里完成bean定义的加载,这里会调用到子类的AbstractXmlApplicationContext中的此方法
this.loadBeanDefinitions(beanFactory);
synchronized(this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
} catch (IOException var5) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + this.getDisplayName(), var5);
}
}
//在这里完成bean定义的加载,这里会调用到子类的AbstractXmlApplicationContext中的此方法
this.loadBeanDefinitions(beanFactory);
进入AbstractXmlApplicationContext类中的loadBeanDefinitions方法:
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
beanDefinitionReader.setEnvironment(this.getEnvironment());
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
this.initBeanDefinitionReader(beanDefinitionReader);
//通过XmlBeanDefinitionReader完成对bean定义的解析加载
this.loadBeanDefinitions(beanDefinitionReader);
}
//通过XmlBeanDefinitionReader完成对bean定义的解析加载
this.loadBeanDefinitions(beanDefinitionReader);
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
Resource[] configResources = this.getConfigResources();
if (configResources != null) {
reader.loadBeanDefinitions(configResources);
}
String[] configLocations = this.getConfigLocations();
if (configLocations != null) {
//完成bean定义的加载
reader.loadBeanDefinitions(configLocations);
}
}
进入AbstractBeanDefinitionReader中的loadBeanDefinitions()方法:
public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
Assert.notNull(locations, "Location array must not be null");
int counter = 0;
String[] var3 = locations;
int var4 = locations.length;
for(int var5 = 0; var5 < var4; ++var5) {
String location = var3[var5];
counter += this.loadBeanDefinitions(location);
}
return counter;
}
最终追踪到AbstractBeanDefinitionReader中的下面方法:
public int loadBeanDefinitions(String location, @Nullable Set actualResources) throws BeanDefinitionStoreException {
ResourceLoader resourceLoader = this.getResourceLoader();
if (resourceLoader == null) {
throw new BeanDefinitionStoreException("Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
} else {
int loadCount;
if (!(resourceLoader instanceof ResourcePatternResolver)) {
Resource resource = resourceLoader.getResource(location);
loadCount = this.loadBeanDefinitions((Resource)resource);
if (actualResources != null) {
actualResources.add(resource);
}
if (this.logger.isDebugEnabled()) {
this.logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");
}
return loadCount;
} else {
try {
Resource[] resources = ((ResourcePatternResolver)resourceLoader).getResources(location);
//进行配置的加载
loadCount = this.loadBeanDefinitions(resources);
if (actualResources != null) {
Resource[] var6 = resources;
int var7 = resources.length;
for(int var8 = 0; var8 < var7; ++var8) {
Resource resource = var6[var8];
actualResources.add(resource);
}
}
if (this.logger.isDebugEnabled()) {
this.logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]");
}
return loadCount;
} catch (IOException var10) {
throw new BeanDefinitionStoreException("Could not resolve bean definition resource pattern [" + location + "]", var10);
}
}
}
}
最终追踪到DefaultBeanDefinitionDocumentReader的registerBeanDefinitions方法:
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
this.logger.debug("Loading bean definitions");
Element root = doc.getDocumentElement();
//开始注册bean定义
this.doRegisterBeanDefinitions(root);
}
进入 //开始注册bean定义
this.doRegisterBeanDefinitions(root);方法
protected void doRegisterBeanDefinitions(Element root) {
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = this.createDelegate(this.getReaderContext(), root, parent);
if (this.delegate.isDefaultNamespace(root)) {
String profileSpec = root.getAttribute("profile");
if (StringUtils.hasText(profileSpec)) {
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(profileSpec, ",; ");
if (!this.getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
if (this.logger.isInfoEnabled()) {
this.logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec + "] not matching: " + this.getReaderContext().getResource());
}
return;
}
}
}
//这里用到了模板方法模式,这个方法是抽象方法,由子类方法
this.preProcessXml(root);
//开始解析bean定义
this.parseBeanDefinitions(root, this.delegate);
//这里用到了模板方法模式,这个方法是抽象方法,由子类方法
this.postProcessXml(root);
this.delegate = parent;
}
进入此方法:
this.parseBeanDefinitions(root, this.delegate);
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
if (delegate.isDefaultNamespace(root)) {
//判断是否是spring默认的命名空间,默认的空间是bean
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;
if (delegate.isDefaultNamespace(ele)) {
this.parseDefaultElement(ele, delegate);
} else {
delegate.parseCustomElement(ele);
}
}
}
} else {
//解析用户自定义的命名空间
delegate.parseCustomElement(root);
}
}
进入 //解析用户自定义的命名空间方法:
delegate.parseCustomElement(root);
进入到
BeanDefinitionParserDelegate的
@Nullable
public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
//获取元素对应的命名空间urI
String namespaceUri = this.getNamespaceURI(ele);
if (namespaceUri == null) {
return null;
} else {
//通过namespaceUri找到对应的NamespaceHandler处理类
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
if (handler == null) {
this.error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
return null;
} else {
//调用handler的parse方法处理。NamespaceHandlerSupport的parse方法会找多对应handler的BeanDefinitionParse,从而调用BeanDefinitionParse的parse方法来处理命名空间数据
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}
}
}
进入 //通过namespaceUri找到对应的NamespaceHandler处理类
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);这个方法,来根据spring是如何根据namespaceUri来找对对应的namespaceHandler呢?
@Nullable
public NamespaceHandler resolve(String namespaceUri) {
//从handlerMappings中根据namespaceUri找多对应的handler对象实例
Map handlerMappings = this.getHandlerMappings();
Object handlerOrClassName = handlerMappings.get(namespaceUri);
if (handlerOrClassName == null) {
return null;
} else if (handlerOrClassName instanceof NamespaceHandler) {
return (NamespaceHandler)handlerOrClassName;
} else {
//如果对象实例为空,则通过反射实例化handler对象,并放入到handlerMappings中。
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");
} else {
NamespaceHandler namespaceHandler = (NamespaceHandler)BeanUtils.instantiateClass(handlerClass);
namespaceHandler.init();
handlerMappings.put(namespaceUri, namespaceHandler);
return namespaceHandler;
}
} catch (ClassNotFoundException var7) {
throw new FatalBeanException("Could not find NamespaceHandler class [" + className + "] for namespace [" + namespaceUri + "]", var7);
} catch (LinkageError var8) {
throw new FatalBeanException("Unresolvable class definition for NamespaceHandler class [" + className + "] for namespace [" + namespaceUri + "]", var8);
}
}
}
那么重点来了,this.getHandlerMappings()是如何存入进去的呢?
进入
DefaultNamespaceHandlerResolver类:
private Map getHandlerMappings() {
Map handlerMappings = this.handlerMappings;
if (handlerMappings == null) {
synchronized(this) {
handlerMappings = this.handlerMappings;
if (handlerMappings == null) {
try {
//加载配置文件,this.handlerMappingsLocation 这里的文件路径就是:"META-INF/spring.handlers",所以是通过此文件找多的。key就是namespaceuri,value就是对应的NamespaceHandler,最后放入map中
Properties mappings = PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Loaded NamespaceHandler mappings: " + mappings);
}
Map mappingsToUse = new ConcurrentHashMap(mappings.size());
CollectionUtils.mergePropertiesIntoMap(mappings, mappingsToUse);
handlerMappings = mappingsToUse;
this.handlerMappings = mappingsToUse;
} catch (IOException var6) {
throw new IllegalStateException("Unable to load NamespaceHandler mappings from location [" + this.handlerMappingsLocation + "]", var6);
}
}
}
}
return (Map)handlerMappings;
}
终于破案了,是通过 Properties mappings = PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);
/加载配置文件,this.handlerMappingsLocation 这里的文件路径就是:"META-INF/spring.handlers",所以是通过此文件找多的。key就是namespaceuri,value就是对应的NamespaceHandler,最后放入map中
所以自定义的命名空间是在spring的刷新容器的时候,在refresh里面,在loadBeanDefinitions的时候,解析自定义命名空间parseCustomElement()方法中解析中,解析到最后是通过读取"META-INF/spring.handlers"这个配置文件,此文件的key为自定义命名空间uri,value是对应的namespacehandler的全限定类名。然后调用对应的namespacehandler的parse方法,parse方法会找到handler中init方法注册的BeanDefinitionParser类,然后调用对应BeanDefinitionParser的parse方法进行自定义命名空间的属性数据解析等。