ningendo

策略模式之Spring 解析xml配置文件分析

一.介紹

  策略模式的作用,主要是把一段业务抽象出一个接口,为上层服务调用。派生出不同的类来实现不同的算法和逻辑。然后就可以根据参数的传入和配置文件,来切换不同的策略功能。

       好处,拓展性强,新加一个功能,添加一个类即可,且不同类之间,关联小,耦合度松,符合开闭原则。

  缺点,功能和多时,需要的类也十分多,造成类的数量的膨胀。

  比如在Spring里解析bean.xml文件,需要解析context:component-scan字段,来完成扫描包功能,解析context:property-placeholder字段来解析properties文件。

解析context:property-override 来把配置文件的属性注入等功能。Spring framwork源码里便是使用了策略模式来实现该功能。

 

二.图解分析

  

 

 

 

 Spring 通过BeanDefinitionParser接口里的parse方法,来统一xml 元素 的解析的调用。NamespaceHandlderSupport中使用一个hashmap类存储不同BeanDefinitionParser接口的具体实现类的对象。通过registerBeanDefinitionParser 方法类注册具体实现类,也就是策略类。

通过findParseForElement方法来找到具体的实现类,然后调用。其中key是xml Element的属性名。

在spring源码中parse解析完后,是要返回一个BeanDefinition对象的,在下面展示的代码中,为了模拟spring原理,做了很多化简,就改成了void。

 

 

 

                                                                            

 

 

其中ComponentScanBeanDefinitionParser负责扫描包和解析包工作,PropertyPlaceholderBeanDefinitionParser负责解析properties文件工作,依次类推。

具体参考官方的bean.xml下面 context名空间下各个属性的说明,

ContextNamespaceHandler 从类名就可以知道 ,改类是负责提供解析context名空间下的属性,来提供策略服务的类。

 

三.bean.xml 主要是context名空间

 

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:context="http://www.springframework.org/schema/context">

    <context:component-scan base-package="com.ninjutsu.beans" />
    <context:property-placeholder location="classpath:foo.properties" />
    <context:property-override location="classpath:beanOverride.cfg"/>
    <context:annotation-config/>
    <context:spring-configured/>
    <context:mbean-server mbeanServer="www.baidu.com" agent-id="123"/>
    <context:load-time-weaver weaver-class="org.springframework.instrument.classloading.SimpleLoadTimeWeaver"/>
    <context:mbean-export server="www.cctv.cn" registration="replaceExisting"/>
</beans>

 

 

四.代码解析

BeanDefinitionParser.java

 

public interface BeanDefinitionParser {

    void parse(Element element, ParserContext parserContext);
}

 

NamespaceHandler.java

public interface NamespaceHandler {
    void init();
    void parse(Element element, ParserContext parserContext);
}

NamespaceHandlerSupport .java

public class NamespaceHandlerSupport implements NamespaceHandler {

    private final Map<String, BeanDefinitionParser> parsers = new HashMap<>();

    @Override
    public void init() {

    }

    @Override
    public void parse(Element element, ParserContext parserContext) {
        BeanDefinitionParser parser = findParserForElement(element, parserContext);
        if(parser!=null){
            parser.parse(element, parserContext);
        }
    }

    private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
        String localName = parserContext.getDelegate().getLocalName(element);
        BeanDefinitionParser parser = this.parsers.get(localName);
        if (parser == null) {

        }
        return parser;
    }

    protected final void registerBeanDefinitionParser(String elementName, BeanDefinitionParser parser) {
        this.parsers.put(elementName, parser);
    }
}
ContextNamespaceHandler.java
public class ContextNamespaceHandler extends NamespaceHandlerSupport{
    @Override
    public void init() {
        registerBeanDefinitionParser("property-placeholder", new PropertyPlaceholderBeanDefinitionParser());
        registerBeanDefinitionParser("property-override", new PropertyOverrideBeanDefinitionParser());
        registerBeanDefinitionParser("annotation-config", new AnnotationConfigBeanDefinitionParser());
        registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser());
        registerBeanDefinitionParser("load-time-weaver", new LoadTimeWeaverBeanDefinitionParser());
        registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
        registerBeanDefinitionParser("mbean-export", new MBeanExportBeanDefinitionParser());
        registerBeanDefinitionParser("mbean-server", new MBeanServerBeanDefinitionParser());
    }
}

 

public class BeanDefinitionParserDelegate {
    public static final String BEANS_NAMESPACE_URI = "http://www.springframework.org/schema/beans";
    public static final String BEAN_ELEMENT = "bean";

    public static final String ID_ATTRIBUTE = "id";
    public static final String NAME_ATTRIBUTE = "name";

    public Logger logger = LoggerFactory.getLogger(DefaultDocumentLoader.class);

    @Nullable
    public String getNamespaceURI(Node node) {
        String url = node.getNamespaceURI();
        return url;
    }

    public boolean isDefaultNamespace(@Nullable String namespaceUri) {

        return (!StringUtils.hasLength(namespaceUri) || BEANS_NAMESPACE_URI.equals(namespaceUri));
    }

    public boolean nodeNameEquals(Node node, String desiredName) {
        return desiredName.equals(node.getNodeName()) || desiredName.equals(getLocalName(node));
    }

    public String getLocalName(Node node) {
        return node.getLocalName();
    }

    public boolean isDefaultNamespace(Node node) {
        return isDefaultNamespace(getNamespaceURI(node));
    }

    public void parseCustomElement(Element ele) {
        //xml解析,需要引入命名空间的解析(命名空间)
        String namespaceUri = getNamespaceURI(ele);
        if (namespaceUri == null) {
            return ;
        }
        //NamespaceHandler 策略模式 NamespaceHandler多态实现策略
        //比如分析 <context:component-scan base-package="****"/> 的原理,
        //应该到 ContextNamespaceHandler 中找
        NamespaceHandler handler = new ContextNamespaceHandler();
        if (handler == null) {
            logger.error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]   "+ele);
            return ;
        }
        handler.init();
        handler.parse(ele, new ParserContext(this));
    }

    public void parseBeanDefinitionElement(Element ele){
        String id = ele.getAttribute(ID_ATTRIBUTE);
        String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
        logger.info("id : "+id);
        logger.info("name : "+nameAttr);
    }
}

 

 

 

 五.测试用例

 

public class XmlTest1 {
    @Test
    public void test1() throws ParserConfigurationException, IOException, SAXException {
        InputSource resource = new InputSource(getClass().getResourceAsStream("bean2.xml"));
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        factory.setNamespaceAware(true);

        // 创建一个DocumentBuilder实例
        DocumentBuilder builder = factory.newDocumentBuilder();

        // 创建一个解析XML的Document实例
        Document doc = builder.parse(resource);

        DefaultBeanDefinitionDocumentReader reader = new DefaultBeanDefinitionDocumentReader();
        reader.registerBeanDefinitions(doc,null);

    }
}

 

输出:

[main] INFO com.ninjutsu.xml.parser.ComponentScanBeanDefinitionParser - basePackage : com.ninjutsu.beans
[main] INFO com.ninjutsu.xml.parser.PropertyPlaceholderBeanDefinitionParser - location : classpath:foo.properties
[main] INFO com.ninjutsu.xml.parser.PropertyOverrideBeanDefinitionParser - location : classpath:beanOverride.cfg
[main] INFO com.ninjutsu.xml.parser.AnnotationConfigBeanDefinitionParser - AnnotationConfigBeanDefinitionParser
[main] INFO com.ninjutsu.xml.parser.PropertyPlaceholderBeanDefinitionParser - SpringConfiguredBeanDefinitionParser
[main] INFO com.ninjutsu.xml.parser.MBeanServerBeanDefinitionParser - mbeanServer : www.baidu.com
[main] INFO com.ninjutsu.xml.parser.MBeanServerBeanDefinitionParser - agent-id : 123
[main] INFO com.ninjutsu.xml.parser.MBeanExportBeanDefinitionParser - server : www.cctv.cn

 

六.总结

spring使用策略模式来实现各个名空间下元素和属性的解析,进而实现DI和IOC的过程。从源码可以看出其层次清晰,拓展性强,是代码设计中的模范框架,经久不衰。

代码下载地址

https://github.com/arxobject/spring_analysis

 

posted on 2020-11-22 22:49  Lunamonna  阅读(175)  评论(0编辑  收藏  举报

导航