【問題背景】
JSP開發中,利用Listener監聽器對象,可以記錄某網站訪客的到訪時間、訪客IP地址等信息。
注意這裏的一個訪客對應一個session對象,而不是具體的某人。
本例中,統計了兩類信息:一是訪客相關信息;二是瀏覽歷史信息。
1、前者字段包括:到訪ID(主鍵,整型,自動遞增)、到訪時間、離開時間(session過期時刻)、IP地址、來源網址等信息,存入數據庫visitorDB的visitor表中。通過一個【歷史訪客】按鈕,將表中內容以“歷史訪客表”的形式顯示到一個JSP頁面。
2、後者字段包括:瀏覽記錄ID(主鍵,整型,自動遞增)、到訪ID(即visitor表的主鍵),訪問頁面時刻,和瀏覽頁面URL。所有記錄存入history表中,通過一個【瀏覽軌跡】按鈕,將表中內容以“訪問記錄表”的形式顯示到另一個JSP頁面。
由於出現了到訪ID這個外鍵,在讀寫history表時,需要調用當前session對應的訪客v的主鍵值。實現方法大致兩種:
(1)在HashMap<String, Visitor>中,調用當前sessionID的鍵值對,用map.get(sessionID)獲取該Visitor對象。這裏的 key=sessionID=arg0.getSession().getId();
(2)將當前訪客Visitor visitorCurrent單獨保存到session的自定義屬性"USER"中,再到requestInitialized()方法中調用該屬性值:
保存:
HttpSession session = arg0.getSession();
session.setAttribute("USER",visitorCurrent);
提取:
Visitor vCurrent =(Visitor) request.getSession().getAttribute("USER");
本例使用方法(2)。
【問題描述】
按照方法(2)保存當前訪客對象visitorCurrent,本應保存到session中,結果根據自動提示錯誤保存到了application中,寫成了:Visitor vCurrent = (Visitor) request.getServletContext().getAttribute("USER");
於是將錯就錯,將保存語句也一併改成:
ServletContext application = arg0.getSession().getServletContext();
application.setAttribute("USER",visitorCurrent);
啓動服務器驗證代碼,結果在調用訪客ID時報空指針錯誤:java.lang.NullPointerException,問題所在代碼爲:
h.setVisitId( vCurrent.getId() );
【原因分析】
沒弄清各監聽方法的執行順序。啓動服務器時,Listener先調用“監聽服務器啓動”的方法contextInitialized(),然後調用“監聽頁面請求啓動”的方法requestInitialized(),處理請求過程中,會根據需要創建session對象,由此觸發“監聽session創建”的方法sessionCreated()。
這樣,報空指針估計和異步執行有關,調用vCurrent的id值的時候,vCurrent還沒從定義在session中的屬性“USER”中,獲取到Visitor對象,因此出現空指針。要消除該異常,
①要麼調用前判定有沒有提前保存,有才賦值,沒有則追加;
②要麼在服務器啓動時就預先保存一個空的Visitor對象visitorEmpty;
優先選用①,因爲②中的空Visitor意味着此時的訪客ID=0,與實際情況不符;而①可以指定當前sessionID對應的Visitor,因此更合理。
【解決方案】
在h.setVisitId( vCurrent.getId() );之前加一句if判定語句,如果“USER”屬性值爲空,則追加sessionID對應的Visitor對象:
ServletContext app = arg0.getServletContext();
if( app.getAttribute( "USER" ) == null ) {
@SuppressWarnings( "unchecked" )
HashMap<String, Visitor> map = ( HashMap<String, Visitor> ) app.getAttribute( "ONLINE" );
Visitor vCurrent = map.get( request.getSession().getId() );
arg0.getServletContext().setAttribute("USER", vCurrent);
}
Visitor vNeeded = (Visitor) app.getAttribute("USER");
h.setVisitId( vNeeded.getId() );
驗證代碼,空指針錯誤消除,顯示的訪客ID也沒有出現0值錯誤,問題解決。
【注意事項】
本例將錯就錯,把外鍵所在的對象vCurrent保存到application屬性中,雖然通過驗證,但邏輯上仍有重大問題。因爲application的屬性只有“USER”一個,當多個用戶同時調用該屬性值時,只能得到一個最晚請求調用的當前訪客,之前的訪客對象將被後來者覆蓋。因此保存到當前session的屬性中,才能避免相互覆蓋,不同的訪客才能看到各自對應的瀏覽記錄。
本例旨在說明至少以下兩個問題:
1、requestInitialized()方法可能先於sessionCreated()執行,若跨方法調用值報告空指針異常(java.lang.NullPointerException);
2、明確出現空指針異常的原因,並知道如何修改代碼消除該異常;但最終還要考慮實際處理邏輯。