(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     }

由於時間與深度關系,暫時不作研究。

 

 

希望從這個問題中,我們不僅僅是知道了這個問題的答案。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM