1. 程式人生 > >Spring手動獲取指定Bean

Spring手動獲取指定Bean

起因

由於專案需要把一個專案的某個功能移植到另一個專案中,但是兩個專案結構不同,新寫的介面不是被Spring容器管理的,沒辦法在介面中使用@Autowired註解自動注入Serivice(Ps.我這裡說的介面是對外介面,不是Java中的interface,實際上是一個類),只能嘗試手動注入。在網上查了查最終嘗試了三種方式,對於我的問題前兩種手動注入bean的方式都無法獲取到例項,有效的只有方法三,先列出方法,問題分析在後面。

方法

方法一:使用BeanFactory

BeanFactory factory = new ClassPathXmlApplicationContext()("applicationContext.xml"
); ac.getBean("beanName");

方法二:使用ApplicationContext

ApplicationContext ac = new ClassPathXmlApplicationContext()("applicationContext.xml");   
ac.getBean("beanName");  

方法三:實現ApplicationContextAware介面

寫一個工具類去實現ApplicationContextAware介面,儲存ApplicationContext為靜態例項供全域性呼叫。

import org.springframework.beans.BeansException;
import
org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.util.Assert; public class BeanUtil implements ApplicationContextAware { private static ApplicationContext applicationContext; /** * 實現ApplicationContextAware介面的context注入函式, 將其存入靜態變數. */
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { BeanUtil.applicationContext = applicationContext; } /** * 取得儲存在靜態變數中的ApplicationContext. */ public static ApplicationContext getContext() { checkApplicationContext(); return applicationContext; } /** * 從靜態變數ApplicationContext中取得Bean, 自動轉型為所賦值物件的型別. */ @SuppressWarnings("unchecked") public static <T> T getBean(String name) { checkApplicationContext(); return (T) applicationContext.getBean(name); } /** * 從靜態變數ApplicationContext中取得Bean, 自動轉型為所賦值物件的型別. */ @SuppressWarnings("unchecked") public static <T> T getBean(Class<T> clazz) { checkApplicationContext(); System.out.println(clazz); return (T) applicationContext.getBeansOfType(clazz); } /** * 清除applicationContext靜態變數. */ public static void cleanApplicationContext() { applicationContext = null; } private static void checkApplicationContext() { Assert.notNull(applicationContext, "applicationContext未注入,請在applicationContext.xml中定義BeanUtil"); } }

還需要在Spring的配置檔案中application.xml中手動註冊一下這個工具類

<bean id="beanUtil" class="com.xxx.util.BeanUtil" lazy-init="false"></bean>

然後在需要的地方就可以直接呼叫

TestService testService = BeanUtil.getBean("testService");

問題分析

一開始在使用@Autowired註解無法注入之後,先後嘗試了前兩種方案,打斷點發現service同樣是null,沒有成功獲取到bean,多方嘗試之後最終僅有第三種方法成功了。至於問題的成因有過幾種猜測:

猜測一: 看到網上有人說由於自己把自動掃包的配置全部放在的SpringMVC的配置檔案裡,而Spring上下文訪問不到SpringMVC的上下文(反之可以),所以get不到bean。
我看了看專案的配置檔案,不大可能是這種情況。

猜測二: 在多執行緒環境下,由於web容器不能感知執行緒的啟動,所以Spring無法向執行緒中注入。Ps.如果由Spring管理執行緒池,似乎是可以自動注入的,這部分暫時沒有細看,以後再做補充吧。
但是因為不清楚新的專案我做的這部分是否涉及到多執行緒,無法確定是否該原因。

猜測三: 這部分類沒有被Spring管理,所以在類中也無法由Spring去注入,或者採取前兩種方案時,由於不在同一個執行緒中所以獲取到的context不是最開始的context,而是new了一個新的。
後兩種猜想在本質上是有相似之處的,因為不管是執行緒還是類,同樣都不是由Spring容器去管理,而是在需要的地方去new。經過與人探討,認為大概率是這種情況。

其他

在上面的解決辦法中還有一些需要注意的小細節
1. applicationContext獲取bean的時候是可以通過byName或者byType的方式去獲取到bean,在我們的工具類中可以看到分別呼叫了applicationContext的getBean和getBeansOfType方法,這裡有一點需要注意,通過byName方式獲取到的bean是直接返回的bean的例項,而byType方式則會返回一個Map集合,其中的key是bean的name,value則是bean的例項。我之前沒注意到這一點,直接把byType方式的返回結果賦給了bean,結果出現了型別不能轉換的錯誤。
2. BeanFactory和ApplicationContext載入bean的時候有一點不太一樣的地方,BeanFactory採用的是延遲載入的方式,第一次用到bean的時候才會載入,而ApplicationContext則是在容器啟動的時候一次性建立了所有的bean。它們還有很多其他不同之處,可以參考Spring中ApplicationContext和beanfactory區別
3. 在手動載入bean的時候,需要注意關閉容器,否則容易造成資料庫連線不能釋放。推薦使用AbstractApplicationContext,使用完之後呼叫close方法釋放資源

AbstractApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
context.close();//釋放資源