这个扩展篇,是基于我的深入理解Spring Ioc 系列写的,主要讲的是spring 装载解析bean这个过程中可以扩展的地方,可能你之前知道一些Spring 中的扩展点,但是却又缺乏一个整体的认识,那么相信我,看完了整个扩展篇,你就能把之前的不熟练的姿势运用的很熟练,并且还能学会很多新的姿势。
我们通过之前正篇前两篇文章,知道了Spring Ioc的标签其实只有四个,那就是beans,bean,import,alias,其他的甚至是aop的,以及componentScan的其实都是自定义标签,只不过那是spring自己其他模块扩展的而已,今天在这篇文章里,我会给大家讲清怎样去定义自己xml标签。
在Spring中,我们定义一个自己的标签有如下步骤:
- 自己定义一个XSD文件
- 定义一个和XSD文件所对应的实体类
- 创建实现了BeanDefinitionParser的类(其实更好的做法是继承抽象类AbstractBeanDefinitionParser),去解析我们的自定义标签
- 创建一个继承了NamespaceHandlerSupport的类,去将我们上面创建的类注册到spring容器
- 编写自己的Spring.handlers和Spring.schemas
我们先来看看自定义XSD文件:
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://demo1.example.com/schema1" targetNamespace="http://demo1.example.com/schema1"> <xsd:complexType name="billType"> <xsd:attribute name="name" type="xsd:string"> </xsd:attribute> <xsd:attribute name="age" type="xsd:int"> </xsd:attribute> </xsd:complexType> <xsd:element name="bill" type="billType"> </xsd:element> </xsd:schema>
我们来看看这个xsd文件的东西吧。首先看到xsd:element这块,这里面的属性name就是我们以后标签的名字,type则指向了上面的标签xsd:complexType这里,这个标签里面有两个子标签都是xsd:attribute,一个代表string类型的name,另一个代表int类型的age,意思就是bill这个标签里面有name和age两个属性。再就是要注意最上面的几行,第二行的xmlns:xsd="www.w3.org/2001/XMLSch…"里面这个url你随便写,但是要和第四行的targetNamespace保持一致。
我们再看看第二步实体类:
@Data public class Model { private String name; private Integer age; }
相信这个是不需要我再多解释了。我们直接看第三步和第四步, 第三步创建的类:
public class BillBeanDefinitionParser implements BeanDefinitionParser { private final Class<?> beanClass; public BillBeanDefinitionParser(Class<?> beanClass) { this.beanClass = beanClass; } @Override public BeanDefinition parse(Element element, ParserContext parserContext) { GenericBeanDefinition genericBeanDefinition = new GenericBeanDefinition(); genericBeanDefinition.setBeanClass(beanClass); genericBeanDefinition.setLazyInit(false); genericBeanDefinition.getPropertyValues().add("name", element.getAttribute("name")); genericBeanDefinition.getPropertyValues().add("age", element.getAttribute("age")); parserContext.getRegistry().registerBeanDefinition(beanClass.getName(),genericBeanDefinition); return null; } }
第四步创建的类
public class BillNameSpaceHandler extends NamespaceHandlerSupport { @Override public void init() { registerBeanDefinitionParser("bill",new BillBeanDefinitionParser(Model.class)); } }
相信看过我正文的这里已经不算什么了,然后我们再编写自己的Spring.handlers和Spring.schemas
Spring.Handlers:
http\://demo1.example.com/schema1=com.example.demo.external1.BillNameSpaceHandler
Spring.schemas:
http\://demo1.example.com/schema1/mytag.xsd=META-INF/mytag.xsd
这两个文件都是properties格式的文件,这两个文件和开头的那个xsd都要放在resource目录下的META-INF文件夹下,再注意Spring.Handlers中的key是要和上面xsd中你自己定义的xmlns一致,value一定要指向你自己定义的NameSpaceHandler的全路径,Spring.schemas中key前半部分是自己定义的xmlns,后半部分的mytag.xsd就是你自己xsd的文件名.
然后在application-context.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:billtag="http://demo1.example.com/schema1" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://demo1.example.com/schema1 http://demo1.example.com/schema1/mytag.xsd"> <!-- 又是一个灰常简单的xml~ --> <bean id="aTest" class="com.example.demo.article2.entity.ATest" name="aTest"> </bean> <billtag:bill name="bill.li" age="18"/> <import resource="classpath*:application-context1.xml"></import> </beans>
最后跑个测试代码看看:
public static void main(String[] args) { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath*:application-context.xml"); Model model = (Model)applicationContext.getBean(Model.class.getName()); System.out.println(model.getAge()); System.out.println(model.getName()); }
如果你跑通了这最后一步的代码,那么恭喜你,你已经可以自己定义一个灰常简单的xml标签了,为什么说是灰常简单的呢?因为我们自己定义的标签只有两个属性啊,并且还不能有子标签的,那么如果是想定义复杂的xml标签怎么搞呢?我来给大家教教学习的办法,我们回到我们的application-context.xml中去看第一行的xmlns,对着后边的http://www.springframework.org/schema/beans按ctrl可以点进去,我们在这里面可以找到关于beans标签的定义,剩下的就是照猫画虎去改我们的xsd就好了,然后记得我们自己的BeanDefinitionParser也要修改下就好了~。
然后还没完,我们需要补上正篇从xml到BeanDefinition那篇里面的坑,那里面代码块12说要在自定义标签这篇讲parseCustomElement,以便于让大家理解这里spring为我们做了什么事情,如果你把之前的代码看懂了之后那么这里的代码还是相对简单很多的:
我们先还原一下之前那篇从xml到BeanDefinition代码块12:
代码块1 protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) { // 就是在看标签中有没有"http://www.springframework.org/schema/beans",如果有,就走if 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)) { // 解析默认标签 parseDefaultElement(ele, delegate); } else { // 解析自定义标签(本篇讲的东西,和上面的是一个方法) delegate.parseCustomElement(ele); } } } }else { // 解析自定义标签(本篇来讲的东西,和上面的是一个方法) delegate.parseCustomElement(root); } }
我们直接来看parseCustomElement这个方法.
代码块2 // 先调的这个方法 public BeanDefinition parseCustomElement(Element ele) { return parseCustomElement(ele, null); } //再调的这个 public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) { // 1.获取我们定义的namespace String namespaceUri = getNamespaceURI(ele); // 2。根据 namespaceUri 获取相应的 Handler // readerContext 其实就是XmlReadContext的实例 NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri); // 3.如果handler是null就报异常 if (handler == null) { error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele); return null; } return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd)); }
上面代码中最后获取到的这个handler的实例,就是我们之前定义的继承了NameSpaceHandlerSupport的类,这个类在parse方法中找到了我们定义的BeanDefinitionParser,然后调用了它的parse方法来解析我们的标签。我们再重点看看2处的代码,2中的这个readerContext是个什么呢?这玩意其实是个XmlReaderContext的实例,这个玩意是在之前的第三篇从xml到BeanDefinition那篇的代码块9中第4处调用XmlBeanDefinitionReader中的createReaderContext方法创建的,我们看看这个方法:
代码块3 public XmlReaderContext createReaderContext(Resource resource) { return new XmlReaderContext(resource, this.problemReporter, this.eventListener, this.sourceExtractor, this, getNamespaceHandlerResolver()); }
就只是很简单的一个new而已,我们来看看最后一个参数调用的方法,这个才是我们关注的重点,因为这个获取到的就是我们之前代码块中第二处拿到的那个NameSpaceHandlerResolver,我们通过追这个方法发现这个NameSpaceHandlerResolver其实是个DefaultNamespaceHandlerResolver,我们来看看它的resolve方法,就是代码块2第二处最后调用的resolve方法:
代码块4 public NamespaceHandler resolve(String namespaceUri) { // 获取到所有已经配置好的handlerMapping的映射 Map<String, Object> handlerMappings = getHandlerMappings(); // 根据nameSpace获取到对应的handler,用上面的Test代码debug的话,发现其实就是我们自己定义的Handler的 // 全路径,也就是Spring.Handler里面配的 Object handlerOrClassName = handlerMappings.get(namespaceUri); if (handlerOrClassName == null) { return null; } // 这个else if就是看之前有没有使用过这个handler,如果有,就直接返回了 else if (handlerOrClassName instanceof NamespaceHandler) { return (NamespaceHandler) handlerOrClassName; } // 没有的话,做初始化这个handler的工作 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的init方法? 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); } } }
再接下来的解析,就是我们之前讲的了,至此,自定义xml标签的定义和解析过程已分析完毕。