【问题背景】
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、明确出现空指针异常的原因,并知道如何修改代码消除该异常;但最终还要考虑实际处理逻辑。