在傳統 Struts+Spring+Hibernate 架構中,Action 透過 getBean() 取得 Service。但若 Service A 需呼叫 Service B,直接 new ServiceB() 會導致 B 內部的 DAO 等依賴無法由容器注入。本文提供一種優雅的解決方案:靜態持有 ApplicationContext。
public class AppContext {
private static ApplicationContext applicationContext;
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
public static void setApplicationContext(ApplicationContext ctx) {
applicationContext = ctx;
}
}
public class SpringBeanService {
private static SpringBeanService instance;
private ApplicationContext applicationContext;
private SpringBeanService() {}
public static synchronized SpringBeanService getInstance() {
if (instance == null) instance = new SpringBeanService();
return instance;
}
public ApplicationContext getApplicationContext() {
return applicationContext;
}
public void setApplicationContext(ApplicationContext ctx) {
this.applicationContext = ctx;
}
// 具體取得 Service 的便利方法
public UserService getUserService() {
return (UserService) AppContext.getApplicationContext().getBean("userService");
}
}
public class ConfigLoadListener implements ServletContextListener {
public void contextInitialized(ServletContextEvent event) {
try {
WebApplicationContext ctx = WebApplicationContextUtils
.getRequiredWebApplicationContext(event.getServletContext());
AppContext.setApplicationContext(ctx); // 關鍵:注入全域持有器
// 可順便載入其他設定檔
ServletContext sc = event.getServletContext();
String path = sc.getInitParameter("setting.properties");
Properties props = new Properties();
props.load(sc.getResourceAsStream(path));
GlobalConstant.setCmdbProperties(props);
} catch (Exception e) {
System.err.println("系統初始化失敗: " + e);
System.exit(1);
}
}
public void contextDestroyed(ServletContextEvent event) {}
}
在 web.xml 註冊該 Listener:
<listener>
<listener-class>com.yourpkg.ConfigLoadListener</listener-class>
</listener>
public class ServiceA {
public void doSomething() {
UserService userService = SpringBeanService.getInstance().getUserService();
userService.someMethod();
}
}
| 優點 | 缺點 |
|---|---|
| 零侵入:既有 Service 程式碼無需修改建構子或 Setter | 引入全域狀態,單元測試需額外 Mock ApplicationContext |
| 適用於舊專案快速遷移 | 違反純粹依賴注入原則,新專案建議改用 @Autowired 或建構子注入 |
public ServiceA(ServiceB serviceB) { this.serviceB = serviceB; }ApplicationContextAware 介面搭配非靜態持有器靜態持有 ApplicationContext 是解決舊架構 Service 互調的實用手段,但在新專案中應優先採用標準依賴注入機制,保持程式碼純淨與可測試性。