自定义标签使用
在使用情况下,我们需要为系统提供可配置化支持,简单的做法可以直接基于 Spring 的标准 bean 来配置,但配置较为复杂或者需要更多的丰富的控制的时候,会显得非常的笨拙,一般的做法会用原生态的方式去解析定义好的 xml文件,然后转换为配置对象,这种方式当然可以解决所有的问题,但实现起来非常繁琐,特别是配置非常复杂的时候,解析工作是不个不得不都试的负担,Spring提供了可扩展 Schema 的支持,这是一个非常不错的折中方案,扩展 Spring 自定义标签配置大致需要以下的几个步骤(前提是要把 Spring 的 core包加入到项目中。)
- 创建一个需要扩展的组件。
- 定义一个 XSD 文件描述组件内容。
- 创建一个文件,实现 BeanDefinitionParser接口,用来解析 XSD 文件中定义和组件 定义。
- 创建一个 Handler 文件,扩展自NamespaceHandlerSupport,目的是将组件注册到 Spring 容器中。
- 编写 Spring.handlers 和 Spring.schemas 文件。
现在我们就按照上面的步骤带领着大家一步步的体验自定义标签的过程
1.首先我们创建一个普通POJO,这个 POJO 没有任何特别之处,只是用来接收配置文件。
User.java
@Data public class User { private String userName; private String email; }
2.定义一个 XSD 文件描述组件内容
Spring-test.xsd
<?xml version="1.0" encoding="UTF-8"?> <schema xmlns="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.lexueba.com/schema/user" xmlns:tns="http://www.lexueba.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>
在上面的 XSD 文件描述了一新的 targetNamespace,并在这个空间中定义了一个 name 为 user 的 element,user 有三个属性 id,userName 和 email ,其实 email 的类型为 string 类型,这三个类主要用于验证 Spring 配置文件中自定义格式,XSD 文件是 XML DTD 的替代者,使用 XML Schema语言进行编写,这里对 XSD Schema 不做太多的解释,有兴趣的读者可以参考相关的资料。
package com.spring_101_200.test_111_120.test_115_custom_label; import com.utils.StringUtils; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.xml.AbstractSimpleBeanDefinitionParser; import org.springframework.beans.factory.xml.ParserContext; import org.w3c.dom.Element; public class UserBeanDefinitionParser extends AbstractSimpleBeanDefinitionParser { // 从 Elment 中找到相应的类 protected Class getBeanClass(Element element){ return User.class; } // 从 element 中解析并提取出对应的元素 @Override protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) { String userName = element.getAttribute("userName"); String email = element.getAttribute("email"); //将提取的数据放到 BeanDefinitionBuilder 中,待完成所有的 bean 注册后,统一注册到 beanFactory 中 if(StringUtils.isNotBlank(userName)){ builder.addPropertyValue("userName", userName); } if(StringUtils.isNotBlank(email)){ builder.addPropertyValue("email",email); } } }
4.创建一个 Handler文件,扩展自NamespaceHandlerSupport,目的就是将组件注册到 Spring 容器中。
public class MyNamespaceHandler extends NamespaceHandlerSupport { @Override public void init() { registerBeanDefinitionParser("user", new UserBeanDefinitionParser()); } }
以上的代码很简单,无非是当遇到自定义标签<user:aaa 这样类似以 user 开头的元素,就会把这个元素扔给对应的 UserBeanDefinitionParser 去解析。
5.编写 Spring.handlers 和 Spring.schemas 文件,默认的位置是工程/META-INF/文件夹下,当然,你可以通过 Spring的扩展或者修改源码的方式改变路径。
-
Spring.handlers。
http://www.lexueba.com/schema/user=com.spring_101_200.test_111_120.test_115_custom_label.MyNamespaceHandler
-
Spring.schemas
http://www.lexueba.com/schema/user.xsd=META-INF/Spring-test.xsd
到这里,自定义标签的配置就结束了,而 Spring加载自定义的大致流程就是遇到自定义标签然后就去 Spring.handlers 和 Spring.schemas 中去找对应的 handler和 XSD,默认位置就是/META-INF/下,进而有找到对应的 handler 以及解析元素的 Parser,从而完成了整个自定义元素的解析,也就是说自定义与 Spring 中默认的标准配置不同在于Spring 将自定义标签的工作委托给了用户去实现。
6.创建配置文件,在配置文件中引入对应的命名空间及 XSD 后,便于可以直接使用自定义标签了。
<?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.lexueba.com/schema/user" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.lexueba.com/schema/user http://www.lexueba.com/schema/user.xsd"> <myname:user id="testBean" userName="aaa" email="bbb"></myname:user> </beans>
7.测试
public class Test115 { public static void main(String[] args) { ApplicationContext bf = new ClassPathXmlApplicationContext("spring_101_200/config_111_120/spring115_custom_label/spring115.xml"); User user = (User) bf.getBean("testBean"); System.out.println(JSON.toJSONString(user)); } }
8.结果输出
{“email”:“bbb”,“userName”:“aaa”}
BeanDefinitionParserDelegate.java
public BeanDefinition parseCustomElement(Element ele) { return parseCustomElement(ele, null); }
BeanDefinitionParserDelegate.java
// containingBd 为父类的 BeanDefinition ,对顶层元素的解析应该设置为 null // 其实思路非常的简单,无非是根据对应的Bean获取对应的命名空间,根据命名空间解析对应的处理器,然后根据用户自定义的处理器进行解析, // 可是有些事情说起来简单做起来难,我们先看看如何获取命名空间吧。 public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) { // 获取对应的命名空间 String namespaceUri = getNamespaceURI(ele); // 根据命名空间找到对应的 NamespaceHandler, // 在 readerContext 初始化的时候,其属性 namespaceHandlerResolver 已经被初始化成了DefaultNamespaceHandlerResolver // 的实例了,所以这里调用 resolver 方法其实调用的是 DefaultNamespaceHandlerResolver 类中的方法,我们进入了 // DefaultNamespaceHandlerResolver 的 resolver 方法进行查看 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 进行解析 // 得到了解析器以及要分析的元素后,Spring 就可以将解析工作委托给自定义的解析器去解析,在 Spring 中的代码为 // return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd)); // 以前提到的示例进行分析,此时的 Handler 已经被实例化成为我们自定义的 MyNameSpaceHandler 了,而 MyNamespaceHandler // 已经完成了初始化的工作,但是我们实现自定义的命名空间处理器并没有实现 parse 方法,所以推断,这个方法是父类中实现的 return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd)); }
DefaultNamespaceHandlerResolver.java
@Override 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("NamespaceHandler class [" + className + "] for namespace [" + namespaceUri + "] not found", ex); } catch (LinkageError err) { throw new FatalBeanException("Invalid NamespaceHandler class [" + className + "] for namespace [" + namespaceUri + "]: problem with handler class file or dependent class", err); } } }
DefaultNamespaceHandlerResolver.java
/** * 同我们想象中的一样,借助于工具类PropertiesLoaderUtils对属性handlerMappingsLocation进行了配置文件的读取,handlerMappingsLocation * 被默认初始化为"META-INF/Spring.handlers" */ private Map<String, Object> getHandlerMappings() { // 如果没有被缓存则,开始缓存 if (this.handlerMappings == null) { synchronized (this) { if (this.handlerMappings == null) { try { // this.handlerMappingsLocation 在构造函数中已经被初始化为:META-INF/Spring.handlers 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()); // 将 properties 格式文件合并到 Map 格式的 handlerMapping 中 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; }
NamespaceHandlerSupport.java
@Override public BeanDefinition parse(Element element, ParserContext parserContext) { return findParserForElement(element, parserContext).parse(element, parserContext); }
AbstractBeanDefinitionParser.java
public final BeanDefinition parse(Element element, ParserContext parserContext) { // 虽说是对自定义配置文件的解析,但是,我们可以看到,这个函数中大部分的代码是用来处理解析后的 AbstractBeanDefinition // 转化为 BeanDefinitionHolder 并注册的功能,而真正的去做解析的事情委托给了函数 ParseInternal,正是这名代码调用了我们 // 自定义的解析函数 // 在 parseInternal 中并不是直接调用自定义的 doParse 函数,而是进行了一系列的数据准备,包括对 beanClass,scope ,lazyInit // 等属性的准备 AbstractBeanDefinition definition = parseInternal(element, parserContext); if (definition != null && !parserContext.isNested()) { try { String id = resolveId(element, definition, parserContext); if (!StringUtils.hasText(id)) { parserContext.getReaderContext().error( "Id is required for element '" + parserContext.getDelegate().getLocalName(element) + "' when used as a top-level tag", element); } String[] aliases = null; if (shouldParseNameAsAliases()) { String name = element.getAttribute(NAME_ATTRIBUTE); if (StringUtils.hasLength(name)) { aliases = StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray(name)); } } // 将 AbstractBeanDefinition 转为 BeanDefinitionHolder 并注册 BeanDefinitionHolder holder = new BeanDefinitionHolder(definition, id, aliases); registerBeanDefinition(holder, parserContext.getRegistry()); if (shouldFireEvents()) { // 需要通过通知监听器则进行处理 BeanComponentDefinition componentDefinition = new BeanComponentDefinition(holder); postProcessComponentDefinition(componentDefinition); parserContext.registerComponent(componentDefinition); } } catch (BeanDefinitionStoreException ex) { parserContext.getReaderContext().error(ex.getMessage(), element); return null; } } return definition; }
AbstractSingleBeanDefinitionParser.java
@Override protected final AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) { BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(); String parentName = getParentName(element); if (parentName != null) { builder.getRawBeanDefinition().setParentName(parentName); } Class<?> beanClass = getBeanClass(element); if (beanClass != null) { builder.getRawBeanDefinition().setBeanClass(beanClass); } else { String beanClassName = getBeanClassName(element); if (beanClassName != null) { builder.getRawBeanDefinition().setBeanClassName(beanClassName); } } builder.getRawBeanDefinition().setSource(parserContext.extractSource(element)); if (parserContext.isNested()) { // Inner bean definition must receive same scope as containing bean. builder.setScope(parserContext.getContainingBeanDefinition().getScope()); } if (parserContext.isDefaultLazyInit()) { // Default-lazy-init applies to custom bean definitions as well. builder.setLazyInit(true); } doParse(element, parserContext, builder); return builder.getBeanDefinition(); }
到这里我们终于完成了自定义标签的解析。下面我们来总结一下。
- Spring 解析Spring.handlers文件获取 handler 和我们代码的对应关系
- 在 handler 中的 init 方法添加标签和解析器的对应关系。
- Spring 根据自定义标签获取解析器。
- 调用解析器的 getBeanClass 方法和 doParse 完成 bean 的解析。
到这里我们终于完成了自定义标签的解析,其实不难,主要还是需要用户自己多调试代码。
本文的 github 地址是
https://github.com/quyixiao/spring_tiny/blob/master/src/main/java/com/spring_101_200/test_111_120/test_115_custom_label