UML软件工程组织

J2EE组件间共享对象技术
作者:龚永生    本文选自:开放系统世界—赛迪网  2003年02月26日
想要用好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中实现,因为大家搜索出来的不是同一个内存对象。

 


UML软件工程组织