在 ServletContextListener 實現類中使用 @Resource 注入 Spring Bean(NoteTableDAO, NoteOperDAO),Tomcat 啟動拋出 NamingException: Cannot create resource instance,堆疊指向 DefaultInstanceManager.processAnnotations。改用 web.xml 配置 Listener 則正常。
Servlet 容器管理的組件不在 Spring IOC 容器生命周期內:
Filter、Listener、Servlet 由 Servlet 容器(Tomcat)實例化、初始化、銷毀;ContextLoaderListener 在 contextInitialized 中啟動 IOC 容器,**晚於** 容器創建 Listener 實例;@Resource、@Inject、@EJB 等),委託 JNDI 查找,**而 Spring Bean 未綁定到 JNDI**,導致 NamingException。public class ApplicationListener implements ServletContextListener {
private NoteTableDAO noteTableDAO;
private NoteOperDAO noteOperDAO;
@Override
public void contextInitialized(ServletContextEvent event) {
ServletContext servletContext = event.getServletContext();
// 關鍵:從 ServletContext 獲取 Spring WebApplicationContext
WebApplicationContext springContext = WebApplicationContextUtils
.getWebApplicationContext(servletContext);
if (springContext != null) {
// 手動獲取 Bean(按名稱或類型)
noteTableDAO = springContext.getBean(NoteTableDAO.class);
noteOperDAO = springContext.getBean(NoteOperDAO.class);
// 業務邏輯...
servletContext.setAttribute("noteTableMap", noteTableDAO.queryAllForMap());
servletContext.setAttribute("noteOperMap", noteOperDAO.queryAllForMap());
}
}
@Override
public void contextDestroyed(ServletContextEvent event) {
// 清理引用
noteTableDAO = null;
noteOperDAO = null;
}
}
ServletContext 屬性 org.springframework.web.context.WebApplicationContext.ROOT 下,此工具類用於取出;contextInitialized 中獲取,此時 Spring 容器已就緒;getBean(Class)(Spring 3.0+),避免硬編碼 Bean 名稱;| 方案 | 優點 | 缺點 |
|---|---|---|
| 手動 getBean(本文方案) | 無侵入、標準、兼容所有容器 | 需手寫樣板代碼 |
@Autowired + SpringBeanAutowiringSupport |
註解風格,自動注入 | 需繼承 SpringBeanAutowiringSupport,仍依賴 JNDI 查找 |
| DelegatingFilterProxy / DelegatingListener | 完全委託 Spring 管理 | 配置繁瑣,Listener 不直接支持 |
Spring Boot @ServletComponentScan |
原生支持註解注入 | 需遷移至 Spring Boot |
Filter/Listener/Servlet 不要指望 Spring 自動注入。它們屬於容器管轄,Spring 啟動更晚。標準做法是在初始化階段通過 WebApplicationContextUtils 手動從 Spring 容器拉取所需 Bean。這也是 Spring 官方文檔推薦的模式。