1. 自定义标签配置的使用
Spring的XML配置中分为默认标签配置和自定义标签配置,前面的文章跟踪了默认标签配置的Bean解析与注册的流程。现在我们将继续跟踪Spring源码,探究Spring自定义标签的实现原理。
// 源码位置: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) {
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;
if (delegate.isDefaultNamespace(ele)) {
parseDefaultElement(ele, delegate);
}
else {
delegate.parseCustomElement(ele);
}
}
}
}
else {
delegate.parseCustomElement(root);
}
}
Spring在解析XML时,最终决定是走默认标签解析方式还是自定义标签解析方式的分支在上面的代码中。通过解析命名空间判断是否默认命名空间。默认命名空间的走默认标签解析,否则走自定义标签解析delegate.parseCustomElement(ele);
。
在之前的默认标签解析过程中,我们知道,其解析工作由BeanDefinitionParserDelegate类的一堆parseXXX方法完成。现在自定义标签的解析工作我们可以看到同样交给了BeanDefinitionParserDelegate类。那么BeanDefinitionParserDelegate类是如何根据不同的命名空间进行具体的解析工作的呢?我们会通过源码找到答案。
在跟踪源码之前,我们先写一个默认标签配置的例子,回顾自定义标签的使用。
1.1. 自定义标签的意义
我们在系统开发的时候,一般通过默认标签<bean配置管理对象之间的依赖,但是当需要较多依赖配置时我们的配置工作就会变的繁琐且容易遗漏,这时我们可以通过自定义标签对其进行封装简化配置,并且在这个过程中我们可以让程序做一些我们希望预处理的一些工作。这些需要我们在实际的开发过程中去体会。
1.2. 自定义标签的示例
下面我们通过写一个自定义标签的示例来简单回顾一下自定义标签的使用。
- 创建一个交给Spring管理的类,为了示例代码简单我们定义一个普通的POJO。
package com.yanglh.ioc.custom;
public class User {
private String userName;
private String email;
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
- 定义一个XSD文件用于校验XML配置时的正确性。
/META-INF/spring-custom-user.xsd
<?xml version="1.0" encoding="UTF-8" ?>
<schema xmlns="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.yanglh.com/schema/user" >
<element name="user">
<complexType>
<attribute name="id" type="string"/>
<attribute name="userName" type="string"/>
<attribute name="email" type="string"/>
</complexType>
</element>
</schema>
在这个示例中,我们让XML的自定义标签对User对象的属性变量进行赋值。因此XML配置中需要定义与User类中的属性变量一致的节点元素。那么这个XSD文件的内容定义了XML配置与User类的变量对应关系及配置规范。
上面的XSD文件定义了一下新的targetNamespace命名空间,其user元素在该命名空间之下。
- 定义用于解析自定义标签的处理类。
既然是自定义的标签配置,那么Spring是无法自动完成解析工作的,需要自定义一个解析类,为了能让这个解析类为Spring所用,就需要融入到Spring体系中,直觉告诉我们,为了达到这一目的,需要该解析类实现或继承什么东西,没错,该解析类继承了AbstractSingleBeanDefinitionParser类并重写了解析方法。
package com.yanglh.ioc.custom;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;
public class UserBenDefinitionParser extends AbstractSingleBeanDefinitionParser {
protected Class getBeanClass(Element element) {
return User.class;
}
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);
}
}
}
- 创建一个Handler文件。
创建一个Handler类,继承至NamespaceHandlerSupport类。将该自定义标签与对应的解析类进行绑定,便于Spring寻找对应的解析类。
package com.yanglh.ioc.custom;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
public class MyNamespaceHandler extends NamespaceHandlerSupport {
public void init() {
registerBeanDefinitionParser("user",new UserBenDefinitionParser());
}
}
- 编写spring.handlers和spring.schemas文件
这两个文件名注意区分大小写,我们把这两个文件放到classpath路径下的META-INF下。
spring.schemas文件放到META-INF下是因为XSD验证模式在寻找本地XSD文件时默认就是从这个目录去读的spring.schemas文件,这在Spring XML默认标签配置解析注册Bean源码分析(三)——Document对象文章中getEntityResolver时有详细说明。
spring.handlers文件放到META-INF下是因为在DefaultNamespaceHandlerResolver类中,根据命名空间寻找对应的NamespaceHandler的时候,会加载命名空间与各NamespaceHandler的对应关系,这个对应关系就在DEFAULT_HANDLER_MAPPINGS_LOCATION = "META-INF/spring.handlers";
下,这是Spring默认固定写法。
spring.handlers
http\://www.yanglh.com/schema/user=com.yanglh.ioc.custom.MyNamespaceHandler
spring.schemas
http\://www.yanglh.com/schema/user.xsd=META-INF/spring-custom-user.xsd
- 在XML中配置自定义标签。
conf/custom-beans.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:myname="http://www.yanglh.com/schema/user"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.yanglh.com/schema/user http://www.yanglh.com/schema/user.xsd">
<myname:user id="testBean" email="yangliehui@163.com" userName="yangliheui"/>
</beans>
上面的配置<myname:user
标签就是我们自定义的标签。xmlns:myname="http://www.yanglh.com/schema/user"
是对应的命名空间,xsi:schemaLocation=
里需要配置命名空间与XSD文件的对应。
- 运行自定义标签。
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(new ClassPathResource("conf/custom-beans.xml"));
User user = (User) factory.getBean("testBean");
System.out.println(user.getEmail()+"-"+user.getUserName());
// 输出:yangliehui@163.com-yangliheui
上面就是自定义标签的简单用法,后面我们将解析源码。