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();//釋放資源