(spring-第7回【IoC基础篇】)BeanDefinition的载入与解析&&spring.schemas、spring.handlers的使用
报错信息:Configuration problem: Unable to locate Spring NamespaceHandler for XML schema namespace [http://www.springframework.org/schema/p],
一桩事故引发的连锁思考。。。开幕——
-------------------------------------------------------------------------------------------------------------------------------------
spring加载XML时,从系统中加载配置信息到把<bean>配置信息解析成BeanDefinition(把xml中<bean>的属性转化成的配置类),然后把BeanDefinition放到注册表中,然后取出,装饰。这个过程就是BeanDefinition的解析过程。那么具体是怎么实现的呢?我想先从一个让人揪心的报错信息开始:
为了方便阅读spring的源码,我把spring的java源码引进我的工程当中(那么之前引入的等价的jar包就要删除),引入后如下图:
一般的XML加载的小测试主要用到的是beans包。和以前一样,我做一个小测试,下面是简单的spring启动三件套(配置文件,bean类,启动类):
配置文件:
1 <?xml version="1.0" encoding="UTF-8" ?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xmlns:p="http://www.springframework.org/schema/p" 5 xsi:schemaLocation="http://www.springframework.org/schema/beans 6 http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"> 7 <bean id="car" class="com.mesopotamia.test1.Car" 8 p:name="汽车" 9 p:brand="宝马" 10 p:maxSpeed="200"/> 11 12 <bean id="car1" 13 p:brand="宝马X5" 14 parent="car" 15 /> 16 </beans>
bean类:
1 package com.mesopotamia.test1; 2 3 import org.apache.commons.logging.Log; 4 import org.apache.commons.logging.LogFactory; 5 6 public class Car { 7 private String name; 8 private String brand; 9 private double maxSpeed; 10 public double getMaxSpeed() { 11 return maxSpeed; 12 } 13 public void setMaxSpeed(double maxSpeed) { 14 this.maxSpeed = maxSpeed; 15 } 16 17 18 private Log log=LogFactory.getLog(Car.class); 19 20 public Car(){ 21 //name="宝马"; 22 log.info("调用了Car的构造函数,实例化了Car.."); 23 } 24 public String getName() { 25 return name; 26 } 27 public void setName(String name) { 28 this.name = name; 29 } 30 public String getBrand() { 31 return brand; 32 } 33 public void setBrand(String brand) { 34 this.brand = brand; 35 } 36 37 38 public String toString(){ 39 return "名字"+name+" 型号"+brand+" 速度:"+maxSpeed; 40 } 41 42 43 }
启动:
1 public static void main(String args[]){ 2 ApplicationContext ctx = new ClassPathXmlApplicationContext("com/mesopotamia/test1/*.xml"); 3 Car car1 = ctx.getBean("car1",Car.class); 4 log.info(car1.toString()); 5 }
路径是正确的,自己的java代码也没有报错,spring源码也没有报错,看来前景很美好。然而,现实总是很骨感,看一下运行结果:
1 2016-12-07 21:21:43,901 INFO [main] (AbstractApplicationContext.java:456) - Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@45a877: startup date [Thu Dec 07 21:21:43 CST 2016]; root of context hierarchy 2 2016-12-07 21:21:44,088 INFO [main] (XmlBeanDefinitionReader.java:315) - Loading XML bean definitions from file [C:\MySoftware\workspace\springtest2\resources\WEB-INF\classes\com\mesopotamia\test1\beans.xml] 3 Exception in thread "main" org.springframework.beans.factory.parsing.BeanDefinitionParsingException: Configuration problem: Unable to locate Spring NamespaceHandler for XML schema namespace [http://www.springframework.org/schema/p] 4 Offending resource: file [C:\MySoftware\workspace\springtest2\resources\WEB-INF\classes\com\mesopotamia\test1\beans.xml] 5 6 at org.springframework.beans.factory.parsing.FailFastProblemReporter.error(FailFastProblemReporter.java:68) 7 at org.springframework.beans.factory.parsing.ReaderContext.error(ReaderContext.java:85) 8 at org.springframework.beans.factory.parsing.ReaderContext.error(ReaderContext.java:80) 9 at org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.error(BeanDefinitionParserDelegate.java:277) 10 at org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.decorateIfRequired(BeanDefinitionParserDelegate.java:1375) 11 at org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.decorateBeanDefinitionIfRequired(BeanDefinitionParserDelegate.java:1351) 12 at org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.decorateBeanDefinitionIfRequired(BeanDefinitionParserDelegate.java:1339) 13 at org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.processBeanDefinition(DefaultBeanDefinitionDocumentReader.java:260) 14 at org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.parseDefaultElement(DefaultBeanDefinitionDocumentReader.java:153) 15 at org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.parseBeanDefinitions(DefaultBeanDefinitionDocumentReader.java:132) 16 at org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.registerBeanDefinitions(DefaultBeanDefinitionDocumentReader.java:93) 17 at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.registerBeanDefinitions(XmlBeanDefinitionReader.java:493) 18 at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.doLoadBeanDefinitions(XmlBeanDefinitionReader.java:390) 19 at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:334) 20 at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:302) 21 at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:143) 22 at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:178) 23 at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:149) 24 at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:212) 25 at org.springframework.context.support.AbstractXmlApplicationContext.loadBeanDefinitions(AbstractXmlApplicationContext.java:126) 26 at org.springframework.context.support.AbstractXmlApplicationContext.loadBeanDefinitions(AbstractXmlApplicationContext.java:92) 27 at org.springframework.context.support.AbstractRefreshableApplicationContext.refreshBeanFactory(AbstractRefreshableApplicationContext.java:130) 28 at org.springframework.context.support.AbstractApplicationContext.obtainFreshBeanFactory(AbstractApplicationContext.java:467) 29 at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:397) 30 at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:139) 31 at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:83) 32 at com.mesopotamia.test1.Main.main(Main.java:13)
暂且先不要焦虑为什么会报错,从下往上看报错信息你会发现,原来跟踪错误提示可以看到整个BeanDefinition的加载过程。从三十行开始向上看:
refresh:开始加载XML
obtainFreshBeanFactory:告诉下面的类,要刷新bean factory了。
refreshBeanFactory:正式开始关闭当前的bean factory,初始化一个崭新的bean factory,开始容器的新生命周期。
loadBeanDefinitions:通过一个XmlBeanDefinitionReader来从XML中加载bean definitions.
doLoadBeanDefinitions:正式开始从XML中加载bean definitions(开始干实事儿啦)
registerBeanDefinitions:注册BeanDefinitions。
parseBeanDefinitions:从根级(at the root level)开始解析XML数据("import", "alias", "bean".等)。
parseDefaultElement:解析默认的元素。
processBeanDefinition:加工BeanDefinition,加工后放到注册表中。
再后面就是取出BeanDefinition进行装饰。在装饰的过程中报错了。
之所以讲清楚每一步,就是让大家有个对过程的认知。
我们来打开调试模式跟踪一下报错(我用的是spring的java源码,当然可以跟踪啦):
下面是跟踪到日志的第10行进入的方法源码(decorateIfRequired):
1 private BeanDefinitionHolder decorateIfRequired( 2 Node node, BeanDefinitionHolder originalDef, BeanDefinition containingBd) { 3 4 String namespaceUri = getNamespaceURI(node); 5 if (!isDefaultNamespace(namespaceUri)) { 6 NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri); 7 if (handler != null) { 8 return handler.decorate(node, originalDef, new ParserContext(this.readerContext, this, containingBd)); 9 } 10 else if (namespaceUri != null && namespaceUri.startsWith("http://www.springframework.org/")) { 11 error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", node); 12 } 13 else { 14 // A custom namespace, not to be handled by Spring - maybe "xml:...". 15 if (logger.isDebugEnabled()) { 16 logger.debug("No Spring NamespaceHandler found for XML schema namespace [" + namespaceUri + "]"); 17 } 18 } 19 } 20 return originalDef; 21 }
下面是调试的截图:
一开始解析几个默认命名空间的时候,总是进入不到"if"内部,循环了三四次,namespaceUri终于不是默认的命名空间了,于是程序开始进入"if"内部,这时我们发现这个命名空间是p规则空间。
继续往下跟踪:
我们发现,handler是null,这个问题导致error()方法的调用,于是第3行的报错日志就被打印出来了。
那么这个handler是何方神圣,为何此刻不灵了?
我们查看API看一下这个NameSpaceHandler的庐山真面目:
看清楚了没,这个handler是加工BeanDefinition的,而且文中说"它的实现者旨在返回一些实现了BeanDefinitionParser"的具体的类。那么也就是说,没找到这个NameSpaceHandler的实现者?为什么没找到?
真相原来是这样的:
XML命名空间(比如p规则等等,或者自定义的规则)都有以下步骤:
1.编写XSD文件。
2.编写NamespaceHandler使用XSD解析XML。
3.编写spring.handlers和spring.schemas串联起所有部件。
具体讲一下:XSD之前发帖讲过,是规则文件(命名空间),若有疑问请翻阅前帖查看。
那么一般情况下,如果不是系统默认命名空间,应该要写一个继承了NamespaceHandler的类来使用相应的XSD规则文件解析bean xml。
既然p规则是spring自己加的,那么特定的NamespaceHandler肯定是写了,不会为空。
而spring.handlers是NamespaceHandler和XSD文件的连接器(NamespaceHandler通过spring.handlers配置文件找到XSD文件)。
所以是spring.handlers出了问题。
一般情况下,spring.handlers和spring.schemas是写在环境路径的META-INF里面的。我们来看一下org.springframework.beans-3.0.5.RELEASE.jar包:
果不其然,用压缩工具打开该jar包,金屋藏娇,里面竟然藏了个META-INF文件夹。来看里面都有什么:
这就是上面提到的那两个文件。
下面是spring.handlers里面的内容:
1 http\://www.springframework.org/schema/p=org.springframework.beans.factory.xml.SimplePropertyNamespaceHandler 2 http\://www.springframework.org/schema/util=org.springframework.beans.factory.xml.UtilNamespaceHandler
以第一行为例,它表示:规则名为http\://www.springframework.org/schema/p的xsd对应对的解析类是org.springframework.beans.factory.xml.SimplePropertyNamespaceHandler
下面是spring.schemas:
1 http\://www.springframework.org/schema/beans/spring-beans-2.0.xsd=org/springframework/beans/factory/xml/spring-beans-2.0.xsd 2 http\://www.springframework.org/schema/beans/spring-beans-2.5.xsd=org/springframework/beans/factory/xml/spring-beans-2.5.xsd 3 http\://www.springframework.org/schema/beans/spring-beans-3.0.xsd=org/springframework/beans/factory/xml/spring-beans-3.0.xsd 4 http\://www.springframework.org/schema/beans/spring-beans.xsd=org/springframework/beans/factory/xml/spring-beans-3.0.xsd 5 http\://www.springframework.org/schema/tool/spring-tool-2.0.xsd=org/springframework/beans/factory/xml/spring-tool-2.0.xsd 6 http\://www.springframework.org/schema/tool/spring-tool-2.5.xsd=org/springframework/beans/factory/xml/spring-tool-2.5.xsd 7 http\://www.springframework.org/schema/tool/spring-tool-3.0.xsd=org/springframework/beans/factory/xml/spring-tool-3.0.xsd 8 http\://www.springframework.org/schema/tool/spring-tool.xsd=org/springframework/beans/factory/xml/spring-tool-3.0.xsd 9 http\://www.springframework.org/schema/util/spring-util-2.0.xsd=org/springframework/beans/factory/xml/spring-util-2.0.xsd 10 http\://www.springframework.org/schema/util/spring-util-2.5.xsd=org/springframework/beans/factory/xml/spring-util-2.5.xsd 11 http\://www.springframework.org/schema/util/spring-util-3.0.xsd=org/springframework/beans/factory/xml/spring-util-3.0.xsd 12 http\://www.springframework.org/schema/util/spring-util.xsd=org/springframework/beans/factory/xml/spring-util-3.0.xsd
以第三行为例,它表示载入xsd文件的地址。
用一开始的栗子来说:
1 <?xml version="1.0" encoding="UTF-8" ?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xmlns:p="http://www.springframework.org/schema/p" 5 xsi:schemaLocation="http://www.springframework.org/schema/beans 6 http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"> 7 <bean id="car" class="com.mesopotamia.test1.Car" 8 p:name="汽车" 9 p:brand="宝马" 10 p:maxSpeed="200"/> 11 12 <bean id="car1" 13 p:brand="宝马X5" 14 parent="car" 15 /> 16 </beans>
第4行表示该beans使用p规则,结合spring.handlers找到p规则对应的解析类是SimplePropertyNamespaceHandler。
第6行表示p规则的地址,结合spring.schemas的第三行找到p规则对应的xsd文件的具体位置。
所以,这两个文件缺一不可。
回到一开始,我说我的spring是直接引入的源码,所以忘记加那两个文件了(jar包中是藏着的,而我用的是java文件夹,没有META-INF),因此系统找不到handler类而报错。
这时,我应该在编译路径下把META-INF放进去。(可能你自己的项目resources下面本身就有个META-INF,但是这个不管用,必须在编译路径下另外存放一个)。然后程序就能够完美运行了。
最后我们欣赏一下系统找到SimplePropertyNamespaceHandler后执行的解析方法:
1 public BeanDefinitionHolder decorate(Node node, BeanDefinitionHolder definition, ParserContext parserContext) { 2 log.info("调用了decorate方法之前的BeanDefinitionHolder:"+definition); 3 if (node instanceof Attr) { 4 Attr attr = (Attr) node; 5 String propertyName = parserContext.getDelegate().getLocalName(attr); 6 String propertyValue = attr.getValue(); 7 MutablePropertyValues pvs = definition.getBeanDefinition().getPropertyValues(); 8 if (pvs.contains(propertyName)) { 9 parserContext.getReaderContext().error("Property '" + propertyName + "' is already defined using " + 10 "both <property> and inline syntax. Only one approach may be used per property.", attr); 11 } 12 if (propertyName.endsWith(REF_SUFFIX)) { 13 propertyName = propertyName.substring(0, propertyName.length() - REF_SUFFIX.length()); 14 pvs.add(Conventions.attributeNameToPropertyName(propertyName), new RuntimeBeanReference(propertyValue)); 15 } 16 else { 17 pvs.add(Conventions.attributeNameToPropertyName(propertyName), propertyValue); 18 } 19 } 20 //added by mesopotamia on 2015.11.19 21 log.info("调用了decorate方法后的BeanDefinitionHolder:"+definition); 22 return definition; 23 }
由于时间与深度关系,暂时不作研究。
希望从这个问题中,我们不仅仅是知道了这个问题的答案。