spring的xml配置與annotation註解混合
問題:
在LVMMCrawlerSuit.java是xml配置的bean, 需要注入用@Component宣告的bean. 但是執行的時候卻報NullPointerException. 說明沒有注入進來.
程式碼:
1. java
Java程式碼- 1. LVMMCrawlerSuit.java
- public class LVMMCrawlerSuit extends AbstractCrawlerSuit{
- @Resource
- private LVMMURLBuilder lvmmurlBuilder;
- public LVMMCrawlerSuit() {
- }
- }
- 2. LVMMURLBuilder.java
- @Component
- public class LVMMURLBuilder extends AbstractResourceURLBuilder {
- public LVMMURLBuilder() { }
- }
2. 配置.
Xml程式碼- <bean id="LVMMCrawlerSuit" class = 'com.qunar.b2c.crawlersuit.impl.LVMMCrawlerSuit'/>
問題查詢:
1. 查詢網上資源,未果.
2. 果斷debug,跟蹤原始碼.
將斷點定位到 org.springframework.context.support.AbstractRefreshableApplicationContext#loadBeanDefinitions ,該方法是載入bean的必經之路.跟蹤發現,該方法共執行兩次,生成了兩個不同的 org.springframework.beans.factory.support.DefaultListableBeanFactory, 並且後者的parentBeanFactory為前者,根據原設計是後者可以呼叫前者的bean 並完成注入.
現在報NullPointerException,很明顯是"父呼叫子",所以肯定拿不到.在列印的log中進行了佐證.
在debug中發現,兩次執行分別來自不同的beans資原始檔: app-web.xml 和 applicationContext-*.xml, 按key查詢,很容易找到了配置資訊如下.
Xml程式碼- <servlet>
- <servlet-name>dispatcherServlet</servlet-name>
- <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
- <init-param>
- <param-name>contextConfigLocation</param-name>
- <param-value>
- classpath:classpath:app-web.xml
- </param-value>
- </init-param>
- <load-on-startup>1</load-on-startup>
- </servlet>
- <context-param>
- <param-name>contextConfigLocation</param-name>
- <param-value>classpath:applicationContext-*.xml</param-value>
- </context-param>
既然,兩次載入,並且載入了不同的beans,雖然有父子的層級關係,但是限制多多. 那麼就嘗試合二為一.
在test中,發現因為修改了spring預設載入的檔名,所以刪除任何一個配置都不能正確執行.那麼就全部設定成一樣的吧. test success......
解決方案:
方案一. 將配置檔案路徑合併, 分別指定給不同配置.
- <servlet>
- <servlet-name>dispatcherServlet</servlet-name>
- <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
- <init-param>
- <param-name>contextConfigLocation</param-name>
- <param-value>
- classpath:applicationContext-*.xml,classpath:app-web.xml
- </param-value>
- </init-param>
- <load-on-startup>1</load-on-startup>
- </servlet>
- <context-param>
- <param-name>contextConfigLocation</param-name>
- <param-value>classpath:applicationContext-*.xml,classpath:app-web.xml</param-value>
- </context-param>
方案二. 原有配置不變,合理規劃Bean的定義及合理使用.
在方案一中, 使用的簡單,粗暴的解決辦法. 沒有考慮到spring的設計思想. 既然有ioc容器的父子級劃分,那麼在使用的時候,一定會有用的.
在使用annotation定義bean 的時候,是需要增加如下程式碼,對使用何種註解的類才管理到ioc容器中.
Xml程式碼- <context:component-scan base-package="com.qunar.b2c">
- <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" />
- <context:include-filter type="annotation" expression="org.springframework.stereotype.Repository" />
- <context:include-filter type="annotation" expression="org.springframework.stereotype.Service" />
- <context:include-filter type="annotation" expression="org.springframework.stereotype.Component" />
- </context:component-scan>
上述提到, 在 spring web的使用中, 會載入兩個ioc容器,
1. 一個是contextConfigLocation定義,用來啟動spring核心框架的. 所以在該步驟中,應載入應用中的基礎服務資訊的bean,如 dao,Service 等等.
2. 另外一個ioc容器是web載入的容器, 那麼只需載入Controller相關的bean.
因為在spring ioc的 DefaultListableBeanFactory類是支援父子關係,
1. 子容器是可以訪問到父容器中的bean,
2. 然而父容器訪問不了子容器的bean,
這就保證了, Controller可以訪問 Service等, 但是Service 訪問不了web層的bean, 這樣就將職責分開了.所以修改的配置如下:
Xml程式碼- applicationContext-beans.xml:
- <context:component-scan base-package="com.qunar.b2c">
- <context:include-filter type="annotation" expression="org.springframework.stereotype.Repository" />
- <context:include-filter type="annotation" expression="org.springframework.stereotype.Service" />
- <context:include-filter type="annotation" expression="org.springframework.stereotype.Component" />
- </context:component-scan>
- app-web.xml
- <context:component-scan base-package="com.qunar.b2c">
- <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" />
- </context:component-scan>
在開發定義bean的時候, 也需要注意,把bean定義到哪一層級.