Spring源码解析-自定义标签的解析
2017-08-15 16:32
465 查看
首先来讲一下这个自定义标签怎么用:
②定义一个XSD结构描述文件,命名为Spring-test.xsd
③创建一个类,实现BeanDefinitionPaser接口,用来解析我们自己定义的标签
④创建一个继承了NamespaceHandlerSupport的类,用来将之前的解析器注册到spring容器中
⑤在工程文件的META-INF下添加两个文件,如果没有META-INF就自己创建,创建一个spring.hadlers
创建一个spring.schemas
记住这两个文件名最好要小写,因为这样idea这个编译环境能识别这两个文件,然后可以检查出文件内容的错误。
⑥接着就是配置spring的配置文件
⑦测试
接着来看对自定义标签的解析过程
当然在解析spring配置文件时,SAX会通过我们在spring.schemas中定义的约束文件的位置,对标签进行校验,接下来来看一下自定义标签的解析。进入parseCustomElement方法。
我们来看一下resolve这个方法
这个是接口方法,所以需要找到实体类,在readerContext初始化时这个NamespaceHandlerResoler已经被初始化为DefaultNamespaceHandlerResolver,然后我们进入这个类的resolve方法。
接着我们来看之前的handler.parse方法
很明显这个返回的解析器是我们之前注册进去的解析器,现在将这解析器取出来执行parse方法,我们定义的解析器没有parse方法,所以肯定在父类或者祖父类中,最后在AbstractBeanDefinitionParser中找到parse方法
我们进入这个方法查看,这是个模板方法,模板方法由子类实现,我们要在AbstractSingleBeanDefinitionParser寻找
继续看原来的方法
②调用handler的parse方法,先找到对应的解析器,然后对元素进行解析,先解析class等一些属性,然后调用我们自己的doParse方法,最后返回承载了各种属性的beandefinition,然后得到元素的id作为beanName,然后将beandefinition,beanName,别名集合装配到beanDefinitionHolder中
③最后将beanDefinitionHolder注册到容器中,正确说是将beanDefinitionHolder中的beandefinition注册到容器中,当然还有别名和beanName之间的映射。这和之前讲过的默认标签的bean注册的过程是一样的。
应用
①创建一个需要拓展的组件,这里我们创建一个pojo类public class User { private String userName; private String email; public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } }
②定义一个XSD结构描述文件,命名为Spring-test.xsd
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <schema xmlns="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.corefuture.cn/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>
③创建一个类,实现BeanDefinitionPaser接口,用来解析我们自己定义的标签
package parser; import bean.User; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser; import org.springframework.util.StringUtils; import org.w3c.dom.Element; /** * Created by WHZ on 2017/8/15 0015. */ public class UserBeanDefinitionPaser extends AbstractSingleBeanDefinitionParser{ @Override protected Class getBeanClass(Element element){ return User.class; } @Override protected void doParse(Element element, BeanDefinitionBuilder builder) { //获取属性userName String userName = element.getAttribute("userName"); //获取属性email String email = element.getAttribute("email"); //添加到BeanDefinitionBuilder if(StringUtils.hasText(userName)){ builder.addPropertyValue("userName",userName); } if(StringUtils.hasText(email)){ builder.addPropertyValue("email",email); } } }
④创建一个继承了NamespaceHandlerSupport的类,用来将之前的解析器注册到spring容器中
package handler; import org.springframework.beans.factory.xml.NamespaceHandlerSupport; import parser.UserBeanDefinitionPaser; /** * Created by Administrator on 2017/8/15 0015. */ public class MyNamespaceHandler extends NamespaceHandlerSupport { public void init() { registerBeanDefinitionParser("user",new UserBeanDefinitionPaser()); } }
⑤在工程文件的META-INF下添加两个文件,如果没有META-INF就自己创建,创建一个spring.hadlers
http\://www.corefuture.cn/schema/user=handler.MyNamespaceHandler
创建一个spring.schemas
http\://www.corefuture.cn/schema/user.xsd=META-INF/Spring-test.xsd
记住这两个文件名最好要小写,因为这样idea这个编译环境能识别这两个文件,然后可以检查出文件内容的错误。
⑥接着就是配置spring的配置文件
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:task="http://www.springframework.org/schema/task" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springfr f0f6 amework.org/schema/tx" xmlns:myname="http://www.corefuture.cn/schema/user" 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.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.corefuture.cn/schema/user http://www.corefuture.cn/schema/user.xsd"> <myname:user id="aa" userName="aaaa" email="aaaa"/> </beans>
⑦测试
package client; import bean.User; import org.junit.Test; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.xml.XmlBeanFactory; import org.springframework.core.io.ClassPathResource; /** * Created by Administrator on 2017/8/14 0014. */ public class BeanFactoryTest { @Test public void testSimpleLoad(){ BeanFactory bf =new XmlBeanFactory(new ClassPathResource(("beanFactoryTest.xml"))); User myTestBean = (User)bf.getBean("aa"); System.out.println(myTestBean.getEmail()); } } 结果 aaaa
接着来看对自定义标签的解析过程
解析过程
回到之前的代码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)) { //解析默认 this.parseDefaultElement(ele, delegate); } else { //解析自定义标签 delegate.parseCustomElement(ele); } } } } else { delegate.parseCustomElement(root); } }
当然在解析spring配置文件时,SAX会通过我们在spring.schemas中定义的约束文件的位置,对标签进行校验,接下来来看一下自定义标签的解析。进入parseCustomElement方法。
public BeanDefinition parseCustomElement(Element ele) { return this.parseCustomElement(ele, (BeanDefinition)null); } public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) { //获取命名空间 String namespaceUri = this.getNamespaceURI(ele); //根据命名空间赵铎对应的NamespaceHandler 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这个方法
这个是接口方法,所以需要找到实体类,在readerContext初始化时这个NamespaceHandlerResoler已经被初始化为DefaultNamespaceHandlerResolver,然后我们进入这个类的resolve方法。
public NamespaceHandler resolve(String namespaceUri) { //获取所有已经配置的handler映射 Map<String, Object> handlerMappings = this.getHandlerMappings(); //根据命名空间找到对应的handler或者类名 Object handlerOrClassName = handlerMappings.get(namespaceUri); if(handlerOrClassName == null) { return null; } else if(handlerOrClassName instanceof NamespaceHandler) { //如果是NamespaceHandler,说明已经调用了init方法,注册自定义的解析器,那么直接返回 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"); } else { //通过反射机制实例化handler NamespaceHandler namespaceHandler = (NamespaceHandler)BeanUtils.instantiateClass(handlerClass); //初始化,就是调用我们自己handler类的方法,将我们自定义的解析器注册到handler中 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); } } }
接着我们来看之前的handler.parse方法
public BeanDefinition parse(Element element, ParserContext parserContext) { //寻找解析器并解析 return this.findParserForElement(element, parserContext).parse(element, parserContext); } private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) { //获取元素名称也就是<myname:user>的user 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; }
很明显这个返回的解析器是我们之前注册进去的解析器,现在将这解析器取出来执行parse方法,我们定义的解析器没有parse方法,所以肯定在父类或者祖父类中,最后在AbstractBeanDefinitionParser中找到parse方法
public final BeanDefinition parse(Element element, ParserContext parserContext) { //对元素进行解析 然后获取beandefiniton AbstractBeanDefinition definition = this.parseInternal(element, parserContext);
我们进入这个方法查看,这是个模板方法,模板方法由子类实现,我们要在AbstractSingleBeanDefinitionParser寻找
protected final AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) { //得到一个genericBeanDefinition BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(); String parentName = this.getParentName(element); if(parentName != null) { builder.getRawBeanDefinition().setParentName(parentName); } //解析调用我们定义的解析器paser的getBeanClass方法 Class<?> beanClass = this.getBeanClass(element); if(beanClass != null) { //设置class builder.getRawBeanDefinition().setBeanClass(beanClass); } else { String beanClassName = this.getBeanClassName(element); if(beanClassName != null) { builder.getRawBeanDefinition().setBeanClassName(beanClassName); } } builder.getRawBeanDefinition().setSource(parserContext.extractSource(element)); if(parserContext.isNested()) { builder.setScope(parserContext.getContainingBeanDefinition().getScope()); } if(parserContext.isDefaultLazyInit()) { builder.setLazyInit(true); } //调用我们的doPase方法 this.doParse(element, parserContext, builder); //将装载完属性的beandefinition返回 return builder.getBeanDefinition(); }
继续看原来的方法
if(definition != null && !parserContext.isNested()) { try { //获取id String id = this.resolveId(element, definition, parserContext); //在这里一定要有id,说明我们的标签一定要有id属性。否则就会报错,也就意味着beanName不能被name所代替 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 = new String[0]; //解析name属性,当然主要还是为了获取别名 String name = element.getAttribute("name"); if(StringUtils.hasLength(name)) { aliases = StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray(name)); } //装配BeanDefinitionHolder BeanDefinitionHolder holder = new BeanDefinitionHolder(definition, id, aliases); //然后注册到spring容器中,当然这个注册和默认标签bean注册是一样的 this.registerBeanDefinition(holder, parserContext.getRegistry()); //如果需要就通知监听器 if(this.shouldFireEvents()) { BeanComponentDefinition componentDefinition = new BeanComponentDefinition(holder); this.postProcessComponentDefinition(componentDefinition); parserContext.registerComponent(componentDefinition); } } catch (BeanDefinitionStoreException var9) { parserContext.getReaderContext().error(var9.getMessage(), element); return null; } } return definition; }
总结
①通过命名空间,从handlermap映射中获取handler,如果取到了handler类,那么直接返回,如果没有,那返回的肯定是全类名,然后通过全类名用反射机制进行实例化,调用这个handler的init方法注册我们定义的解析器,再放回handlermap中。②调用handler的parse方法,先找到对应的解析器,然后对元素进行解析,先解析class等一些属性,然后调用我们自己的doParse方法,最后返回承载了各种属性的beandefinition,然后得到元素的id作为beanName,然后将beandefinition,beanName,别名集合装配到beanDefinitionHolder中
③最后将beanDefinitionHolder注册到容器中,正确说是将beanDefinitionHolder中的beandefinition注册到容器中,当然还有别名和beanName之间的映射。这和之前讲过的默认标签的bean注册的过程是一样的。
相关文章推荐
- Spring源码解析(五)——自定义标签解析
- Spring源码解析之二 ------ 自定义标签的解析和注册(IOC的第一步)
- Spring源码解析笔记3——自定义标签的解析
- [置顶] Spring 源码解析 ---- 自定义标签
- spring源码解析-IOC原理
- Spring事务源码解析之事务执行篇
- Spring源码解析-容器功能扩展
- springIOC源码解析(一)
- Spring提取@Transactional事务注解的源码解析
- spring源码(2)之解析配置文件原理
- 解析Spring源码(1)--ClassPathResource("xxx.xml");
- Spring-cloud & Netflix 源码解析:Eureka 服务注册发现接口 ****
- Spring源码解析之零 ------ 容器初始化过程(refresh()方法)概要
- sharding-jdbc源码解析之spring集成分片构造实现
- (Spring源码解析)一步一步分析,springMVC项目启动过程(二)
- 关于Spring 的Classpath: 和 Classpath*: 源码解析
- Spring对注解(Annotation)处理源码分析2——解析和注入注解配置的资源
- Spring 源码解析错误来源
- spring boot 源码解析37-CounterService详解
- spring 代理对象方法增强源码解析222222