理想情况下,ASP.NET和JSF web页面应该包含很少的代码而且只是包含必要的HTML和标签来生成页面的组件,一个页面的事件逻辑驻留于代码文件中。在ASP.NET中,每个web页面与一个相应的子类化页面ASP.NET类的.NET类文件相关联。有时,这些文件被引用为"code-behind"文件。在JSF中,每个web页面都有一个相关联的支持JavaBean类。ASP.NET的code-behind文件和JSF支持bean都包含页面属性(例如标签和输入域)。JSF bean是用Java编写的,而ASP.NET code-behind文件可以使用任何.NET语言(例如VB.NET或C#)编写。ASP.NET code-behind类负责处理相关联的页面事件。这个处理一个组件事件的Java类不必是页面的支持JavaBean。过一会我们再讨论它。值得注意的是,在ASP.NET中分离的code-behind文件可能是不必要的-有可能在页面本身的代码中实现事件处理。然而,一般来说,这被认为是一种不好的编码实践,因为这样以来将会导致混淆HTML和代码问题。
下面是我们的示例ASP.NET和JSF应用程序的两幅快照。由于两种组件的内在特性以及我也没有对之应用匹配的视觉式样,所以它们看起来略微有些不同。在两个页面中,显示一个会议房间表格,还有进一步了解相应房间的View按钮和预订该房间的Reserve按钮。
ASP.NET程序快照
JSF程序快照
上面这些组件都是通过拖动添加的,然后通过修改一个属性面板定制它们的外观和行为。当然,我还可以通过编辑它们在HTML源码中的标签来定制这些组件。在此不赘述。下面,我们重点分析一下在这些web页面背后的代码文件,从而进一步分析事件代码。
我认为,以表格形式显示数据是不错的开始,因为这种情况在应用程序是常见的。为此,ASP.NET和JSF也都没有忽略这种实现,两类被显示的组件都提供了内置功能来实现诸如排序和分页显示等效果。在ASP.NET 2.0之前,ASP.NET就已经提供了许多数据显示组件,其中DataGrid组件是使用最广泛的。另外,ASP.NET 2.0发行中引入了一个新的GridView组件。在本例中,我使用了这种组件,因为它添加了一些新的有用的特性。ASP.NET组件利用ADO.NET技术,这也是整个.NET框架的数据存取技术-ADO.NET提供了一种健壮的对象模型来操作各种类型的数据源。例如,Dataset对象允许你以一种断开的方式来使用数据。这意味着,为了使用数据,你的应用程序并不需要连续地连接到数据库上。一个Dataset还允许你隐蔽在它的接口后面的数据库细节,从而使你以对应用程序的其它部分极小的影响来切换数据库。我在本例中所使用的JSF组件是一个与Java Studio Creator一同发行的表格组件。它使用一个DataProvider对象-它允许你利用JDBC Rowset技术。JDBC Rowset还能使你在断开的情况下以一种易于使用的方式使用数据库。
每个这些启动页面都各自包含一个单一的标签组件实现页面头部。在一个页面的事件处理器中可以存取和修改该页面组件。通过在可视化编辑器中简单地双击该组件,你可以把大多数的事件挂接到一个组件上。而且,这将会把你导向代码文件中,在此你可以添加代码。如我们前面已经注意到的,与web页面相关联的ASP.NET页面类正是你的事件代码将驻留的地方。在JSF中,并不象这样简单。JSF事件遵循Observer设计模式。需要被通知某些事件的对象都要把它们自己注册为该事件相应的听者(listener)。在JSF中共有两种类型的事件:Value Changed事件和Action事件。典型地,Value Changed事件发生在例如列表框选择这样的行为中,而Action事件将产生例如按钮点击这样的用户行为。任何Java类都能够响应于一个web表单的事件。然而,JSF页面的支持bean是实现事件方法的很方便的位置,例如Sun Java Studio Creator就假定你把相应的实现放置于此。
当启动一个ASP.NET应用程序时,位于一个文件web.config中的配置信息被分析和应用。每个ASP.NET应用程序都有一个web.config。我通过把连接串存储到Mysql数据库中来利用这个文件。web.config经常用来存储数据库连接串以达到在代码外保持这个连接。下面展示了web.config文件的一部分代码片断:
<connectionStrings>
<add name="MyConnectionString"
connectionString="Driver={MySQL ODBC 3.51 Driver};server=localhost;database=test;uid=testuser;pwd=testpassword"
providerName="System.Data.Odbc"/>
</connectionStrings>
JSF应用程序依赖于典型的基于Servlet的Java应用程序架构。一个'WEB-INF'文件夹下带有一些子文件夹和一个包含应用程序设置(很类于ASP.NET的web.config)信息的文件web.xml。注意,这里的Mysql连接串并不保存在这个JSF应用程序的web.xml文件中。而是,Studio Creator自动地把该连接添加到服务器的配置文件中,而我们的应用程序通过JNDI(Java命名和目录接口)来存取它-这是一种搜索J2EE服务的常用方式。每一个JSF应用程序的web.xml文件都指定一个FacesServlet类型的Java Servlet。这个FacesServlet负责配置应用程序的相应于它的JSF使用的设置。在一个JSF应用程序中,所有的请求都要"流经"FacesServlet。这个控件Servlet控制基于组件事件结果的应用程序页面之间的流程。这被称作"Front Controller"设计模式。在ASP.NET中没有使用这种级别的间接方式,则是由页面本身控制页面流,称作"Page Controller"设计模式。通过参考存储在一个配置文件faces-config.xml中的页面到页面映射,这个FacesServlet知道如何路由请求。这个配置文件也包含一些我们将要强调的JSF配置信息。
另外,ASP.NET和JSF页面拥有非常相似的生命周期。在前面,我们已经讨论了组件是如何基于用户行为生成事件的,而事件仅是这种生命周期的一部分。从一种高级视图角度来观察一个页面的生命周期,其大致流程为:用户发出一个请求并初始化页面,然后,页面的整个组件树被读入并且组件的状态被恢复,根据用户行为处理事件并更新组件值,最后结果页面被生成到用户端。当然,实际情形可能比这更为复杂些,而且还有一些重要阶段(例如数据校验)掺杂在这些步骤中。在JSF和ASP.NET之间的一个关键区别在于,组件如何被生成到用户。ASP.NET组件在页面上生成自己;而在JSF中,组件能够自我生成,但是更为经常的是,它们把生成代理到特定的Renderer对象(可用于一个RenderKit中)。针对每一种不同类型的描述媒体提供一种不同的RenderKit。每一个JSF实现都会提供一个缺省的HTML RenderKit。这意味着,同一个JSF组件可能被以不同形式生成到一种web浏览器或无线设备上-通过简单地加入一个不同的Render对象,这是一种相当强的生成能力。
一个很方便的添加代码而又不依赖于任何特定类型的用户请求的时机是当页面对象被初始化时。ASP.NET提供了一个Page_Load事件-它当用户作任何类型的页面请求时被调用。在JSF页面中,你可以使用类构造器方法来实现同样的页面初始化逻辑。下面是两种应用程序的相应于用户在数据显示组件上所做动作的事件处理代码。
//ConferenceRooms.Java事件代码:
public String btnViewReservations_action() {
//把选定行的相应的房间的id和name保存起来...
Object id = getValue("#{currentRow.value['conference_rooms.room_id']}");
Object name = getValue("#{currentRow.value['conference_rooms.room_name']}");
ReservationsSessionBean resBean =(ReservationsSessionBean)this.getBean("ReservationsSessionBean");
resBean.setRoomId(id.toString());
resBean.setRoomName(name.toString());
return "view";
}
public String btnMakeReservation_action() {
Object id = getValue("#{currentRow.value['conference_rooms.room_id']}");
ReservationsSessionBean resBean = (ReservationsSessionBean)this.getBean("ReservationsSessionBean");
resBean.setRoomId(id.toString());
return "reserve";
}
//ConferenceRooms.cs事件代码
protected void GrdVwRooms_RowCommand(object sender, GridViewCommandEventArgs e)
{
if (e.CommandName.Equals("Sort")) {
SortDirection sd;
if (((SortDirection)Session["sortRooms"]).Equals(SortDirection.Ascending)) {
sd = SortDirection.Descending;
}
else {
sd = SortDirection.Ascending;
}
Session.Add("sortRooms", sd);
this.GrdVwRooms.Sort(e.CommandArgument.ToString(), sd);
}
else
{
DataKey data = GrdVwRooms.DataKeys[Convert.ToInt32(e.CommandArgument)];
Session.Add("roomId", data.Values["room_id"].ToString());
Session.Add("roomName", data.Values["room_name"].ToString());
if (e.CommandName.Equals("Reserve")) {
Server.Transfer("Reserve.aspx");
}
else
{
if (e.CommandName.Equals("View")) {
Server.Transfer("RoomReservations.aspx");
}
}
}
}
这里的代码有一点不同,Java类被分成了两个方法。这两个Java事件都是Action事件。我把这个ASP.NET应用程序中的所有的事件逻辑都纳入到一个单一的RowCommand事件中并且查问该事件的参数来决定哪一个用户事件发生。在这个特殊的事件处理器中,我实现了view和reserve按钮点击相应事件,还有针对于ASP.NET组件情况的排序请求。两种应用程序都保存roomId和roomName属性以便于应用程序的后面使用。具体请参考本文相应源码中的ConferenceRooms.java和ConferenceRooms.cs文件。记住,这些类文件中的大多数代码都是工具生成的,你可以使用编辑器提供的代码重叠功能来使代码更具可读性。在这些事件方法中,你可以立即存取这个页面组件的值以实现读取和修改。ASP.NET和JSF保存视图状态-通过把它写到隐藏的HTML域中并把它从一个请求传递到另一个请求。当然,使用相同的方法来存储状态的传统型ASP和JSP应用程序仍然是可用的。例如,在ASP.NET和JSF页面中,房间id和name都被放置到用户的Session中,这样它就可以在后面被检索。
注意,在上面的事件代码中,ASP.NET文件调用一个方法"Server.Transfer"实现把控制传递给另一个页面。然而,在JSF事件代码中,你不会找到这样的到另一个页面的直接的参考,而仅有返回串"view"和"reserve"。这是因为,JSF中经由我们更早讨论的faces-config配置文件处理页面流问题,任何使用过开源框架Struts的Java开发者应该对此很熟悉。在ConferenceRooms.jsp文件的faces-config.xml文件中共有两个入口-字符串"view"和"reserve"。在运行时刻,应用程序使用这些字符串查询页面地址。下面是在ConferenceRooms.jsp页面的faces-config文件中发现的XML形式的导航规则。我是使用Java Studio Creator中的一个可视化设计器创建的这些映射。
<navigation-rule>
<from-view-id>/ConferenceRooms.jsp</from-view-id>
<navigation-case>
<from-outcome>view</from-outcome>
<to-view-id>/RoomReservations.jsp</to-view-id>
</navigation-case>
<navigation-case>
<from-outcome>reserve</from-outcome>
<to-view-id>/ReserveRoom.jsp</to-view-id>
</navigation-case>
</navigation-rule>
可视化设计器展示了在Java Studio Creator中的导航规则
这个faces-config文件也是你列举与你的JSP页面文件相关联的所有Java Bean的地方。事实上,你可以通过这个文件配置任何你的应用程序需要参考的Java对象-这些Java对象被参考为'managed beans'。所有的JSF支持bean都将在这个配置文件中被列举为托管bean,但是任何你需要的其它对象也都可以在这里找到。一个托管bean包含完全限定的类名、该bean(是把它存储在应用程序的Request,Session还是应用程序级)的使用范围以及当参考这个bean时要使用的名字。在下面的XML中,列举出了初启web页面的支持bean,还有一个名为ReservationsSessionBean(我们使用它来共享应用程序中的数据)的EJB的入口:
<managed-bean>
<managed-bean-name>ReservationsSessionBean</managed-bean-name>
<managed-bean-class>webreservations.ReservationsSessionBean</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
</managed-bean>
<managed-bean>
<managed-bean-name>ConferenceRooms</managed-bean-name>
<managed-bean-class>webreservations.ConferenceRooms</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
</managed-bean>
让我们再回到前面的应用程序中来,我们的查看预订信息的请求把我们引向ASP.NET版本的RoomReservations.cs.aspx(相应于JSF版本的RoomReservations.