Spring实现自定义Bean的扩展

一、关于自定义Bean

Spring在解析xml文件中的元素的时候会区分元素的命名空间是否Spring的命名空间,如果不是则会按照自定义的逻辑解析当前的元素。

二、步骤描述

Spring XML配置扩展是基于 XML Schema的,实现自定义Bean需要进行以下步骤:
1.创建XML schema来描述自定义元素(创建XSD文件)
2.实现NamespaceHandler 接口
3.实现BeanDefinitionParser接口来描述Bean解析规则。
4.注册 Handler 和 XML schema

三、案例展示

项目结构.png

譬如有一个类,这个类只有一个属性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 - 元素

你可能感兴趣的:(Spring实现自定义Bean的扩展)