目录
3.3 XmlBeanDefinitionReader解读BeanDefinition
1.前言
spring提供了接口可以供我们自定义xml配置标签,如它自身的aop空间的config标签,Spring是如何解析它们的?我们如何使用Spring提供的机制自定义xmlns命令空间和标签,且能被spring解析呢?
2.Spring自定义标签
2.1 开发步骤
- 设计配置属性和JavaBean
- XSD文件
- BeanDefinitionParser标签解析类
- 标签解析类的NamespaceHandler类
- spring.handlers和spring.schemas供Spring读取
- 在Spring中使用
2.2 编码实践demo
1.javaBean
public class User {
private String userName;
private String email;
// 省略getter setter toString
}
2.xsd定义标签模式
xml schema definition文件,用于定义自定义标签的层次结构模式,放置在项目的META-INF/user.xsd,定义user标签
<?xml version="1.0" encoding="UTF-8" ?>
<schema xmlns="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.wy.com/schema/user"
xmlns:tns="http://www.wy.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>
3. 定义解析类
解析xml配置,注入到java bean的属性
public class UserBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
@Override
protected Class getBeanClass(Element element) {
return User.class;
}
@Override
protected void doParse(Element element, BeanDefinitionBuilder bean) {
String userName = element.getAttribute("userName");
String email = element.getAttribute("email");
if (StringUtils.hasText(userName)) {
bean.addPropertyValue("userName", userName);
}
if (StringUtils.hasText(email)) {
bean.addPropertyValue("email", email);
}
}
}
4. NamespaceHandler命名空间处理
为防止一个标签重复起冲突,一般都会用命令空间隔离,如<aop:config>标签中aop就是命名空间,config是一个标签,命令空间中可以有很多标签,每个标签都有对应的parser解析类,一个命名空间有一个命名空间处理器。
自定义命名空间custom,重写init方法,将user标签的解析类注册进处理器
public class CustomNamespaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
// 关联元素名 和解析类
registerBeanDefinitionParser("user", new UserBeanDefinitionParser());
}
}
5.spring.handlers和spring.schemas供spring读取
#spring.schemas 注册自定义的标签xsd
http\://www.wy.com/schema/user.xsd=META-INF/user.xsd
#spring.handlers 注册命名空间处理器
http\://www.wy.com/schema/user=framework.spring.customXmlElement.CustomNamespaceHandler
6.在spring使用
直接启动spring容器,spring就可以解析到自定义的标签,并生产出BeanDefinition
<?xml version="1.0" encoding="UTF-8"?>
<beans default-autowire="byName" xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:custorm="http://www.wy.com/schema/user"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.wy.com/schema/user
http://www.wy.com/schema/user.xsd">
<custorm:user id="user" userName="张三" email="[email protected]" />
</beans>
public class Main {
public static void main(String[] args) {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("framework/springCustomElement.xml");
User user = (User)applicationContext.getBean("user");
System.out.println(user);
}
}
执行结果:
User{userName='张三', email='[email protected]'}
3.源码解析
3.1 加载xsd文件
Spring的PluggableSchemaResolver会根据META-INF/目录下的spring.schemas文件,配置了标签的模式xsd文件路径
3.2 加载自定义NamespaceHandler
DefaultNamespaceHandlerResolver解析spring.handlers到map,拿到命名空间处理器,并调用其init方法,注册解析类
public static final String DEFAULT_HANDLER_MAPPINGS_LOCATION = "META-INF/spring.handlers";
private Map<String, Object> getHandlerMappings() {
if (this.handlerMappings == null) {
synchronized (this) {
if (this.handlerMappings == null) {
try {
Properties mappings =
PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);
if (logger.isDebugEnabled()) {
logger.debug("Loaded NamespaceHandler mappings: " + mappings);
}
Map<String, Object> handlerMappings = new ConcurrentHashMap<String, Object>(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 this.handlerMappings;
}
3.3 XmlBeanDefinitionReader解读BeanDefinition
在容器启动,加载beanDefinition时会有一步解析自定义元素,这一步会使用上一个步骤配置的命令空间处理器NamespaceHandler去解析标签元素,NamespaceHandler会在自己的parsers map中查找标签对应的解析器进行解析。