一、关于自定义Bean
Spring在解析xml文件中的元素的时候会区分元素的命名空间是否Spring的命名空间,如果不是则会按照自定义的逻辑解析当前的元素。
二、步骤描述
Spring XML配置扩展是基于 XML Schema的,实现自定义Bean需要进行以下步骤:
1.创建XML schema来描述自定义元素(创建XSD文件)
2.实现NamespaceHandler 接口
3.实现BeanDefinitionParser接口来描述Bean解析规则。
4.注册 Handler 和 XML schema
三、案例展示
譬如有一个类,这个类只有一个属性name:
public class GameServer {
private String name;
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "GameServer{" +
"name='" + name + '\'' +
'}';
}
}
如果想在XML中通过自定义标签描述该类,如下:
并让它被Spring管理。这就要通过上面的4个步骤来实现:
1.创建XSD文件来描述自定义元素server
"http://www.game.com/schema/server"
就是自定义的命名空间URI。其中:
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
显示 schema 中用到的元素和数据类型来自命名空间:"http://www.w3.org/2001/XMLSchema"
。
targetNamespace="http://www.game.com/schema/server"
显示被此 schema 定义的元素来自命名空间:"http://www.game.com/schema/server"
。
2.实现NamespaceHandler 接口
此处直接继承了NamespaceHandlerSupport ,NamespaceHandlerSupport 是实现了NamespaceHandler 接口的抽象类。
public class ServerNameSpaceHandler extends NamespaceHandlerSupport {
//DefaultBeanDefinitionDocumentReader会在对自定义元素解析前调用该方法
@Override
public void init() {
registerBeanDefinitionParser("server", new ServerBeanParser());//对自定义元素注册自定义解析器
}
}
3.实现BeanDefinitionParser接口来定义Bean解析规则
public class ServerBeanParser implements BeanDefinitionParser {
@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
RootBeanDefinition mbd = new RootBeanDefinition();
mbd.setBeanClassName(element.getAttribute("class"));
String beanName = element.getAttribute("id");
MutablePropertyValues mutablePropertyValues = new MutablePropertyValues();
mutablePropertyValues.add("name", element.getAttribute("name"));
mbd.setPropertyValues(mutablePropertyValues);
parserContext.getRegistry().registerBeanDefinition(beanName, mbd);//向Spring注册该Bean
return mbd;
}
}
4.注册 Handler 和 XML schema
(1)在META-INF下创建spring.handler文件。该文件是schema的URI和namespace handler全限定类名的映射。
http\://www.game.com/schema/server=com.base.bean.handler.ServerNameSpaceHandler
此处key部分需要和XSD文件中指定的targetNamespace 属性值一致。
(2)在META-INF下创建spring.schema文件。该文件是schemaLocation和schema资源文件类路径的映射。
http\://www.game.com/schema/server.xsd=META-INF/server.xsd
5.补充好完整的xml文件
引入自定义命名空间:
ps:
xmlns="http://www.springframework.org/schema/beans"
表示默认的命名空间使用了Spring,
xsi:schemaLocation中配置了namespace空格schemaLocation,因为一个命名空间可能有多个xsd文件,所以需要指定使用哪个。
6.测试
@Test
public void test(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:game.xml");
System.out.println(applicationContext.getBean("gameServer").toString());
}
四、原理分析
对自定义Bean进行解析涉及到Spring的IOC初始化。
1.Spring IOC初始化粗略步骤:
(1)从给定的导入路径加载Bean定义资源到Spring IoC容器中
(2)按照Spring的Bean规则将Bean定义资源解析并转换为容器内部数据结构
(3)Bean的定义加载完成后,向容器注册bean
对自定义Bean的实现来说,主要是集中在Spring初始化的第2个步骤
在Spring的DefaultBeanDefinitionDocumentReader的registerBeanDefinitions()
方法中(下面的doc参数是已经将xml文件解析的Document对象):
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
doRegisterBeanDefinitions(doc.getDocumentElement());//从根节点开始解析,也就是标签的属性,如default-init-method等
}
在doRegisterBeanDefinitions()
方法中使用BeanDefinitionParserDelegate对具体元素进行解析(BeanDefinitionParserDelegate中定义了Spring Bean定义XML文件的各种元素 ),在创建BeanDefinitionParserDelegate后,先解析根节点填充属性到BeanDefinitionParserDelegate的属性DocumentDefaultsDefinition中,然后调用parseBeanDefinitions(root, this.delegate)
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
if (delegate.isDefaultNamespace(root)) {
NodeList nl = root.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {//循环调用解析子元素(标签下的子元素("import", "alias", "bean"))
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);
}
}
根据XML命名空间配置决定是使用默认Spring的Bean规则解析,还是自定义Bean规则解析。
2.自定义命名空间元素是怎么解析的
public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
String namespaceUri = getNamespaceURI(ele);//1.获取命名空间URI
if (namespaceUri == null) {
return null;
}
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);//2.根据URI获取对应的parser
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));//3.用自定义parser解析,此处会调用到上面自定义的实现BeanDefinitionParser接口的ServerBeanParser 对自定义元素进行解析。
}
上面代码分为了三步:
(1)获取命名空间URI
(2)调用NamespaceHandlerResolver的resolve(String namespaceUri)
下面的handlerMappings 存放了这样的key-value("http://www.game.com/schema/server" -> "com.base.bean.handler.ServerNameSpaceHandler")
public NamespaceHandler resolve(String namespaceUri) {
Map handlerMappings = getHandlerMappings();//此处会进行handlerMappings的初始化,如果是null会去META-INF/spring.handlers下读取进行初始化。
Object handlerOrClassName = handlerMappings.get(namespaceUri);
if (handlerOrClassName == null) {//1.
return null;
}
else if (handlerOrClassName instanceof NamespaceHandler) {//2.已经初始化过,直接返回
return (NamespaceHandler) handlerOrClassName;
}
else {//3.
String className = (String) handlerOrClassName;
try {
Class> handlerClass = ClassUtils.forName(className, this.classLoader);//3.1通过全限定类名反射实例化该handler
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();//3.2调用NamespaceHandler的init()方法
handlerMappings.put(namespaceUri, namespaceHandler);//3.3更新放入初始化的Handler
return namespaceHandler;
}
。。。
}
}
程序初次调用会走到上面的第3步。
(3)调用NamespaceHandlerSupport的parse方法
public BeanDefinition parse(Element element, ParserContext parserContext) {
BeanDefinitionParser parser = findParserForElement(element, parserContext);
return (parser != null ? parser.parse(element, parserContext) : null);
}
private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
String localName = parserContext.getDelegate().getLocalName(element);
BeanDefinitionParser parser = this.parsers.get(localName);//从parsers map里根据元素名获取对应的BeanDefinitionParser
if (parser == null) {
parserContext.getReaderContext().fatal(
"Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
}
return parser;
}
3.回到上面实现自定义Bean的步骤描述2
public class ServerNameSpaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
registerBeanDefinitionParser("server", new ServerBeanParser());
}
}
/**
* Stores the {@link BeanDefinitionParser} implementations keyed by the
* local name of the {@link Element Elements} they handle.
*/
private final Map parsers = new HashMap<>();
protected final void registerBeanDefinitionParser(String elementName, BeanDefinitionParser parser) {
this.parsers.put(elementName, parser);//将server元素和BeanDefinitionParser形成映射
}
参考:
[1]Core Technologies
[2]XSD -