内容


Java Web应用开发杂谈

Comments

Web应用开发的主要特点

相对于传统应用程序的设计和开发,Web应用设计带来了独特的挑战。传统的应用程序一般都可以按照功能模块初步划分为内核程序与用户界面。然而,在 参考文献[1]中,John Ganci认为Web应用由商务逻辑(Business Logic)和表现逻辑(Presentation Logic)构成:

商务逻辑 实现商务引擎(运行服务)功能,它保证组件的事务完整性,维护并快速的存取数据,起到了内核的作用;Java支持各种形式的商务逻辑实现技术,包括Java Servlets、JavaBeans、Enterprise JavaBeans(EJB)等。

表现逻辑 负责产生浏览器客户端的可视化界面,通常可由JSP编写,只在需要结果数据时与服务器通讯交互,并不介入商务逻辑。

笔者认为:从技术实现的角度来说, Web应用开发的主要特点就在于表现逻辑与商务逻辑的分离。两者的分离有利于隔离抽象的商务逻辑和具体的网页编程,有助于提高组件的可复用性和商业逻辑的安全性及柔性。

Java Web应用开发中的三个基本问题

笔者刚刚完成了一个电子商务解决方案应用系统,这是一个支持WAP/HTTP的电子影院系统:电子影院以中间商形式提供服务,能够实现影院在线管理、WAP/HTTP网上订票等一系列复杂的功能。在开发过程中产生了一些有益想法,认为在开发基于Web的电子商务系统时,有以下几个方面是必须注意的:

  • 2.1 Java代码的健壮性
    所谓系统健壮性,就是指在发生硬件故障、输入的数据无效或操作错误等意外环境下,系统能做出适当响应的能力。我们常说"用户永远是对的",只有能够在错误发生时还稳定运行的系统才能真正满足客户的需要。JAVA应用开发中需要考虑这一因素,采用相应更健壮的编码措施,才能使系统的质量获得提升。
  • 2.2 电子商务应用系统的国际化
    当一个电子商务系统需要在全球范围应用的时候,就得考虑它在不同的地域和语言环境下面的使用情况。一个优秀的全球化产品关于国际化和本地化的要求并不仅局限于界面的文字显示部分,还应包括所有商务逻辑的国际化和本地化。Java 语言的内核是基于Unicode2.1标准的,它提供了对不同国家和不同语言文字的内部支持,因此,Java对于国际化的支持远远要比其他语言优越。
  • 2.3 参数设置的外部化
    如果编写调试一个Java Bean,在遇到诸如DB2数据库服务器URL的参数时,往往会选择手动更新:URL改变时就在代码中改变参数,再重新编译一次。可是这样合理吗?显然编译代码不在服务器管理员的工作范畴以内。所以,我们要做的是将参数设置外部化,即在Java代码中不出现任何的动态参数。

我们将在下面的样本案例中描述这三个基本问题的技术实现。

Java所支持的商务逻辑-表现逻辑 技术体系

3.1 商务逻辑-表现逻辑的技术实现体系如图1所示。

基于Java的 Web应用对 商务逻辑-表现逻辑 的分层架构提供了Java Server Page, Java Servlet和Java Bean的实现技术(EJB 和其他相关的技术本身就是Java Bean的扩展)。

图1 - 商务逻辑-表现逻辑技术实现
图1 - 商务逻辑-表现逻辑技术实现
图1 - 商务逻辑-表现逻辑技术实现
  1. HTML通过URL 指向一个应用服务器上的servlet。
  2. Servlet起控制请求的作用,调用JavaBeans。JavaBeans代表商业逻辑元素。
  3. Servlet调用JSP,JSP被应用服务器解析。由JSPs调用的JavaBeans允许对商业逻辑信息进行存取。
  4. JSP 向浏览器客户端发送已生成的HTML页面。

3.2 商务逻辑

商务逻辑代码最终是为了满足客户端的请求,因此必须全面的考虑到所有可能的要求,这包括:

  1. 保证应用程序组件的事务完整性。
  2. 维护并快速的存取应用程序数据。
  3. 为现存的应用程序结合新的应用程序组件。

3.3 表示逻辑

表示逻辑负责产生用于返回浏览器的HTML。在一般情况下,Servlet将动态数据传递到显示页面以便格式化。有时显示页面会直接调用商务逻辑去获得动态数据。

JSP为向网页中插入动态内容提供了一套简单而又强大的机制。在JSP下可通过一定的HTML TAG将服务器端的Java逻辑实现直接嵌入到返回浏览器的HTML或XML页面中。这样JSP可以自动的访问Java Bean等隐式对象。

3.4 注意

JSP技术是Java Servlet 的扩展。实际上,应用服务器在执行之前先将JSP编译成HttpServlet。在与Servlet通信时,为了从商务逻辑获得信息,要使用JavaBean作为消息交换器。虽然也可以在JSP中编写内嵌Java代码,Java 代码会被直接编译成servlet。虽然可以在JSP中编写复杂的代码,但并不推荐这样,因为电子商务应用系统的主要特征是:商务逻辑和表现逻辑分离!

样本案例的基本体系

本文样例为设计一个简化版本的电子影院票务系统,客户可以通过浏览器登陆网站,浏览并选择电影,最后确认定票。尽管,对于一个完善的电子商务系统,这些功能还不足够,但本文主要把注意力集中在Web应用开发的要则上。

样本案例的体系流程图如下图2。

各模块功能描述如表1。

?i18n: 就是internationalization, 国际化,由于首字母"i"和末尾字母"n"间有18个字符,所以简称i18n. internationalization指为了使应用程序能适应不同的语言和地区间的变化而不作系统性的变化所采取的设计措施。

图2 应用流程示意图
图2 应用流程示意图
图2 应用流程示意图
表1 模块功能描述
表现逻辑所调用的商务逻辑功能描述
LoginJSPAuthenticationBean生成会员身份验证页面
WelcomJSP生成欢迎主页面,可通过Java Script编写的日历选择一定日期的电影放映信息列表
MovieListJSPMovieListBean生成电影放映信息列表
MovieDetailJSPMovieDetailBean生成电影详情介绍页面
PlaceOrderJSPInsertOrderBean生成确认订单页面
ErrorHandlingJSPErrorHandlingBean生成错误处理器页面
商务逻辑功能描述
AuthenticationBean从数据库中读取会员身份资料,验证登陆者身份
MovieListBean根据输入条件(日期/影院)产生结果集--电影放映信息列表
MovieDetailBean根据选择的电影场次查询电影介绍和放映时间地点信息
InsertOrderBean在用户确认订票后将订单信息插入数据库中的订单表
ErrorHandlingBean截获各种逻辑错误,通过session 和重定向完成错误处理
i18nServlet完成国际化的预处理,对各语言版本页面集进行分派

样本案例中的技术实现举例

5.1 Web应用中Java代码的健壮性--建立自己的错误处理器

当您在开发过程中不断遭遇到Java抛出烦人的异常或错误时,是否想过建立自己的错误处理器? 通过集中的错误管理可以提高代码复用程度和通用性,另外,风格一致的错误警告页面也是很不错的。

5.1.1 错误信息来源分类
在电子商务的逻辑操作中,错误信息的来源大概可以分为:

  • 数据错误 DATA_ERR[ 主要来自用户的错误输入 ]
  • 系统错误 SYSTEM_ERR[ 由于服务器守护进程的错误所导致的错误 ]
  • 数据库错误 DB_ERR [ 由UDB数据库对违反DB2主外关键字等设定逻辑错误返回的错误代码 ]
  • 用户定义 USER_ERR[ 由系统管理员所决定人为中断错误,主要用于跟踪调试 ]

5.1.2 错误提示信息
错误处理器的通用性在于可以帮助使用者知道错误的原因以及解决的办法。错误提示信息用于阐释错误的根源。从以上错误信息来源跟踪分析。我们把错误信息分为:

系统信息来源,主要通过 java.lang.Exception 类的 getMessage() 方法得到系统跑出的异常错误。此信息主要给系统管理员来分析使用。

用户定义信息,主要通过程序给出用户自己设定错误信息。由网站程序员自己设定错误信息。主要根据系统Exception错误信息进行分析,从而在中间层给出第二级错误信息映射。

内置错误信息,主要针对一些常见的错误,由系统[Java Bean]内嵌错误。最通用的如未知错误:用于处理无法从错误信息源中找到的错误或者是非法调用错误中心程序而导致的错误。

因此,错误处理器插件[bean]就有很广泛的实用性,既可以从最底层的系统(给系统程序员)又可以从中间层(给一般用户),适应性比较大,对无法告知的错误也不会给出令人不解的Error 500。

5.1.3 解决"错误"的方法
"错误"的解决方法是错误处理控件针对错误信息出现的内容,决定程序下一步所要做出的动作。根据不同层次的需要。我们可以使用两种机制来完成:

  1. 系统内定的尝试解决方法
  2. 用户自定义的解决方法

5.1.4 系统预定义的尝试解决方法
尝试解决的方法多种多样,最基本的包括:

关闭浏览器窗口 CLOSE_WINDOW

回到前一个页面 PREV_WINDOW

通过这两个动作,用户可以完成一些基本的操作,比如,当密码输入错误时,错误页面出现后,返回到前一个页面等等。当然,对于尝试解决方法,错误系统控件本身还可以继续扩充。比如,各种尝试解决方法之类等等,这只需要在Bean中进行简单的扩充就可以了。

5.1.5 用户定义解决方法
用户可以自定义错误解决方法。这样,程序设计员就可以决定在下一步的操作是什么。他主要包括跳转到相应的解释页面,或者工作页面。如此一来,我们的控件就可以很方便的和各种系统接口了。

5.1.6 一个简易错误处理器
在电子影院系统中,我们设计了一个简单易用的错误处理器,它由两部分组成: 代表商务逻辑的ErrorHandlingBean和代表表示逻辑的ErrorHandlingJSP。

ErrorHandlingBean 是一个错误信息管理中心,通过session 和重定向完成对常见的错误,包括系统错误[SYSTEM_ERR]、用户错误[USER_ERR]、数据库错误[DB_ERR]和数据错误[DATA_ERR]以及未知错误[UNKOWN_ERR],提供多种处理措施。这些措施包括关闭窗口[CLOSE_WINDOW]和返回上页[PREV_WINDOW]以及用户指定页面等。此外,ErrorHandlingBean提供国际化的支持,可以通过获得会话中的国际化信息选择相应的输出语言。这一点将在下面的国际化讨论中祥述。成员函数gatherErrortoCenter()先调用dowrite( )将错误信息写入会话中,接着在成员toErrorCenter()中通过response.sendRedirect()重定向到错误显示页面ErrorHandlingJSP.jsp中。

ErrorHandlingJSP 负责产生错误提示页面,显示错误信息,错误源并提供简易解决方法。

以下就是一个简单的输出页面,当然,您可以按自己的风格定制它。

图4 - 错误处理器输出示例
图4 - 错误处理器输出示例
图4 - 错误处理器输出示例

您可以在附件中看到完整的源代码,这个Bean使用起来非常方便:

通过ErrorHandlingBean ErrorHandler = new ErrorHandlingBean (session ,response)初始化一个实例,其中session指示当前的会话对象。在判断错误后,可以调用方法gatherErrortoCenter()设定错误类型、错误提示信息和重定向页面特征。例如,当判断出会话并不存在后,需要设定错误类型为SYSTEM_ERR,错误提示信息为"The Session is Invalid!!",并且希望在错误提示页面上显示"关闭窗口"的按钮,函数调用应写成

ErrorHandler.gatherErrortoCenter(ErrorHandler.SYSTEM_ERR, ErrorHandler.CLOSE_WINDOW,"The Session is Invalid!!")。

系统通过response.sendRedirect()重定向到错误显示页面ErrorHandlingJSP.jsp。

类似这样的一个错误管理器能增强系统的健壮性,减少系统直接抛出的异常,并且意味着:同样的模块实现,您的处理设计将比普通的异常处理设计显得更为方便。

5.2 电子商务应用系统的国际化

国际化是电子商务网站必不可少的一个特性。样本案例的电子商务模型由前端的表示逻辑(JSP)、中间层的商务逻辑(Bean)和后台数据库构成。由于数据部分属于用户操作系统所负责,所以系统国际化的工作主要集中在表示逻辑和商务逻辑上。

5.2.1 表示逻辑的国际化
表示逻辑的国际化工作主要在于输出页面文字的国际化。

在样本案例系统中,我们通过Servlet--i18nServlet完成国际化的预处理,并将国际化信息写入会话中,然后根据所选择的语言[locale]重定向到相应的目录,例如,如果选择English版本,系统会重定向到英文化的首页,由locale设定来选择目录。在公用的语言相关的Servlet和Bean中都采用了事先判断的方式来区分语言信息,确保输出的语言与用户选择的保持一致。在错误处理器中就使用了这项技术,向表现逻辑ErrorHandling.jsp中输出特定的语言信息。

其中,您可以在附录清单中看到i18nServlet.java的源代码,其核心代码很简单:

首先确认用户选择的locale,设置好目录名,然后通过调用

getServletContext().getRequestDispatcher(Lang_pre+"/login.jsp").forward(request,response)分派页面。

5.2.2 商务逻辑国际化
在商务逻辑中实现国际化要考虑到Java语言的特性。JAVA语言使用Unicode编码,在java.lang.String类中有各种Unicode类型转化的方法。

java.lang.String 的构造函数 String (byte[] bytes ,int offset, int length, String encoding)和String (byte[] bytes, String encoding)都可以从指定编码方式的字节中创建一个Unicode 串。

而在实际情况中,当有页面请求到达服务器时,在Content_Type标头中有对语言条件申请的属性:

Content-Type: text/html; charset=UTF-8

Accept-Charset: UTF-8 ,ISO-8859-1,UTF-7;q=0.9等信息。

当HTTP Server 收到请求以后,按照servlet的设定来语言设定。我们的系统选择适合用户和WEB客户(可显示的字符编码)的变体,以实现内容协商。

5.3 系统参数外部化

在Microsoft Windows系统中,我们可以通过向注册表写属性的方式将系统参数外部化,而在Linux和其他操作系统下这种方法是行不通的。但采用外部文件的方法通用性最强,原因很简单:所有的操作系统都支持最基本的文件读写功能。采用配置文件的系统可以在各种操作系统平台上平移。

在系统设计时,考虑到系统独立性和可移植性,应该将所有在安装配置应用服务器时所需的参数放置在外部文件中,需要动态参数的Java Bean在初始化时从所设定的配置文件中读取参数。允许管理员在任何时候进行更改服务器设置、转移数据库、改变SMTP邮件服务器设置或防火墙设置,这样实现了外部参数的灵活设定,使服务器管理员可以方便的调试系统。

5.3.1 Properties类
编写需要配置参数的商务逻辑(Bean)的时候,构造函数读写属性列表时可能会用到Properties类。它是Hashtable的一个子类,可以用来表示一个属性列表。属性列表中的每一项成为一个属性,包括一个名-值 二元组,每一个属性名和属性值都是一个双字节字符集的字符串。其成员及描述如下:

Properties()构造属性列表
GetProperty()获取属性值
List()向输出流中写入属性列表
Load()从输入流中读取属性列表
PropertiyNames()获得属性名
SetProperty()设置属性值
Store()向输出流中写入属性列表

5.3.2 样本案例中的参数配置实现
可能与样本系统有关的配置参数包括:

  • 数据库接口驱动程序参数
  • UDB/DB2数据库的访问URL
  • 数据库名
  • 数据库默认连结用户账号
  • 数据库连结账号口令
  • SMTP邮件服务器URL
  • 代理服务器URL
  • 代理服务器端口

我们在自定义的路径中放置配置文件,将服务器的配置参数写入其中。

配置文件样本:

Driver=COM.ibm.db2.jdbc.app.DB2Driver
myURL=jdbc:db2:cinema0
myUser=Administrator
myPassword=1208
SmtpHost=smtp.163.com
ProxyHost=202.114.20.1
ProxyPort=8080

在Java Bean的构造函数中,通过调用成员函数setParams()获得外部参数。

public SampleBean(String Profile) {
        super();
        try {
            setParams(Profile);
        } catch (Exception e) {
            e.printStackTrace();
        };
    }

成员函数setParams():

public void setParams(String propFile) throws java.lang.Exception {
        Properties props = new Properties();
        try {
            InputStream is = new FileInputStream(propFile);
            if (is == null) {
                throw new Exception();
            }
            props.load(is);
            is.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        myDriver = props.getProperty("myDriver");
        myURL = props.getProperty("myURL");
        myUser = props.getProperty("myUser");
        myPassword = props.getProperty("myPassword");
       mySmtpHost=props.getProperty("SmtpHost");
myProxyHost=props.getProperty("ProxyHost");
myProxyPort=props.getProperty("ProxyPort");
    }

5.3.3 进一步的扩展
通过Properties类输出属性列表的功能,可以为服务器管理员开发一个系统参数配置器。这也是完全基于Web浏览器的应用,一个修改系统参数的页面可以避免在调试过程中的对配置文件的手动更新,更重要的是能减少不必要手动输入错误。我想,这对于开发人员来说,是轻而易举的。

小结

本文从WEB应用开发特点及JAVA技术体系出发,对JAVA Web应用开发中代码健壮性(错误处理机制)、电子商务系统国际化及参数设置外部化等三个重要问题进行分析,提出了可行的编程实现方案,并举出一个开发实例。读者可以从附录中获得完整的源代码,希望我提出的东西对大家有用。

附录

源程序(见文件 Src.doc


相关主题

  • John Ganci, e-Commerce Patterns Using WebSphere Commerce Suite--Patterns for e-business Series
  • Ayers,D. Professional Java Server Programming.2001(1),PHEI,WROX.Chapter 1,3,4,8
  • Karl Avedal ,Danny Ayers,Timothy Briggs. Professional JSP . PHEI,WROX,2001(1)
  • Cay S. Horstmann,Gary Cornell. Core Java 2 Volume II:Advanced Features, Prentic Hall,2001(1)
  • http://www.redbooks.ibm.com

评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Java technology, Web development
ArticleID=53267
ArticleTitle=Java Web应用开发杂谈
publish-date=09232001