原創(chuàng)文章,轉(zhuǎn)載請注明出處
? 從開年到現(xiàn)在兩個月時間汰蜘,一直想慢慢開始寫點東西关霸,將工作以來學(xué)到的知識和技能進行總結(jié)催首。但是偏偏年后項目比較急殖属,一個集中刻錄項目就寫了一個月左右输虱,但是得到的收獲也不少。好不容易清明有了一點的空閑盈电,就將之前的一些筆記和開發(fā)中遇到的問題好好的總結(jié)一下吧沿癞。
? 萬事開頭難躁绸,能提筆開始寫這篇文章等缀,相信以后能養(yǎng)成定時總結(jié)的習(xí)慣枷莉,也希望能和大家進行探討,有錯誤和疏漏的地方還請不吝賜教尺迂。
? 廢話也就不多說了笤妙,今天主要總結(jié)一下 JavaWeb 開發(fā)中最基本的三大類:Listener、Filter與 Servlet中的 Listener噪裕。對于 JavaWeb 程序來講蹲盘,這三個類是維持其基本運作的基石,在現(xiàn)代開發(fā)中州疾,各種框架(最常見的 Spring辜限、Struts2皇拣、Hibernate等)為程序員搭建好了舒適的開發(fā)環(huán)境严蓖,以至于讓人忽略了之三大類能做到的許多事情⊙跫保互聯(lián)網(wǎng)時代每時每刻都在變化颗胡,但是這些變化也是基于一些不變的東西,學(xué)習(xí)一門技術(shù)也要從最基本的吩坝、不變的底層學(xué)起毒姨,往上才能開枝散葉,正所謂萬變不離其宗钉寝。
Listener
? Listener 是專門用來監(jiān)聽另一個 Java 對象方法的調(diào)用或?qū)傩愿淖兓∧牛瑢崿F(xiàn)了特定接口的特殊類 。剛好最近在閱讀有關(guān)設(shè)計模式的書籍嵌纲,而這里 Listener 正是通過觀察者模式進行實現(xiàn)的俘枫,而在前一個月做的項目中有多處都用到了這種設(shè)計模式。
? 在 JavaWeb 應(yīng)用中逮走,被監(jiān)聽的對象主要是:ServletContext鸠蚪、HttpSession、ServletRequest。
? 針對以上三個對象的操作茅信,Listener 又被劃分成為三種類型:
- 域?qū)ο髣?chuàng)建和銷毀的事件監(jiān)聽器
- 域?qū)ο笾袑傩愿淖兊氖录O(jiān)聽器
- 綁定到 HttpSession域中的某個對象的狀態(tài)的事件監(jiān)聽器
域?qū)ο髣?chuàng)建和銷毀的事件監(jiān)聽器
? 這類Listener的作用是監(jiān)聽對象的創(chuàng)建和銷毀盾舌,自定義的Listener 類需要實現(xiàn)特定接口中的兩個方法:XXXInitialized,XXXDestroyed蘸鲸。下面的代碼顯示了針對不同的被監(jiān)聽對象的Listener實現(xiàn):
web.xml
<listener>
<listener-class>com.yzhang.listener.test.MyServletContextListener</listener-class>
</listener>
<listener>
<listener-class>com.yzhang.listener.test.MyHttpSessionListener</listener-class>
</listener>
<listener>
<listener-class>com.yzhang.listener.test.MyServletRequestListener</listener-class>
</listener>
MyServletContextListener
//***
public class MyServletContextListener implements ServletContextListener {
private static Logger logger = Logger.getLogger(MyServletContextListener.class);
public void contextInitialized(ServletContextEvent sce) {
logger.debug("servletContextInitialized");
}
public void contextDestroyed(ServletContextEvent sce) {
logger.debug("servletContextDestroyed");
}
}
MyHttpSessionListener
//***
public class MyHttpSessionListener implements HttpSessionListener {
private static Logger logger = Logger.getLogger(MyHttpSessionListener.class);
public void sessionCreated(HttpSessionEvent se) {
logger.debug("sessionCreated");
logger.debug(se.getSession().getId());
}
public void sessionDestroyed(HttpSessionEvent se) {
logger.debug("sessionDestroyed");
}
}
MyServletRequestListener
public class MyServletRequestListener implements ServletRequestListener {
private static Logger logger = Logger.getLogger(MyServletRequestListener.class);
public void requestInitialized(ServletRequestEvent sre) {
logger.debug("requestInitialized");
HttpServletRequest request = (HttpServletRequest)sre.getServletRequest();
logger.debug(request.getRequestURL());
}
public void requestDestroyed(ServletRequestEvent sre) {
logger.debug("requestDestroyed");
}
}
? ServletContextListener 監(jiān)視的是整個 Servlet 容器對象妖谴,當(dāng) Servlet 容器啟動時,監(jiān)聽器的contextInitialized
方法被調(diào)用棚贾,當(dāng)這個方法被調(diào)用時窖维,其實傳達的是這么一條信息:我(Servlet 容器)已經(jīng)啟動完成,你(對應(yīng)的程序)可以開始你自己的工作了妙痹。所以在contextInitialized
中往往做一些初始化的工作铸史,同理contextDestroyed
在 Servlet 容器對象被銷毀時調(diào)用,可做一些收尾工作怯伊。
? HttpSessionListener監(jiān)視的是 Session 對象琳轿,在整個 web 應(yīng)用生命周期中,每當(dāng)有 Session 被創(chuàng)建和銷毀時耿芹,都會調(diào)用其中的sessionCreated
或sessionDestroyed
崭篡,在方法中可以通過HttpSessionEvent.getSession()
來獲取 Session 的詳細信息。
? ServletRequestListener與 HttpSessionListener 大致相同吧秕,區(qū)別在于其監(jiān)視的是 ServletRequest 對象琉闪,通俗來講:每當(dāng)有一次頁面訪問請求到達,都會創(chuàng)建一個ServletRequest 對象砸彬,從而導(dǎo)致requestInitialized
方法被調(diào)用颠毙,而當(dāng)這次請求返回以后,ServletRequest 對象被銷毀時砂碉,requestDestroyed
方法將被調(diào)用蛀蜜。
? 進行一次實驗,啟動 Tomcat -> 訪問index.jsp 增蹭,將得到下面的輸出:
? 很容易可以分析出各監(jiān)聽器的調(diào)用順序滴某,這里就不過多贅述。
ServletContextListener 的應(yīng)用——— Spring 的入口
? 大家肯定都記得在使用 Spring 的時候需要在 Web.xml 中配置一個 Spring 的監(jiān)聽器:
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:/applicationContext.xml</param-value>
</context-param>
? context-param 是 Spring 的配置文件位置滋迈,在 Spring 啟動后會根據(jù)applicationContext.xml
中的配置對 Bean 進行實例化霎奢,這里主要觀察org.springframework.web.context.ContextLoaderListener
這個監(jiān)聽器。
? 當(dāng)我們進入到這個監(jiān)聽器內(nèi)部會發(fā)現(xiàn)饼灿,這個監(jiān)聽器正是實現(xiàn)了 ServletContextListener幕侠,這也符合之前所講:在contextInitialized
中程序可以進行自己的初始化工作了。在這里Spring就會開始自己實際的工作赔退。
//...
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
public ContextLoaderListener() {
}
public ContextLoaderListener(WebApplicationContext context) {
super(context);
}
public void contextInitialized(ServletContextEvent event) {
this.initWebApplicationContext(event.getServletContext());
}
public void contextDestroyed(ServletContextEvent event) {
this.closeWebApplicationContext(event.getServletContext());
ContextCleanupListener.cleanupAttributes(event.getServletContext());
}
}
? 而在initWebApplicationContext
方法中將會進行 Bean 的初始化等工作橙依。
? 下面看一個在項目中實際遇到過的問題证舟,新手常常會犯這種錯誤,如果沒有理解 Listener 的作用時間和范圍窗骑,往往不能很快找出錯誤原因女责。
在一個刻錄系統(tǒng)需要維護多條線程,每條線程分別與不同的刻錄機服務(wù)通過 WebService 進行通信创译。在程序啟動時抵知,需要從數(shù)據(jù)庫中獲取用戶配置過的刻錄機信息,并啟動相應(yīng)的線程软族。這里可以很快想到的做法是實現(xiàn)一個 ServletContextListener刷喜,并在
contextInitialized
中調(diào)用PrinterService.listAllLivePrinter()
方法獲取活躍的刻錄機列表,然后開啟線程立砸。
? 然而事與愿違掖疮,在程序啟動時報錯:找不到 PrinterService。問題就出在自己實現(xiàn)的ServletContextListener和 Spring 的ContextLoaderListener同樣都是對 Servlet 容器的監(jiān)聽颗祝,那么誰先執(zhí)行呢浊闪?結(jié)論是在 web.xml中誰排在前面誰先執(zhí)行。
? 這里犯的第一個錯誤就是將自定義的 ServletContextListener放在了 Spring 的 Listener 之前螺戳。在進一步的測試中搁宾,把自定義 Listener 放到 Spring 的 Listener 之后,并且在contextInitialized
方法中以如下形式調(diào)用:
private UserService userService;
private WebApplicationContext springContext;
public void contextInitialized(ServletContextEvent sce) {
logger.debug("servletContextInitialized");
springContext = WebApplicationContextUtils.getWebApplicationContext(sce.getServletContext());
if (springContext!=null){
logger.debug("get Spring ApplicationContext success");
userService = (UserService) springContext.getBean("userService");
}else{
logger.error("get SpringContext failed");
return;
}
userService.listAllAlive();
logger.debug("call userService.listAllAlive() success");
}
? WebApplicationContext 描述的是 Web 應(yīng)用中的Spring上下文倔幼,這里需要通過直接獲取 bean 的方式得到 userService 實例對象盖腿。修改以后的控制臺輸出:
? 可以看到,在自定義的 Listener 中成功獲取到userService 并調(diào)用了userService.listAllAlive()
损同。稍作總結(jié)翩腐,在這個例子中,想要在程序初始化時做一些工作揖庄,有兩點值得我們注意的地方:
- 在自定義的 ServiceContextListener 中獲取 Spring 裝配的實例對象栗菜,必須要將自定義 Listener 寫到 Spring 的 ContextLoaderListener 之后欠雌。
- 新手可能直接會在自定義 Listener中
new UserService()
蹄梢,這么做說明根本沒有理解 Spring 的IoC,new
出來的對象與 Spring Bean 工廠生成的根本不是同一個實例富俄,其中的各種 dao 必然沒有注入禁炒,當(dāng)調(diào)用需要 dao 的方法時(往往是必然的)則會報 NPE。
? 通過這個例子可以對 ServletContextListener 有更深的認識霍比,而對于其他兩個 Listener幕袱,除了監(jiān)聽的對象不同,在用法上都是大同小異悠瞬,在理解了其調(diào)用關(guān)系之后應(yīng)該能夠很快上手使用们豌。