一、前言
Spring 为基于 XML 构建的应用提供了一种扩展机制,用于定义和配置 Bean。 它允许使用者编写自定义的 XML bean 解析器,并将解析器本身以及最终定义的 Bean 集成到 Spring IOC 容器中。
二、自定义 XML Schema 扩展
为了搞懂 Spring 的 XML 扩展机制,最直接的方式便是实现一个自定义的扩展。实现的步骤也为四步:
- 编写一个 XML schema 文件描述的你节点元素。
- 编写一个 NamespaceHandler 的实现类
- 编写一个或者多个 BeanDefinitionParser 的实现 (关键步骤).
- 注册上述的 schema 和 handler
1. 编写 resources/META-INF/Car.xsd
1 <?xml version="1.0" encoding="UTF-8"?> 2 <xsd:schema xmlns="http://www.mycompany.com/schema/my" 3 xmlns:xsd="http://www.w3.org/2001/XMLSchema" 4 xmlns:beans="http://www.springframework.org/schema/beans" 5 targetNamespace="http://www.mycompany.com/schema/my1" 6 elementFormDefault="qualified" 7 attributeFormDefault="unqualified"> 8 9 <xsd:import namespace="http://www.springframework.org/schema/beans"/> 10 11 <xsd:element name="car"> 12 <xsd:complexType> 13 <xsd:complexContent> 14 <xsd:extension base="beans:identifiedType"> 15 <xsd:attribute name="brand" type="xsd:string" use="required"/> 16 <xsd:attribute name="engine" type="xsd:float"/> 17 <xsd:attribute name="horsePower" type="xsd:int"/> 18 </xsd:extension> 19 </xsd:complexContent> 20 </xsd:complexType> 21 </xsd:element> 22 </xsd:schema>
这里,targetNamespace对Car标签很重要,比如说注册一个bean
1 <my1:car id="magic" brand="Magic" engine="4.5" horsePower="605" />
2. 编写 CarNamespaceHandler
1 public class CarNamespaceHandler extends NamespaceHandlerSupport { 2 3 @Override 4 public void init() { 5 //遇到car元素的时候交给CarBeanDefinitionParser来解析 6 registerBeanDefinitionParser("car", new CarBeanDefinitionParser()); 7 8 } 9 10 }
编写的NamespaceHandler 来帮助 Spring 解析 XML 中不同命名空间的各类元素。不同的命名空间需要不同的 NamespaceHandler 来处理。使用 CarNamespaceHandler 来解析 car 的命名空间。
1 public interface NamespaceHandler { 2 void init(); 3 BeanDefinition parse(Element element, ParserContext parserContext); 4 BeanDefinitionHolder decorate(Node source, BeanDefinitionHolder definition, ParserContext parserContext); 5 }
CarNamespaceHandler 继承 NamespaceHandlerSupport 抽象类,NamespaceHandlerSupport 抽象类实现了 NamespaceHandler 接口,并实现了parse()和decorate()方法,在CarNamespaceHandler 类中实现 NamespaceHandler 接口的init()方法,注册 BeanDefinitionParser 来完成解析节点以及注册 Bean 的工作。
3. 编写 CarBeanDefinitionParser
BeanDefinitionParser 是最为关键的一环。每一个 BeanDefinitionParser 实现类都负责一个映射,将一个 XML 节点解析成 IOC 容器中的一个实体类。
1 public class CarBeanDefinitionParser extends AbstractSingleBeanDefinitionParser { 2 3 @Override 4 protected Class<?> getBeanClass(Element element) { 5 //car元素对应Car对象类型 6 return Car.class; 7 } 8 9 @Override 10 protected void doParse(Element element, BeanDefinitionBuilder builder) { 11 12 String brand = element.getAttribute("brand"); 13 String engine = element.getAttribute("engine"); 14 String hp = element.getAttribute("horsePower"); 15 16 //把对应的属性设置到bean中 17 if(StringUtils.hasText(brand)) 18 builder.addPropertyValue("brand", brand); 19 20 if(StringUtils.hasText(engine)) 21 builder.addPropertyValue("engine", engine); 22 23 if(StringUtils.hasText(hp)) 24 builder.addPropertyValue("horsePower", hp); 25 26 } 27 }
parse() 方法会解析一个个 XML 中的元素,使用 RootBeanDefinition 组装成对象,并最终通过 parserContext 注册到 IOC 容器中。
至此,我们便完成了 XML 文件中定义的对象到 IOC 容器的映射。
4. 注册 schema 和 handler
最后一步还需要通知 Spring,告知其自定义 schema 的所在之处以及对应的处理器。
resources/META-INF/spring.handlers
1 http://www.mycompany.com/schema/my1=spring.xml.ext.schema.CarNamespaceHandler
resources/META-INF/spring.schemas
1 http://www.mycompany.com/schema/my1.xsd=META-INF/car.xsd
一个自定义的 XML schema 便扩展完成了,接下来验证一下自定义Schema扩展。
三、验证扩展
定义好Spring的bean配置文件
1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xmlns:my1="http://www.mycompany.com/schema/my1" 5 xsi:schemaLocation=" 6 http://www.springframework.org/schema/beans 7 http://www.springframework.org/schema/beans/spring-beans.xsd 8 http://www.mycompany.com/schema/my1 9 http://www.mycompany.com/schema/my1.xsd"> 10 11 <my1:car id="magic" brand="Magic" engine="4.5" horsePower="605" /> 12 13 </beans>
编写测试方法
1 @RunWith(SpringJUnit4ClassRunner.class) 2 @ContextConfiguration(locations = { "classpath:app.xml" }) 3 public class SchemaTest { 4 5 @Autowired 6 @Qualifier("magic") 7 private Car car; 8 9 @Test 10 public void propertyTest() { 11 assertNotNull(car); 12 13 String brand = car.getBrand(); 14 float engine = car.getEngine(); 15 int horsePower = car.getHorsePower(); 16 17 System.out.println("=============================="); 18 assertEquals("Brand incorrect.Should be Magic.", "Magic", brand); 19 assertEquals("Engine incorrect.Should be 4.5L.", 4.5, engine, 0.000001); 20 assertEquals("HorsePower incorrect.Should be 605hp.", 605, horsePower); 21 22 } 23 }
控制台输出无异常,断言成功。