配置文件自定义标签
说明
自定义标签的意思是,在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);
}
}
}
步骤
例如:我们自定义一个标签<test:user username=“zhangsan” email=“testEmail” password=“test123456” />
-
定义一个实体类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; } } /** * 自定义标签分析器。<br> * 不继承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,加入处理类的映射。
- 注意:在idea中创建这个文件时,要确保他是properties类型的。不能是txt类型的。
http\://www.test.com/schema/user=com.test.selftag.UserNamespaceHandler
-
在项目配置文件目录(resources/META-INF/)中,新增文件spring.schemas,加入命名空间和xsd的映射。
- 注意:在idea中创建这个文件时,要确保他是properties类型的。不能是txt类型的。
http\://www.test.com/schema/user.xsd=META-INF/user.xsd
-
在项目配置文件目录(resources/META-INF/)中,新增文件user.xsd。
-
<?xml version="1.0" encoding="UTF-8"?> <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
-
<?xml version="1.0" encoding="UTF-8"?> <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="lisi@163.com" 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 // } // } } }
-
应用场景
暂无。工作中很少用到。