自定义标签的意思是,在Spring的配置文件中(例如:applicationContext.xml)加入自己定义的标签,同时加入处理类,让IOC容器启动时可以自动解析到beanFactory中。
在ioc容器初始化过程中,会调用类(XmlBeanDefinitionReader.java)的下面这个方法。在这个方法的(createReaderContext(resource))中会初始化上下文。同时会确定配置文件地址。
//org/springframework/beans/factory/xml/XmlBeanDefinitionReader.java
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
// 对xml的beanDefinition进行解析
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
int countBefore = getRegistry().getBeanDefinitionCount();
// 完成具体的解析过程,createReaderContext这个方法会读取配置文件,读出不同命名空间对应的处理类
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
return getRegistry().getBeanDefinitionCount() - countBefore;
}
/**
* 接着进入getNamespaceHandlerResolver()这个方法
*/
public XmlReaderContext createReaderContext(Resource resource) {
return new XmlReaderContext(resource, this.problemReporter, this.eventListener,
this.sourceExtractor, this, getNamespaceHandlerResolver());
}
/**
* 第一次进来肯定为空,所以进入createDefaultNamespaceHandlerResolver()
*/
public NamespaceHandlerResolver getNamespaceHandlerResolver() {
if (this.namespaceHandlerResolver == null) {
this.namespaceHandlerResolver = createDefaultNamespaceHandlerResolver();
}
return this.namespaceHandlerResolver;
}
/**
* 进到这个方法后,配置文件路径确定
*/
protected NamespaceHandlerResolver createDefaultNamespaceHandlerResolver() {
ClassLoader cl = (getResourceLoader() != null ? getResourceLoader().getClassLoader() : getBeanClassLoader());
return new DefaultNamespaceHandlerResolver(cl);
}
在下面的这个parseCustomElement方法,会解析非默认命名空间的配置项。这里面会使用上下文调用resolve方法找到命名空间对应的处理类。针对不同命名空间调用不同类的方法来解析。
//org.springframework.beans.factory.xml.BeanDefinitionParserDelegate
public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
// 获取对应的命名空间
String namespaceUri = getNamespaceURI(ele);
if (namespaceUri == null) {
return null;
}
// 根据命名空间找到对应的Namespace Handler
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进行解析
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}
//org.springframework.beans.factory.xml.DefaultNamespaceHandlerResolver
public NamespaceHandler resolve(String namespaceUri) {
// 获取所有已经配置好的handler映射
Map<String, Object> handlerMappings = getHandlerMappings();
// 根据命名空间找到对应的信息
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) 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);
}
}
}
例如:我们自定义一个标签
定义一个实体类User
写一个类继承AbstractSingleBeanDefinitionParser,并覆盖父类方法。
写一个类继承NamespaceHandlerSupport,覆盖父类方法。可参考ContextNamespaceHandler。
上述步骤参考代码
/**
* 1.实体类,用于承载自定义标签中的信息
*/
public class User {
private String username;
private String email;
private String password;
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;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
/**
* 自定义标签分析器。
* 不继承AbstractPropertyLoadingBeanDefinitionParser是因为,我们标签中暂时不需要location,properties-ref等等属性。
*/
public class UserBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
/**
* 返回属性值所对应的对象
*
* @param element the {@code Element} that is being parsed
* @return
*/
@Override
protected Class<?> getBeanClass(Element element) {
return User.class;
}
/**
* 标签解析方法。负责解析标签的自定义属性。
*
* @param element the XML element being parsed
* @param builder used to define the {@code BeanDefinition}
*/
@Override
protected void doParse(Element element, BeanDefinitionBuilder builder) {
String userName = element.getAttribute("userName");
String email = element.getAttribute("email");
String password = element.getAttribute("password");
if (StringUtils.hasText(userName)) {
builder.addPropertyValue("username", userName);
}
if (StringUtils.hasText(email)) {
builder.addPropertyValue("email", email);
}
if (StringUtils.hasText(password)) {
builder.addPropertyValue("password", password);
}
}
}
/**
* 3.自定义命名空间处理类。参考ContextNamespaceHandler
*/
public class UserNamespaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
registerBeanDefinitionParser("user",new UserBeanDefinitionParser());
}
}
在项目配置文件目录(resources/META-INF/)中,新增文件spring.handlers,加入处理类的映射。
http\://www.test.com/schema/user=com.test.selftag.UserNamespaceHandler
在项目配置文件目录(resources/META-INF/)中,新增文件spring.schemas,加入命名空间和xsd的映射。
http\://www.test.com/schema/user.xsd=META-INF/user.xsd
在项目配置文件目录(resources/META-INF/)中,新增文件user.xsd。
<schema xmlns="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.test.com/schema/user"
xmlns:tns="http://www.test.com/schema/user"
elementFormDefault="qualified">
<element name="user">
<complexType>
<attribute name ="id" type = "string"/>
<attribute name ="userName" type = "string"/>
<attribute name ="email" type = "string"/>
<attribute name ="password" type="string"/>
complexType>
element>
schema>
在Spring配置文件中,使用我们的自定义标签。
applicationContext.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:test="http://www.test.com/schema/user"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.test.com/schema/user http://www.test.com/schema/user.xsd">
<test:user id="testTag" username="lisi" email="[email protected]" password="123456">test:user>
<bean id="person" class="com.test.Person" scope="prototype">
<property name="id" value="1">property>
<property name="name" value="zhangsan">property>
bean>
beans>
写一个容器启动测试类。测试刚才的自定义标签。
public class Test {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
User user = (User) ac.getBean("testTag");
System.out.println(user);
}
}
问题排查。
如果报错assert short name !=key,可能是docs.gradle中的这段代码引起的,把它注释掉就行了。
task schemaZip(type: Zip) {
group = "Distribution"
archiveBaseName.set("spring-framework")
archiveClassifier.set("schema")
description = "Builds -${archiveClassifier} archive containing all " +
"XSDs for deployment at https://springframework.org/schema."
duplicatesStrategy DuplicatesStrategy.EXCLUDE
moduleProjects.each { module ->
def Properties schemas = new Properties();
module.sourceSets.main.resources.find {
(it.path.endsWith("META-INF/spring.schemas") || it.path.endsWith("META-INF\\spring.schemas"))
}?.withInputStream { schemas.load(it) }
//把下面的代码注释。
// for (def key : schemas.keySet()) {
// def shortName = key.replaceAll(/http.*schema.(.*).spring-.*/, '$1')
// assert shortName != key
// File xsdFile = module.sourceSets.main.resources.find {
// (it.path.endsWith(schemas.get(key)) || it.path.endsWith(schemas.get(key).replaceAll('\\/','\\\\')))
// }
// assert xsdFile != null
// into (shortName) {
// from xsdFile.path
// }
// }
}
}
暂无。工作中很少用到。