Spring 父類變量註入失敗的解決

Spring 父類變量註入失敗

昨天遇到一個Action裡面Service註入失敗,換種說法應該說是根本沒有發生註入,本來很簡單的一個問題,但由於在項目中多個Action進行瞭繼承,才最終導致瞭這個看似奇怪的問題。

下面小記下這個過程

收到同事問題,“有個Action請求一直調用報控指針,service一直是空的導致的!”

初步看瞭代碼及配置,沒有發現什麼問題,起初懷疑是Action沒有get方法所致,然後加上仍然無效;然後單步做瞭各種變量名的替換,一直一樣問題 ,這過程中一直關註java代碼確忽略瞭頁面請求,通過頁面請求發現代碼真正邏輯是頁面請求瞭一個子類Action的方法,而這個方法裡面調用瞭父類的一個方法,此時父類裡面的Service一直無法註入,對於上面所提的這種需求,實際上是需要在子類做Spring註入的同時也進行父類的Spring註入,那麼這種需要這樣的配置:

<bean id="****Action" class="com.**.**.contrl.**.mgr.action.**Action" scope="prototype" parent="termCommonAction">
  <property name="orderVerifyApiFacade" ref="ord.bizprov.orderVerifyApiFacade"/>
  <property name="orderListQryApiFacade" ref="ord.query.orderListQryApiFacade"/>
  <property name="channelQryApiFacade" ref="cfguse.channel.channelQryApiFacade" />
</bean> 

經過上面的設置以後,請求子類的Action方法,子類方法中調用父類方法時,就不會出現父類不發生註入的問題瞭。

Spring通過父類註入公用屬性的技巧

XML配置方式提取父類

在使用Spring + Hibernate框架,或者SSH2等框架的時候,在開發中隻有一個基本的DAO是現在的非常流行的做法。然後,在看過多份這種代碼以後,都是在每個業務類中聲明瞭一個DAO屬性,並且在Bean配置中,對每個業務類分別註入DAO。具體情形示例如下:

BaseDAO代碼:

public class BaseDAO {
 public String service() {
  return "Success!";
 }
}

Services代碼:

//第一個業務類
public class ServiceA {
 public String service() {
  return baseDAO.service();
 }
 
 protected BaseDAO baseDAO;
 
 public void setBaseDAO(BaseDAO baseDAO) {
  this.baseDAO = baseDAO;
 }
}
 
//第二個業務類
public class ServiceB {
 public String service() {
  return baseDAO.service();
 }
 
 protected BaseDAO baseDAO;
 
 public void setBaseDAO(BaseDAO baseDAO) {
  this.baseDAO = baseDAO;
 }
}

Spring的Bean配置如下:

<bean id="baseDAO" class="com.watson.BaseDAO" />
<bean id="serviceA" class="com.watson.ServiceA">
 <property name="baseDAO" ref="baseDAO" />
</bean>
<bean id="serviceB" class="com.watson.ServiceB">
 <property name="baseDAO" ref="baseDAO" />
</bean>

這樣的做法是現在的主流。這樣做不是說那裡錯瞭,還是那句老話:這樣做肯定不優美,誰讓人有時候是一根筋呢?

能夠想到的辦法是用一個父類來包含一些業務層公用的業務邏輯和屬性。所以可以將上面的代碼和配置。

Services代碼改寫如下:

//所有業務類的父類
public class BaseService {
 protected BaseDAO baseDAO;
 
 public void setBaseDAO(BaseDAO baseDAO) {
  this.baseDAO = baseDAO;
 }
}
//第一個業務類
public class ServiceA extends BaseService  {
 public String service() {
  return baseDAO.service();
 }
}
//第二個業務類
public class ServiceB extends BaseService  {
 public String service() {
  return baseDAO.service();
 }
}

Spring的Bean配置改寫如下:

<bean id="baseDAO" class="com.watson.BaseDAO" />
<bean id="BaseService" class="com.watson.BaseService" />
 <property name="baseDAO" ref="baseDAO" />
</bean>
<bean id="serviceA" class="com.watson.ServiceA" />
<bean id="serviceB" class="com.watson.ServiceB" />

這樣一來是不簡潔瞭很多?尤其在實際項目有太多Bean的時候。然後,這裡不會達到我們預想的結果,因為這裡會出現如下的錯誤:

exception:

org.springframework.web.util.NestedServletException: Request processing failed; nested exception is

java.lang.NullPointerException

……

root cause:

java.lang.NullPointerException:……

而出錯代碼就是每個業務中調用baseDAO的那行代碼。這說明註入失敗瞭。翻閱Spring的Bean註入詳解之後,很快就可以找應該設置子類Bean配置的parent屬性。所以這裡可以修改設置。

Spring的Bean配置改寫如下:

<bean id="baseDAO" class="com.watson.BaseDAO" />
<bean id="BaseService" class="com.watson.BaseService" />
 <property name="baseDAO" ref="baseDAO" />
</bean>
<bean id="serviceA" class="com.watson.ServiceA" parent="baseService" />
<bean id="serviceB" class="com.watson.ServiceB" parent="baseService" />

這個時候再運行,就不會報錯瞭。原理是:在Spring的子類Bean配置中,其parent屬性作用是指定其父類,並繼承父類的註入屬性。不僅如此,子類還可以修改或者覆蓋父類的屬性值。例如上述代碼中的子類修改父類的baseDAO到屬性:

<bean id="BaseService" class="com.watson.BaseService" />
 <property name="baseDAO" ref="baseDAO" />
</bean>
<bean id="serviceA" class="com.watson.ServiceA" parent="baseService" />
<property name="baseDAO" ref="baseDAO2" />
</bean>

而對於父類的List等集合屬性,子類可以繼承父類的值,並且在其基礎上進行增加新的值:

<bean id="BaseService" class="com.watson.BaseService" />
 <property name="listValue">  
  <list>  
   <value>listValue1</value>  
   <value>listValue2</value>  
  </list>  
 </property> 
</bean>
<bean id="serviceA" class="com.watson.ServiceA" parent="baseService" />
 <property name="listValue">  
  <list>  
   <value>listValue3</value>  
   <value>listValue4</value>  
  </list>  
 </property> 
</bean>

Annotation方式提取父類

上面的方法是在XML配置文件中進行的配置。而對現在Spring3流行的Annotation方式,其實更加的方便,完整示例如下:

BaseDAO代碼:

@Component
public class BaseDAO {
 public String service() {
  return "Success!";
 }
}

Services代碼:

//所有業務類的父類
public class BaseService {
 @Autowired
 protected BaseDAO baseDAO;
}
 
//第一個業務類
@Component
public class ServiceA extends BaseService  {
 public String service() {
  return baseDAO.service();
 }
}
//第二個業務類
@Component
public class ServiceB extends BaseService  {
 public String service() {
  return baseDAO.service();
 }
}

Action層代碼:

@Controller
@RequestMapping(value = "/testaction")
public class TestAction {
 @Autowired
 private ServiceA service;
 
 @RequestMapping(value = "/")
 public @ResponseBody String home(Model model) {
  return service.service();
 }
}

這裡根本就不需要進行parent屬性子類的配置,可以完美的提取父類,並且可以順利的使用父類的公用屬性。至於原理,沒有去看源碼的處理方式,估計和上述XML配置是異曲同工的,隻是在這裡增加瞭對父類的檢測。

以上為個人經驗,希望能給大傢一個參考,也希望大傢多多支持WalkonNet。

推薦閱讀: