怎么样使用.NET实现JavaTM Pet Store J2EETM蓝图应用程序?
怎么样使用.NET实现JavaTM Pet Store J2EETM蓝图应用程序?
使用Microsoft .NET实现Sun Microsystem的JavaTM Pet Store J2EETM 蓝图应用程序
10, 2001
内容
摘要
必需的代码行数的比较
性能和扩展性比较
必需的CPU %资源比较
介绍
功能预排
Microsoft .NET Pet Shop
该应用程序的体系结构
数据库
中间层
表现层
ASP.NET输出缓存
安全
比较代码尺寸
为什么.NET代码基数如此之小且效率更高
性能和扩展性比较
新特性
XML Web Service
移动设备支持
结论
附录1:计算代码行
关于Java Pet Store的版权以及法律信息,请参看本文的最后一页。
摘要
该研究的目的就是理解Sun的J2EE蓝图应用程序——Sun Java Pet Store,并使用Microsoft .NET实现与之相同的功能。根据该Sun的J2EE实现最好的示例应用程序的.NET版本,消费者可以从多个侧面直接比较Microsoft .NET和基于J2EE的应用程序服务器。另外,该研究比较了每个平台的体系结构和编程模型,从而估价出相对的开发员生产率。其中同等地比较了对象、文件、以及在每个平台上通过三层结构实现相同功能的应用程序的代码行数。该研究还表明可以很容易地扩展.NET Pet Shop应用程序,使其支持XML Web Service(基于SOAP和UDDI标准)以及移动设备。要得到基于.NET实现的完全的源代码请访问http://www.gotdotnet.com/compare。Java Pet Store应用程序的源代码在Java 2平台企业版蓝图网站http://java.sun.com/j2ee/blueprints/index.html上是公开的。
必需的代码行数的比较
(该代码计数对照了每个平台上实现完全相同的应用程序功能。在本文的后面将会涉及到关于精确的代码计数的完全细节信息,请参看附录1。)
性能和扩展性比较
基于作为Oracle 9i应用程序服务器挑战一部分的Oracle针对完全调整和优化的Java Pet Store发布的基准数据。参看http://www.gotdotnet.com/compare上基准的Microsoft? .NET vs. Sun? Microsystem’s J2EE:性能和扩展性的研究有关性能和扩展性比较的全部的细节信息。
必需的CPU %资源比较
基于作为Oracle 9i应用程序服务器挑战一部分的Oracle针对完全调整和优化的Java Pet Store发布的基准数据。参看http://www.gotdotnet.com/compare上基准的Microsoft? .NET vs. Sun? Microsystem’s J2EE:性能和扩展性的研究有关性能和扩展性比较的全部的细节信息。
介绍
Java Pet Store是一个依照由Sun Microsystem维护的J2EE蓝图的分布式应用程序的参考实现。现在,该示例应用程序发行了1.1.2版,是Sun创建的,用来帮助开发员和设计者理解如何使用J2EE技术,以及J2EE平台组件如何组合在一起工作。Java Pet Store蓝图包括文档、关于Enterprise Java BeansTM (EJB)体系结构的完全的源代码、Java Server Pages(JSP)技术、标签库、以及建立应用程序的servlet。另外,通过特定的例子,Java Pet Store蓝图示范了某些模型和设计模型。对于J2EE来说,Java Pet Store是实现最好的参考应用程序,并且再分布在下面的基于J2EE的应用程序服务器产品中:
? IBM? Websphere? 4.0
? BEA? WebLogic? 6.1
? Oracle? 9i应用程序服务器
? Sun Microsystems iPlanet?
完全的Java Pet Store包含三个示例应用程序:
1. Java Pet Store: 主要的J2EE蓝图应用程序。
2. Java Pet Store管理员:Java Pet Store的管理员模块。
3. Blueprints Mailer:在一个较小的包内的一个小型的表现某些J2EE蓝图设计方针的应用程序。
Java Pet Store的原始版本可以使用下面的数据库:Oracle,Sybase?和Cloudscape?。IBM已经创建了该应用程序的DB2端口。该应用程序在Java 2平台企业版蓝图站点http://java.sun.com/j2ee/blueprints/index.html是公开可用的。
Java Pet Store应用程序被设计成一个J2EE“实现最好”的应用程序。该应用程序重点强调那些用于显示现实世界编码示例的要素和技术。
功能预排
原始的应用程序Java Pet Store设想为是一个消费者可以在线购买宠物的电子商务应用程序。当你一开始使用该应用程序,你就能浏览并查找从狗到爬行动物范围内的各种类型的宠物。
一个使用Java Pet Store典型的会话如下:
Homepage - 这是当用户第一次开始使用该应用程序时装载的主页。
类别视图 –这里有5个顶层的类别:每个类别都有几个与之关联的产品。
产品 – 当在一个应用程序内选中一个产品时,该产品的所有变量都显示出来。典型地,产品的变量为雌或雄。
产品细节–每个产品变量(用项表示)都有一个详细的视图用来显示该产品描述、产品图像、价格、以及库存量。
购物车–允许用户操作购物车(加、减、以及更新行项)。
结帐–结帐页面以只读方式显示购物车。
登陆重定向 –当用户在结帐页面上选择“Continue”,如果用户还没有注册,就转向到登陆页面。
注册检验–在验证登陆到站点后,用户于是就重定向到信用卡和帐单地址表单。
确认定购–显示帐单和运送地址。
提交定购–这是定购处理流水线内的最后一步。此刻,该定购在这一点上提交给数据库。
Microsoft .NET Pet Shop
.NET Pet Shop的目标就集中于Java Pet Store自身(在该阶段并没有把管理和mailer组件引入到.NET[注意在所有的代码计数比较中,我们只针对功能实现做了1:1的代码计数,没有针对Java管理和mailer应用程序计算其代码行。])。除了实现Java Pet Store应用程序的功能外,还额外增加了两个目标:
- 在.NET和J2EE的实现之间,比较和对照这两个实现最好的应用程序的体系结构、编程逻辑以及全部的代码尺寸。在该文档内包含了这些信息。
- 提供在不同用户负载下每个应用程序执行方面相关的性能基准数据。在本文中通过详细讨论基准的Microsoft? .NET vs. Sun? Microsystem’s J2EE:性能和扩展性的研究(在http://www.gotdotnet.com/compare可以得到)总结出了这些信息。
一个.NET Pet Shop的示例可以参看图1。
图1. Microsoft .NET Pet Shop
.NET Pet Shop的总体逻辑体系结构详解如图2所示。
图2. .NET Pet Shop逻辑体系结构
这里有三个逻辑层:表现层、中间层以及数据层。这三层结构将分布式应用程序的不同方面清楚地分开。事务逻辑被封装到一个.NET部件(实现为一个C#类库)内。数据库访问通过一个处理所有和SQL Server管理的提供者交互的类实现。通过存储过程访问存储在数据库中的数据。该应用程序体现了一个使用.NET的完全的逻辑三层实现,并且图解说明了针对Microsoft.NET平台实现最好的代码。
图3. 示例.NET Pet Shop物理部署图
该应用程序的体系结构
前面部分针对的是该应用程序的高层体系结构。为了更好的理解应用程序是如何工作的,这部分将预排一部分代码,示范表现层、中间层以及数据层之间的交互。为了进一步阐明该设计,我们将详细地研究一下购物车在每层上的交互和实现细节,如图4所示。
图4. 体系结构预排
这里有多种设计n层应用程序的方法。.NET Pet Shop的实现采用了一种数据驱动的方法。
数据库
好几种数据库供应商格式对于Java Pet Store的数据库来说都是可用的。我们采取的第一步就是把Java Pet Store的数据库的模式(schema)导入到SQL Server 2000。这不需要改变Sybase版本的模式。该数据库总体的表结构如下:
表名 | 用途 |
Account | 描述基本的消费者信息。 |
BannerData | 存储ad banner信息。 |
Category | 分类类别(即鱼、狗、猫等)。 |
Inventory | 产品存货状态。 |
Item | 单个产品的细节信息。 |
LineItem | 定购详情。 |
Orders | 消费者定购的订单。一个定单包含一个或多个行项。 |
OrderStatus | 订单状态 |
Product | 分类产品。每个产品可能有一个或多个变量(项)。一个典型的变量通常是雌或雄。 |
Profile | 消费者用户简介。 |
Signon | 消费者的登录表 |
Supplier | 供应商有关的信息。 |
表1. 数据库表名
.NET Pet Shop完全的物理数据库模式如图5所示。
图5. .NET Pet Shop物理数据库模式
经过仔细的考虑之后,对关系做了某些小的改动,从而改善总体的数据完整性。然而这些改动并不是必需的,并且不会改善应用程序的性能,它们为阐明数据库设计增加了相当大的好处。改动为:
? 在Signon和Account表之间为userid和username列增加了外键关系。
? 在Account和Profile表之间为userid和username列增加了外键关系。
? 在Profile和BannerData表之间为每个表的favcategory列增加了外键关系。
? 去掉了OrderStatus表linenum列上的主键约束。
上面提到的改动在图5上显示。
Sun Java Pet Store在其中间层Enterprise Java Bean(EJB)大量使用了Bean Managed Persistence (BMP)。本质上,这为对象提供了一种将其状态保存到数据库的机制。.NET Pet Shop采取了与之不同的方法,中间层组件调用数据库中的存储过程。对于分布式应用程序来说,在数据库层使用存储过程确实是一种最好的实施手段。它提供了与中间层更为清楚的分离,还有助于阐明事务上下文和范围。在存储过程中只封装了基本的查询,业务逻辑放在中间层.NET类内。
存储过程名称 | 用途 |
UpAccountAdd | 为消费者数据库添加一个帐户。 |
UpAccountGetAddress | 为消费者添加一个新的地址。当帐单和运送信息不同时使用该存储过程。 |
UpAccountGetDetails | 返回系统中消费者帐户的详细资料。 |
upAccountLogin | 用来验证登录到站点内的用户。 |
upAccountUpdate | 更新消费者帐户信息。 |
upCategoryGetList | 返回目录中的一列种类。 |
upInventoryAdd | 为特定的项添加存货信息。 |
upInventoryGetList | 得到存货状态。 |
upItemAdd | 将一个产品变量(称为一项)添加到目录中。 |
upItemGetDetails | 得到和一个特定产品变量有关的产品信息。 |
upItemGetList | 返回属于特定产品的一列项(产品变量)。 |
upItemGetList_ListByPage | 返回属于特定产品的一列项(产品变量)。按照用户定义的页尺寸返回数据。 |
upOrdersAdd | 向数据库添加一项消费者定购。这个存储过程使用OpenXML和SQL Server事务完成其工作。 |
upOrdersGet | 用来得到一列消费者的定购。 |
upOrdersGetDetails | 返回完全的定购的详细资料。 |
upOrderStatusGet | 返回一个特定的定购的状态。 |
upProductAdd | 将一个产品添加到目录中。 |
upProductGetList | 返回目录中的一列产品。 |
upProductGetList_ListByPage | 返回目录中的一列产品。按照用户定义的页尺寸返回数据。 |
upProductSearch | 执行一个产品查找。 |
upProductSearch_ListByPage | 执行一个产品查找。按照用户定义的页尺寸返回数据。 |
upProfileGetBannerOption | 返回消费者显示广告条的偏好。 |
upProfileGetListOption | 返回消费者显示“Pet Favorites”列的偏好。 |
表2. 数据库存储过程
为了增强该应用程序的设计,SQL Server 2000的OpenXML特性用来返回XML文档,而不是传统的行集合。在定购结帐过程频繁地使用了这一点,因为它简化了在中间层组件和数据库存储过程之间必需处理的参数的数目。
中间层
Sun使用EJB来实现应用程序的商业逻辑。Sun通过混合使用实体和会话Bean来处理所有的逻辑来实现该应用程序。
EJB名称 | EJB类型 |
Account | 实体Bean |
Inventory | 实体Bean |
Order | 实体Bean |
ProfileMgr | 实体Bean |
ShoppingCart | 状态会话Bean |
ShoppingClientController | 状态会话Bean |
AdminClientController | 无状态会话Bean |
Catalog | 无状态会话Bean |
Customer | 无状态会话Bean |
Mailer | Stateless Session Bean |
表3. Java Pet Store商业逻辑
.NET Pet Shop中间层商业逻辑封装到单个.NET集合内。名为Pet Shop.Components的命名空间如图6所示。
图6. 中间层组件类窗口和方案资源管理器文件窗口
这里有9个类,每个类在其自己的.cs文件内实现。生成的集合命名为Pet Shop.Components.dll。
Class名 | 用途 |
BasketItem | 购物车内的一个行项。 |
Customer | 帐号管理和登录确认。 |
Database | 使用SQL Server Managed Provider的ADO.NET数据访问。 |
Error | 用于日志错误的帮助功能。 |
Item | 代表一个产品变量。 |
Order | 在线定购事务。 |
Product | 目录中的一个产品。 |
Profile | 用来更新消费者的简介信息。 |
ShoppingCart | 购物车功能。 |
表4.中间层组件
对于代码预排,我们将分析Java和.NET的购物车功能,首先从Java Pet Store开始。
ShoppingCart EJB是一个状态会话Bean,意味着当客户端请求的时候在服务器端该对象保留其内容特定的时间。任何购物车页面的一个共同的任务是向用户栏添加项。在添加一项前,该应用程序必须首先决定添加的是哪个产品。为了得到产品信息,我们看一下CatalogDAOImpl.java:
public Product getProduct(String productId, Locale locale) throws
CatalogDAOSysException
{
String qstr =
"select productid, name, descn " +
"from " +
DatabaseNames.getTableName(DatabaseNames.PRODUCT_TABLE, locale) +
" where " +
"productid='" + productId + "'";
Debug.println("Query String is:" + qstr);
Product product = null;
Statement stmt = null;
ResultSet rs = null;
try {
getDBConnection();
stmt = dbConnection.createStatement();
rs = stmt.executeQuery(qstr);
while (rs.next()) {
int i = 1;
String productid = rs.getString(i++).trim();
String name = rs.getString(i++);
String descn = rs.getString(i++);
product = new Product(productid, name, descn);
}
} catch(SQLException se) {
throw new CatalogDAOSysException("SQLException while getting " +
"product " + productId + " : " + se.getMessage());
} finally {
closeResultSet(rs);
closeStatement(stmt);
closeConnection();
}
return product;
}
Java Petstore中的代码片段。
要指出的是,在类定义内中间层组件包含一个SQL语句(参看qstr字符串对象)。.NET Pet Shop采取了一种不同的方法,并清楚地把所有的数据库逻辑封装到了存储过程内。
public SqlDataReader GetList_ListByPage(string catid, int currentPage,
int pageSize, ref int numSearchResults)
{
// create data object and params
SqlDataReader dataReader = null;
try
{
// create params for stored procedure call
Database data = new Database();
SqlParameter[] prams = {
data.MakeInParam("@cat_id", SqlDbType.Char, 10, catid),
data.MakeInParam("@nCurrentPage", SqlDbType.Int, 4, currentPage),
data.MakeInParam("@nPageSize", SqlDbType.Int, 4, pageSize),
data.MakeOutParam("@totalNumResults", SqlDbType.Int, 4) };
// run the stored procedure
data.RunProc("upProductGetList_ListByPage", prams, out dataReader);
numSearchResults = dataReader.RecordsAffected;
}
catch (Exception ex)
{
Error.Log(ex.ToString());
}
return dataReader;
}
.NET Pet Shop中的代码片断
你可能注意到.NET版调用了RunProc()。这是我们的可重用数据库类中的一个方法,并且在这种情况下将执行一个存储过程返回一个SqlDataReader(和ADO中的read-only、forward-only游标非常相似):
public void RunProc(string procName, SqlParameter[] prams,
out SqlDataReader dataReader)
{
SqlCommand cmd = CreateCommand(procName, prams);
dataReader = cmd.ExecuteReader(System.Data.CommandBehavior.CloseConnection);
}
.NET Pet Shop数据层的代码片断
这个NET Pet Shop的代码片断实质上就是该应用程序内所有数据访问的框架。Java应用程序数据访问机制从EJB到EJB变动。这里有一些会话对象,但是对于大部分来说,组件是由bean管理的,意味着对象本身负责为数据库保留自身,因此需要内置的SQL代码。
.NET Pet Shop使用ASP.NET会话状态存储购物车的内容(与Java Pet Store的功能匹配)。该会话状态存储在过程内,但是ASP.NET也包含运行专用的状态服务器的能力,并还支持SQL Server数据库会话。
表现层
Pet Shop的表现层是使用ASP.NET Web Form结合用户空间写的。由于使用Visual Studio .NET创建该站点,因此使用代码在后的方式,在此每个ASPX页面的代码封装在一个单独的文件内。Java Pet Store频繁使用了Servlet(或者小型的发布HTML的java服务器程序)。Servlet使用一种称为Model View Controller的设计模型,允许以几种不同的方式显示和操作数据。除了用户控件外,.NET Pet Shop使用ASP.NET服务器控件实现相同的功能。
既然.NET Pet Shop应用程序使用服务器控件和会话状态,因此要仔细地考虑其参数从而达到高性能。该配置的详细情况请参看表5。
ASP.NET WebForms | EnableSessionState | EnableViewState |
Cart.aspx | true | True |
Category.aspx | true | True |
CheckOut.aspx | readonly | True |
CreateNewAccount.aspx | false | True |
Default.aspx | false | False |
EditAccount.aspx | false | True |
Help.aspx | false | False |
OrderAddressConfirm.aspx | readonly | False |
OrderBilling.aspx | true | True |
OrderProcess.aspx | readonly | False |
OrderShipping.aspx | true | True |
Product.aspx | false | True |
ProductDetails.aspx | false | False |
Search.aspx | false | True |
SignIn.aspx | false | False |
SignOut.aspx | true | False |
ValidateAccount.aspx | false | False |
VerifySignIn.aspx | false | False |
Table表 5. WebForm会话状态和窗口状态设置。这些设置作为一行命令包含在每个aspx文件的顶部。
ASP.NET输出缓存
提高一个数据库驱动应用程序的性能的最好的方法之一就是取消每个查询都命中数据库。ASP.NET提供了各种可以大大提高应用程序性能的缓存机制。使用输出缓存取出页面的内容并把结果存储在内存中。后面对该页的请求就来自缓存,且不需要再次访问该数据库。由Sun实现的Java Pet Store对此不具备非常简单的实现方式。虽然不同的J2EE应用程序服务器具有输出缓存能力(比如IBM Websphere 4.0),但是这个特性随产品而不同。使用这些特性,在使用特定的J2EE应用程序服务的时候,需要修改Java Pet Store源代码。
.NET Pet Shop应用程序还使用部分页(片段)缓存来缓存页的不同区域。比如,缓存出现在每页顶部的报头信息。然而,报头信息依赖于用户是否注册(于是,出于技术的原因我们不得不缓存该页的两个不同的版本)。通过使用OutputCache命令上的VaryByCustom,ASP.NET能容易地实现这一点。使用VaryByCustom仅需要重载GetVaryByCustomString方法从而得到报头的定制的缓存。
public override string GetVaryByCustomString(HttpContext context, String arg)
{
// cache key that is returned
string cacheKey = "";
switch(arg)
{
// Custom caching for header control. We want to create two views
// of the header... one if the user is logged in and another if
// they are logged out.
case "UserID" :
cacheKey
= (context.User.Identity.Name == "") ? "UserID_Out" : "UserID_In";
break;
}
return cacheKey;
}
ASP.NET代码片断
表6总结了所有的缓存设置。
ASP.NET WebForms | 缓存设置 | 持续时间 | 注释 |
ControlHeader <%@ OutputCache |
<%@ OutputCache Duration="43200" VaryByParam="none" VaryByCustom="UserID" %> |
12小时 | 使用定制的缓存调用GetVaryByCustomString。返回两个缓存关键字中一个。一个用于用户登录,另一个用于用户注销。 |
Default |
<%@ OutputCache Duration="43200" VaryByParam="none" %> |
12小时 | 主页上的静态内容。可以缓存更长的时间。 |
Help |
<%@ OutputCache Duration="43200" VaryByParam="none" %> |
1小时 | 帮助页上的静态内容。可以缓存更长的时间。 |
Category |
<%@ OutputCache Duration="3600" VaryByParam="*" %> |
1小时 | 针对每个类别不同的缓存。 |
Product |
<%@ OutputCache Duration="3600" VaryByParam="*" %> |
1小时 | 针对每个产品不同的缓存。 |
ProductDetails |
<%@ OutputCache Duration="60" VaryByParam="*" %> |
1小时 | 既然显示了项的数量,那么持续时间就比较短。 |
Search |
<%@ OutputCache Duration="3600" VaryByParam="*" %> |
1小时 | 既然缓存通用的查询且当某人查找猫的时候不需要命中数据库,所以这是非常有用的。 |
表6. .NET Pet Shop缓存设置
正如前面提到的,除了输出缓存WebForm外,.NET Pet Shop还充分利用片段缓存,从而允许缓存页的区域。缓存了每页上出现的报头ASP.NET用户控件。
ASP.NET用户控件 | EnableViewState | 输出缓存 |
Control_Address.ascx | true | no |
Control_Banner.ascx | true | no |
Control_Cart.ascx | true | no |
Control_FavList.ascx | true | no |
Control_Header.ascx | false | yes, VaryByCustom |
Control_StaticAddress.ascx | true | no |
Table 7. 用户控件显示状态和输出缓存设置
安全
任何应用程序的关键组件都是应用程序的安全因素。Java Pet Store和.NET Pet Shop都综合了多种的安全机制来防止侵入和攻击。.NET Pet Shop使用基于ASP.NET Form的验证。这些设置集中在web.config文件内:
<!-- AUTHENTICATION
This section sets the authentication policies of the application.
Possible modes are "Windows", "Forms", "Passport" and "None"
-->
<authentication mode="Forms">
<forms name="Pet ShopAuth"
loginUrl="SignIn.aspx"
protection="All"
path="/" />
</authentication>
Java Pet Store显示出了相似的功能,但是安全元数据是在Web组件展开描述符内表示的:
<web-app>
<login-config>
<auth-method>FORM</auth-method>
<form-login-config>
<form-login-page>login.jsp</form-login-page>
<form-error-page>login.jsp</form-error-page>
</form-login-config>
<login-config>
<web-app>
Java Pet Store和.NET Pet Shop的安全行为是相同的:ASP.NET允许所有的安全配置集中到一个配置文件内,而Java版本有数个配置文件。
比较代码数量
通过比较该应用程序在两种版本下实现代码的全部行数,从而理解Microsoft .NET较Sun的J2EE开发平台开发员相对的生产率。一个详尽的代码计数显示了实现Java Pet Store的代码行数是在.NET Pet Shop内实现相同功能代码行数的3.5倍以上。下面的表按层做了更为完全的分类。
Microsoft .NET Pet Shop |
Sun Java Pet Store [Java Pet Store的代码计数只包括那些导入到.NET的模块。] |
总体比较 | |||
代码行数 | # 文件 | 代码行数 | # 文件 | ||
表现层 | 2,865 | 67 | 5,891 | 131 | J2EE版的UI代码的数量是.NET版的两倍。 |
中间层 | 710 | 10 | 5,404 | 121 | J2EE版的中间层代码的数量是.NET版的7.6倍。 |
数据库 | 761 | 2 | 412 | 1 | J2EE版数据层代码的数量比.NET版多40%。 |
配置 | 74 | 1 | 2,566 | 19 | J2EE版的配置代码的数量是.NET版的34.5倍。 |
总计 | 4,410 | 80 | 14,273 | 273 | J2EE版的总体的代码数量是.NET版的3.2倍。 |
表 8. 按层分类的代码行数。
图 7:按层分类的代码行数。
.NET总结 | Java总结 | |
代码行数 | 4,410 | 14,273 |
文件的数目 | 80 | 273 |
文件夹计数 | 8 | 110 |
尺寸(字节) | 465,706 | 811,094 |
表 9. 其他比较。
完成代码计数的机制在附录1中有详尽的描述。
为什么.NET代码基数如此之小且效率更高
Java Pet Store包含几个复杂的设计模式,从而使得重用J2EE蓝图应用程序对开发员充满挑战。Java Pet Store使用一种完全面向对象的方法,导致了中间层对象非常庞大。使用J2EE推荐的BMP以及其他的EJB功能增加了设计的复杂性。另一方面,.NET Pet Shop采用了一种改进的组件方法,该方法使用小型、轻量的对象,并利用了几个.NET特性,从而显著减少了总体的代码尺寸并提高了开发员的生产率。比如,使用ASP.NET服务器控件和ASP.NET Web Form有助于显著减少开发员服务器端和客户端的编程负担。同样,Java代码还必须使用Model View Controller设计模式手工更新页,而.NET代码可以象DataGrid一样仅仅绑定ASP.NET服务器控件。
在数据层,实质上,该应用程序的两个版本的核心的数据库模式(表和所有的关系和约束)是相同的。在这两个程序中,模式粗略地有400行SQL代码。由于增加了3个额外的外键约束来确保数据完整性,所以.NET版的行数稍微多了点。
然而,.NET Pet Shop的数据层比Java Pet Store大的原因如下:.NET Pet Shop使用存储过程封装了所有的数据库插入、更新、和删除。Java Pet Store EJB使用BMP来处理插入、更新、以及删除。这意味着Java Pet Store的SQL语句放在中间层代码中,而不是数据层。于是Java的该功能计算在中间层而不是数据层。我们相信使用存储过程的.NET实现不但是一种比J2EE BMP模型更为高级的设计模式,而且还能提供比其更好的性能和扩展性。该应用程序的两个版本的基准数据证实了这一点。
最后,还有3个存储过程支持“额外的” Web Service和移动特性。Java Pet Store并不具备这些特性。在.NET Pet Shop应用程序数据层代码计数中,我们把这些也计算在内了,从而消费者下载.NET代码就可以自己进行代码计数了——而不用去掉任何特性。
性能和扩展性比较
.NET Pet Shop经过基准测试并与一个最优的、高度调整的、并作为Oracle公司基准的Java Pet Store版本进行比较。这里总结了该应用程序总体性能的结果,在基准的Microsoft? .NET vs. Sun? Microsystem’s J2EE:性能和扩展性的研究(http://www.gotdotnet.com/compare)中有完全的描述。这里显示的J2EE的所有结果引自其白皮书Oracle9iAS J2EE性能研究结果中Oracle公开发布的数据。在同样的硬件配置下使用NET Pet Shop精确的复制了Oracle Java Pet Store基准测试,硬件配置包括2-CPU中间层应用程序服务器和4-CPU数据库服务器。
450个并发用户下,每页用户响应时间 | 450个并发用户下,App服务器CPU利用率 | 1秒平均响应时间下支持的用户负载 | |
带有ASP.NET 输出缓存的.NET | Microsoft .NET Pet Shop比 Java Pet Store快28倍 | Java Pet Store需要使用的CPU要高出6倍 | .NET Pet Shop比Java Pet Store支持的并发用户多6倍 |
不带ASP.NET输出缓存的.NET | .NET Pet Shop 比Pet Store快8倍 | Java Pet Store 需要使用的CPU要高出4.3倍 | .NET Pet Shop比Java Pet Store支持的并发用户多4倍 |
新特性
当将Java Pet Store导入.NET时,我们发现不需要多少开发时间和额外的代码就可以很容易地扩展该应用程序的特性列表使其包含XML Web Service并支持移动设备。
XML Web Service
使用Visual Studio.NET,我们快速地增加了XML Web Service(基于SOAP和UDDI标准),如果给定order id的话,就可以返回定购的详细资料。关于描述如何在Visual Studio.NET中建立Web Service的完全的文档,其中包含与使用IBM WebSphere 4.0建立Web Service的直接比较,请参看文档在Microsoft .Net内和在IBM Websphere 4.0内建立基于XML的Web Service的比较,该文档http://www.gotdotnet.com/compare可以找到。
在OrderWebService.asmx Web Service内的GetOrderDetails() SOAP方法的示例XML输出如图所示。
图 8. GetOrderDetails XML Web Service的示例输出
移动设备支持
ASP.NET提供一些很好的机制,这些机制面向从IE先前的版本到Netscape广大范围的多种Web浏览器。Web应用程序已经扩展到可以使用从运行掌上IE的掌上电脑(Windows CE)到使用WAP浏览器的移动电话的多种设备进行浏览。Microsoft的移动Internet工具箱提供了一组集合允许开发者写一个能支持多种不同设备的代码基准。在导入.NET期间,我们发现如果消费者能使用移动电话查看他们宠物定购状态的话就绝妙了。
图 9.移动定购状态页
使用移动Internet工具箱是非常简单的。在运行和安装之后,就有了一个新的称为System.Web.Mobile(System.Web.Mobile.dll)的命名空间。该工具箱还和Visual Studio .NET集成得很好,这就可以使用可视化设计界面创建页,如图10所示。
Figure 10. 使用Visual Studio .NET开发obileOrderStatus.aspx
为移动设备开发的代码如下:
<body xmlns:mobile="Mobile Web Form Controls">
<mobile:form id="WelcomeForm" runat="server">
<mobile:Image id="imgLogoWelcome" runat="server"
AlternateText="Pet Shop"
ImageURL="../images/mobile_title.gif"></mobile:Image>
<mobile:Label id="lblUsername" runat="server">Username</mobile:Label>
<mobile:TextBox id="txtUsername" runat="server">dotnet</mobile:TextBox>
<mobile:Label id="lblPassword" runat="server">Password</mobile:Label>
<mobile:TextBox id="txtPassword" runat="server"
Password="True"></mobile:TextBox>
<mobile:Command id="btnGo" runat="server">Go</mobile:Command>
</mobile:form>
<mobile:form id="RecentOrdersForm" runat="server">
<mobile:Image id="imgLogoRecentOrders" runat="server"
AlternateText="Pet Shop"
ImageURL="../images/mobile_title.gif"></mobile:Image>
<mobile:Label id="lblRecentOrders" runat="server">
Recent Orders
</mobile:Label>
<mobile:SelectionList id="lstRecentOrder" runat="server"
SelectType="ListBox">
</mobile:SelectionList>
<mobile:Command id="btnCheckStatus" runat="server">
Check Status
</mobile:Command>
</mobile:form>
<mobile:form id="ResultsForm" runat="server" StyleReference="subcommand">
<mobile:Image id="imgLogoResults" runat="server"
AlternateText="Pet Shop"
ImageURL="../images/mobile_title.gif"></mobile:Image>
<mobile:Label id="lblResults" runat="server">
Order Status Results
</mobile:Label>
<mobile:Label id="lblOrderStatus" runat="server"
Font-Size="Normal"></mobile:Label>
</mobile:form>
</body>
代码后置文件MobileOrderStatus.aspx.cs包含两个按钮点击事件处理。第一个按钮“Go”将验证用户信任然后显示属于该消费者的一列定购信息。
// go command event handler
private void btnGo_Click(object sender, System.EventArgs e)
{
// verify login
Customer customer = new Customer();
if (customer.Login(txtUsername.Text, txtPassword.Text) != null)
{
// get the list of orders for the specified customer
Order order = new Order();
lstRecentOrder.DataValueField = "orderid";
lstRecentOrder.DataSource = order.GetOrder(txtUsername.Text);
lstRecentOrder.DataBind();
lstRecentOrder.SelectedIndex = 0;
// advance to the next card
ActiveForm = RecentOrdersForm;
}
}
最后的按钮将检查系统中选中的定购状态。
// order status command event handler
private void btnCheckStatus_Click(object sender, System.EventArgs e)
{
// get the order status
Order order = new Order();
lblOrderStatus.Text =
order.GetOrderStatus(lstRecentOrder.Selection.Text);
// advance to the results card
ActiveForm = ResultsForm;
}
使用移动Internet工具箱的好处在于实现了无论何时开发者都能支持带有多种功能的广大范围内的设备和浏览器。为了测试移动电话对于移动页的支持,在Openwave.com开发员部分可以找到一个著名的仿真器。我们使用Openwave SDK version 4.1测试该移动页,可以在http://developer.openwave.com/download/index.html下载Openwave SDK version 4.1。
结论
Java Pet Store是Sun使用J2EE技术建立企业Web应用程序的参考蓝图应用程序。.NET Pet Shop是用于突出关键的Microsoft .NET技术和体系结构并可以用来建立企业Web应用程序的蓝图应用程序。现在,通过使用每个平台上经过最好编码、功能相同的参考应用程序,设计者和开发者就可以并排地比较.NET和J2EE。
附录1:计算代码行
对应.NET Pet Shop应用程序中实现的精确功能,我们非常谨慎地对Java Pet Store的代码进行了计数。由于.NET Pet Shop内不包含管理功能和蓝图mailer应用程序,所以并没有把这些代码行计算在内。另外,我们也没计算Java Pet Store内支持除了Oracle外其他数据库的代码行。为了计算Java和.NET版本代码的行数,我们创建了一个程序查找那些在每个文件内重要的代码行。例如,计算包含一个文件内的C#或Java代码的行数,我们希望忽略空行和注释行。表10更为详细地列出了在具体类型的文件内对那些代码行计数。
文件类型 | 对那些行计数 |
.cs | 所有含有C#代码的行 |
.java | 所有含有Java代码的行 |
.js | 所有含有JavaScript代码的行 |
.asax, .aspx | 所有含有服务器端代码 (C#)的行 |
.jsp | 所有含有服务器端代码 (Java)的行 |
.sql | 所有含有SQL代码的行 |
表10. 计数的代码行
另外,对于.asax,.aspx和.jsp文件,如果在同一行中有多于一个的服务器端块(由<%和%>分割),每个都作为单独的行计数。对于所有其他的文件类型(比如.xml),对所有的行计数。程序中参与计数的行符合lex词汇规范,lex是一个词汇分析器和代码生成器。在我们的案例里,我们使用flex,是GNU的lex,其源代码可以从自由软件协会免费得到。每个都可以用来从规范文件中产生一个C源文件。可以依次将C文件编译成一个可执行文件。用于VC6和VC7的makefile包含在该分布内。
该程序的用法十分简单。依赖于要计数的文件类型、是否在命令行上指定被计数的文件、或者被计数的文件是否是从另外的源头传送而来,该程序接受零个、一个或二个命令行命令行变量。表11描述了接受的命令行变量以及它们是如何使用的。
变量 | 作用 | 适用的文件类型 |
(空) | 假设文件混合包含HTML和服务器端代码。 | .asax, .aspx, .jsp |
-all | 计算所有行 | 所有其他的文件 (比如.html,.xml和.txt) |
-code | 只计算代码行 | .cs, .java, .js |
-sql | 只计算SQL代码行 | .sql |
表11. cloc命令行变量
由于该计数函数每次不能接受多于一个文件,我们为Java和.NET版创建批文件集合,从而有助于对所有目录中的所有行计数。这些文件也包含在该分布内。
在我们的makefile里,我们决定使用flex产生一个带有.cpp扩展名的C源文件。既然VC编译诸如C++文件,那么就允许我们使用bool类型并为我们提供更好的类型安全。然而,还要求引用一个名为unistd.h文件。这是许多系统上标准的引用文件,但并不是Visual C++中的一部分。然而,由于这个文件并不包含任何与我们行计数工具相关的声明,所以最好使用一个空文件。
如果你希望生成行计算结果,你可以自己运行该批文件。首先,扩展Used.bat,生成一列用于目录层次内的文件扩展名。将目录层次的顶级目录路径指定为唯一的命令行变量。没有扩展名的文件并不显示在该列内,但是这里并没有我们需要计数的文件没有扩展名。我们在Java版和.NET版上运行该批文件从而决定每个文件扩展名。表12列出了每个版本所用到的文件扩展名,以及用来计算总行数的文件扩展名。
平台 | 被计数的扩展名 | 没被计数的扩展名 |
Java | java, js, jsp, sql, tld, txt, xml | bat, html, mf, sh |
.NET | asax, ascx, aspx, config, cs, sql, txt | bat, cmd, css, html, gif, jpg, resx, vbs, vsdisco |
表12. 扩展名
其他两个批文件,Java Lines.bat和.NET Lines.bat,各自计算了Java和.NET版的文件行数。因为这些批文件查找的扩展名不同,所以它们是独立的,如表12中所示。这两个批文件的用途是相同的。它们都使用目录层次的顶层目录的路径作为单个命令行变量来查找要计数的文件。
为了得到不同层的行计数,我们运行了特定的子目录内且只包含了特定的扩展名的批文件,如表13所示。
平台 | 表现 | 逻辑 | 数据库 | 配置 | |
Java | 扩展名 | java, js, jsp, txt, xml | java, txt, xml | Sql | tld |
子目录 | Web | Components | Database | <全部> | |
.NET | 扩展名 | asax, ascx, aspx, cs, css | cs | Sql | Config |
子目录 | Pet Store | components | Src | <全部> |
表13. 最后的代码分类
即使不期望跨平台的话,跨不同文件类型确定一个令人满意的行计数也足够困难了。比如,我们并没有对HTML文件计数,是因为在不同的平台上使用不同的工具生成的HTML的格式可能都不同,这样,代码计数就会不同。即便都应用于逻辑层文件如何格式化,值得说明的是java文件和cs文件之间的差别仍然很大,这些数字足以说明.NET平台上基础结构的数量了,这有利于开发者将精力集中于问题本身,而不是将精力花费在弥补基础结构的不足上。
该文档包含的信息代表了Microsoft公司在该文出版时对于这个问题的观点。因为Microsoft必须响应不断变化的市场条件,因此不应该认为这就是Microsoft方面的承诺,Microsoft并不能担保该文出版以后本文给出的信息的精确性。
该白皮书仅用于报告的目的。对于该文档内的信息,Microsoft并没做任何明确的授权或者暗许。
遵循适当的版权法是每个用户的责任。如果没有在版权下限定权利,并且没有Microsoft公司明确的书面许可,不管出于何种目的,该文档的所有部分都不可以被复制、贮存或者引入到一个检索系统中去,也不能以任何形式(电子的、机械的、影印的、录音的、或其它的)传播。
该文档内涉及到了Microsoft的专利权、专利应用程序、商标、版权、以及其他知识产权。除非Microsoft明确地提供了书面许可协议,否则该文档的内容不提供这些专利、商标、版权以及知识产权的任何许可。
? 2001 Microsoft Corporation. All rights reserved.
Microsoft,Visual Basic,Visual Studio.Net,Visual C++ 和Windows不是注册商标就是在美国和/或其他国家的Microsoft公司的商标。
在此提到的实际的公司和产品可能是其各自所有者的商标。
由于Sun Java Pet Store许可的需要,我们引入下面的关于Java Pet Store应用程序的合法信息:
Copyright 1999-2001 Sun Microsystems, Inc. ALL RIGHTS RESERVED
如果以下的条件满足,不管是否修改,都允许重新分布、使用源程序和二进制形式:
——重新分布源代码必须保留上面的版权公告、这列条款以及下面的弃权。
——重新分布二进制形式必须在文档和/或该分布提供的其他资料内重现上面的版权公告、这列条款以及下面的弃权。
如果没有专门的书面许可,Sun Microsystems, Inc. 的名称和赞助者的名称都不可以被用来认可或宣传源自该软件的产品。
> >
如上所言,该软件没有任何类型的授权。所有明确的或暗示的状况、陈述以及授权,包含任何市场性的暗示的授权、对于特殊目的的适应、或者非违反,都因此排除在外。SUN及其许可将不负责任何由领到许可证的人使用、修改和重新分布该软件及其派生物的结果所引起的损失。在 所有事件中,SUN及其许可证颁发者都不负责任何的税收、利润以及数据的损失和直接、非直接、特定的、必然的、偶然的以及惩罚性的破坏。然而,即便SUN已经建议了这样破坏的可能性,这些仍然不可避免地出现。
> >
该软件未经许可不得用于设计、构造、运转或维护任何核设备。