在上一篇文章https://www.cnblogs.com/redwinter/p/16165274.htmlSpring BeanFactory
的创建过程中了解了BeanDefinition
的加载和BeanFactory
的创建,并且提到了Spring
留了一个扩展点就是用户可以自定义标签进行解析BeanDefinition
。
基于Spring
源码在处理定制的标签时是通过定制的命名空间处理器和xsd
文件进行解析的,在spring
的classpath
下的META-INF/spring.schemas
和META-INF/spring.handlers
,并且需要将标签的解析器注册到BeanDefinition
的解析器中,这样说起来比较抽象,接下来我们自己定义一个标签就明了了。
创建一个需要解析的标签的属性,比如在Spring
配置文件中经常看到的<context:component-scan base-package="com.redwinter.test"/>
,这里的component-scan
就是标签属性。
/** * @author <a href="https://www.cnblogs.com/redwinter/">redwinter</a> * @since 1.0 **/ public class Redwinter { private String username; private String email; private String password; public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } }
定义一个Redwinter
类,里面三个属性,当然你可以自己定义你需要的属性,我这里就随便写啦。
定义好标签的属性之后就需要定义一个解析器对这些属性进行解析,定义解析器需要继承AbstractSingleBeanDefinitionParser
,这个类是实现了BeanDefinitionParser
的类,他下面有很多实现类,一般来说我们的Bean都是单例的,那就继承AbstractSingleBeanDefinitionParser
即可。
/** * @author <a href="https://www.cnblogs.com/redwinter/">redwinter</a> * @since 1.0 **/ public class RedwinterBeanDefinitionParser extends AbstractSingleBeanDefinitionParser { @Override protected Class<?> getBeanClass(Element element) { return Redwinter.class; } @Override protected void doParse(Element element, BeanDefinitionBuilder builder) { /** * 自定义解析xml的自定义字段,并做相应的其他处理 */ String username = element.getAttribute("username"); String email = element.getAttribute("email"); String password = element.getAttribute("password"); if (StringUtils.hasText(username)){ builder.addPropertyValue("username",username); } if (StringUtils.hasText(email)){ builder.addPropertyValue("email",email); } if (StringUtils.hasText(password)){ builder.addPropertyValue("password",password); } } }
这个解析器主要是重写了父类的两个方法,一个是getBeanClass
用于返回对应的标签属性类,一个是解析属性doParser
,这里我只是从element
中获取出来然后进行了下判断在加入到属性值中,当然这里你可以自定义自己的逻辑处理。
定义命名空间处理器需要继承NamespaceHandlerSupport,然后重写他的init方法,将解析器注册进去,这个解析器就是上面定义的用来解析标签属性的解析器。
/** * @author <a href="https://www.cnblogs.com/redwinter/">redwinter</a> * @since 1.0 **/ public class RedwinterNameSpaceHandler extends NamespaceHandlerSupport { @Override public void init() { // 这里的属性必须和xsd中指定的属性一致,否则报错 //org.springframework.beans.factory.parsing.BeanDefinitionParsingException: Configuration problem: Cannot locate BeanDefinitionParser for element [dl] registerBeanDefinitionParser("dl",new RedwinterBeanDefinitionParser()); } }
这里需要注意的是,进行注册时需要指定一个elementName
,这个值必须和xml中定义的名称一致,否者的话就会报如下错:
org.springframework.beans.factory.parsing.BeanDefinitionParsingException: Configuration problem: Cannot locate BeanDefinitionParser for element [dl]
我这里定义的元素名称叫dl。
xsd文件就是spring进行xml解析时解析的标签,当然你可以定义dtd文件,不过现在一般都用xsd文件,我这里命名为redwinter.xsd,完整文件如下:
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <xsd:schema xmlns="http://www.redwinter.com/schema/redwinter" xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.redwinter.com/schema/redwinter" elementFormDefault="qualified" attributeFormDefault="unqualified"> <xsd:element name="dl"> <xsd:complexType> <xsd:attribute name="id" type="xsd:string"/> <xsd:attribute name="username" type="xsd:string" use="required"/> <xsd:attribute name="email" type="xsd:string" use="required"/> <xsd:attribute name="password" type="xsd:string" use="required"/> </xsd:complexType> </xsd:element> </xsd:schema>
这里有几个点需要注意:schema
标签下有个targetNamespace
,这里指定了命名空间叫http://www.redwinter.com/schema/redwinter ,那么在进行spring
配置文件的时候引入的namespace
就是这个,然后有个name="dl"
,这里的这个dl
就是处理器中定义的元素名称,而且必须一致,不然解析不到,下面定义的属性就是标签属性类中定义的刷新,这个id
是表示唯一的Bean
名称。
这里直接列出完整文件内容:
spring.schemas
文件http\://www.redwinter.com/schema/redwinter.xsd=META-INF/redwinter.xsd
这里需要注意的是,这里配置的key
也是需要在spring
配置文件中引入的,value
就是上一步定义的xsd
文件所在路径
spring.handlers
文件http\://www.redwinter.com/schema/redwinter=com.redwinter.test.RedwinterNameSpaceHandler
这里配置的key
就是上一步定义的xsd
文件中定义的targetNamespace
,value
就是定义的命名空间处理器。
到这里自定义标签和解析就完成了,最后就需要在spring配置文件中引入并配置。
我这里定义个角spring-test.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:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" xmlns:redwinter="http://www.redwinter.com/schema/redwinter" xsi:schemaLocation="http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans-3.1.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.redwinter.com/schema/redwinter http://www.redwinter.com/schema/redwinter.xsd "> <!--自定义标签--> <redwinter:dl id ="redwinter" email="abc@qq.com" password="123456" username="冬玲记忆"/> <redwinter:dl id ="redwinter123456" email="123456-abc@qq.com" password="123456" username="冬玲记忆"/> </beans>
验证是否配置正确:
public class BeanCreate { @Test public void classPathXml() { // ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring-test.xml"); ClassPathXmlApplicationContext context = new MyClassPathXmlApplicationContext("classpath:spring-test.xml"); Redwinter redwinter = (Redwinter) context.getBean("redwinter"); System.out.println(redwinter.getEmail()); Redwinter redwinter123456 = (Redwinter) context.getBean("redwinter123456"); System.out.println(redwinter123456.getEmail()); } }
输出:
abc@qq.com 123456-abc@qq.com
那说明自定义标签生效了,并且成功解析出来。
接下来就是继续介绍Spring
容器的实现AbstractApplicationContex#refresh
的第三个方法,这个方法其实就是BeanFactory
使用的前戏准备,而第一个方法是BeanFactory
刷新的前戏准备。