策略模式之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