报错信息: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 2015-11-19 21:21:43,901 INFO [main] (AbstractApplicationContext.java:456) - Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@45a877: startup date [Thu Nov 19 21:21:43 CST 2015]; root of context hierarchy
2 2015-11-19 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 }
相关推荐
6.8.1. 在Spring中使用AspectJ来为domain object进行依赖注入 6.8.1.1. @Configurable object的单元测试 6.8.1.2. 多application context情况下的处理 6.8.2. Spring中其他的AspectJ切面 6.8.3. 使用Spring IoC来...
除了`spring.handlers`文件用于映射命名空间到`NamespaceHandler`,还有`spring.schemas`文件用于定义XML元素和它们对应的XML Schema约束文件。这确保了XML配置文件的正确性,因为Spring会根据`spring.schemas`来...
6.8.4. 在Spring应用中使用AspectJ Load-time weaving(LTW) 6.9. 其它资源 7. Spring AOP APIs 7.1. 简介 7.2. Spring中的切入点API 7.2.1. 概念 7.2.2. 切入点实施 7.2.3. AspectJ切入点表达式 7.2.4. ...
接下来要实现NameSpaceHandler和BeanDefinitionParser来完成解析工作,最后是编写spring.handlers和spring.schemas文件来将自定义的标签和处理类结合起来,从而让这些自定义标签能够被Spring容器所识别和使用。...
6.8.1. 在Spring中使用AspectJ进行domain object的依赖注入 6.8.2. Spring中其他的AspectJ切面 6.8.3. 使用Spring IoC来配置AspectJ的切面 6.8.4. 在Spring应用中使用AspectJ加载时织入(LTW) 6.9. 更多资源 7...
Spring Framework 开发参考手册 Rod Johnson Juergen Hoeller Alef Arendsen Colin Sampaleanu Rob Harrop Thomas Risberg Darren Davison Dmitriy Kopylenko Mark Pollack ...19.2. 使用Spring JMS ...
6.8.1. 在Spring中使用AspectJ进行domain object的依赖注入 6.8.2. Spring中其他的AspectJ切面 6.8.3. 使用Spring IoC来配置AspectJ的切面 6.8.4. 在Spring应用中使用AspectJ加载时织入(LTW) 6.9. 更多资源 7...
"3spring.schemas"可能是指Spring框架定义的XML架构,这些架构文件用于验证配置文件的结构和语法,确保它们符合Spring框架的标准约定。 "kP3ospring-form.tld"和"spring.tld"是Tag Library Descriptors(TLD)文件...
<resource>META-INF/spring.handlers implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"> <resource>META-INF/spring.schemas *:* <exclude>META-INF/*.SF ...
<resource>META-INF/spring.handlers <transformer implementation="org.springframework.boot.maven.PropertiesMergingResourceTransformer"> <resource>META-INF/spring.factories ...
该项目是一个基于Spring框架构建的轻量级工作流设计源码,包含52个文件,其中38个为Java源文件,5个为XML配置文件,以及少量其他类型的文件,如XSD、handlers、schemas、gitignore、md和txt等。该框架旨在提供灵活且...
<resource>META-INF/spring.handlers <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"> <resource>META-INF/spring.schemas <!-- 指定主类入口 --> ...
2. **配置文件格式**:这里选择了ini文件来存储命名空间元信息,而不是Spring中的"schemas"和"handlers"文件,因为ini文件的读取API更为灵活,便于扩展其他命名空间。 3. **解析流程**:XML解析可以分解为三个部分...