想要用好Struts应用框架,必须了解J2EE Web级JSP和Servlet技术存放共享对象的几种方式。同时,要利用J2EE 开发Web应用程序也必须掌握组件间对象共享的机制。
像Java程序有类级别变量、方法级别变量一样,J2EE Web应用程序有四个对象存放共享对象。这些共享对象存放在那里,以便存放者或者其它程序代码日后使用。这四个对象分别是页面、请求、会话和应用程序,它们都是以数据结构键/值对的形式保存的。同时这四个对象形成了四个级别的共享对象存放地,即应用程序对象中的共享对象是全局性的,在整个应用程序的生命周期内有效(当然主动去掉除外),属于所有的上网用户;会话对象中的共享对象是在一个会话期内有效,属于用户的当前会话;请求对象中的共享对象在一个请求期内有效,属于用户发送的当前请求;页面对象中的共享对象只属于当前页面的执行实例。本文主要分析共享对象的设置和访问方法,包括共享对象的有效范围、具体访问方法、辅助显示手段和多线程下的实现策略。
在JSP中访问共享对象
Servlet运行时已经准备好了这些范围对象,如表1所示。
表1 JSP中的共享对象
|
变量名 |
变量类名 |
对象可访问范围 |
页面 |
pageContext |
javax.servlet.jsp.PageContext |
在执行某一个JSP时,Servlet运行时会为它初始化pageContext变量,这个变量可以被整个JSP代码访问,包括INCLUDE指示符插进来的代码。 |
请求 |
ruquest |
javax.servlet.http.HttpServletRequest |
用户提交一个HTTP请求给Servlet容量,Servlet运行时会把请求封装成HttpServletRequest的一个实例,在JSP中表现为request变量。能访问pageContext的JSP代码也能访问request,另外被处理这个请求的JSP代码FORWARD到的JSP代码也能访问。 |
会话 |
session |
javax.servlet.http.HttpSession |
一个HttpSession会话由被创建到关闭或失效期间的用户请求组成。处理这些请求的JSP可以访问到这期间的session对象中的共享对象。在会话关闭或失效时,这些对象会丢失。 |
应用程序 |
application |
javax.servlet.ServletContext |
这个对象在应用程序的整个生命周期间都有效,存放在这个对象内的数据任何JSP都能访问到。 |
在Servlet中访问共享对象
Servlet中的共享对象如表2。
表2 Servlet中的共享对象
请求 |
SERVLET类的一系列服务方法的request参数。 |
javax.servlet.http.HttpServletRequest |
用户提交一个HTTP请求给Servlet容器,Servlet运行时会把请求封装成HttpServletRequest的一个实例,并作为Servlet服务方法的request参数传递给Servlet。这个Servlet也可以把这个实例传递给其它Web组件。 |
会话 |
request.getSession()或者request.getSession(boolesn)方法获得。 |
javax.servlet.http.HttpSession |
一个HttpSession会话由被创建到关闭或失败期间的用户请求组成。处理这些请求的Servlet可以访问到这期间的session对象中的共享对象。在会话关闭或失效时,这些共享对象会丢失。 |
应用程序 |
SERVLET的.getServletContext() |
javax.servlet.ServletContext |
这个对象在应用程序的整个生命周期间都有效,存放在这个对象内的数据任何Web组件都能访问到。 |
资源组合
在JSP技术中,有两种把资源片断组合成一个资源的技术:include 指示符和jsp:include元素。指示符的语法为:
<%@ include file="fragmentresource.jsp" %>
|
当一个JSP被翻译成Servlet时,它会被处理。jsp:include元素的语法是:
<jsp:include page="included.jsp"/>
|
当这个JSP页面被执行时,它会被处理。指示符是代码的组合,元素则是结果的组合。fragmentresource.jsp和主页面具有一样的上下文,如页面对象;而included.jsp不具有和主页面一样的页面对象,但请求对象是同一个。
在Servlet中,RequestDispatcher.include(request,response)实现结果的整合,示例代码如下:
RequestDispatcher dispatcher =request.getRequestDispatcher("/template.jsp");
if (dispatcher !=null)
dispatcher.include(request,response);
|
控制传递
在利用RequestDispatcher.forware(request,response)把控制传给另一个Web组件设置形成一个控制管道时,要严格遵循“前面的组件处理request,最后的组件处理response”的准则。前面的组件甚至不能企图获取response输出流的引用。这个控制管道中的组件具有同一个request对象,不具有相同的pageContext对象(针对JSP)。Servlet中传递控制的例子如下:
RequestDispatcher dispatcher =request.getRequestDispatcher("/template.jsp");
if (dispatcher !=null)
dispatcher.forward(request,response);
|
JSP中传递控制的例子如下:
<jsp:forward page="/main.jsp"/>
|
在JSP中,可以通过jsp:param元素来增加请求对象的参数,适用于jsp:include和jsp:forward两元素。 示例如下:
<jsp: forward page="included.jsp">
<jsp:param name="param1 " value=="value1"/>
</jsp:include>
|
显示共享对象
图1 显示共享信息类图
在网页上显示各个级别的共享对象是一个非常不错的调试手段。下面的显示共享对象类图(如图1)实现了这个功能。它以类org.i18.struts.AttributeUtils为核心,这个类负责把四个对象的共享对象保存为键/值对的HashMap对象。通过它可以得到请求对象中的共享对象及参数信息,以及页面对象、会话对象、应用对象这些共享对象的函数接口。在JSP页面中,通过下面的代码可以给这个类的对象提供输入:
<jsp:useBean id="bean3" scope="application"
class="org.i18.struts.AttributeUtils" />
<jsp:setProperty name="bean0" property="object" value="<%= pageContext %>" />
|
JSP页面可以利用Struts提供的logic标签库显示这些共享对象:
<li> 这是页对象内的共享对象 </li>
<table align="center" cellpadding="5" border="0">
<tbody valign="center">
<tr>
<td class="header"> 共享对象名 </td>
<td class="header"> 共享对象相关内容 </td>
</tr>
<logic:iterate id="element" name="bean0" property="pageProp" >
<tr><td>
<bean:write name="element" property="key"/>
</td>
<td>
<bean:write name="element" property="value"/>
</tr>
</logic:iterate>
</tbody>
</table>
|
在Servlet中,AttributeDisplayHelper帮助者类完成JSP中logic标签库相应的功能。帮助者类完成工作后,AttrServlet把帮助者类完成的结果放在request中的一个共享属性Attr中,接着把控制传给servletAttr.jsp,再由它访问AttrServlet在request中设置的共享属性Attr,并显示结果。
使用者可以通过attr.jsp、AttrServlet的url映射和index.jsp的提交按钮来查看当前上下文所有级别的共享对象。
关于多线程问题
J2EE系列规范中,EJB规范保证了组件开发者在单线程的环境下编程,但Servlet规范没有规定Servlet的系列服务方法在单线程模式下运作,所以开发者在使用共享对象时要注意线程同步问题。一个比较通用的原则是:在一个专职的组件中设置共享对象,当设置和访问破坏数据的一致性时,使用Java的同步控制。
一个Servlet(包括JSP)的生命周期由其所在的容器控制。当有一个Servlet请求时,容器执行如下步骤:
1.如果此Servlet的实例不存在,容器先装载Servlet的类代码,创建一个Servlet实例,接着调用这个实例的init方法。
2.如果此Servlet的实例存在,容器分配一个处理用户请求的工作线程。如果Servler实现了SingleThreadModel接口,工作线程会在这个实例上同步,并且在取得访问权限后调用实例的service方法,否则直接调用实例的service方法。javax.servlet.http.HttpServlet的service方法会根据用户的HTTP请求类型调用相应的doxxx方法。
3.只有当所有的线程从这个实例中退出,容器在回收这个实例时才会调用这个实例的destroy方法。
Servlet实例线程图(如图2)体现了这个生命周期模型。
图2 Servlet实例线程图
虽然可以让所有的Servlet实现SingleThreadModel接口,但这会严重影响程序的性能。要解决多线程的同步问题,我们首先要分析共享对象的访问模式。在一个Web程序中,共享对象按访问模式可以分为以下两类:一次设置、多次读取的共享对象和多次设置、多次读取的共享对象。
一次设置、多次读取
对于这种共享对象,可以开发一个事件监听器,监听程序启动和停止事件,代码如下:
package org.i18.listen
import org.i18.utils.*;
import javax.servlet.*;
import util.Counter;
public final class ContextListener implements ServletContextListener {
private ServletContext context =null;
public void contextInitialized(ServletContextEvent event){
context =event.getServletContext();
SynObject synObject = new SynObject();
//在这里把共享对象放在应用对象中
context.setAttribute("SYNOBJECT",synObject);
}
public void contextDestroyed(ServletContextEvent event){
context =event.getServletContext();
//清除保存在应用对象中的共享属性
context.removeAttribute("SYNOBJECT ");
}
}
|
这样,当Web应用程序启动时,容器会调用监听器,从而设置共享对象。共享对象的访问只需直接调用getAttribute方法即可。
多次设置、多次读取
对于多次设置、多次读取的共享对象,必须利用Java的同步机制,访问步骤如下:
第一步,首先设计一个同步类。这个同步类的代码非常简单:
package org.i18.utils
public final class SynObject{
}
|
以上代码可以看出,它其实什么都没做,但继承了Object关于同步的方法和机制。
第二步,把这个类的实例放到应用对象中作为一个共享对象。可以看出这个同步对象属于一次设置、多次使用的共享对象。在应用程序启动事件监听器中设置它,请参见前面的代码。
第三步,设置共享对象。如果有对象需要整个应用程序共享,可以在Servlet的service中利用同步机制来设置:
public void doGet (HttpServletRequest request,HttpServletResponse response)
throws ServletException,IOException {
ServletContext context= getServletContext();
//得到同步对象
Object obj = context.getAttribute("SYNOBJECT");
//获取同步钥匙
synchronized(obj){
//先检查是否存在这个共享对象
Object obj2 = context.getAttribute("Attr");
if (obj2 == null) {
//不存在,设置共享对象
obj2 = new TestBean();
context.setAttribute("Attr",obj2);
}
}
}
|
第四步,读取共享对象。当需要访问多次设置、多次访问的共享对象时,同样需要利用同步机制,代码如下:
public void doGet (HttpServletRequest request,HttpServletResponse response)
throws ServletException,IOException {
ServletContext context= getServletContext();
//得到同步对象,因为这个对象是一次设置、多次读取型的,不需要同步
Object obj = context.getAttribute("SYNOBJECT");
//获取同步钥匙
synchronized(obj){
//得到需要的共享对象
Object obj2 = context.getAttribute("Attr");
}
}
|
会话对象内共享对象的多线程问题可以和应用对象一样处理。请求对象和页面对象正常情况下是线程安全的,除非开发者自己引入了额外的线程。
本文分析了开发好的J2EE应用必须掌握的、组件间对象共享的技术,这种分析技术同样适用于EJB中。在EJB容器中,信息存放的位置变成了实现JNDI的服务提供者,大家通过JNDI的接口方法查询、绑定共享对象。需要注意的是,同步对象不能在JNDI中实现,因为大家搜索出来的不是同一个内存对象。
|