在非 Spring 管理的類(如工具類、JMS 監聽器、定時任務、舊代碼集成)中獲取 Spring Bean,是常見需求。本文對比三種方案,強力推薦第三種通用靜態持有者模式,一次配置,全局隨處可用。
適用於 Spring MVC DispatcherServlet 初始化的上下文。
// web.xml
<servlet>
<servlet-name>springMVC</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:/springMVC.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
Spring 容器存放在 ServletContext 的 key 為:
org.springframework.web.servlet.FrameworkServlet.CONTEXT.springMVC
// 其中 springMVC 為 servlet-name 值
獲取代碼:
ServletContext sc = ...; // 從 request/session/servletContext 獲取
WebApplicationContext ctx = (WebApplicationContext) sc.getAttribute(
"org.springframework.web.servlet.FrameworkServlet.CONTEXT.springMVC");
MyBean bean = ctx.getBean(MyBean.class);
缺點:強耦合 Servlet API、key 魔法字符串、僅限 Spring MVC 場景。
// web.xml
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
獲取代碼:
ServletContext context = getServletContext();
WebApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(context);
MyBean bean = ctx.getBean(MyBean.class);
缺點:仍依賴 ServletContext、需在 Web 環境下。
實現 ApplicationContextAware,在容器啟動時自動注入 ApplicationContext 到靜態變量,提供靜態 getBean() 方法,完全脫離 Web 環境,任意類隨時調用。
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
public class SpringContextHolder implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext ctx) {
SpringContextHolder.applicationContext = ctx;
}
public static ApplicationContext getApplicationContext() {
checkApplicationContext();
return applicationContext;
}
@SuppressWarnings("unchecked")
public static T getBean(String name) {
checkApplicationContext();
return (T) applicationContext.getBean(name);
}
@SuppressWarnings("unchecked")
public static T getBean(Class clazz) {
checkApplicationContext();
return (T) applicationContext.getBeansOfType(clazz);
}
public static void cleanApplicationContext() {
applicationContext = null;
}
private static void checkApplicationContext() {
if (applicationContext == null) {
throw new IllegalStateException(
"applicaitonContext未注入,請在applicationContext.xml中定義SpringContextHolder");
}
}
}
<!-- 用於持有ApplicationContext,可使用SpringContextHolder.getBean('xxxx')靜態方法得到spring bean對象 -->
<bean class="com.xxxxx.SpringContextHolder" lazy-init="false" />
// 按名稱獲取
UserService userService = SpringContextHolder.getBean("userService");
// 按類型獲取(單例 Bean)
UserService userService = SpringContextHolder.getBean(UserService.class);
| 維度 | 方式一 ServletContext | 方式二 Listener | 方式三 SpringContextHolder |
|---|---|---|---|
| 適用場景 | Spring MVC | 傳統 Spring | 通用(含非 Web、定時任務、測試) |
| 依賴環境 | Web + Servlet API | Web + Servlet API | 無(純 Spring) |
| 調用便捷度 | 需獲取 ServletContext | 需獲取 ServletContext | 靜態方法一行代碼 |
| 線程安全 | 是 | 是 | 是(靜態變量寫一次讀多次) |
| 推薦指數 | ⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐⭐ |
lazy-init="false" 必須設置,否則 Bean 延遲初始化導致 setApplicationContext 不執行,靜態變量為 null。SpringContextHolder.cleanApplicationContext() 避免上下文殘留污染。ApplicationContext 非共享,每個 JVM 獨立持有,無跨節點影響。ApplicationContext 或用 @Autowired 更簡潔,無需此工具類。