spring源码-自定义标签-4
一、自定义标签,自定义标签在使用上面相对来说非常常见了,这个也算是spring对于容器的拓展。通过自定义标签的方式可以创造出很多新的配置方式,并且交给容器直接管理,不需要人工太多的关注。这也是spring对于配置拓展的一个很重要的方式。
二、自定义标签的几个步骤:1、创建可扫描的标签和对应的解析类 2、读取页面元素解析 3、加入容器管理
三、涉及到的常用类:BeanDefinitionParser、NamespaceHandlerSupport;文件:spring.handlers、spring.schemas、*.xsd
四、实现过程:
1)需要实现的类:
pojo:
public class User{ private String id; private String name; private String age; public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getAge() { return age; } public void setAge(String age) { this.age = age; } }
UserBeanDefinitionParser:
public class UserBeanDefinitionParser implements BeanDefinitionParser { //解析xml public BeanDefinition parse(Element element, ParserContext parserContext) { //通过读取xml的元素来实现具体配置 String id = element.getAttribute("id"); String name = element.getAttribute("name"); String age = element.getAttribute("age"); //注册到spring容器 BeanDefinitionRegistry registry = parserContext.getRegistry(); BeanDefinition beanDefinition = null; try { //这里的RootBeanDefinition为我们常用的注册形式 beanDefinition = new RootBeanDefinition(User.class); beanDefinition.getPropertyValues().add("id", id); beanDefinition.getPropertyValues().add("name", name); beanDefinition.getPropertyValues().add("age", age); //注册 registry.registerBeanDefinition(id, beanDefinition); } catch (Exception e) { e.printStackTrace(); } return beanDefinition; } }
UserNameSpaceHandler:
//用于提供namespace的扫描 public class UserNameSpaceHandler extends NamespaceHandlerSupport { //初始化 public void init() { //注册解析手段 registerBeanDefinitionParser("user",new UserBeanDefinitionParser()); } }
2)因为来了,这个没有啥关联啊。那么久需要用到具体的配置来做关联(spring.handlers、spring.schemas、*.xsd)
备注:这个自己默认配置在resources里面就可以。目录必须为META-INF,默认两个文件:spring.handlers、spring.schemas。标签:*.xsd
spring.handlers:默认处理的NamespaceHandlerSupport
http\://www.pinnet.com/schema/user=com.pinnet.customLabel.UserNameSpaceHandler
spring.schemas:用于使用的标签格式以及验证
http\://www.pinnet.com/schema/user.xsd=META-INF/user.xsd
user.xsd:这里写了一个简单的例子(标签的编写这里不做介绍,可以自己查询schema的官网)
<?xml version="1.0" encoding="UTF-8"?> <xsd:schema xmlns="http://www.pinnet.com/schema/user" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:beans="http://www.springframework.org/schema/beans" targetNamespace="http://www.pinnet.com/schema/user" elementFormDefault="qualified"> <xsd:import namespace="http://www.springframework.org/schema/beans"/> <xsd:element name="user"> <xsd:complexType> <xsd:complexContent> <xsd:extension base="beans:identifiedType"> <xsd:attribute name="name" type="xsd:string"/> <xsd:attribute name="age" type="xsd:string"/> </xsd:extension> </xsd:complexContent> </xsd:complexType> </xsd:element> </xsd:schema>
3)好了上面的基本准备工作ok了。具体就是容器做的处理了,我们来关注源码的实现部分。
spring-bean.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:pinnet="http://www.pinnet.com/schema/user" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.pinnet.com/schema/user http://www.pinnet.com/schema/user.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <pinnet:user id="user1" name="name1" age="age1"/> <pinnet:user id="user2" name="name2" age="age2"/> <pinnet:user id="user3" name="name3" age="age3"/> </beans>
注意:有下划线的部分,这里就是引用过后,然后需要进行的配置。xmlns:pinnet中的pinnet可以自己随便取
测试:
public class Test { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml"); User user1 = (User) context.getBean("user1"); User user2 = (User) context.getBean("user2"); User user3 = (User) context.getBean("user3"); System.out.println(user1); System.out.println(user2); System.out.println(user3); } }
4)好了重点来了,源码部分。
这里从自定义标签开始讲起:其他部分可以参考:spring源码-bean之初始化-1到7)部分
parseCustomElement:
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) { //判断根节点是否是默认的(也就是spring提供的beans) 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)) { this.parseDefaultElement(ele, delegate); } else { //这里就是自定义的配置方式 delegate.parseCustomElement(ele); } } } } else { //如果根节点不是自定义的那就自己自定义处理 delegate.parseCustomElement(root); } }
public BeanDefinition parseCustomElement(Element ele) { //解析配置 return this.parseCustomElement(ele, (BeanDefinition)null); } public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) { //获取Namespace的uri String namespaceUri = this.getNamespaceURI(ele); //然后获取NamespaceHandler的实现类NamespaceHandlerSupport的对应实现 NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri); if (handler == null) { this.error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele); return null; } else { //处理、解析 return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd)); } }
resolve:
public NamespaceHandler resolve(String namespaceUri) { //获取所有NamespaceHandler的NamespaceHandlerSupport实现类(过程不详解了) Map<String, Object> handlerMappings = this.getHandlerMappings(); //获取具体的NamespaceHandlerSupport实现类 Object handlerOrClassName = handlerMappings.get(namespaceUri); if (handlerOrClassName == null) { return null; } else if (handlerOrClassName instanceof NamespaceHandler) { return (NamespaceHandler)handlerOrClassName; } else { //我们默认第一次通过spring.handlers,一般都是String类型的 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"); } else { //获取提前加入容器的NamespaceHandler NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass); //调用初始化这里的初始化查看前面的调用 namespaceHandler.init(); //加入缓存 handlerMappings.put(namespaceUri, namespaceHandler); return namespaceHandler; } } catch (ClassNotFoundException var7) { throw new FatalBeanException("NamespaceHandler class [" + className + "] for namespace [" + namespaceUri + "] not found", var7); } catch (LinkageError var8) { throw new FatalBeanException("Invalid NamespaceHandler class [" + className + "] for namespace [" + namespaceUri + "]: problem with handler class file or dependent class", var8); } } }
parse:
public BeanDefinition parse(Element element, ParserContext parserContext) { //发现解析的BeanDefinitionParser,并调用实现类的parse方法 return this.findParserForElement(element, parserContext).parse(element, parserContext); } private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) { //获取本地的localName,在NamespaceHandlerSupport中进行了init,所以会直接获取到UserNameSpaceHandler String localName = parserContext.getDelegate().getLocalName(element); BeanDefinitionParser parser = (BeanDefinitionParser)this.parsers.get(localName); if (parser == null) { parserContext.getReaderContext().fatal("Cannot locate BeanDefinitionParser for element [" + localName + "]", element); } return parser; }
五:上面基本的实现过程就是这样子了,包含了源码的实现逻辑。这里需要关注的点在于匹配的过程。其他都是自己手动完成的。
六、用处:相对于我们人的解析过程,自定义标签的解析的过程并不是很复杂,更多需要自己手动去完成解析。自定义标签的好处在于,可以很大程度的减少代码冗余的情况。通过同一套流程,开发者只需要关注配置就可以了,而不需要关注具体的解析过程和实现逻辑。另外可以更加方便的与spring容器进行深度的整合!