贯通tomcat --- 电子书
http://www.educity.cn/jiaocheng/j10865.html
第1章 认识Tomcat
【本章导读】
Tomcat服务器是一个免费的开放源代码的Web应用服务器。它是Apache软件基金会(Apache Software Foundation)的Jakarta项目中的一个核心项目,由Apache、Sun和其他公司及个人共同开发而成。由于有了Sun的参与和支持,最新的Servlet和JSP规范总是能在Tomcat中及时地得到体现,Tomcat 6支持最新的Servlet 2.5和JSP 2.1规范。因为Tomcat技术先进、性能稳定且免费,所以深受Java爱好者的喜爱并得到了部分软件开发商的认可,成为目前比较流行的Web应用服务器。
本章作为本书的第1章,简要回顾了Java Web应用的历史、技术发展概况,以及Tomcat如何融入到这个技术发展过程中。通过本章的学习,读者能够了解Tomcat的体系结构、Tomcat新的版本特征,以及Tomcat与其他Web服务器、应用服务器的区别。
本章主要内容包括动态页面技术的基本知识、Tomcat有哪些新的特性、Tomcat的体系结构,以及如何区分Tomcat与Web服务器、应用服务器的关系。
1.1 Java Web应用简介
在Sun的Java Servlet规范中,对Java Web应用做了这样的定义:"Java Web应用由一组Servlet、HTML页面、类,以及其他可以被绑定的资源构成。它可以在第三方供应商提供的实现Servlet规范的Web应用容器中运行。"Java Web应用的主要特征之一就是与ServletContext的关联。每个Web应用都有且只有一个ServletContext.当Java Web应用运行时,Servlet容器为每个Web应用创建唯一的ServletContext对象,使得它能被同一个Web应用中的所有组件共享。Servlet容器(例如Tomcat)控制这种关联,并且保证在ServletContext中存储对象时,不会发生冲突。在Java Web应用中可以包含如下内容:
Servlet
JSP
实用类
静态文档,如HTML、图片等
客户端类
描述Web应用的信息(Web.xml)
在生成一个Web应用时,第一步要做的工作就是生成Web应用的目录结构。表1-1中通过一个名为example的例子,描述了Web应用应该包含的目录结构。这些目录都应该放在Servlet容器的<SERVER_ROOT>目录下,比如在Tomcat中就是%CATALINA_HOME%/webapps.
表1-1Web应用目录结构
从目录结构中可以看到,对于一个Web应用来说,classes既可以放置在/WEB-INF/classes目录下,也可以放置在/WEB-INF/lib目录下。对于类加载器来说,将首先从/classes目录里加载类,然后再从/lib目录加载。如果在这两个目录中放置了同样的类文件,则/classes目录里面的类被加载使用。
所有Web应用的核心就是它的部署描述符。部署描述符是一个XML文件,命名为web.xml,存放在/<SERVER-ROOT>/applicationname/WEB-INF/目录下。它描述整个Web应用的配置信息。针对上面的案例来说,web.xml存放在/<SERVER-ROOT>/example/WEB-INF目录下。部署描述符主要包含以下描述信息:
ServletContext初始化参数
本地目录
会话配置
Servlet/JSP定义
Servlet/JSP映射
MIME类型映射
欢迎文件列表
错误页面
安全
1.1.1 Java API
Tomcat作为Servlet容器,是Java 2企业版平台(J2EE)中的关键组件。
J2EE定义了一组基于Java的API,满足生成企业级的Web应用。
无论企业的大小,都可以使用J2EE技术,但是J2EE的目标是为了解决大的软件系统带来的问题。
J2EE建立在Java 2标准版之上(J2SE),J2SE包含了Java二进制代码(例如JVM和字节码编译器),以及核心的Java代码库。
J2EE依赖J2SE的功能。J2EE和J2SE都包含在http://Java.sun.com,且都作为应用程序能够基于的平台选择种类。
正如上面所述,J2EE是Java API的标准集合。应用程序接口(API)用于软件开发人员描述服务商所提供的服务接口。在Java中,API用于描述Java虚拟机(JVM)及其代码库所能提供的服务接口。Java中的API都由Java标准制定组织Java Community Process(JCP)来维护。JCP是一个开放的国际组织,主要由Java开发者,以及被授权者组成,职能是发展和更新Java技术规范、参考实现(RI)、技术兼容包(TCK)。Java技术和JCP两者的原创者都是Sun计算机公司。然而,JCP已经由Sun于1995年创造Java的非正式程序,演进到如今有数百名来自世界各地Java代表成员一同监督Java发展的正式程序。
JCP维护的规范包括J2ME,J2SE,JavaEE,XML,OSS和JAIN等。组织成员可以提交JCR(Java Specification Requests),通过特定程序以后,进入到下一版本的规范里面。
所有声称符合JavaEE规范的JavaEE类产品(应用服务器、应用软件、开发工具等),必须通过该组织提供的TCK兼容性测试(需要购买测试包),通过该测试后,需要缴纳JavaEE商标使用费。完成了前述的两项工作即是通过了JavaEE认证(Authorized Java Licensees of JavaEE)。
版权方授权希赛网发布,侵权必究
1.1.2 J2EE API
1.J2EE 1.4简介
J2EE 1.4平台包含了众多的API,Servlet和JSP API仅仅是其中的两个。表1-2中描述了其他的J2EE API。
表1-2 J2EE 包含的其他API
除了J2EE制定的API,J2EE应用也在很大程度上依赖J2SE API.近年来,J2EE API有多个已经移植到J2SE平台。一个是Java命名目录接口(JNDI),另外一个是XML处理Java API(JAXP)。J2EE和J2SE构成了企业软件开发的平台。近年来,虽然微软的。NET平台声称作为J2EE平台的替代品,然而对于。NET来说未来的路还很遥远。
2.J2EE 5简介
J2EE 5不是间接地由J2EE改名而来的,Sun对其做了重大修改,算是一种新的技术。从提交公开审查的规范草案J2EE 5来看,J2EE 5的关注重点是简化应用开发,尤其是大量采用元数据标注(annotation)和POJO(普通Java对象)驱动的开发方式,对平台进行了重新定义。对比此前的J2EE规范,J2EE 5最重要的新增特性就是Java持久化API(即EJB 3 entity bean)、JSF、JSTL等。
从整个EJB规法的角度来说,EJB 3相对于EJB 2.0的最大变更在于Entity Bean持久化API上。在EJB 3中,Entity Bean持久化已经单独作为一个Persistence API规范和其他的EJB部分分离开来。 EJB 2.0模型存在一些缺陷:
(1)EJB 2.0模型要求创建多个组件接口,并实现多个不必要的回调方法。
(2)组件接口要求实现EJBObject或EJBLocalObject,以及处理许多不必要的异常。
(3)基于XML的EJB 2.0部署描述符比较复杂且容易出错。
(4)基于EJB模型的容器管理持久性在开发和管理方面过于复杂,并且失去了几个基本特性--如使用数据库序列定义主键的标准方法。
(5)EJBQL语法非常有限,而且是静态的,无法做到运行期间的动态查询。
(6)EJB 2.0组件并非是真正面向对象的,因为它们在继承和多态性方面有使用限制。
(7)查找和调用EJB 2.0是一项复杂的任务,即使是在应用程序中使用最基本的EJB也需要对JNDI有一个详细的了解。
(8)对容器的依赖使得EJB 2.0只能用于服务器组件的开发,无法实现一次编写,来进行四处运行的面向构件的开发。
所有这些复杂性和缺陷,都导致EJB 2.0的采用无法真正简化开发并提高生产力。EJB3.0旨在解决以往EJB2.0模型的复杂性和提高灵活性,具体体现在:
(1)去除了不必要的接口Remote,Home,EJB,以及回调方法实现。
(2)实体Bean采用了POJO模型,一个简单的Java bean就可以是一个Entity Bean.无需依赖容器运行和测试。
(3)全面采用O/R Mapping技术来实现数据库操作。
(4)实体Bean可以运行在所有需要持久化的应用,不管是客户端还是服务器端。从而真正实现面向构件的开发。
(5)实体Bean现在支持继承和多态性。
(6)灵活丰富的EJB3查询语言。
(7)SQL支持。
(8)使用元数据批注代替部署描述符,减少复杂性。
Java EE5作为J2EE平台诞生6年后的第4代规范,重点关注的是目前Java开发的几个热点:开发效率、运行效率和企业应用整合。目标也是让J2EE开发简单、简单再简单。J2EE 5作为新一代Java架构的规范,已经越来越受到开发者的重视。
1.1.3 CGI
Web页面并不仅仅只由静态页面组成,显示同样的页面给每个用户。许多页面内容的产生依赖于不同的浏览者。静态页面在Web应用中占有重要的位置,但是如果没有动态页面参与,很多重要的页面站点将不能良好的运行,来满足其功能需求。
通用网关接口(Common Gateway Interface CGI)是最初的动态内容机制,它在Web服务器上执行,允许网站管理员定制他们的页面。CGI模型描述如下:
(1)客户端发送请求给服务端,比如超文本链接标示语言(HTML)页面;
(2)服务器接受用户请求并交给CGI程序处理;
(3)CGI程序将处理结果传送给服务器;
(4)服务器将结果作为HTTP响应返回给用户。
CGI可以使用多种编程语言来实现,例如Perl.但是CGI不是非常有高效,每次服务端接受到请求,它都必须启动新的CGI进程来处理。在用户量不大的情况下,这个问题还不至于太明显。但是如果用户请求量较大,每次都启动新的CGI进程处理,相对服务器资源代价就会较高。
由于CGI本身的缺陷,因此许多替代方案应运而生。一般来说,在这些方案中如果提供环境存在于已经存在的服务器中或者作为服务器的功能组件的话,它将获得更大的成功。由于Apache良好的模块应用程序接口(API),许多CGI的替代品都建立在Apache服务器(www.apache.org)之上。开发者可以通过API扩展Apache的功能,而不用更改服务器本身代码。对于程序员来说,生成动态内容,不失为一个好的策略。当Apache开始传输HTTP请求给模块时,Apache加载模块到它的内存;然后只要模块处理完请求,Apache把响应返回给客户端。由于模块已经加载在服务端的内存中,因此加载解释器的开销几乎可以忽略不计,使得脚本(Scipts)执行得更快。
目前很少有开发者有能力开发类似的模块,因此许多第三方模块提供了其基本的功能,甚至比通常的CGI更加强大。以下是一些例子:
Mod_perl:内嵌在服务器中的Perl解释器,减少了每次请求加载Perl解释器的开销。
Mod_php4:与上面mod_perl一样,提高PHP的执行效率。
Mod_fastcgi:驻留在内存,提高CGI的响应速度。
微软也提供了Internet信息服务(IIS)Web服务器接口,称为Internet服务应用程序接口(ISAPI)。由于IIS作为Windows操作系统的组成部分,因此IIS目前被广泛地使用。在第19章中,将会详细讲述Tomcat与IIS如何集成,如何整合利用它们最好的性能特征。
微软也开发了动态服务器主页(ASP)技术,使得用户能够嵌入Scripts脚本,比如嵌入VBScript到标准的HTML页面中。事实证明这个技术非常有效,而且对于Java Web技术起到了推进作用。
1.1.4 Servlet
Servlet是一种独立于平台和协议的服务器端的Java应用程序,可以生成动态的Web页面。Servlet是位于Web服务器内部的服务器端的Java应用程序,与传统的从命令行启动的Java应用程序相同,Servlet由Web服务器进行加载,该Web服务器必须包含支持Servlet的Java虚拟机。
现将Java Servlet与Applet技术做如下的比较:
1.相似之处
它们都不是独立的应用程序,没有main()方法。
它们都不是由用户或程序员调用,而是由另外一个应用程序(容器)调用。
它们都有一个生存周期,包含init()和destroy()方法。
2.不同之处
Applet具有很好的图形界面(AWT),与浏览器一起,在客户端运行。
Servlet 则没有图形界面,运行在服务器端。
与传统的CGI和许多其他类似CGI的技术相比,Java Servlet具有更高的效率,更容易使用,功能更强大,具有更好的可移植性,更节省投资。在未来的技术发展过程中,Servlet有可能彻底取代CGI,这是因为Java Servlet相对CGI具有如下的优点:
(1)高效
在传统的CGI中,每个请求都要启动一个新的进程。如果CGI程序本身的执行时间较短,那么启动进程所需要的时间很可能超过实际执行时间。而在Servlet中,每个请求由一个轻量级的Java线程处理(而不是重量级的操作系统进程)。
在传统CGI中,如果有N个并发的对同一CGI程序的请求,则该CGI程序的代码在内存中就会重新装载了N次;而对于Servlet,处理请求的是N个线程,只需要一份Servlet类代码。在性能优化方面,Servlet也比CGI有着更多的选择。
(2)方便
Servlet提供了大量的实用工具例程,例如,自动地解析和解码HTML表单数据、读取和设置HTTP头、处理Cookie、跟踪会话状态等。
(3)功能强大
在Servlet中,许多使用传统CGI程序很难完成的任务都可以轻松地完成。例如,Servlet能够直接和Web服务器交互。而普通的CGI程序不能。Servlet还能够在各个程序之间共享数据,使得数据库链接池之类的功能很容易实现。
(4)可移植性好
Servlet用Java编写,Servlet API具有完善的标准。因此,为IPlanet Enterprise Server写的Servlet无须任何实质上的改动即可移植到Apache、Microsoft IIS或者WebStar,几乎所有主流服务器都直接或通过插件支持Servlet.
(5)节省投资
不仅有许多廉价甚至免费的Web服务器可供个人或小规模网站使用,而且对于现有的服务器,如果它不支持Servlet的话,要加上这部分功能也往往是免费的(或只需要极少的投资)。
Java Server Pages(JSP)是一种实现普通静态HTML和动态HTML混合编码的技术,JSP并没有增加任何本质上不能用Servlet实现的功能。但是,在JSP中编写静态HTML更加方便,不必再用println语句来输出每一行HTML代码。更重要的是,借助内容和外观的分离,页面制作中不同性质的任务可以方便地分开:比如,由页面设计者进行HTML设计,同时留出供Servlet程序员插入动态内容的空间。
如果对于写过Applet程序的人来说,相信写Servlet程序并不难,甚至更为简单。因为Servlet没有图形界面,写起来与传统的文字界面程序一样简单。与Applet一样,在Servlet中一样定义了一个生命周期:
(6)初始化时期
与Applet相似,在Servlet中,也有一个Init()方法
Public void init(ServletConfig config) throws ServletException
{
Super.init();//做一些初始化动作
}
当Servlet被Servlet引擎加载后,接下来就会执行init()方法,因此我们可以重载init()方法,用于完成一些所需要的初始化动作。比如打开文件,读取设定的默认值,以及建立一个链接池(Connection Pool)等。但是不要忘记,自己的Init()执行完后,要记得调用super.init(),以免有些系统的初始化被不小心省略掉了。
(7)执行时期
提供了一些与HTTP协议对应的有关方法。因为它是抽象类别,所以必须继承它,然后重载该方法。执行时期分为数类,分别是对应HTTP方法中的Get,Post等方法,例如doGet()对应到Get或doPost()对应到Post方法。以doGet()为例,如果浏览器传过来的是Get请求模式,那么就会执行doGet()方法。同样地,doPost()方法也就是在HTML Form传过来的Post请求时执行。如果对HTTP协议不是很了解,那么请参阅相关书籍和资料。
(8)结束时期
在Init()方法中,开启了某些资源。当网页服务器系统要重新启动,或是要回收资源时,就会调用destroy()这个方法,执行这个最后的部分。但是同样也要记得调用super.destroy()方法。
(9)执行架构
Servlet属多线程执行方式,同一时间会有多个线程处理对应的客户端的请求,因此它的service()方法会被同时调用。从图1-1中可以看到,其中的Init()与destroy()方法永远只会执行一次,因为即使是多线程方式执行,Servlet的实体还是只有一个,故初始化与结束仅需一次即可。这是Servlet引擎管理的,Servlet引擎会保证这样的执行结果。
图1-1 Servlet执行架构
2005年9月26日,Sun公司和JSR154的专家组发布了Servlet API的一个新的版本。在一般情况下,一个JSR的新版本仅仅就是对以前少数有名无实的规范进行去除更新。但这次,新版本中增加了新的特征,它们对Servlets的产生了重要影响,使得Servlet的版本升到了2.5.
Servlet 2.5的一些变化,包括它基于最新的J2SE 5.0开发的;支持annotations;Web.xml中的配置更加方便;去除了少数的限制;优化了一些实例等等。
从一开始,Servlet 2.5 规范就列出J2SE 5.0(JDK 1.5)作为它最小的平台要求。它使得Servlet 2.5只能适用基于J2SE 5.0开发的平台,这个变动意味着所有J2SE5.0的新特性都可以保证对Servlet 2.5程序员有用。
在传统意义上,Servlet和J2EE版本一直与JDK的版本保持同步发展。但是,这次Servlet的版本跳过了1.4版本。专家组认为版本的加速增长是正常的,因为J2SE 5.0提出了一个引人注目的、Servlet和JEE规范都要利用的特征--Annotations.
Annotations是作为JSR175的一部分(一种为Java语言设计提供便利的Metadata)提出的一种新的语言特色。它是利用Metadata为Java编码结构(类、方法、域等)装饰的一种机制。它不能像代码那样执行,但是可以用于标记代码。这个过程是基于Metadata信息的代码处理机,通过更新它们的事件行为来实现的。
Annotations可以凭借不同的技巧来注释类和方法,例如连续地标记接口或者是@deprecated Javadoc评论。这种新式的Metadata可以便利地提供一种标准的机制来实现注释功能,以及通过库来创建用户自己的注释类型的变量。
下面是一个简单的Web service 注释例子:
import Javax.jws.WebService;
import Javax.jws.WebMethod;
@WebService
public class HelloWorldService {
@WebMethod
public String helloWorld() {
return "Hello World!";
}
}
@WebService和@WebMethod这两个注释类型,在JSR181(为Java平台提供的Web ServicesMetadata)有详细说明,可以像类一样的引用,标记这个类作为一个Web service并且标记它的helloWorld()方法作为一个Web service方法。对于它们本身来说,注释只是写在那里并没有什么作用,好像在岗位上做记录一样。但是,一个容器一旦加载这个类并对那些注释进行二进制编码,就可以把这个类连到Web service上。
注释可以接受属性/值这些参数。它保存着参数的信息并且可以利用这些参数来更改被请求的事件行为。例如下面更高级的注释例子:
@WebService(
name = "PingService",
targetNamespace=http://acme.com/ping
)
@SOAPBinding(
style=SOAPBinding.Style.RPC,
use=SOAPBinding.Use.LITERAL
)
public class Ping {
@WebMethod(operationName = "Foo")
public void foo() { }
}
一旦加载了这个类,一个正确配置的容器就会识别出注释及其参数,并将此作为一个PingService,然后将它通过利用remote-procedure-call/literal的编码方式与一个Foo operation相连。实际上,注释就是指明了类和类的容器之间的联系。
不论使用注释与否,即使在不使用时--它对于理解服务器上程序的执行依然有着重要意义。为了让服务器识别类中的注释,它必须加载这些类,这就意味着服务器必须是启动着的。服务器通过WEB-INF/classes目录下和WEB-INF/lib目录下的所有类文件来查找注释。(在任何规范下,服务器都只需查找这两个目录。)可以通过下面的方法指明<Web-app>根的属性而不必使用任何注释:
<Web-app xmlns=http://Java.sun.com/xml/ns/Javaee
version="2.5" full="true">
</Web-app>
Servlet 2.5还为Web.xml引入了几个小的变动,使得它更加方便。
首先,当写<filter-mapping>,可以在<Servlet-name>标签中使用*号来代表所有的Servlets.而以前,必须一次把一个Servlet绑定到过滤器上,像这样:
<filter-mapping>
<filter-name>Image Filter</filter-name>
<Servlet-name>ImageServlet</Servlet-name>
</filter-mapping>
现在就可以一次绑定所有的Servlets:
<filter-mapping>
<filter-name>Image Filter</filter-name>
<Servlet-name>*</Servlet-name>??<!-- 新特征 -->
</filter-mapping>
这有很大的用途,例如:
<filter-mapping>
<filter-name>Dispatch Filter</filter-name>
<Servlet-name>*</Servlet-name>
<dispatcher>FORWARD</dispatcher>
</filter-mapping>
其次,当写<Servlet-mapping> 或者 <filter-mapping>时,可以在同一标签中采用复合匹配的标准。以前一个<Servlet-mapping>只支持一个<url-pattern>元素,现在它可以支持更多个元素,例如:
<Servlet-mapping>
<Servlet-name>color</Servlet-name>
<url-pattern>/color/*</url-pattern>
<url-pattern>/colour/*</url-pattern>
</Servlet-mapping>
同样地,以前<filter-mapping>也是只支持一个<url-pattern> 或者一个 <Servlet-name>,现在它都可以支持任意多个元素:
<filter-mapping>
<filter-name>Multipe Mappings Filter</filter-name>
<url-pattern>/foo/*</url-pattern>
<Servlet-name>Servlet1</Servlet-name>
<Servlet-name>Servlet2</Servlet-name>
<url-pattern>/bar/*</url-pattern>
</filter-mapping>
Servlet 2.5还可以将合法的HTTP/1.1方法名放进<http-method>元素中。当使用这些方法时,<http-method>将指明<security-constraint>标记里的方法应该被应用。从以前来看,它仅限于HTTP/1.1的7个标准方法:GET,POST,PUT,DELETE,HEAD,OPTIONS和TRACE.现在,HTTP/1.1允许对方法进行扩展,WebDAV(Web-based Distributed Authoring and Versioning)就是用于这种扩展的普遍技术。在Servlet 2.5中,你可以安全地约束任何可能的HTTP方法名、标准及扩展,包括WebDAV方法,例如LOCK,UNLOCK,COPY及MOVE.
当需要写一个WebDAV的Servlet时,不再必须使用doLock()和doCopy()方法,而只需写自己的service()方法及分派request.getMethod()方法。正是由于这种变化,工作人员不必再考虑系统的安全性。
Servlet 2.5去除了关于错误处理和回话跟踪的一些限制。对于错误处理,在Servlet 2.5之前,配置在<error-page>中的错误处理页面不能通过调用setStatus()方法来修改触发它们的错误代码。这一规范是基于这样的观点,即错误页面的工作是指出每个错误而不是修改错误。但是,在实际使用中,错误页面不能只是用于指出错误,而是还能做更多的事情,甚至可以代替在线帮助来帮助用户解决问题。于是,Servlet 2.5减弱了这一规范,这一规范也将不再限制错误页面所产生的反馈信息。
对于会话跟踪,在Servlet 2.5之前,调用RequestDispatcher.include()的Servlet不能设置响应的标题头,而又是Servlet 2.5减弱了这一规范。原规范的目的是使内部的Servlets限制在自己的页面空间中,不可以影响外部的页面。现在这个规范已经被减弱,就允许在内部的Servlet中使用request.getSession()命令,这样这个命令就可以悄悄地创建一个会话跟踪Cookie的标题头。虽然在逻辑上要求限制内部的资源,但在逻辑上也要求这些限制不应该取消其启动Session的功能。这个变动对于Portlet规范来说显得尤其重要。其作用是:如果响应已经有效,则getSession()命令就会抛出一个IllegalStateException9(异常)。而在此之前,就没有这个功能。
Servlet 2.4规范规定响应在这几种情况下应该是有效的,在响应的setContentLength方法中内容已经明确说明,以及内容已经写进了响应中。这种情况只有您的代码像下面这样才可以使响应重新定向:
response.setHeader("Host", "localhost");
response.setHeader("Pragma", "no-cache");
response.setHeader("Content-Length", "0");
response.setHeader("Location", "http://www.apache.org");
Servlet技术忽略特定区域的标题头,因为内容满足0字节长度,响应就会立即生效。而在它开始之前,响应就已失效了!Servlet容器通常拒绝执行这种行为,而Servlet 2.5版本增加了"长度必须大于0"的这个原则。
Servlet 2.4规范规定必须在调用request.getReader()方法之前调用request.setCharacterEncoding()方法。但是,如果你忽略这个原则而在其之后去调用request.setCharacterEncoding()方法,那么会产生什么样的后果,这个问题在规范里并没有说明。为了简便起见,现在排除这种情况!
Cross-context sessions(不同上下文目录间的会话)
最近,关于Cross-context会话处理的规则已经明确说明。当Servlets指派从一个上下文到其他上下文的请求时,这个规则就发挥了作用--在目标调用过程中,包括哪些会话。这个版本的出现使得一个上下文目录主页里的Portlets可以通过几种内部的命令来对别的上下文目录里的Portlets起作用。Servlet 2.5明确指出一个上下文目录里的资源可以访问其他上下文目录的Session(会话),而不用考虑这个请求从哪里开始的。这意味着Portlets可以脱离主页的范围而在自己的范围里运行,而且这个规范还会应用在不兼容的Serlvet容器中。
由于Servlet 2.5版本保持了一些原来的性质,几个大的概念不得不延后到下一个阶段。它们包括:
新的输入/输出(NIO)支持:使Servlets进行客户端通信成为可能。
过滤器wrap-under或wrap-over语义:有时用过滤器包装请求,或者响应对象去修改方法行为或者启用新的方法。当把这种包装和服务器对请求和响应的包装结合起来时,又应该怎么包装在一起呢?
用于欢迎的Servlets文件:作为索引应该充当欢迎作用的文件吗?在此之前,这个回答是肯定的。但是规范没有明确地说明如何使用这个功能,尤其在没有索引的情况下。
用于欢迎的文件的分派规则:如何分派欢迎文件,这个细节并没有完全说明,而是遗留了一些开放的缺口来应对不兼容问题。
登录后选择默认页面:如果用户通过他们的书签访问Servlet的登录页面,那么在成功登录后页面应该转向哪里呢?这个问题至今尚未明确说明。
用户的主题日志:在通过网站正确地注册之后,不通过传统地登录方式没有办法使Servlet信任用户。
如果抛开注释来看Servlet 2.5的变化,可见在配置文件Web.xml中去除了一些限制,同时又优化了实例行为,使其更适合、更便于开发Web系统(网页)。
Servlet 2.5中注释的作用也有所变化。Servlets本身并不能声明注释类型的变量,甚至性能弱的Servlet容器都不支持注释。然而在J2EE5环境下的Servlets编写者可以看到,通过公共的注释及EJB3.0和JAX-WS2.0规范而引入的注释类型会对代码产生很大变化,并且这也将对Servlet如何管理外部资源、对象的持久化及EJB的构成产生重大影响。
Servlet尽管在性能和服务端负载方面超越了CGI,但是它仍然有自己的缺点:
在复杂的HTML网页中加入动态部分,如果用Servlet来处理的话,编写代码的工作量将相当之大。
由于在程序中的硬编码,使得应用程序维护性极差。因为如果在HTML中修改文本内容,则Servlet必须重新编译。
Servlet要求页面设计人员必须对Java有足够的了解。
正是由于Servlet存在上面的问题,才导致了JSP技术的出现。
1.1.5 JSP
JSP (Java Server Pages)是由Sun公司倡导、许多公司参与一起建立的一种动态网页技术标准,实现了普通静态HTML和动态HTML混合编码的技术。JSP是在服务器端建立的动态网页,更明确地说,JSP是能在Web服务器端整个Java语言至HTML网页的环境中,利用HTML网页内含的Java程序代码取代原有的CGI、ISAPI或者IDC的程序,以便执行原有CGI/WinCGI、ISAPI的功能。许多由CGI程序生成的页面大部分仍旧是静态HTML,动态内容只在页面中有限的几个部分出现。但是包括Servlet在内的大多数CGI技术及其变种,总是通过程序生成整个页面,JSP技术可以分别创建这两个部分。
现将JSP与ASP、PHP技术相比较,应会发现有如下的优点。
将内容的生成和显示进行分离
使用JSP技术,Web页面开发人员可以使用HTML或者XML标识来设计和格式化最终页面。使用JSP标识或者小脚本来生成页面上的动态内容,其生成的内容的逻辑被封装在标识和JavaBeans组件中,并且捆绑在小脚本中,所有的脚本在服务器端运行。因为核心逻辑被封装在标识和Beans中,所以其他人,如Web管理人员和页面设计者,能够编辑和使用JSP页面,而不影响内容的生成。在服务器端,JSP引擎解释JSP标识和小脚本,生成所请求的内容(例如:通过访问JavaBeans组件、使用JDBC技术访问数据库等),并且将结果以HTML(或者XML)页面的形式发送至浏览器。这有助于开发者既保护自己的代码,又保证任何基于HTML的Web浏览器的完全可用性。
强调可重用的组件
绝大多数的JSP页面依赖于可重用的、跨平台的组件(JavaBeans或者Enterprise JavaBean组件)来执行应用程序所要求的更为复杂的操作。开发人员能够共享和交换执行普通操作的组件,使得这些组件为更多的使用者或者客户团体所使用。基于组件的方法加速了总体开发过程,使得各种组织在他们现有的技能和优化结果的开发努力中得到平衡。
采用标签简化页面开发
Web页面开发人员不会都是熟悉脚本语言的编程人员。JSP技术封装了许多功能,这些功能是在生成易用的、与JSP相关的XML标识的动态内容时所需要的。标准的JSP标识能够访问和实例化JavaBeans组件、设置或者检索组件属性、下载Applet,以及执行用其他方法更难于编码和耗时的功能。
通过开发定制标签库,JSP技术是可以扩展的。今后第三方开发人员和其他人员可以为常用功能创建自己的标签库。这使得Web页面开发人员能够使用如同标签一样的工具来执行特定功能。
健壮性与安全性
由于JSP页面的内置脚本语言是基于Java编程语言的,而且所有的JSP页面都被编译为Java Servlet,JSP页面就具有Java技术的所有好处,包括健壮的存储管理和安全性。
良好的移植性
作为Java平台的一部分,JSP拥有Java编程语言"一次编写,各处运行"的特点。随着越来越多的供应商将JSP支持添加到他们的产品中,可以使用自己所选择的服务器和工具,而且更改工具和服务器并不影响当前的应用。
企业级的扩展性和性能
当与Java 2平台、企业版(J2EE)和Enterprise JavaBeans技术整合时,JSP页面将具有更好的扩展性和性能。
JSP并没有增加任何本质上不能用Servlet实现的功能。但是,在JSP中编写静态HTML更加方便,不必再用Println语句来输出每一行HTML代码。更重要的是,借助内容和外观的分离,页面制作中不同性质的任务可以方便地分开。比如,由页面设计专家进行HTML设计,同时留出供Servlet程序员插入动态内容的空间。
SSI是一种受到广泛支持的在静态HTML中引入外部代码的技术。JSP在这方面的支持更为完善,因为它可以用Servlet而不是独立的程序来生成动态内容。另外,SSI实际上只用于简单的包含,而不是面向那些能够处理表单数据、访问数据库的"真正的"程序。
JavaScript能够在客户端动态地生成HTML.虽然JavaScript很有用,但它只能处理以客户端环境为基础的动态信息。除了Cookie之外,HTTP状态和表单提交数据对JavaScript来说都是不可用的。另外,由于是在客户端运行,JavaScript不能访问服务器端资源,比如数据库、目录信息等。
2006年Sun发布了JSP和JSF新技术规范,其中最重要的一点是两者将表达式语言(Expression Language,EL)部分合二为一。在不久的将来,这两种技术有可能更进一步地彼此融合,成为一种统一的表现层技术。JSP 2.1把Expression Language(EL)输出到它自己各自分离的文档中,在技术上,这些文档是JSP规范的子文档。这些统一的EL规范定义了一个更高层的Java 包--Javax.el.这个包与使用它的技术之间完全独立,并且允许此技术将自身插入EL处理过程。更改的JSP规范遵从使用标准化EL的规范。对于前面提到的JSR-252,这个规范并没什么新特性。Faces 1.2支持新的标准化EL,还包含一些Bug修复的相关规范。
Faces和JSP在JSRs下的结盟带来了一些新功能,也为将来的发展打下了坚实的基础。例如,在同时使用Faces和JSP的Web应用中,网页仅使用JSP(不包含任何Faces内容)来访问Managed Beans成为可能。在JSP规范的附录E中和Faces规范的前言中都可以看到更改内容的细节。
1.1.6 JSP标签库
JSP技术中,actions是一系列元素的集合,包括生成、访问程序语言对象,影响输出流。JSP规范中定义了6个标准actions,除了这些标准actions,JSP技术也支持可重用组件,称为自定义actions.在JSP页面中,actions通过自定义标签来触发。一个标签库是自定义标签的集合,是一种通过JavaBean生成基于XML的脚本的方法。从概念上讲,标签就是很简单而且可重用的代码结构。下面的JSP文件是引入了标签库后的代码:
<% @ page language ="Java " %>
<html>
<head>
<title>Hello World</title>
</head>
<body>
<app: HelloWorld/>
</body>
</html>
相对于没有采用标签库的JSP文件代码:
<% @ page language ="Java " %>
<html>
<head>
<title>Hello World</title>
</head>
<body>
<%
String message = request.getAttribute("message");
if (message == null || message.equals (""))
{
message = "Hello World";
}
%><%=message%>
</body>
</html>
可以看到一些改进。一个类似HTML标签封装了整个功能。实际上,越复杂的应用程序,采用JSP标签越能提高程序的可读性。每个标签都有一个相对应的包含代码的Java类。标签都是成对出现,一个开始标签紧接着就是一个结束标签。例如:
<aTag> Something here </aTag>
标签生命周期包含:当遇到开始标签时,称为doStartTag()方法和一个doEngTag()方法,以及一个为下一次请求做准备,重设所有状态的方法。
自定义标签库封装了可重用的任务,因此它们在多个应用中被使用。精通Java语言的开发者生成JSP标签库,Web应用设计人员使用标签库,专注于表示层的显示,而不需要关注具体的细节。
标签库的特征如下:
能通过调用的页面传递的属性来定制。
能访问JSP页面中所允许的所有对象。
能修改调用页面的响应。
能相互通信。
能相互嵌套,支持复杂的JSP页面交互。
1.1.7 JSP EL
JSP EL(Exression Language)表达式语言,是包含在JSTL(JavaServer Page Standard Library)1.0的一个简单的资料存取与运算的语言。自从JSP2.0后,则纳入了JSP正式标准,称为JSP所支持的特性之一。JSP Exression Language定义了变量存取、运算、函数等内容,配合JSTL其他标签或流程控制标签,就可以实现更好的逻辑视图分离之目的。例如:下面是一个JSP网页利用Exression Language来计算使用者所请求的两个数字相加结果:
<html>
<head><title>EL Test</title>
</head>
<body>
简单的EL运算
<H1>${param.a}+${param.b}=${param.a+param.b}</H1>
</body>
</html>
如果使用表单或直接在网址上传入a与b的值,例如:http://localhost:8080/myjsp/elTest.jsp?a=10&b =14则会取得下面的结果:
<html>
<head><title>EL Test</title></head>
<body>
简单的EL运算:
<H1> 10 + 14 = 24 </H2>
</body>
</html>
在这个简单的例子中,可以看到Expression Language是使用 ${ 与 } 来包括所要存取的隐含对象、变量与其进行运算,param是Expression Language的隐含对象,表示使用者的请求参数,param.a表示取得使用者请求参数a的值。大致而言,Expression Language中的每一个隐含对象,其访问的数据与作用范围对应于JSP隐含对象。至于+则是Expression Language中定义的操作符,EL操作符同一般的程式语言一样,提供有算术运算、逻辑运算、关系运算等运算符。
1.1.8 Servlet容器介绍
Servlet容器也叫做Servlet引擎,是Web服务器或应用程序服务器的一部分,用于在发送的请求和响应之上提供网络服务,解码基于MIME的请求,格式化基于MIME的响应。Servlet容器在Servlet的生命周期内包容和管理Servlet.
根据Servlet容器工作模式的不同,可以将Servlet容器分为以下3类。
(1)独立的Servlet容器
当使用基于Java技术的Web服务器时,Servlet容器作为构成Web服务器的一部分而存在。然而大多数的Web服务器并非基于Java,因此,就有了下面两种容器的工作模式。
(2)进程内的Servlet容器
Servlet容器由Web服务器插件和Java容器两部分的实现组成。Web服务器插件在某个Web服务器内部地址空间中打开一个JVM(Java虚拟机),使得Java容器可以在此JVM中加载并运行Servlet.如有客户端调用Servlet的请求到来,插件取得对此请求的控制并将它传递(使用JNI技术)给Java容器,然后由Java容器将此请求交由Servlet进行处理。进程内的Servlet容器对于单进程、多线程的服务器非常适合,提供了较高的运行速度,但伸缩性有所不足。
(3)进程外的Servlet容器
Servlet容器运行于Web服务器之外的地址空间,它也是由Web服务器插件和Java容器两部分的实现组成的。Web服务器插件和Java容器(在外部JVM中运行)使用IPC机制(通常是TCP/IP)进行通信。当一个调用Servlet的请求到达时,插件取得对此请求的控制并将其传递(使用IPC机制)给Java容器。进程外Servlet容器对客户请求的响应速度不如进程内的Servlet容器,但进程外容器具有更好的伸缩性和稳定性。
Tomcat作为一个免费的开源Web服务器,它是Apache软件基金会(Apache Software Foundation)的Jakarta项目中的一个核心项目,由Apache、Sun和其他公司及个人共同开发而成。由于有了Sun的参与和支持,最新的Servlet和JSP规范总是能在Tomcat中得到及时地体现。因为Tomcat技术先进、性能稳定,并且免费,所以深受Java爱好者的喜爱并得到了部分软件开发商的认可,成为目前比较流行的Web服务器。
1.1.9 选用合适的Web技术
JSP作为J2EE的一部分,既可以用于开发小型的Web站点,也可以用来开发大型的、企业级的应用程序。到底如何选用合适的Web技术来开发自己的站点,本节着重讲述对于不同规模的Web系统,使用JSP进行开发的不同方式。
1.直接使用JSP
对于最小型的Web站点来说,可以直接使用JSP来构建动态网页。这种站点最为简单,所需要的也是简单的留言板、动态日期等基本的功能。对于这种开发模式,一般可以将所有的动态处理部分都放置在JSP的Scriptlet中。
这种结构的优点就是编程简单,成本很低。允许页面的设计者根据资源的状态、动态来生成页面的内容。但是当系统规模增大的时候,这样的结构不适合多个客户同时访问资源,因为同时会有大量的请求需要服务端来处理。每个请求都会建立一个链接,消耗一定的资源。这样,就需要让这些链接共享一些资源。其中最明显的例子就是用JDBC链接数据库中用到的链接池(Connection pools)。另外,该结构也会导致JSP中出现大量的JAVA代码。这虽然对Java程序设计人员来说不会有什么问题,可是大量的代码分散在JSP中,不利于维护和修改。
2.JSP+JavaBeans(Model 1)
中型站点面对的是数据库查询、用户管理和小量的商业业务逻辑。对于这种站点,不能将所有的东西全部交给JSP页面来处理。在单纯的JSP中加入JavaBeans技术,将有助于这种中型网站的开发。利用JavaBeans将很容易完成如数据库链接、用户登录与注销、商业业务逻辑封装的任务。例如:将常用的数据库链接写成一个JavaBeans,既方便了使用,又可以使JSP文件简单而清晰。通过封装,还可以防止一般开发人员直接获得访问数据库的权限。
3.JSP+JavaBeans+Servlet
无论用ASP还是PHP开发动态网站,长期以来都有一个比较重要的问题,就是网站的逻辑关系和网站的显示页面不容易分开。这使得代码的可读性和可维护性不高。另一方面,动态Web的开发人员也在抱怨,将网站美工设计的静态页面和动态程序合并过程是一个异常痛苦的过程。
如何解决这个问题呢?JSP问世后,有些人认为Servlet已经完全可以被JSP代替。然而,事实是在Servlet不再担负动态页面生成的任务以后,开始担负起决定整个网站逻辑流程的任务。在逻辑关系异常复杂的网站中,借助于Servlet与JSP良好的交互关系和JavaBeans的协助,完全可以将网站的整个逻辑结构放在Servlet中,而将动态页面的输入放在JSP页面中来完成。在这种开发方式中,一个网站可以有一个或几个核心的Servlet来处理网站的逻辑,通过调用JSP页面来完成客户端的请求。读者在后面将可以看到,在J2EE模型中,Servlet的这项功能可以被EJB等取代。
4.MVC开发模型
在MVC开发模型中,整个系统可以分为三个主要的部分:
(1)视图
视图就是用户界面部分,在Web应用程序中也就是HTML、XML、JSP页面。这个部分主要处理用户能看到的东西,动态的JSP部分处理了用户可以看见的动态网页,而静态网页则由HTML、XML输出。
(2)控制器
控制器负责网站的整个逻辑。它用于管理用户与视图发生的交互。读者可以将控制器想象成处在视图和数据之间,对视图与模型交互进行管理的部分。通过使视图完全独立于控制器和模型,就可以轻松替换前端客户程序,也就是说,网页制作人员可以独立自由改变Web页面而不用担心影响这个基于Web应用程序的功能。
在J2EE中,控制器的功能一般是由Servlet、JavaBeans、Enterprise JavaBeans中的SessionBeans来担当。
图1-2 MVC体系结构
MVC的优点表现在以下几个方面:
可以为一个模型在运行时同时建立和使用多个视图。变化-传播机制可以确保所有相关的视图得到及时的模型数据变化,从而使所有关联的视图和控制器做到行为同步。
视图与控制器的可接插性,允许更换视图和控制器对象,而且可以根据需求动态地打开或关闭、甚至在运行期间进行对象替换。
模型的可移植性。因为模型是独立于视图的,所以可以把一个模型独立地移植到新的平台工作。需要做的只是在新平台上对视图和控制器进行新的修改。
潜在的框架结构。可以基于此模型建立应用程序框架,不仅仅是用在设计界面的设计中。
MVC的不足之处表现在以下几个方面:
增加了系统结构和实现的复杂性。对于简单的界面,严格遵循MVC,使模型、视图与控制器分离,会增加结构的复杂性,并可能产生过多的更新操作,降低运行效率。
视图与控制器间的过于紧密的链接。视图与控制器是相互分离,但确实联系紧密的部件。视图没有控制器的存在,其应用是很有限的,反之亦然。这样就妨碍了它们的独立重用。
视图对模型数据的低效率访问。依据模型操作接口的不同,视图可能需要多次调用才能获得足够的显示数据。对未变化数据的不必要的频繁访问,也将损害操作性能。
1.2 Tomcat体系结构介绍
Tomcat目前最新的版本是6.0,支持Servlet 2.5和JSP2.1规范。它由一系列嵌套的组件组成。
顶层组件:位于整个配置文件的顶层。
链接器:链接代表了介于客户与服务之间的通信接口,负责将客户的请求发送给服务器,并将服务器的响应结果传递给客户。
容器组件:包含了其他组件的集合。
嵌套组件:可以加入到容器中的组件,但不能包含其他组件。
图1-3描述了Tomcat配置的结构。
图1-3 Tomcat配置体系结构图
当配置Tomcat时,可以移除一些不需要的对象,但并不会影响服务。一般来说,如果使用Web服务器集成的话,如Apache,则引擎和主机组件是不必要的。Tomcat的组件都是可以在server.xml文件中进行配置的,每个Tomcat组件在Server.xml文件中对应一种配置元素。在第三章中,将会详细讲述Servel.xml文件配置,现在主要对Tomcat的基本组件进行介绍。
1.顶层组件
顶层组件包含Server组件和Service组件。Server组件是Tomcat服务器的实例,可以在Java虚拟机(JVM)中生成唯一的服务器实例。它还可以在一个服务器中,为不同的端口设置单独的服务配置。这样,既方便单独地重启应用程序,又可以在某特定的JVM崩溃时,确保其他实例上的应用程序是安全的。Service组件用来访问请求,把请求转发给合适的Web应用程序,然后返回请求的处理结果;与它的链接器组成引擎组件。引擎也就是Servlet引擎,是请求处理的组件。引擎检查HTTP头,然后决定传送给哪个主机或者应用程序。每个Service都被命名,方便管理员能够通过日志记录每个Service的信息。
2.链接器组件
链接器链接Web应用程序和客户端,代表和客户端实际交互的组件。它负责接受来自客户端的请求,以及向客户返回响应结果。Tomcat的默认端口是8080,为了避免与其他的Web服务器标准端口(80)相冲突。比较常见的链接器是HTTP connector和Apache JServ Protocl(AJP)connector.
3.容器组件
容器组件负责接受来自顶层组件的请求,然后处理这些请求,并把处理结果返回给上层组件。容器组件包括引擎组件(Engine Component)、主机组件(Host Component)和上下文组件(Context Component)。引擎组件负责接受和处理来自它所属的Service中的所有Connector的请求。每个Service组件只能包含一个引擎组件。主机组件定义了一个虚拟主机,它允许在同一台物理机器上,配置多个Web应用;多个主机组件可以包含在引擎组件中。上下文组件是使用最为频繁的组件,每个上下文组件代表了允许在虚拟主机上的每个Web应用。一个虚拟主机能够运行多个Context,它们通过各自的Context Path进行相互区分。
4.嵌套组件
嵌套组件嵌套在容器内,为管理人员提供管理服务。包括全局资源组件(The Global Resources Component)、加载器组件(Loader Component)、日志组件(Logger Component)、管理器组件(Manager Component)、域组件(Realm Component)、资源组件(Resources Component)和阀组件(Valve Component)。
全局资源组件只能嵌套在Server组件中,用于配置server中其他组件所用到的全局JNDI资源。加载器组件只能嵌套在上下文组件中,用于指定一个Web应用程序的类加载器,并将该应用程序的类和资源加载到内存中。一般来说,Tomcat中默认的类加载器就能满足大部分的需求,因此开发人员没有必要定制自己的类加载器。日志组件能借助Log4J来实现记录日志。
管理器组件也只能在Context中有效。管理器组件是会话管理器,负责会话的创建和维护。域组件是一个包含用户名、密码和用户角色的数据库。角色与Unix的group类似。域的不同实现允许将Catalina集成到认证信息已经被创建和维护的环境中,然后利用这些信息来实现容器管理的安全性。在任何组件中(如引擎、主机或者上下文组件)都可以嵌套域组件。另外,引擎或者主机的域会自动被低层次的容器集成,除非被明确覆盖。资源组件只在上下文组件中支持,它代表的是Web应用程序中的静态资源,以及它们被允许存放的格式,例如压缩文件等。
阀组件用于在请求在被到达目的之前,截取该请求,并处理它。有点类似于Servlet规范中定义的过滤器。它是Tomcat专有的、目前还不能用于其他的Servlet/JS容器。阀组件可以嵌入到其他组件中,如引擎、主机和上下文组件。阀组件通常用于记录请求、客户端IP地址,以及服务器端利用率信息,这种技术被称为请求转储(Request Dumping)。一个请求转储阀记录HTTP头的信息和Cookies信息。响应转储阀记录响应HTTP头和Cookies信息。阀是可重用的组件,能按照用户的需求增删。
1.3 Tomcat与应用Web服务器
1.3.1 Tomcat与应用服务器
到目前为止,Tomcat一直被认为是Servlet/JSP API的执行器,也就所谓的Servlet容器。然而,Tomcat并不仅仅如此,它还提供了JNDI和JMX API的实现机制。尽管如此,Tomcat仍然还不能算是应用服务器,因为它不提供大多数J2EE API的支持。
很有意思的是,目前许多的应用服务器通常把Tomcat作为它们Servlet和JSP API的容器。由于Tomcat允许开发者只需通过加入一行致谢,就可以把Tomcat嵌入到它们的应用中。遗憾的是,许多商业应用服务器并没有遵守此规则。
对于开发者来说,如果是为了寻找利用Servlet、JSP、JNDI和JMX技术来生成Java Web应用的话,选择Tomcat是一个优秀的解决方案;但是为了寻找支持其他的J2EE API,那么寻找一个应用服务器或者把Tomcat作为应用服务器的辅助,将是一个不错的解决方案;第三种方式是找到独立的J2EE API实现,然后把它们跟Tomcat结合起来使用。虽然整合会带来相关的问题,但是这种方式是最为有效的。
1.3.2 Tomcat与Web服务器
Tomcat是提供一个支持Servlet和JSP运行的容器。Servlet和JSP能根据实时需要,产生动态网页内容。而对于Web服务器来说, Apache仅仅支持静态网页,对于支持动态网页就会显得无能为力;Tomcat则既能为动态网页服务,同时也能为静态网页提供支持。尽管它没有通常的Web服务器快、功能也不如Web服务器丰富,但是Tomcat逐渐为支持静态内容不断扩充。大多数的Web服务器都是用底层语言编写如C,利用了相应平台的特征,因此用纯Java编写的Tomcat执行速度不可能与它们相提并论。
一般来说,大的站点都是将Tomcat与Apache的结合,Apache负责接受所有来自客户端的HTTP请求,然后将Servlets和JSP的请求转发给Tomcat来处理。Tomcat完成处理后,将响应传回给Apache,最后Apache将响应返回给客户端。
1.4 小结
本章简要地回顾了Java Web的历史、技术发展状况,介绍了动态Web页面的各种类型。对于不同规模的Web系统来说,有多个开发框架可供选择:直接使用JSP、JSP+JavaBeans+Servlet、MVC模型等开发方式。
Tomcat 6作为Tomcat目前的最新版本,支持最新的Servlet 2.5和JSP 2.1规范。Tomcat由于仅仅只提供了Servlet/Jsp支持,因此它不能称为应用服务器;Tomcat既能提供静态网页支持,也能为动态网页服务,但是处理静态页面的效率没有其他的Web服务器快、功能完善。因此通常的解决方案是将Tomcat与其他J2EE应用服务器或其他的Web服务器结合起来使用。
第2章 安装Tomcat
【本章导读】
在本章中,读者将学会如何在不同操作系统上进行Tomcat的安装,以及Tomcat源码安装。同时,通过本章的学习,读者将能掌握Tomcat的安装目录结构,以及Web应用程序的目录结构。
本章主要内容有:如何在Windows和Linux中安装Tomcat,如何安装JDK,Tomcat的安装目录结构,以及Tomcat安装过程出现的问题和解决的办法。
2.1 安装JDK
Tomcat跟其他基于Java的应用程序一样,都需要Java虚拟机(JVM)的支持。Sun公司发布了免费的、针对Windows、Linux和Solaris的不同JVM版本。
2.1.1 在Windows上安装JDK
从Sun公司的网站(http://java.sun.com/javase/downloads/index.jsp)上下载最新的版本Tomcat 6.Tomcat 6虽然需要运行在JSE5.0或更高版本之上,但是它仅需要Java运行环境就可,不再像以前的版本需要完全的Java开发包。目前JDK的最新版本为JDK 6,因此下载最新的JDK 6 Update3就可以了。JDK 6 Update 3包含了Java运行环境和命令行开发工具。如图2-1所示。
图2-1 SunJDK下载页面
【例2-1】在Windows上安装JDK
在Windows上Java安装包是一个标准的Windows安装包,操作简单方便。双击jdk-6u3-windows-i586-p.exe,出现对话框,如图2-2所示。
选择【接受(A)】按钮,就会出现自定义安装对话框,如图2-3所示。
图2-2 JDK安装许可证界面 图2-3 JDK自定义安装界面
选择Java安装的目录,本书将Java安装目录改为:C:\JDK1.6.修改完后,单击【下一步】按钮,出现安装进度条,进入安装过程,安装完后会提示JDK安装完毕。然后出现JRE自定义安装对话框,如图2-4所示。
同样更改安装目录为c:\JRE1.6,当然用户可以根据自己的需要更改安装目录。单击【下一步】按钮,出现安装进度条,进入安装过程,安装完后会提示JRE安装完毕。如图2-5所示。
图2-4 JRE自定义安装 图2-5 JRE安装完成
此时Java安装完毕,下一步设置环境变量。选择Windows开始菜单栏上"开始→设置→控制面板",然后选择"系统"选项。如图2-6所示。
选择"高级"标签,单击【环境变量】按钮。这时出现如图2-7所示。
图2-6 系统属性 图2-7 环境变量
单击系统变量【新建】按钮,输入JAVA_HOME作为变量名,然后输入Java刚刚安装的目录路径。如图2-8所示。
然后修改%PATH%变量,增加%JAVA_HOME%\bin路径。增加该路径,是为了使得Java能够在命令行中可以执行。如图2-9所示。
图2-8 添加JAVA_HOME新的环境变量 图2-9 修改PATH环境变量
验证变量是否修改成功,可以打开命令行,输入:
> java –version
应该可以看到结果,如图2-10所示。
图2-10 Java版本显示结果
至此,在Windows上Java安装完成。
2.1.2 在Linux上安装JDK
从Sun网站下载合适的Linux版本,如图2-11所示。
图2-11 Sun下载Linux版本页面
Sun提供Linux平台上JDK的两个版本Linux RPM in self-extracting file和Linux self-extracting file.这两个版本的区别在于:前者把RPM安装包封装在压缩包的二进制格式,适合低级用户安装;后者则适合高级用户,相当于一个"绿色"的zip版本JDK,没有安装程序,安装完后可能需要做一些链接。
【例2-2】在Linux上安装JDK
使用self-extracting binary安装Java,首先必须设置它的执行权限,在命令行中输入如下命令:
# chmod +x jdk-6u3-linux-i586.bin
【提示】
用self-extracting binary在Linux上安装Java时,不必设置成Root权限,因为该二进制包不会覆盖任何系统文件。
然后改变目录到需要安装的目录下(本书安装目录是/usr/java/jdk1.6),执行二进制文件,输入如下命令:
# ./ jdk-6u3-linux-i586.bin
之后会出现licence对话框,单击【agree】按钮,开始Java安装。跟Windows安装一样,必须设置$JAVA_HOME环境变量,指明JDK的安装目录。一种是修改用户所对应的~/.bashrc文件,在该文件中增加:
JAVA_HOME=/usr/java/jdk1.6/
export CLASSPATH=.
export PATH = $JAVA_HOME/bin:$PATH
另外一种是设置Linux的全局环境变量(这个需要Root权限),在/etc/profile文件增加如下几行:
JAVA_HOME=/usr/java/jdk1.6/
export JAVA_HOME
PATH=$JAVA_HOME/bin:$PATH
CLASSPATH=.:$JAVA_HOME/lin/tools.jar:$JAVA_HOME/lib/dt.jar
export PATH JAVA_HOME CLASSPATH
然后保存文件,注销当前用户。重新登录后,在控制台下输入java –version,如果出现版本信息,表明安装成功。
使用RPM包安装Java.首先必须确定是Root用户,然后在命令行中输入如下命令:
# chmod a+x jdk-6u3-linux-i586-rpm.bin
# ./ jdk-6u3-linux-i586-rpm.bin
# rpm –iv jdk-6u3-linux-i586-rpm.bin
之后,按照之前所述的修改系统环境变量,测试安装是否成功。
2.2.1 在Windows上安装Tomcat
从Apache网站上(http://Tomcat.apache.org/download-60.cgi)下载最新的Tomcat版本,目前Apache发布的最新Tomcat版本是6.014.如图2-12所示。
图2-12 Apache网站Tomcat下载页面
在"Binary Distribution"栏的Core子栏下提供了三种Tomcat 6的方式:zip、tar.gz、Windows Service Installer.zip方式下载后得到的是一个zip文件,无须安装,解压缩后即可使用。tar.gz方式下载后得到一个tar.gz文件,是在GNU操作系统(一种类似于Unix的操作系统,其源码是可以被复制、修改和重新发布的)中用tar命令打包而成的,因此必须在与GNU相兼容的操作系统中解包,Solaris和Mac OS X操作系统中不能使用。"Windows Service Installer"方式下载后得到的是一个exe文件(如图2-12所示的版本为apache-Tomcat-6.0.14.exe)。Tomcat 6是在Windows操作系统下的安装程序,这种方式安装的Tomcat 6可以通过Windows的服务来控制启动、停止。
【例2-3】在Windows上安装Tomcat
双击下载apache-Tomcat-6.0.14.exe文件,出现安装Licence Agreement对话框,如图2-13所示。
图2-13 Tomcat安装页面License对话框
单击【I Agree】按钮,进入到Choose Component对话框,如图2-14所示。
图2-14 Tomcat自定义安装对话框
一般默认Normal即可,单击【Next】按钮进入到选择安装路径选项,如图2-15所示。
修改安装的目录,本书Tomcat的安装目录为C:\Tomcat 6.单击选择【Next】按钮,进入到配置对话框,如图2-16所示。
图2-15 Tomcat安装目录修改 图2-16 Tomcat安装配置对话框
可以修改Connector端口和管理员用户名、密码。本书使用的默认值为8080端口,管理员用户名是admin,密码为空。单击【Next】按钮,进入JVM路径选择对话框,如图2-17所示。
图2-17 Tomcat安装选择JRE安装目录
选择JDK安装的路径后,单击【Install】按钮,开始Tomcat安装,完成后会提示安装完成。
接下来设置环境变量。同JDK设置环境变量一样,在系统变量中增加一项为%CATALINA_HOME%,它表明Tomcat在本机上安装的路径。修改后如图2-18所示。
图2-18 Tomcat环境变量设置
为了测试Tomcat安装是否成功,必须运行Tomcat服务器,选择"开始→Apache Tomcat 6.0→Monitor Tomcat",出现Tomat控制台,如图2-19所示。
图2-19 Tomcat控制台对话框
单击【Start】按钮,开始Tomcat服务。成功启动后,控制台服务状态会变成Started,如图2-20所示。
图2-20 Tomcat启动成功后对话框
Tomcat启动后,打开浏览器,输入http://localhost:8080,如果能够看到Tomcat的默认页面(如图2-21所示),则表明安装成功。
图2-21 Tomcat默认页面
2.2.2 在Linux或Mac OS上安装Tomcat
在Linux或者Mac系统上安装Tomcat也是非常简便的。基本流程为:
从Apache网站上(http://Tomcat.apache.org/download-60.cgi)下载Tomcat的zip格式或者gzip tar格式文件。
解压文件到硬盘,例如/usr/java/Tomcat 6.
输出$CATALINA_HOME变量,使用下列Shell命令
# CATALINA_HOME=/usr/java/Tomcat 6
# export CATALINA_HOME
类似与JDK的安装,可以把这些命令加到~/.bashrc或者/etc/profiles文件里面。
最后使用上面Shell命令启动Tomcat.
打开浏览器,输入http://localhost:8080/,查看Tomcat是否安装成功。也可以选择Tomcat中左边菜单栏的JSP例子,运行其中一个,确保Tomcat运行没有任何错误信息。
至此,Tomcat在Linux或者MAC系统上安装完毕。
2.2.3 Tomcat端口配置
Tomcat一般用8080端口作为默认端口。如果用户需要修改Tomcat的端口配置,打开%CATALINA_HOME%\conf目录下的server.xml文件,找到下面文本:
<!-- A "Connector" represents an endpoint by which requests are received
and responses are returned. Documentation at :
Java HTTP Connector: /docs/config/http.html (blocking & non-blocking)
Java AJP Connector: /docs/config/ajp.html
APR (HTTP/AJP) Connector: /docs/apr.html
Define a non-SSL HTTP/1.1 Connector on port 8080
-->
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
可以看到端口一项写的是8080,修改这个设置为用户定制的端口。比如将端口修改为port="80",那么重新启动Tomcat后,打开浏览器,只需输入http://localhost就可看到Tomcat的默认页面,不再像以前后面还需加上冒号和端口号。
2.2.4 安装Ant
Ant工具是Apache的一个开源项目,它是一个优秀的软件工程管理工具。Ant类似于Make工具,但克服了传统的Make工具的缺点。传统的Make往往只能在某一平台上使用,Ant本身用Java语言实现,并且使用XML格式的配置文件来构建成,可以很方便地实现多平台编译,非常适合管理大型工程。
从Ant官方网站(http://ant.apache.org/bindownload.cgi)下载最新的二进制版本。目前最新的版本为1.7.0,如图2-22所示。
图2-22 Ant下载页面
【例2-4】安装Ant
将下载的apache-ant-1.7.0-bin.zip文件解压,设置如下环境变量:
ANT_HOME ant的安装目录(本书默认安装目录为C:\ant1.7)如图2-23所示。
图2-23 Ant安装目录变量设置
PATH把%ANT_HOME%/bin目录添加到PATH变量中,便于命令行能直接运行Ant.如图2-24所示。
图2-24 Path变量设置
测试Ant是否安装成功,打开命令行,输入ant –version,如果能看到Ant信息,如图2-25所示,则表明Ant安装成功。
图2-25 Ant版本信息
至此,Ant安装完毕。下一节讲述如何使用Ant来安装Tomcat。
2.2.5 安装Tomcat源码
从Apache网站上(http://Tomcat.apache.org/download-60.cgi)下载最新的Tomcat源码版本,目前最新的版本为apache-Tomcat-6.0.14-src.然后执行下面操作:
进入到Tomcat解压目录(本书默认为c:\ apache-Tomcat-6.0.14-src)。
确定JDK和ANT已经安装。
在源码目录下新建一个build.properties文件,输入以下代码:
# ----- Proxy setup -----
# Uncomment if using a proxy server
#proxy.host=proxy.domain
#proxy.port=8080
#proxy.use=on
# ----- Default Base Path for Dependent Packages -----
# Replace this path with the directory path where dependencies binaries
# should be downloaded
base.path=/usr/share/java
在命令行输入命令Ant.
构建的过程大概会持续几分钟,最后构建好的项目存放在${Tomcat.source}\output\build目录下。当然也可以在用户指定的目录下新建目录,将编译好的Tomcat二进制代码移植过来,环境变量的设置同上。
如果用户想更新源码,并重新编译,需输入以下命令:
> ant checkout
> ant build
至此,Tomcat源码安装完毕。
2.3 Tomcat安装目录
前面介绍了Tomcat在不同平台上的安装流程,包括使用二进制版本和源码版本。现在介绍一下Tomcat的安装目录。在本节中,可以了解到Tomcat的主要配置文件,以及目录结构。Tomcat安装目录结构如图2-26所示。
图2-26 Tomcat安装目录结构
1.Bin目录
Bin目录存放着Windows平台,以及Linux平台上启动和关闭Tomcat的脚本文件。Tomcat的早期版本,有多个不同的启动和关闭脚本。Tomcat 6将这些统一到下列执行脚本中:
Tomcat 6 Windows可执行文件
如果Tomcat安装作为NT服务的话,可以使用Tomcat 6.exe可执行文件启动服务器。
Tomcat 6w Windows可执行文件
如果Tomcat安装作为服务的话,可以使用Tomcat 6w.exe可执行文件来启动服务器。它将调用Tomcat的控制台,可以手动启动或关闭Tomcat服务。在上节Tomcat安装中有详细介绍。
2.Conf目录
Conf目录存放在Tomcat服务器的各种配置文件,其中包括:
Catalina.policy:为Catalina运行在安全管理的上下文中,设置必要的权限。
Catalina.properties:设置不同类加载器目录的位置。默认的是Common目录和它的子目录。设置的目的是为了区分哪些类可以被Tomcat使用,哪些类可以被Web应用使用。
Context.xml:设置默认的Web应用上下文。
Logging.properties:管理默认的日志等级。
Server.xml:是Tomcat的最为主要的配置文件。可以配置任何元素,包括日志、过滤器、端口、主机等,详细的讨论在第三章。
Tomcat-user.xml:默认的用户数据库,用于容器认证管理。
Web.xml:默认的Web应用程序部署描述符。
3.Logs目录
ogs目录是Tomcat的日志文件默认的存放位置。
4.Lib目录
ib目录存放Tomcat服务器所需的各种JAR文件。
5.Temp目录
Temp目录存放Tomcat使用的临时文件。
6.Webapps目录
Webapps目录是当发布Web应用时,在默认情况下把Web应用文件放于此目录下。
7.Work目录
Work目录是Tomcat把由JSP生成的Servlet的文件存放的位置。
2.4 Web应用目录结构介绍
Web应用程序是一些Web资源的集合,包括JSP页面、HTML页面、Servlets和配置文件。它们按照Servlet规范中所描述的那样,分层次地组织起来(表2-1)。它们可以通过两种途径构建Web应用程序:一是打包,另外一个是不打包。打包的被称为Web档案(War)文件,未打包的是存放文件系统的目录结构。
未打包的格式对于Web应用开发人员来说非常便利,可以允许他们在开发和调试期中,随时、方便地更新单独的文件。然后在部署环境中,提供一个单独的文件自动部署,可能更为便利。这是因为减少了部署过程中放置文件和设置系统变量的步骤。
表2-1 Web应用程序描述
2.4.1 Web应用上下文
从上一章Tomcat的体系结构介绍可以知道,上下文组件表示的每个Web的应用。因此每个Web应用对应于一个上下文组件,它可以为每个Web应用分配一个上下文路径。默认的上下文组件是Root,可以通过输入http://localhost:8080地址,访问到默认的应用程序。用户通过向服务器请求一个Web应用的上下文,可以访问到该Web应用程序。例如,用户可以输入URL:http://localhost:8080/manager来访问Tomcat的Web管理应用程序。
如果把Web应用程序放置在Webapps目录下,那么访问该Web应用的URL为:http://localhost:8080/xxx(xxx为Web应用目录名称)。例如Tomcat的Webapps中默认自带了jsp例子,它放置在Webapps的目录名称为example,这样通过输入http://localhost:8080/examples/jsp/来访问该应用程序,如图2-27所示。
图2-27 JSP例子
如果在ROOT目录下放置的子目录与Web应用程序的目录名一致,则会出现混淆,例如:
webapps/
ROOT/
shop/
index.html
shop/
index.html
从上面的目录结构中可以看出,在ROOT目录里面放置了跟Shop同名的目录和文件,通过http://localhost:8080/shop可能映射到ROOT目录和Webapps下Shop目录里的文件,这样就造成了混淆。当然Tomcat的处理机制是它会忽视ROOT目录里与Web应用重名的文件,直接显示来自Shop应用程序里面的文件。如果开发人员的出发点是想用户访问Root目录里的Web应用程序,那么就必须特别注意了。
2.5 常见问题与解决办法
1.Tomcat窗口消失
有时,启动Tomcat后,Tomcat窗口显示了一下,然后就会立刻消失。这个问题主要是由于某些错误造成了Tomcat的崩溃。错误信息本身是可以显示的,但是由于窗口消失得太快,以至于错误信息不能被用户看到。可按以下方式解决。
在Linux输入:
# $CATALINA_HOME/bin/catalina.sh run
或者在Windows里面输入:
> %CATALINA_HOME/bin/catalina run
输入上述命令后,将使得Tomcat正常启动,任何错误信息都将被显示。错误信息也可以通过stdout.log文件看到。
2.端口被占用
可能会出现选定的端口被占用的情况,类似于下面的错误信息:
LifecycleException: Protocol handler initialization failed:
Java.net.BindException: Address already in use: JVM_Bind:8080
Tomcat默认使用8080端口,在Windows或Linux中,可以通过输入netstat命令来查看谁使用了那个端口,可以看到谁跟Tomcat引起了冲突。解决该问题的方式有两种:一是关闭占用Tomcat端口的进程,一个是改变Tomcat的默认端口配置。
3.类版本错误
Tomcat 6需要Java SE 5或后期更为高级的版本,如果使用了早期Tomcat版本,在Tomcat启动时,将会出现下列错误:
Exception in thread "main"
java.lang.UnsupportedClassVersionError:
org/apache/catalina/startup/Bootstrap (Unsupported major.minor version 49.0)
解决方法:检查JAVA_HOME环境变量设置是否指向了Java SE 5的安装目录。
第3章 配置Tomcat
【本章导读】
在本章中,通过查看CATALINA_HOME/conf目录下的文件,主要介绍Tomcat的基本配置。在启动时,Tomcat默认安装使用这些文件配置服务器,它们对于用户了解默认的配置将做些什么和用户能够修改什么有至关重要的作用。
本章还介绍了主要的配置文件--server.xml和Tomcat的其他配置文件。在第一章中,曾经看到Tomcat使用基于组件、层次似的结构。该模型在很大程度上简化了复杂的服务器配置。最后还讲述了Tomcat的认证配置和Web应用配置,用户可以根据自己的需要加以修改。
3.1 Tomcat 6配置元素
Tomcat 6服务器启动后,读取一系列的XML配置文件。为了配置Tomcat 6,用户必须修改这些XML文件。Tomcat的配置文件位于CATALINA_HOME/conf目录下,它包含以下配置文件:
表3-1 Tomcat基本配置元素
Tomcat 6在指定的配置目录中搜寻这些配置文件。配置目录由环境变量指定。Tomcat 6首先检查$CATALINA_BASE(%CATALINA_BASE%在Windows)环境变量,如果这个环境变量已经定义了,则Tomcat 6搜寻该环境变量指定目录下的conf子目录。对于在同一台机器上配置多个并发的Tomcat也非常直接和方便,分别为每个Tomcat实例设置不同的$CATALINA_BASE目录即可,也可以使用不同的shell脚本和batch文件来设置$CATALINA_BASE变量和启动各自实例。
如果$CATALINA_BASE没有被指定,则Tomcat使用$CATALINA_HOME(%CATALINA_HOME%在Windows)环境变量来代替。$CATALINA_HOME环境变量指定了Tomcat 6的安装目录。Tomcat 6会在$CATALINA_HOME指定的conf子目录中搜寻配置文件。在后续的章节中,会重点讲述配置文件的基本配置。
第16章 集成Tomcat与Eclipse
【本章导读】
Eclipse是一个开放源代码的项目,是非常优秀的开发Java程序的集成开发环境(IDE)。Eclipse可以通过开发新的插件来扩展现有的插件功能,而且有着统一的外观、操作和系统资源管理界面。比如:在现有Eclipse的Java开发环境中加入Tomcat服务器插件。Tomcat提供了JSP/Servlet容器,是目前应用最为广泛的服务器之一。把Tomcat与Eclipse集成,可以很方便地在Eclipse的IDE环境中进行Tomcat Web应用项目的开发和调试。为快捷方便地开发Tomcat Web应用项目提供了具有实用意义的模式。
本章主要内容包括Eclipse的简介、Eclipse的安装和配置、Tomcat与Eclipse的集成、Tomcat与Eclipse集成开发应用。
16.1 Eclipse简介
Eclipse是一个开放源代码的项目。Eclipse 是替代IBM Visual Age for Java(简称IVJ)的下一代IDE开发环境,但它未来的目标不仅仅是成为专门开发Java程序的IDE环境。根据Eclipse的体系结构,通过开发插件,它能扩展到任何语言的开发,甚至能成为图片绘制的工具。下面对Eclipse的各种特性作简单介绍:
Eclipse开发环境
Eclipse开发环境被称为Workbench,它主要由三个部分组成:视图(Perspective),编辑窗口(Editor)和观察窗口(View)。编辑窗口包含所有文件的显示和编辑,用指定的编辑器可以打开和编辑相应的文件。观察窗口主要是配合编辑窗口,并提供多种相关信息和浏览方式。一个视图包括一个或多个编辑窗口和观察窗口。视图是Eclipse中最灵活的部分,可以自定义每个视图中所包含的观察窗口种类,也可以自定义一个新视图。
Eclipse版本管理
Eclipse的版本管理分为个人(或称为本地)版本管理和团队版本管理两种。Eclipse提供了强大的个人版本管理机制,每一次被保存的更改都可以得到恢复,而且可以精确到每一个方法的版本恢复。强大的个人版本管理功能为程序员提供了更多的信心--只管编写下去,任何不小心的错误都可以恢复。Eclipse默认为版本管理工具CVS提供了接口,可以非常方便的连接到CVS服务器上。通过CVS版本管理,Eclipse为团队开发提供了良好的环境。
Eclipse插件扩展
使用插件可以丰富Eclipse的功能。可以通过开发新的插件扩展现有插件的功能,而且有着统一的外观、操作和系统资源管理界面,这也正是Eclipse的潜力所在。比如在现有的Java开发环境中加入Tomcat服务器插件。插件扩展使得Eclipse非常易于拓展,更重要的是Eclipse平台提供了一个非常好的方式,使得各个插件能协同工作,以至新的特性可以简单而无缝的融入平台。
Eclipse体系结构
Eclipse的体系结构,除了小巧的Eclipse平台核心运行时之外,Eclipse平台仅由工作台(WorkBench)、工作空间(Workspace)、帮助(Help)和小组组件(Team Compenent)四部分构成,其他的工具以插件的形式集成进框架以创建应用程序。
Eclipse 多国语言包
Eclipse的多国语言包中不仅有Eclipse的中文翻译,同时也包含了其他几种主要语言的翻译。Eclipse能够自动根据Windows操作系统的语言环境来选择使用语言包中的哪一种语言,极具智能化的特点。
Eclipse语言和平台中立
尽管Eclipse是以Java 语言编写的,并且通常是作为Java IDE来使用的,但它却是语言中立的。Java开发是由一个插件组件来支持的,如上所述,可以添加其他的插件来支持其他语言开发,例如:C/C++,Cobol和C#.Eclipse并不只是一个Java IDE,同时它是一个可拓展的、开放的开发工具平台。例如,它可以作为其他语言的IDE。
16.2 Eclipse的安装和配置
Eclipse的安装和配置都很简单,安装前需要先下载Eclipse开发包。可以从Eclipse的官方网站(http://www. eclipse.org)免费下载。目前Eclipse的最新版本是Eclipse-SDK-3.3.0,Eclipse版本不断更新,请密切关注Eclipse的官方网站。一般Eclipse提供以下几个下载版本:Release,Stable Build,Integration Build和Nightly Build,建议下载Release或Stable版本。此文所用的是Release版本的Eclipse 3.3.0。
16.2.1 安装Eclipse
Eclipse本身是用Java语言编写的,所以Eclipse需要运用在Java环境下。如果机器上还没有安装Java运行环境(JRE),则需要先安装JRE.最新的JDK可以从Sun官方网站(http://www. sun.org)免费下载。同时Eclipse3.0以上的版本,需要JDK 1.4以上版本。
【例16-1】安装Eclipse
Eclipse的安装非常简单,只需要将下载的软件压缩包直接解压即可。假如解压到C盘的根目录下,进入C盘,就可以看到解压后的Eclipse目录。打开Eclipse目录,可以看到如图16-1所示的Eclipse文件或目录。
图16-1 Eclipse解压后的目录界面
其中:
/configuration:Eclipse配置文件的目录
/features:存在功能部件的目录
/plugins:存在插件的目录
/readme:Eclipse描述文件的目录
eclipse.exe:Eclipse启动程序
……
【提示】
如果在机器里有Eclipse的老版本,应先删除老版本,不可用新版本解压到老版本的目录下直接覆盖老版本。
16.2.3 Eclipse多国语言包
之前安装的Eclipse程序,IDE界面都是英文界面。如果想要显示熟悉的中文界面,则可以安装Eclipse多国语言包来实现。Eclipse支持多国语言,可以让界面变成不同语言的界面。Eclipse多国语言包同样可以从Eclipse的官方网站(http://www. eclipse.org)免费下载,一般下载与当前安装的Eclipse版本相对应的多国语言包。
【例16-2】安装Eclipse多国语言包
Eclipse多国语言包的安装与Eclipse的安装一样简单,可以按照以下步骤完成安装:
如果Eclipse正在运行使用,需要先关闭Eclipse应用。
把Eclipse多国语言包解压到本地某一个硬盘,假如D盘。解压后,可以看到2个文件夹features和plugins,如图16-5所示。
图16-5 Eclipse多国语言包解压后目录界面
分别把features和plugins文件夹下的所有文件拷贝覆盖到Eclipse安装目录对应的features和plugins文件夹下。
重新运行Eclipse.exe程序,则可以看到Eclipse的默认界面变成了中文界面,如图16-6所示。
图16-6 Eclipse中文默认界面
【提示】
如果重新运行Eclipse.exe程序后,Eclipse的默认界面没有变成中文界。原因可能是Eclipse多国语言包版本过低或版本不一致所造成。需要检查Eclipse多国语言包是否是对应的Eclipse版本,可以下载更高版本的多国语言包,最好是对应版本的多国语言包。
16.3.1 Tomcat插件安装和初始化配置
Eclipse的Tomcat插件可以从Sysdeo网站(http://www.sysdeo.com/eclipse/)或是(http://www.eclipsetotale.com/tomcatPlugin.html)免费下载。目前最新版本为3.2.0.
【例16-3】在Eclipse中安装和初始化Tomcat插件
Tomcat插件安装
下载了Eclipse的Tomcat插件后,把Tomcat插件解压到本地硬盘,假如D盘。解压后可以在D盘目录下看到com.sysdeo.eclipse.tomcat_3.2.0文件夹,把整个com.sysdeo.eclipse.tomcat_3.2.0文件夹拷贝到Eclipse主目录下的plugins文件夹里面即可。
Tomcat插件初始化配置
Eclipse的Tomcat插件安装成功后,并不是就马上可以在Eclipse中使用Tomcat插件。为了能够在Eclipse中使用Tomcat插件,还需要在Eclipse中对Tomcat插件进行一定的初始化配置。Tomcat插件的初始化配置,可以按照以下的步骤完成:
重新启动Eclipse。
单击菜单栏的"Window",在下拉菜单中选择"Preferences".如图16-7所示。
在弹出的"Preferences"窗口中,选择左边目录树里的"Tomcat"项目。正确选择机器中所安装的Tomcat版本,指定Tomcat home的安装目录,指定Tomcat的server.xml配置文件和配置文件的目录。其配置之后的界面如图16-8所示。
展开"Tomcat",选择下面的"JVM Setting",选择JRE为机器所安装的JDK,即"jre 1.6.0",确认无误后按【OK】关闭窗口即可。如图16-9所示。
图16-7 Eclipse "Window/ Preferences"菜单界面
图16-8 Eclipse"Preferences"窗口下"Tomcat"项目配置后界面
图16-9 Eclipse "Preferences"窗口下"JVM Setting"界面
单击菜单栏的"Window",在下拉菜单中选择"Customize Perspective…".如图16-10所示。
图16-10 Eclipse"Window Customize Perspective…"菜单界面
在弹出的"Customize Perspective"窗口中,选择左边的"Commands"标签。在左边的"Available commands groups"组中,下拉选框可以看到Tomcat选项,在Tomcat选项中打上钩选中,按【OK】关闭窗口。如图16-11所示。
图16-11 Eclipse 选择Tomcat选项界面
这时在IDE的工具栏中就看到Tomcat小猫图标,同时在菜单栏中也可以看到添加了"Tomcat"菜单。工具栏与"Tomcat"菜单中包含了"Start Tomcat","Stop Tomcat"和"Restart Tomcat".如图16-12所示。
到此,Tomcat插件初始化配置的工作都完成了。可以从工具栏或是"Tomcat"菜单中启动Tomcat,在控制台Console显示区就可以看到Tomcat启动信息(如图16-13所示)。为了测试配置是否成功,可以打开浏览器,在地址栏键入"http://localhost:8080".如果可以看到Tomcat的欢迎页面则说明配置成功。
图16-12 Eclipse中添加了Tomcat菜单的界面
图16-13 Eclipse中Tomcat启动信息界面
16.3.2 Eclipse和Tomcat集成开发实例
Eclipse和Tomcat集成配置成功后,就可以方便快捷的在Eclipse IDE环境里开发、调试Tomcat项目。下面以一个简单的例子来描述在Eclipse和Tomcat集成环境下开发调试Web应用的一般方法。开发一个Tomcat项目,一般需要先创建一个Tomcat工程,然后在已经创建的Tomcat工程里进行开发应用,最后就是调试发布。
【例16-4】Eclipse和Tomcat集成开发实例
新建一个Tomcat工程
新建一个Tomcat工程,可以按照下面的步骤来完成:
单击菜单栏的"File/New",在右边菜单中选择"Project…".如图16-14所示。
图16-14 "File/New/Project…"菜单界面
在"New Project/Select a wizard"窗口中,选择"Java/Tomcat Project"创建Tomcat工程,然后按【Next】进行下一步骤。如图16-15所示。
图16-15 选择创建Tomcat工程界面
在"New Tomcat Project/Java Project Settings"窗口中,需要填写Tomcat工程的名称。为了便于理解,应该填写一个能够体现项目内容的Tomcat工程名称。这里假如为"TomcatProject".还有需要选择工作目录,可以使用默认的工作目录,也可以另外选择工作目录,然后按【Next】进行下一步骤。如图16-16所示。
在"New Tomcat Project/ Tomcat Project Settings"窗口中,需要设置Tomcat Context的名称,一般采用默认的Context Name.同时需要选择是否自动更新server.xml.为简单起见,一般都选择自动更新server.xml文件,这样当服务配置改变的时候,server.xml文件也会跟着改变。在"是否自动更新server.xml"方框里打勾选中,然后按【Finish】完成。如图16-17所示。
到此,一个Tomcat工程就创建完成了。在左边的"Packa"观察窗口里,可以看到已经创建好的Tomcat工程。其工程结构如图16-18所示。
图16-16 填写Tomcat工程名称和选择工作目录界面
图16-17 Tomcat 设置Tomcat工程界面
图16-18 Tomcat工程结构界面
创建一个JSP文件
单击菜单栏的"File/New",在右边菜单中选择"Other".在弹出的"New/Select a wizard"窗口,选择"Web/Jsp",然后按【Next】按钮。在弹出的"New JavaServer Page/JavaServer Page"窗口,需要选择JSP的创建目录,这里为"/WEB-INF"目录下。JSP的名称为index.jsp,然后按【Next】按钮进行下一步。如图16-19所示。
图16-19 创建JSP界面
在弹出的"New JavaServer Page/Select JSP Template"窗口,可以选择使用JSP模板,也可以不选择使用。为了方便,一般选择使用JSP模板,然后按【Finish】完成。如图16-20所示。
图16-20 选择JSP模板界面
下面要在index.jsp页面显示"Hello Tomcat!"和当前的时间。index.jsp的代码如下所示:
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Insert title here</title>
</head>
<body>
Hello Tomcat!<p>
<%java.util.Date d = new java.util.Date();%>
Now Is:<%= d.toLocaleString()%>
</body>
</html>
启动Tomcat插件进行测试
要启动Tomcat 服务器,只需简单的单击工具条中的【Start Tomcat】按钮,或是在主菜单中选择Tomcat 菜单,然后再选择"Start Tomcat"即可。
当Tomcat服务器启动的时候,在控制台Console显示区就可以看到Tomcat服务器启动信息。可以通过查看Tomcat服务器启动信息来检查Tomcat服务器在启动中的问题和结果。同时还可以查看访问Tomcat服务器的信息。
当Tomcat服务器已经启动之后,可以打开系统的Web 浏览器,也可以启动Eclipse IDE里自带的Web 浏览器,在地址栏键入:"http://localhost:8080/TomcatProject".则可以看到运行index.jsp的页面。在Eclipse IDE的Web浏览器里的运行页面如图16-21所示。
图16-21 在Eclipse的Web浏览器里的运行index.jsp页面
16.4 小结
本章主要介绍了Eclipse的特性,Eclipse IDE和中文包的安装过程,Eclipse和Tomcat的集成步骤方法,以及在Eclipse和Tomcat的集成环境下进行Tomcat Web应用开发的一般步骤方法。
Eclipse可以通过加入Tomcat插件来扩展现有插件功能,而且有着统一的外观、操作和系统资源管理界面。Eclipse是一个非常优秀的Java开发环境,把Tomcat与Eclipse集成,可以方便快捷地在Eclipse IDE环境中进行Tomcat Web应用项目的开发和调试。Eclipse作为开发调试用的IDE,Tomcat作为Web应用服务器,为方便快捷的开发Tomcat Web应用项目提供了具有实用意义的模式。
第25章 实战网上书店
【本章导读】
通过前面对Tomcat知识由浅入深、由简到繁、由易到难的系统学习,从本章开始将通过具体的开发实例来对前面知识进行实际的运用。在软件开发过程中,知识的学习固然重要,但对知识的运用也很重要,尤其是在实际中开发可用、实用、易用的程序系统更加重要。只有在实际应用过程中,不断地积累经验、吸取精华,才可以让自己的实际开发水平得到更大的提高,才可以开发出称心的软件系统。
本章主要是通过一个典型的实例--网上书店,从软件工程和软件生命周期的角度出发,一步一步地介绍具体的开发过程和实现方法。网上书店系统需要实现的功能较多,但为了介绍主要的过程与方法,在此主要是讲解实现购物车的功能。在技术选型上,网上书店系统主要是采用了"JSP+JavaBean+JDBC+MS Access"的技术。同时系统采用了JSP Model1模型,使用JavaBean通过JDBC直接访问MS Access数据库。
25.1 需求分析
要开发一个系统,首先需要了解该系统到底想做什么,也就是需要实现怎么样的功能,这就是系统需求。只有把系统需求弄清楚、分析透彻后才会开始考虑怎么做的问题。如果还没有进行系统的需求分析,就盲目地进行设计,到了后期就会出现实现的功能与需求有比较大的差别,达不到预先的效果,最终导致整个软件不符合要求,白白地浪费了时间和精力。所以开发一个系统,必需先要进行需求分析。
需求分析就是明确需要做什么的问题。网上书店系统从大的需求方面看,就是要实现通过互联网能够进行书籍的购买及相关的管理等功能,所以本章主要是讨论书籍的购买部分功能。从顾客进入网站开始直到完成购买书籍,大致需要提供下面的一系列功能:
按照不同的分类,能够浏览书籍。
选择了某本书籍后,可以查看该书籍的详细信息。
选择需要购买的书籍,并且能把该书籍放进购物车。
可以继续选择别的书籍,并且可以购买该书籍。
选择了需要购买的书籍后,进行购买书籍操作。
购买完毕后,继续进行别的操作。
上面是一个网上书店实现购买书籍部分的一些主要部分功能,除此之外还可以实现顾客的注册、登陆功能,书籍的分类查找功能,顾客留言板交流功能,网站发布新闻信息功能等。为了更好地说明如何在Tomcat环境下开发Web应用程序,这里主要是实现以下功能:
显示书籍的列表功能。
浏览某本书籍的详细信息功能。
选择需要购买的书籍,并且把该书籍放进购物车。
删除购物车中某本书籍。
客户填写定单信息,进行购买书籍操作。
至此,就实现了客户购买书籍功能。把该功能用UML表示为图25-1的网上书店用例图。
图25-1 网上书店用例图
25.2 结构分析
确定了系统的需求,就已经是解决了需要做什么的问题。明确了目标,接下来就要考虑怎么做的问题。解决怎么做的问题,首先要确定整个系统打算采用什么软件结构、采用什么软件技术,即先从大的方向把握,明确总的方向。只有确定了总的结构,后面的详细设计就能围绕这个结构开展工作,才不至于脱离目标愿望。
在目前的软件开发当中,应用最多且最为广泛的软件结构莫过于C/S的两层结构和B/S的三层结构。下面是对这两种结构的简单介绍:
C/S的两层结构:C/S软件结构(即客户机和服务器模式)分为客户机和服务器两层。客户机不是毫无运算能力地输入、输出设备,而是具有了一定的数据处理和数据存储能力,通过把应用软件的计算和数据合理地分配在客户机和服务器两端,可以有效地降低网络通信量和服务器运算量。由于服务器链接个数和数据通信量的限制,这种结构的软件适于在用户数目不多的局域网内使用。国内目前的大部分ERP(财务)软件产品即属于此类结构。
B/S的三层结构:B/S软件结构(即Browser/Server结构,浏览器和服务器结构),它是随着Internet技术的兴起,对C/S结构的一种变化或者改进的结构。在这种结构下,用户工作界面是通过WWW浏览器来实现,极少部分事务逻辑在前端(Browser)实现,其主要事务逻辑在服务器端(Server)实现,形成所谓三层结构。这种结构大大简化了客户端电脑载荷,减轻了系统维护与升级的成本和工作量,降低了用户的总体成本。以目前的技术看,局域网建立B/S结构的网络应用,并通过Internet/Intranet模式下数据库应用,相对易于把握、成本也是较低的。它是一次性到位的开发,能实现不同的人员从不同的地点,以不同的接入方式(比如LAN、WAN、Internet/Intranet等)访问和操作共同的数据库;它能有效地保护数据平台、管理访问权限、服务器数据库。
网上书店是一个Java Web应用,它将采用典型的三层软件结构。其软件结构如图25-2所示。
图25-2 网上书店B/S软件结构图
在图25-2所示的B/S软件结构中,共分为三个层次结构:客户层、Web服务层和数据库层。在客户层中,主要由一些静态页面和大部分的JSP动态页面组成,用于向客户展示信息和接受客户输入的信息。Web服务层主要是运行在Tomcat服务器下,利用了Tomcat服务器所提供的JSP/Servlet容器;在Web服务层中,主要是由JSP页面调用JavaBean类和公用的实用方法类来实现业务逻辑处理。在数据库层中,主要是实现了数据的持久性,为了简单起见,在此使用的是MS Access数据库系统。
同时从图25-2所示的B/S软件结构图中,也可以看出主要采用的技术是:JSP+JavaBean+JDBC.在展示和获取信息方面,主要是通过JSP来实现。JSP页面通过调用JavaBean类来实现具体的业务逻辑处理。对数据库的操作,主要是在JavaBean类里通过JDBC来进行的。
25.3 数据库设计
软件结构和技术选型都已经确定后,下面接着先设计数据库部分。目前主要的数据库管理系统是关系型数据库管理系统,像大部分的系统应用中都采用关系型数据库。目前最为流行的中大型的数据库管理系统包括:Oracle、MS SQL、DB2、MySQL、SysBase等。本章采用的数据库系统是微软的小型数据库管理系统--MS Access2003.在小型的Web应用中,MS Access是采用最多的数据库之一。因为MS Access数据库设计和操作相对比较简单,并且功能完全满足小型Web应用系统的需求,所以使用和维护起来都很方便。
在数据库设计的过程中,主要是要遵循规范化的数据库设计原则。数据库规范化就是在设计和操作维护数据库时,使用正确的数据结构,不仅便于对数据库进行相应的存取操作,而且可以极大地简化应用程序的其他内容(查询、窗体、报表、代码等)。要实现数据库规范化,在数据库设计的过程中就应尽量地参考和实现数据库的第三范式。第三范式简而言之就是数据表中如果不存在非关键字段对任一候选关键字段的传递函数依赖。(数据库的设计范式,请自行参考学习)。数据库的第三范式消除了数据冗余、更新异常、插入异常和删除异常等现象。
在设计数据库字段之前,需要先对系统显示和收集的数据进行采集分析。在网上书店中,需要采集的数据主要是表示事物的实体类。根据前面的需求分析,这里主要设计下面的几项信息:表示书籍的具体信息,书籍购买的时候需要记录的订单信息。在此只是为了说明,在实际应用中需要根据具体需求进行设计,而且相对要复杂很多。
在设计数据库字段的时候,字段名要尽量用最能够表达字段含义的英文单词或是组合单词,杜绝使用中文作为表名和字段。就算一时没有合适的英文,也可以先用字母拼音。一个数据库表尽量设置一个主键,主键采用不表示任何含义的字段表示,比如自动编号等。在数据表里尽量避免使用联合主键,对于外键限制也尽量少使用。
下面是具体如何实现数据库的设计的内容。在Web项目里创建一个目录用于存放数据库表,该网上书店的项目名称为bookShop,在bookShop目录下创建一个名字为dataBase的目录。在dataBase目录下创建MS Access数据库,数据库名称为BookDB.mdb.在BookDB.mdb里创建三个数据库表,一个名字为book,用于纪录书籍的详细信息;一个为orderList,用于纪录书籍的订单列表信息,即订单信息;一个为orderDetail,用于纪录书籍的订单详细信息,即订单的书籍信息。其中数据库表book的详细设计信息如表25-1所示,数据库表orderList的详细设计信息如表25-2所示,数据库表orderDetail的详细设计信息如表25-3所示。
表25-1 数据库表book的详细设计信息
表25-2 数据库表orderList的详细设计信息
表25-3 数据库表orderDetail的详细设计信息
为了便于后面程序的开发和测试,在此先往数据库表book里添加几条纪录,纪录如表25-4所示:
表25-4 往数据库表book里添加纪录
25.4 详细设计
数据库设计完毕后,下面将进行网上书店整个系统的详细设计。详细设计是在需求分析的前提下,对系统所要实现的每一个功能、每一个环节进行细化。详细设计的细化程度,只要达到可以作为指导代码编写的功能就可以了。详细设计是对概要设计、总设计的进一步具体和细化,是代码具体编写的前提,具有指导作用。在详细设计当中,需要考虑全面,更要考虑细节,包括页面、程序流程,以及需要用到的技术等,都要有所顾及。下面将通过页面设计、JaveBean业务逻辑设计和程序流程设计等方面对网上书店进行详细设计分析,为后面的系统开发、代码编写做好充分的准备工作。
25.4.1 页面设计
页面是系统与用户进行信息交互的最有效途径。向用户展示输出信息、获取用户的输入信息都是通过页面来完成的。页面是展示层的东西,页面设计的好坏直接关系到信息交互的质量和方便性。好的页面设计,要求有好的页面流程:首先在页面流程上要合理和符合生活实际操作情况;其次页面在展示上和操作上要人性化,简单易用;最后是要求页面美观大方,能给用户很好的视觉效果,留下美好的印象。
通过前面的需求分析可知,网上书店主要是实现展示书籍的列表信息、显示某本书籍的具体信息、选择书籍放入购物车、从购物车删除其中已经选择的书籍、下订单购买书籍等主要功能。按照电子购物的一般习惯和常理,用户先是浏览书籍的列表信息;其次对某本书籍感兴趣后,查看该本书籍的详细信息介绍,觉得该本书籍适合自己并且有购买意向后,把该本书籍放入购物车中,继续进行选择购物。期间,如果觉得购物车中的某本书籍不想购买了,这时可以把该本书籍从购物车中删除。完成选择书籍并且确定购买时需要向系统下订单。只有在用户填写基本的联系信息并提交之后,这才完成了购书过程。该购书过程可以用图25-3所示来展示。
图25-3 购书过程图
从上图可以看出,需要一些怎么样的页面来显示信息和收集信息,需要的页面如表25-5所示。
表25-5 需要的页面描述信息
在表25-5中,只是列出了几个主要的页面,虽然这几个页面已经可以满足需求,但还可以增加别的页面,比如整站的首页、工具条页面、网站页脚信息页面。而在此只是为了说明方法和过程,所以忽略了一些无关紧要的页面。从表25-5中,也不难看出各个页面之间的关联关系,它们之间的访问关系如图25-4所示。
图25-4 页面之间的访问关系
25.4.2 业务逻辑设计
在软件结构分析中,系统采用的软件结构是三层的B/S结构。在该软件结构中,JSP页面和JavaBean实用类等组成了Tomcat服务器的Web应用层,其中JSP页面和JavaBean类是分工合作的。JSP页面负责信息的展示和信息的输入,在JSP页面里不做任何实际性的业务逻辑处理,而把业务逻辑处理交给JavaBean类来完成,这其实就是在第15章第2节中所提到的JSP Model 1模型。JSP Model 1模型的好处就是:JSP直接处理Web浏览器送来的请求(Request),并辅以JavaBean 处理应用相关逻辑。并且Model 1模型单纯,编写比较容易,可以快速地完成Web应用开发。
业务逻辑处理主要是要实现完成JSP页面的请求处理。业务逻辑处理的设计,主要也是围绕如何实现完成JSP页面的请求处理来设计。JSP页面需要怎么样的请求处理,业务逻辑就应该要实现相应的操作,一切以满足JSP页面的请求为目标。JSP页面的请求也就是用户的需求。既然前面已经完成了JSP页面的设计,那么就可以根据JSP页面的需求来进行业务逻辑的设计,确定需要哪些业务逻辑处理。
从图25-3的购书过程图中可知,根据页面需要请求的业务逻辑处理,看哪些需要业务逻辑处理,如图25-5所示。
图25-5 页面中需要的业务逻辑处理
从上图可以看出,页面中需要的业务逻辑处理。需要的业务逻辑处理类如表25-6所示。
表25-6 业务逻辑处理类的描述信息
在浏览书籍列表信息时,先要调用BookDB.java类,链接数据库并从数据库中读取书籍信息,然后在页面显示信息。当查看书籍具体信息时,需要调用BookDetail.java类。当把该书放入购物车的时候,需要调用ShopCar.java和ShopCarItem.java类。当下订单,确定购买书籍的时候,需要调用BookDB.java类,链接数据库并往数据库中写订单信息。综上所述,可以知道各个JSP页面调用业务逻辑处理类之间的关联关系,它们之间的访问关系如图25-6所示。
图25-6 JSP页面调用业务逻辑处理类的关系
25.5.1 创建项目
该网上书店Web系统名字为bookshop,在"<CATALINA_HOME>/webapps/"目录下创建一个Web应用项目,名称为bookshop.bookshop的目录结构如下所示:
bookshop/
--bookshop/ //JavaBean和实用类文件夹
--images/ //图片图标文件夹
--WEB-INF/
--lib/
--classes/
--web.xml
--*.jsp
其中web.xml文件如下所示,在后面的应用中可以根据需要,再在该web.xml文件中进行配置与修改,web.xml文件的配置请参考前面的章节介绍。
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<display-name>BookShop</display-name>
<welcome-file-list>
<welcome-file>bookList.jsp</welcome-file>
</welcome-file-list>
</web-app>
25.5.2 开发业务逻辑处理类
在bookshop应用中,将要在"bookshop/bookshop"目录下创建以下的Java类:
BookDetail.java
Order.java
ShopCar.java
ShopCarItem.java
BookDB.java
1.BookDetail.java、Order.java
BookDetail.java类是一个实体类,主要是表示书籍的信息,与数据库表book相对应。在BookDetail.java类中,主要的方法是get和set方法,其具体代码实现如下所示:
package bookshop;
import java.util.Date;
public class BookDetail implements Comparable {
private String bookId="";
private String title="";
private String name="";
private Date time;
private String info="";
private String pie="";
private double price=0;
private int saleAmount=0;
public BookDetail(String bookId,String title,String name,
Date time,String info,String pie,double price,int saleAmount){
this.bookId=bookId;
this.title=title;
this.name=name;
this.time=time;
this.info=info;
this.pie=pie;
this.price=price;
this.saleAmount=saleAmount;
}
public String getBookId() {
return bookId;
}
public String getInfo() {
return info;
}
public String getName() {
return name;
}
public String getPie() {
return pie;
}
public double getPrice() {
return price;
}
public int getSaleAmount() {
return saleAmount;
}
public Date getTime() {
return time;
}
public String getTitle() {
return title;
}
public void setBookId(String string) {
bookId = string;
}
public void setInfo(String string) {
info = string;
}
public void setName(String string) {
name = string;
}
public void setPie(String string) {
pie = string;
}
public void setPrice(double d) {
price = d;
}
public void setSaleAmount(int d) {
saleAmount = d;
}
public void setTime(Date date) {
time = date;
}
public void setTitle(String string) {
title = string;
}
public int compareTo(Object o) {
BookDetail bd = (BookDetail)o;
int lastCmp = title.compareTo(bd.title);
return (lastCmp);
}
}
Order.java类是一个实体类,主要是表示订单的信息,与数据库表orderList相对应。在Order.java类中,主要的方法是get和set方法,其具体代码实现如下所示:
package bookshop;
import java.io.Serializable;
import java.util.Date;
public class Order implements Serializable{
private String orderID="";
private String status="";
private Date time;
private int allAmount=0;
private double allMoney=0;
private String name="";
private String phone="";
private String code="";
private String info="";
private String Address="";
public Order (){
}
public Order (String orderID,String status, Date time, int allAmount,
double allMoney,String name,String phone,String code,String info){
this.orderID=orderID;
this.status=status;
this.time=time;
this.allAmount=allAmount;
this.allMoney=allMoney;
this.name=name;
this.phone=phone;
this.code=code;
this.info=info;
}
public int getAllAmount() {
return allAmount;
}
public void setAllAmount(int allAmount) {
this.allAmount = allAmount;
}
public double getAllMoney() {
return allMoney;
}
public void setAllMoney(double allMoney) {
this.allMoney = allMoney;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getInfo() {
return info;
}
public void setInfo(String info) {
this.info = info;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getOrderID() {
return orderID;
}
public void setOrderID(String orderID) {
this.orderID = orderID;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public Date getTime() {
return time;
}
public void setTime(Date time) {
this.time = time;
}
public String getAddress() {
return Address;
}
public void setAddress(String address) {
Address = address;
}
}
2.ShopCar.java、ShopCarItem.java
ShopCar.java和ShopCarItem.java类实现了网上书店的购物车部分功能,分别代表购物车和购物车中的书籍条目。在一个购物车中,可以包含多个购物车书籍条目,购物车书籍条目代表了用户需要购买的书籍信息、数量及金额等信息。
例如,某个用户的购物车放置了两种书籍:一种是编号为IS00000001的《Java》书籍2本;另一种是编号为IS00000004的《Delphi》书籍1套。那么在ShopCar对象中就包含了两个ShopCarItem对象。ShopCarItem包含两个成员变量:Object item和int quantity,其中Object item代表购买书籍的详细信息,它引用了前面建立的ShopCarItem对象;int quantity代表购买书籍的数量。ShopCar、ShopCarItem与BookDetail三个对象之间的关系可以用图25-7表示:
图25-7 ShopCar、ShopCarItem与BookDetail对象的关系
ShopCarItem.java类主要是实现了get、set方法和增、减书籍数量的方法,代码如下所示:
package bookshop;
public class ShopCarItem {
Object item;
int quantity;
public ShopCarItem(Object anItem) {
item = anItem;
quantity = 1;
}
public void incrementQuantity() {
quantity++;
}
public void decrementQuantity() {
quantity--;
}
public Object getItem() {
return item;
}
public int getQuantity() {
return quantity;
}
}
ShopCar.java类主要是实现了购物车功能。其add方法是实现往购物车中放置书籍,remove方法是实现从购物车中删除已选择的书籍,getTotal方法是计算购物车中书籍的总金额。其代码实现如下所示:
package bookshop;
import java.util.*;
public class ShopCar {
HashMap items = null;
int numberOfItems = 0;
public ShopCar() {
items = new HashMap();
}
public synchronized void add(String bookId, BookDetail book) {
if(items.containsKey(bookId)) {
ShopCarItem scitem = (ShopCarItem) items.get(bookId);
scitem.incrementQuantity();
} else {
ShopCarItem newItem = new ShopCarItem(book);
items.put(bookId, newItem);
}
numberOfItems++;
}
public synchronized void remove(String bookId) {
if(items.containsKey(bookId)) {
ShopCarItem scitem = (ShopCarItem) items.get(bookId);
scitem.decrementQuantity();
if(scitem.getQuantity() <= 0)
items.remove(bookId);
numberOfItems--;
}
}
public synchronized Collection getItems() {
return items.values();
}
protected void finalize() throws Throwable {
items.clear();
}
public synchronized int getNumberOfItems() {
return numberOfItems;
}
public synchronized double getTotal() {
double amount = 0.0;
for(Iterator i = getItems()。iterator(); i.hasNext(); ) {
ShopCarItem item = (ShopCarItem) i.next();
BookDetail bookDetails = (BookDetail) item.getItem();
amount += item.getQuantity() * bookDetails.getPrice();
}
return roundOff(amount);
}
private double roundOff(double x) {
ong val = Math.round(x*100);
return val/100.0;
}
public synchronized void clear() {
items.clear();
numberOfItems = 0;
}
}
3.BookDB.java
该网上书店系统是使用JDBC访问MS Access数据库。它是通过JDBC-ODBC驱动类型链接数据库,所以需要先建立ODBC数据源。建立ODBC数据源的方法可以参考第9章第3节的介绍,在这里ODBC数据源的名称为BookShopDB.
BookDB.java类主要是对数据库的访问操作类,包括数据库链接、断开数据库链接、查询数据库纪录和修改数据库纪录等方法。在BookDB.java类里实现了以下方法:getConnection方法实现数据库链接;getBookDetail方法实现读取书籍列表的详细信息;buyBooks方法实现购买书籍;order方法实现了下订单的处理。其代码实现主要部分如下所示:
package bookshop;
import java.sql.*;
import java.util.*;
public class BookDB {
private ArrayList alBooks;
private String dbUrl="jdbc:odbc:BookShopDB";
private String orderId;
public BookDB () throws Exception{
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
}
public Connection getConnection()throws Exception{
return java.sql.DriverManager.getConnection(dbUrl);
}
public void closeConnection(Connection con){
try{
if(con!=null) con.close();
}catch(Exception e){
e.printStackTrace();
}
}
public void closePrepStmt(PreparedStatement prepStmt){
try{
if(prepStmt!=null) prepStmt.close();
}catch(Exception e){
e.printStackTrace();
}
}
public void closeResultSet(ResultSet rs){
try{
if(rs!=null) rs.close();
}catch(Exception e){
e.printStackTrace();
}
}
public int getNumberOfBooks() throws Exception {
Connection con=null;
PreparedStatement prepStmt=null;
ResultSet rs=null;
alBooks = new ArrayList();
try {
con=getConnection();
String strSql = "select * " + "from book";
prepStmt = con.prepareStatement(strSql);
rs = prepStmt.executeQuery();
while (rs.next()) {
BookDetail bd = new BookDetail(rs.getString("bookID"), rs.getString("title"),
rs.getString("name"),rs.getDate("time"), rs.getString("info"),
rs.getString("pie"),rs.getDouble("price"),rs.getInt("saleAmount"));
alBooks.add(bd);
}
}finally{
closeResultSet(rs);
closePrepStmt(prepStmt);
closeConnection(con);
}
return alBooks.size();
}
public Collection getBookList()throws Exception{
Connection con=null;
PreparedStatement prepStmt=null;
ResultSet rs =null;
alBooks = new ArrayList();
try {
con=getConnection();
String strSql = "select * " + "from book order by bookID";
prepStmt = con.prepareStatement(strSql);
rs = prepStmt.executeQuery();
while (rs.next()) {
BookDetail bd = new BookDetail(rs.getString("bookID"), rs.getString("title"),
rs.getString("name"),rs.getDate("time"), rs.getString("info"),
rs.getString("pie"),rs.getDouble("price"),rs.getInt("saleAmount"));
alBooks.add(bd);
}
}finally{
closeResultSet(rs);
closePrepStmt(prepStmt);
closeConnection(con);
}
Collections.sort(alBooks);
return alBooks;
}
public BookDetail getBookDetail(String bookId) throws Exception {
Connection con=null;
PreparedStatement prepStmt=null;
ResultSet rs =null;
try {
con=getConnection();
System.out.println("getConnection");
String strSql = "select * from book where bookID = ? ";
prepStmt = con.prepareStatement(strSql);
prepStmt.setString(1, bookId);
rs = prepStmt.executeQuery();
if (rs.next()) {
BookDetail bd = new BookDetail(rs.getString("bookID"), rs.getString("title"),
rs.getString("name"),rs.getDate("time"), rs.getString("info"),
rs.getString("pie"),rs.getDouble("price"),rs.getInt("saleAmount"));
prepStmt.close();
return bd;
}
else {
return null;
}
}finally{
closeResultSet(rs);
closePrepStmt(prepStmt);
closeConnection(con);
}
}
public void buyBooks(ShopCar car)throws Exception {
Connection con=null;
Collection items = car.getItems();
Iterator i = items.iterator();
try {
con=getConnection();
con.setAutoCommit(false);
while (i.hasNext()) {
ShopCarItem sci = (ShopCarItem)i.next();
BookDetail bd = (BookDetail)sci.getItem();
String bookId=bd.getBookId();
int quantity = sci.getQuantity();
buyBook(bookId, quantity,con);
orderDetail(bd, quantity,con);
}
con.commit();
con.setAutoCommit(true);
} catch (Exception ex) {
con.rollback();
throw ex;
}finally{
closeConnection(con);
}
}
public void buyBook(String bookId, int quantity,Connection con) throws Exception {
PreparedStatement prepStmt=null;
ResultSet rs=null;
try{
String strSql = "select * " + "from book where bookID = ? ";
prepStmt = con.prepareStatement(strSql);
prepStmt.setString(1, bookId);
rs = prepStmt.executeQuery();
if (rs.next()) {
prepStmt.close();
strSql ="update book set saleamount = saleamount + ? where bookID = ?";
prepStmt = con.prepareStatement(strSql);
prepStmt.setInt(1, quantity);
prepStmt.setString(2, bookId);
prepStmt.executeUpdate();
prepStmt.close();
}
}finally{
closeResultSet(rs);
closePrepStmt(prepStmt);
}
}
public void orderDetail(BookDetail bd, int quantity,Connection con) throws Exception {
PreparedStatement prepStmt=null;
try{
String strSql ="insert into orderDetail(orderID,bookId,price,amount,moneys)" +
" values(?,?,?,?,?)";
prepStmt = con.prepareStatement(strSql);
System.out.println(orderId);
prepStmt.setString(1,orderId);
prepStmt.setString(2, bd.getBookId());
prepStmt.setDouble(3, bd.getPrice());
prepStmt.setInt(4, quantity);
prepStmt.setDouble(5, bd.getPrice()*quantity);
prepStmt.executeUpdate();
prepStmt.close();
}finally{
closePrepStmt(prepStmt);
}
}
public void order(Order order) throws Exception{
Connection con=null;
PreparedStatement prepStmt=null;
try {
con=getConnection();
String strSql = "insert into orderList(orderID,status,allAmount,allMoney,name,phone,address,code,info) " +
" values(?,?,?,?,?,?,?,?,?)";
prepStmt = con.prepareStatement(strSql);
orderId=getNowTime();
order.setOrderID(orderId);
prepStmt.setString(1, orderId);
prepStmt.setString(2, "有效");
prepStmt.setInt(3, order.getAllAmount());
prepStmt.setDouble(4, order.getAllMoney());
prepStmt.setString(5, order.getName());
prepStmt.setString(6, order.getPhone());
prepStmt.setString(7, order.getAddress());
prepStmt.setString(8, order.getCode());
prepStmt.setString(9, order.getInfo());
prepStmt.executeUpdate();
}finally{
closePrepStmt(prepStmt);
closeConnection(con);
}
}
public static String getNowTime() {
java.util.Date ctime = new java.util.Date();
String rTime = "";
java.text.SimpleDateFormat cf = new java.text.SimpleDateFormat(
"yyyyMMddHHmmss");
rTime = cf.format(ctime);
return rTime;
}
}
25.5.3 开发页面
在bookshop应用中,根据前面的页面详细设计,将要在"bookshop/jspsrc"目录下创建以下的JSP页面:
common.jsp
bookList.jsp
bookDetail.jsp
addBook.jsp
bookCar.jsp
order.jsp
buy.jsp
success.jsp
error.jsp
1.common.jsp
common.jsp页面主要是引用了BookDB的JavaBean,以方便给别的页面使用,是一个通用的页面。
common.jsp页面的代码实现如下所示:
<%@ page import="bookshop.*" %>
<%@ page import="java.util.Properties" %>
<%@ page errorPage="error.jsp" %>
<jsp:useBean id="bookDB" scope="application" class="bookshop.BookDB"/>
<%!
public String convert(String s){
try{
return new String(s.getBytes("ISO-8859-1"),"GB2312");
}catch(Exception e){return null;}
}
%>
2.bookList.jsp
bookList.jsp页面主要是实现查看书籍列表信息的功能。在页面中调用了BookDB.java类的getBookDetail方法,该方法实现了从数据库表book中读取数据,并且把数据放进ArrayList数组。然后页面从ArrayList数组里把数据逐条地显示出来。详细代码实现如下所示:
<%@ page contentType="text/html; charset=GB2312" %>
<%@ include file="/common.jsp" %>
<%@ page import="java.util.*" %>
<html>
<head><title>bookList.jsp</title></head>
<body>
<CENTER><TABLE border="0">
<TBODY align="center">
<TR>
<TD colspan="4" align="center">书籍列表</TD>
</TR>
<TR>
<TD width="100">序号</TD>
<TD width="150">书籍编号</TD>
<TD width="200">书籍名称</TD>
<TD width="150">作者</TD>
</TR>
<%
Collection c = bookDB.getBookList();
Iterator i = c.iterator();
int j=1;
while (i.hasNext()) {
BookDetail book = (BookDetail)i.next();
out.println("<TR>");
out.println("<TD width='100'>"+j+"</TD>");
out.println("<TD width='150'><a href='/bookshop/bookDetail.jsp?bookId=
"+book.getBookId()+"'>"+book.getBookId()+"</a></TD>");
out.println("<TD width='200'>"+book.getTitle()+"</TD>");
out.println("<TD width='150'>"+book.getName()+"</TD>");
out.println("</TR>");
j=j+1;
}
%>
<TR>
<TD colspan="4" align="center"><a href="/bookshop/bookCar.jsp">我的购物车</a></TD>
</TR>
</TBODY>
</TABLE></CENTER>
</body>
</html>
3.bookDetail.jsp
bookDetail.jsp页面主要是实现显示某本书籍的详细信息的功能。它的详细信息是从bookList.jsp页面通过URL传递下来的。详细代码实现请参考随书附带的光盘。
4.addBook.jsp
addBook.jsp页面主要是实现把选中的书籍放进购物车的功能,它主要是调用ShopCar.java的add方法。如下所示:
<%@ page contentType="text/html; charset=GB2312" %>
<%@ include file="/common.jsp" %>
<%@ page import="java.util.*" %>
<jsp:useBean id="car" scope="session" class="bookshop.ShopCar"/>
<html>
<head><title>addBook.jsp</title></head>
<body>
<%
String bookId = request.getParameter("Add");
String title = request.getParameter("title");
if (bookId != null) {
BookDetail book = bookDB.getBookDetail(bookId);
car.add(bookId, book);%>
<CENTER><TABLE border="0">
<TBODY align="center">
<%
String str="你已将编号为:"+bookId+",书名为:"+title+"的书籍放进购物车!";
out.println("<TR>");
out.println("<TD colspan='2' align='center'>"+str+"</TD>");
out.println("</TR>");
}%>
<TR>
<TD colspan="2" align="center">-------------------------------------</TD>
</TR>
<TR>
<TD colspan="2" align="center">
<a href="/bookshop/bookList.jsp">继续购书</a>
<a href="/bookshop/bookCar.jsp">我的购物车</a>
</TD>
</TR>
</TBODY>
</TABLE></CENTER>
</body>
</html>
5.bookCar.jsp
bookCar.jsp页面主要是实现购物车的功能。它主要是调用ShopCar.java和ShopCarItem.java类,ShopCar实现了购物车的列表信息功能,ShopCarItem实现了购物车中的书籍详细信息功能。在bookCar.jsp页面可以继续选择书籍,也可以删除某个已经选择了的书籍,还有就是下订单。下订单后将跳转到order.jsp页面,进行订单信息的填写。bookCar.jsp页面的代码实现如下所示:
<%@ page contentType="text/html; charset=GB2312" %>
<%@ include file="/common.jsp" %>
<%@ page import="java.util.*" %>
<jsp:useBean id="car" scope="session" class="bookshop.ShopCar"/>
<html>
<head><title>bookCar.jsp</title></head>
<body>
<CENTER><TABLE border="0">
<TBODY align="center">
<TR>
<TD colspan="8" align="center">我的购物车</TD>
</TR>
<%
String bookId = request.getParameter("Remove");
if (bookId != null) {
car.remove(bookId);
BookDetail book = bookDB.getBookDetail(bookId);
out.println("你删除了一本编号为:"+bookId+",书名为:"+book.getTitle()+"的书籍");
}
if (request.getParameter("Clear") != null) {
car.clear();
}
%>
<TR>
<TD width="60">序号</TD>
<TD width="150">书籍编号</TD>
<TD width="200">书籍名称</TD>
<TD width="100">单位</TD>
<TD width="100">单价</TD>
<TD width="100">数量</TD>
<TD width="120">金额</TD>
<TD width="100"></TD>
</TR>
<TR>
<TD colspan="8" align="center">--------------------------------------------------------</TD>
</TR>
<%
Iterator i = car.getItems()。iterator();
int j=1;
int allQuantity=0;
double allMoney=0;
while (i.hasNext()) {
ShopCarItem item = (ShopCarItem)i.next();
BookDetail book = (BookDetail)item.getItem();
out.println("<TR>");
out.println("<TD width='60'>"+j+"</TD>");
out.println("<TD width='150'><a href='/bookshop/bookDetail.jsp?bookId=
"+book.getBookId()+"'>"+book.getBookId()+"</a></TD>");
out.println("<TD width='200'>"+book.getTitle()+"</TD>");
out.println("<TD width='100'>"+book.getPie()+"</TD>");
out.println("<TD width='100'>"+book.getPrice()+"</TD>");
out.println("<TD width='100'>"+item.getQuantity()+"</TD>");
out.println("<TD width='120'>"+item.getQuantity()*book.getPrice()+"</TD>");
out.println("<TD width='100'><a href='/bookshop/bookCar.jsp?Remove=
"+book.getBookId()+"'>删除</a></TD>");
out.println("</TR>");
j=j+1;
allQuantity=allQuantity+item.getQuantity();
allMoney=allMoney+item.getQuantity()*book.getPrice();
}
%>
<TR>
<TD colspan="8" align="center">------------------------------------------------------</TD>
</TR>
<TR>
<TD width="60"></TD>
<TD width="150"></TD>
<TD width="200"></TD>
<TD width="100"></TD>
<TD width="100">合计:</TD>
<TD width="100"><%=allQuantity%></TD>
<TD width="120"><%=allMoney%></TD>
<TD width="100"></TD>
</TR>
<TR>
<TD colspan="8" align="center">
<a href="/bookshop/bookList.jsp">继续购书</a>
<a href="/bookshop/bookCar.jsp?Clear=clear">清空购物车</a>
<a href="/bookshop/order.jsp">购书,下订单</a>
</TD>
</TR>
</TBODY>
</TABLE></CENTER>
</body>
</html>
6.order.jsp
order.jsp页面主要是实现填写订单信息的功能,用于接受用户的输入信息。当用户输完信息,并且提交订单后,会调用BookDB.java类的order方法。详细代码实现如下:
<%@ page contentType="text/html; charset=GB2312" %>
<%@ include file="/common.jsp" %>
<%@ page import="java.util.*" %>
<html>
<head><title>order.jsp</title></head>
<body>
<FORM action=buy.jsp method="POST">
<CENTER><TABLE border="0">
<TBODY align="center">
<TR>
<TD colspan="2" align="center"><br>请填写您的联系信息</TD>
</TR>
<TR>
<TD width="120">您的姓名:</TD>
<TD><input type="text" name="name" size="40"/></TD>
</TR>
<TR>
<TD width="120">联系电话:</TD>
<TD><input type="text" name="phone" size="40"/></TD>
</TR>
<TR>
<TD width="120">详细地址:</TD>
<TD><input type="text" name="address" size="40"/></TD>
</TR>
<TR>
<TD width="120">邮政编号:</TD>
<TD><input type="text" name="postcode" size="40"/></TD>
</TR>
<TR>
<TD width="120">备注信息:</TD>
<TD><input type="text" name="info" size="40"/></TD>
</TR>
<TR>
<TD colspan="2" align="center">
<a href="/bookshop/bookCar.jsp">返回</a>
<input type=submit value="确定下订单">
</TD>
</TR>
</TBODY>
</TABLE></CENTER>
</FORM>
</body>
</html>
7.buy.jsp
buy.jsp页面主要是为用户提供确认订单信息的功能。生成临时的订单后,就会把生成的订单信息显示出来,包括订单用户的联系信息和订单的书籍信息。确认订单信息无误后提交,生成正式订单。在此调用BookDB.java类的buyBooks方法,该方法是先把订单的书籍信息全部记入数据库表,然后修改前面临时订单的状态,将之改成正式订单。详细代码实现如下所示:
<%@ page contentType="text/html; charset=GB2312" %>
<%@ include file="/common.jsp" %>
<%@ page import="java.util.*" %>
<jsp:useBean id="car" scope="session" class="bookshop.ShopCar"/>
<jsp:useBean id="order" scope="session" class="bookshop.Order"/>
<html>
<head><title>buy.jsp</title></head>
<body>
<CENTER><TABLE border="0">
<TBODY align="center">
<TR>
<TD colspan="7" align="center">请确认您的订单信息</TD>
</TR>
<TR>
<TD colspan="7" align="center">--------------------------------------------------------</TD>
</TR>
<TR>
<TD colspan="7" align="left">订单的书籍信息:</TD>
</TR>
<TR>
<TD width="60">序号</TD>
<TD width="150">书籍编号</TD>
<TD width="200">书籍名称</TD>
<TD width="100">单位</TD>
<TD width="100">单价</TD>
<TD width="100">数量</TD>
<TD width="120">金额</TD>
</TR>
<%
Iterator i = car.getItems()。iterator();
int j=1;
int allQuantity=0;
double allMoney=0;
while (i.hasNext()) {
ShopCarItem item = (ShopCarItem)i.next();
BookDetail book = (BookDetail)item.getItem();
out.println("<TR>");
out.println("<TD width='60'>"+j+"</TD>");
out.println("<TD width='150'><a href='/bookshop/bookDetail.jsp?bookId=
"+book.getBookId()+"'>"+book.getBookId()+"</a></TD>");
out.println("<TD width='200'>"+book.getTitle()+"</TD>");
out.println("<TD width='100'>"+book.getPie()+"</TD>");
out.println("<TD width='100'>"+book.getPrice()+"</TD>");
out.println("<TD width='100'>"+item.getQuantity()+"</TD>");
out.println("<TD width='120'>"+item.getQuantity()*book.getPrice()+"</TD>");
out.println("</TR>");
j=j+1;
allQuantity=allQuantity+item.getQuantity();
allMoney=allMoney+item.getQuantity()*book.getPrice();
}
%>
<TR>
<TD width="60"></TD>
<TD width="150"></TD>
<TD width="200"></TD>
<TD width="100"></TD>
<TD width="100">合计:</TD>
<TD width="100"><%=allQuantity%></TD>
<TD width="120"><%=allMoney%></TD>
<TD width="100"></TD>
</TR>
<TR>
<TD colspan="7" align="center">---------------------------------------------------</TD>
</TR>
<TR>
<TD colspan="7" align="left">订单的联系信息:</TD>
</TR>
<%
String name = request.getParameter("name");
String phone = request.getParameter("phone");
String address = request.getParameter("address");
String postcode = request.getParameter("postcode");
String info = request.getParameter("info");
order.setName(name);
order.setPhone(phone);
order.setAddress(address);
order.setCode(postcode);
order.setInfo(info);
order.setAllAmount(allQuantity);
order.setAllMoney(allMoney);
%>
<TR>
<TD width="60"></TD>
<TD width="150">姓名:</TD>
<TD width="200" align="left"><%=name %></TD>
<TD width="100">电话:</TD>
<TD width="100" align="left"><%=phone %></TD>
<TD width="100">邮编:</TD>
<TD width="120" align="left"><%=postcode %></TD>
</TR>
<TR>
<TD width="60"></TD>
<TD width="150">地址:</TD>
<TD colspan="5" align="left"><%=address %></TD>
</TR>
<TR>
<TD colspan="7" align="center">-----------------------------------------</TD>
</TR>
<TR>
<TD colspan="8" align="center">
<a href="/bookshop/booCar.jsp">返回</a>
<a href="/bookshop/success.jsp">确定购买</a>
</TD>
</TR>
</TBODY>
</TABLE></CENTER>
</body>
</html>
8.success.jsp、error.jsp
订单确认购买提交后,系统后台将进行相关的处理。如果购买书籍成功,则返回一个成功页面success.jsp;如果购买书籍失败或是系统出错,则返回一个失败页面error.jsp.success.jsp详细代码实现如下所示:
<%@ page contentType="text/html; charset=GB2312" %>
<%@ include file="/common.jsp" %>
<%@ page import="java.util.*" %>
<jsp:useBean id="car" scope="session" class="bookshop.ShopCar"/>
<jsp:useBean id="order" scope="session" class="bookshop.Order"/>
<html>
<head><title>success.jsp</title></head>
<body>
<%
bookDB.order(order);
bookDB.buyBooks(car);
session.invalidate();
%>
<CENTER><TABLE border="0">
<TBODY align="center">
<TR>
<TD colspan="2" align="center">恭喜您,订单成功!</TD>
</TR>
<TR>
<TD colspan="2" align="center">您需要购买的书籍已经订购成功,请您在7个工作日内注意查收。</TD>
</TR>
<TR>
<TD colspan="2" align="center">
<a href="/bookshop/bookCar.jsp">返回</a>
</TD>
</TR>
</TBODY>
</TABLE></CENTER>
</body>
</html>
error.jsp详细代码实现如下所示:
<%@ page import="java.io.*" %>
<%@ page contentType="text/html; charset=GB2312" %>
<%@ page isErrorPage="true" %>
<html><head><title>Error Page</title></head>
<body>
<p>
服务器端发生错误:<%= exception.getMessage() %>
</p>
<p>
错误原因为:<% exception.printStackTrace(new PrintWriter(out));%>
</p>
</body></html>
【提示】
在系统开发、具体代码编写的过程中,要不时地进行软件测试。当写了一个类、一个方法、一个页面的时候,都需要调试;当页面之间、类之间、页面和类之间互相调用的时候,都需要调试;当一个模块完成的时候,需要进行单元测试;当系统开发完成的时候,需要进行系统测试,还有集成测试、上线前的上线测试等。测试贯穿整个开发、运行过程。通过测试来发现问题,解决问题。所以软件测试非常重要。
25.6 系统运行
至此,网上书店Web系统的开发就算告一段落了。经过在开发中的反复测试,现在需要运行系统,检验开发的结果到底如何。重新启动Tomcat服务器,打开浏览器,在地址栏内输入"http://localhost:8080/bookshop/jspsrc/bookList.jsp",将会看到显示书籍列表的页面,如图25-8所示。
图25-8 显示书籍列表的页面
在书籍列表的页面里显示了所有的书籍信息,这里是把主要的信息以列表的形式显示出来。在这可以查看"我的购物车",也可以选择查看书籍的详细信息。单击"IS0000001",则进入查看详细信息页面,如图25-9所示。
图25-9 查看书籍详细信息页面
在书籍详细信息页面里,可以查看到书籍的编号、书名、作者、出版日期、单位、单价和书籍描述等具体的信息。在这里也可以返回书籍列表页面,也可以把该本书籍放入购物车,也可以直接前去我的购物车。单击"放入购物车",则进入"提示该书籍已经放入购物车"页面,如图25-10所示。
图25-10 提示该书籍已经放入购物车页面
如果单击"我的购物车",则进入"我的购物车"页面,如图25-11所示。
图25-11 购物车信息页面
在购物车信息页面里,显示已经选择了的书籍信息,包括书籍编号、书名、单位、单价、数量和金额等信息。在这里可以删除已经选择了的书籍,也可以继续选择书籍,还可以前去购书下订单。单击"购书,下订单",则进入"填写用户联系信息"页面,如图25-12所示。
图25-12 填写用户联系信息页面
在填写用户联系信息页面,填写姓名、联系电话、联系地址、邮政编号和备注信息等信息后单击【确定下订单】,进入"确认用户订单信息"页面,如图25-13所示。
图25-13 确认用户订单信息页面
在确认用户订单信息页面中,显示了订单的书籍信息和订单的联系信息。如果对所实现的信息确认无误后,单击【确认购买】进行购买书籍。如果购买成功则返回成功页面(如图25-14所示),失败则返回失败页面(如图25-15所示)。
图25-14 购买成功页面
图25-15 购买失败页面
25.7 小结
本章主要是介绍了网上书店Web系统的设计开发过程。从软件工程和软件生命周期的角度出发,一步一步地介绍具体的开发过程和实现方法。先是进行需求分析、结构分析,然后是数据库设计、详细设计,最后才进行系统开发具体代码的编写,直到经过软件测试后运行系统。
该系统采用三层的B/S软件结构,JSP Model 1模型。其中JSP页面负责信息的展示和信息的输入,在JSP页面里不做任何实际性的业务逻辑处理,而把业务逻辑处理交给JavaBean类来完成。JSP Model 1模型比较适合应用于小型的Web应用中,实现快速开发。在技术选型上,该系统主要是采用了"JSP+JavaBean+JDBC+MS Access"的技术。JSP结合应用JavaBean,在JavaBean里通过JDBC方式访问操作MS Access数据库。
第26章 实战OA系统
【本章导读】
在上一章的网上书店Web系统中,采用的是三层B/S软件结构,并且是JSP Mode l 1模型。JSP Mode l 1模型的好处就是模型单纯,编写比较容易,可以快速地完成应用开发。但在Mode l 1中JSP可能同时肩负View与Controller的角色,这样两类程序代码就会混杂在一起而不易维护。要解决这个问题,可以使用JSP Mode l 2模型。Mode l 2是基于MVC模式的框架,通过这种设计模型把应用逻辑、处理过程和显示逻辑分成不同的组件实现。Mode l 2具有组件化的优点从而更易于实现对大规模系统的开发、维护及二次开发。
本章主要是介绍OA系统的实现,同时也是采用三层B/S软件结构、JSP Mode l 2模型。从软件工程和软件生命周期的角度出发,一步一步地介绍具体的开发过程和实现方法。在技术上,OA系统主要是采用了"Struts+Hibernate+MS SQL Server"的技术。OA系统也采用了Struts的MVC框架,通过Hibernate来实现MS SQL Server数据的持久性。
26.1 需求分析
OA就是办公自动化,是Office Automation的缩写。OA是采用Internet/Intranet技术,基于工作流的概念,使企业内部人员方便快捷地共享信息,高效地协同工作;改变过去复杂、低效的手工办公方式,实现迅速、全方位的信息采集、信息处理,为企业的管理和决策提供科学的依据。一个企业实现办公自动化的程度也是衡量其实现现代化管理的标准。
办公自动化是一个企业与整个世界联系的渠道,企业的Intranet网络可以和Internet相连。一方面,企业的员工可以在Internet上查找有关的技术资料、市场行情,与现有或潜在的客户、合作伙伴联系;另一方面,其他企业可以通过Internet访问你对外发布的企业信息,如企业介绍、生产经营业绩、业务范围、产品或服务等信息,从而起到了宣传介绍的作用。随着办公自动化的推广,越来越多的企业通过自己的Intranet网络链接到Internet上,因为这种网上交流的潜力非常巨大。
办公自动化不仅兼顾个人办公效率的提高,更重要的是可以实现群体协同工作。协同工作意味着要进行信息的交流、工作的协调与合作。由于网络的存在,这种交流与协调几乎可以在瞬间完成,并且不必担心对方是否在电话机旁边或是否有传真机可用。
办公自动化可以和一个企业的业务结合得非常紧密,甚至是定制的,因而可以将诸如信息采集、查询、统计等功能与具体业务密切关联。操作人员只需单击一个按钮就可以得到想要的结果,从而极大地方便了企业领导的管理和决策。
OA系统的功能错综复杂,覆盖面非常广。一般的OA系统都会实现下面的一些基本功能:
系统管理:基础设置、权限设置、初期数据录入。
人力资源:组织结构、人事管理、考勤管理、制度管理、流程管理、绩效管理等。
办公事务:申请管理、公文管理、传真管理、办公物品管理、车辆管理、证照管理、会务管理等。
物流管理:采购管理、销售管理、终端管理、库存管理等。
营销管理:终端销售实景管理、公关促销管理、赠品管理、竞争对手管理等。
财务管理:管理费用、销售费用管理、统计分析等。
客户服务:客户基本资料、服务记录、销售记录等。
知识管理:文档管理、网络硬盘、培训与考核、内部交流平台等。
其他:网上调查、投诉台、意见箱等。
下面是某一个OA系统的功能导航图,如图26-1所示。
从图26-1的某OA系统功能导航图可以看出,OA系统覆盖面广,功能众多,是一个大型的应用管理系统。而需进行一个这么大系统的开发,需求分析尤为重要。需求分析要分析得透彻,收集的功能、材料也要花比较多的时间和精力,并且要经过开发人员和业务人员、客户之间进行多方的沟通讨论,逐步确定系统需求。鉴于篇幅有限,在此不可能对整个OA系统的设计开发进行介绍,所以只能够有针对性地选择其中的一个模块进行讨论。这里主要是讨论OA系统的进销存管理模块,以说明在Tomcat中使用"Struts+Hibernate+MS SQL Server"技术开发Web系统的一般应用方法及步骤。进销存管理是OA系统尤为重要的一部分,是物流管理上的主要环节。从进销存字面上可以理解,它主要包括进货、库存、销售和与之有直接关联的财务管理。进销存管理模块主要是实现以下几大功能:
(1)基本信息管理,主要包括:
商品信息管理,包括录入商品信息、编辑商品信息等。
客户信息管理,包括录入客户信息、编辑客户信息等。
仓库信息管理,包括设置仓库信息。
操作员管理,包括添加操作员、删除操作员、修改操作员信息及密码等。
(2)进销存管理,主要包括:
商品进货入库管理,包括修改商品的库存数量,并且生成供应商应收款项。
图26-1 某OA系统功能导航图
商品销售出库管理,包括修改商品的库存数量,并且生成客户应付款项。
商品库存管理,查看商品的库存信息。
(3)财务管理
客户应收应付款项管理,包括款项结算,查询统计款项。
由于篇幅有限,该OA系统进销存管理模块主要是实现以上的主干功能,而忽略掉了一些无关的功能。在该模块中,操作人员暂时分为三种:系统管理员、仓库管理员和财务人员。不同的操作角色,具有不一样的操作权限。系统管理员可以进行基本信息管理;仓库管理员具有进销存管理操作权限;财务人员可以操作财务管理模块的功能。把上面的功能用UML例图表示就如图26-2所示。
图26-2 OA系统的进销存管理模块用例图
26.2 结构分析
确定了系统的需求,就已经解决了需要做什么的问题。明确了目标之后,接下来就要考虑整个系统打算采用什么软件结构、采用什么软件技术、先从大的方向把握,明确总的方向。OA系统是一个Java Web应用,同样它也采用典型的三层软件结构。在技术上,OA系统将采用"Struts+Hibernate+MS SQL Server"的技术。
由于采用JSP Mode l 1模型时,不利于代码的维护,也不方便后期的二次开发,同时软件重用性也比较差,所以JSP Mode l 1模型比较适合小型的快速的Web应用开发。而JSP Mode l 2模型是基于MVC模式的框架,通过这种模型把应用逻辑、处理过程和显示逻辑分成不同的组件实现。JSP Mode l 2模型具有组件化的优点,从而更易于实现对中大规模系统的开发和管理。该OA系统将采用JSP Mode l 2模型,并且是Struts框架。Struts框架基于JSP Mode l 2模型基础上,更易于扩展和控制,极大地提高软件开发效率。
对数据库持久层的实现,将采用Hiberbate组件。利用Hiberbate实现对象-关系的映射,通过操作对象来实现数据的持久性。当数据库结构或是数据库类型发生变化的时候,只需要修改Hibernate的对象-关系映射配置文件即可。这给后期项目的维护提供了极大的方便,将由数据库引起的代码变化减到最少。OA系统软件结构如图26-3所示。
在图26-2所示的软件结构中,共分为三个层次结构:客户层、服务层和数据库层。在客户层中,主要向客户展示信息和接受客户输入的信息。服务层主要是运行在Tomcat服务器下,利用Tomcat服务器所提供的JSP/Servlet容器。其实服务层还可以细分为Web业务层和数据持久层。Web业务层是一个Struts的MVC框架,数据持久层是一个Hibernate的ORM映射。在Struts中通过Action来对Hibernate的对象进行操作,Hibernate将通过OR映射来实现数据的持久性。在数据库层中,主要是存储大量的系统数据,为了适用中大系统的需求,在此使用的是MS SQL Server数据库系统。
图26-3 OA系统软件结构图
26.3 数据库设计
软件结构和技术选型都已经确定后,下面接着进行数据库部分的设计。目前主要的数据库系统是关系型数据库系统,绝大部分的系统应用中都采用关系型数据库。当前最为流行的中大型的数据库系统包括:Oracle、MS SQL、DB2、MySQL、SysBase等。本章采用的数据库系统是MS SQL Server2000.在中大型的Web应用中,MS SQL Server与别的数据库,比如Oracle、DB2和MySQL等都是应用最多的数据库之一。MS SQL Server数据库功能强大,支持完善,操作也比较方便,完全满足中大型Web应用系统的数据库要求。
在设计数据库字段之前,需要先对系统显示和收集的数据进行采集分析。在OA系统进销存模块中,需要采集的数据主要是表示事物的实体和表示进销存单据的记录。根据前面的需求分析,这里需要设计下面的信息:
(1)基本信息,包括:
商品信息
客户信息
仓库信息
操作员信息
(2)进销存记录信息,包括:
商品进货入库记录信息,分为入库单信息和入库单中的商品信息。
商品销售出库记录信息,分为出库单信息和出库单中的商品信息。
(3)财务应收应付信息,包括:
客户款项清算记录信息
根据上面需要设计的数据库信息,下面是实现数据库具体字段的设计。OA系统进销存模块的数据库具体字段设计如下面所示。
商品信息表GOODS,主要是记录商品的基本信息。具体如表26-1所示。
表26-1 商品信息表GOODS的详细设计信息
客户信息表CUSTOMER,主要是记录与之产生业务来往的客户信息,包括一些账务上的信息。具体如表26-2所示。
表26-2 客户信息表CUSTOMER的详细设计信息
仓库信息表WAREHOUSE,主要是记录商品所存放的仓库信息。具体如表26-3所示。
表26-3 仓库信息表WAREHOUSE的详细设计信息
操作员信息表USERS,主要是用于记录使用该系统的人员信息,包括登录密码等。具体如表26-4所示。
表26-4 操作员信息表USERS的详细设计信息
入库单信息表DEPOST,主要是记录商品进行入库时的单据信息,一般一条入库单属于一个客户,包括商品的总数量和总金额等信息。具体如表26-5所示。
表26-5 入库单信息表DEPOST的详细设计信息
入库明细信息表DEPOSTDETAIL,主要是记录入库单里所对应的具体商品信息。一条入库单可以对应多个商品。具体如表26-6所示。
表26-6 入库明细信息表DEPOSTDETAIL的详细设计信息
出库单信息表POOLS,主要是记录商品进行出库时的单据信息,一般一条出库单对应一个客户,包括商品的总数量和总金额等信息。具体如表26-7所示。
表26-7 出库单信息表POOLS的详细设计信息
出库明细信息表POOLSDETAIL,主要是记录出库单里所对应的具体商品信息。一条出库单可以对应多个商品。具体如表26-8所示。
表26-8 出库明细信息表POOLSDETAIL的详细设计信息
客户款项清算记录表LIQUIDE,主要是记录入库与出库中,商家所产生的费用交纳与收取情况信息。具体如表26-9所示:
表26-9 客户款项清算记录表LIQUIDE的详细设计信息
以上数据库表对应的SQL创建脚本如下所示:
if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[Customer]') and OBJECTPROPERTY(id, N'IsUserTable') = 1)
drop table [dbo].[Customer]
GO
if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[Depost]') and OBJECTPROPERTY(id, N'IsUserTable') = 1)
drop table [dbo].[Depost]
GO
if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[DepostDetail]') and OBJECTPROPERTY(id, N'IsUserTable') = 1)
drop table [dbo].[DepostDetail]
GO
if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[Goods]') and OBJECTPROPERTY(id, N'IsUserTable') = 1)
drop table [dbo].[Goods]
GO
if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[Liquide]') and OBJECTPROPERTY(id, N'IsUserTable') = 1)
drop table [dbo].[Liquide]
GO
if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[Pools]') and OBJECTPROPERTY(id, N'IsUserTable') = 1)
drop table [dbo].[Pools]
GO
if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[PoolsDetail]') and OBJECTPROPERTY(id, N'IsUserTable') = 1)
drop table [dbo].[PoolsDetail]
GO
if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[Users]') and OBJECTPROPERTY(id, N'IsUserTable') = 1)
drop table [dbo].[Users]
GO
if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[WareHouse]') and OBJECTPROPERTY(id, N'IsUserTable') = 1)
drop table [dbo].[WareHouse]
GO
CREATE TABLE [dbo].[Customer] (
[id] [int] IDENTITY (1, 1) NOT NULL ,
[customerId] [varchar] (20) COLLATE Chinese_PRC_CI_AS NULL ,
[name] [varchar] (40) COLLATE Chinese_PRC_CI_AS NULL ,
[type] [varchar] (10) COLLATE Chinese_PRC_CI_AS NULL ,
[contact] [varchar] (20) COLLATE Chinese_PRC_CI_AS NULL ,
[phone] [varchar] (20) COLLATE Chinese_PRC_CI_AS NULL ,
[mainSale] [varchar] (100) COLLATE Chinese_PRC_CI_AS NULL ,
[receive] [decimal](18, 2) NULL ,
[pay] [decimal](18, 2) NULL ,
[info] [varchar] (200) COLLATE Chinese_PRC_CI_AS NULL
) ON [PRIMARY]
GO
CREATE TABLE [dbo].[Depost] (
[id] [int] IDENTITY (1, 1) NOT NULL ,
[depostId] [varchar] (20) COLLATE Chinese_PRC_CI_AS NULL ,
[depostTime] [datetime] NULL ,
[customerId] [varchar] (20) COLLATE Chinese_PRC_CI_AS NULL ,
[warehouseId] [varchar] (20) COLLATE Chinese_PRC_CI_AS NULL ,
[allAmount] [decimal](18, 2) NULL ,
[allMoney] [decimal](18, 2) NULL ,
[prepay] [decimal](18, 2) NULL ,
[status] [varchar] (10) COLLATE Chinese_PRC_CI_AS NULL ,
[userId] [varchar] (20) COLLATE Chinese_PRC_CI_AS NULL ,
[info] [varchar] (200) COLLATE Chinese_PRC_CI_AS NULL
) ON [PRIMARY]
GO
CREATE TABLE [dbo].[DepostDetail] (
[id] [int] IDENTITY (1, 1) NOT NULL ,
[depostId] [varchar] (20) COLLATE Chinese_PRC_CI_AS NULL ,
[goodsId] [varchar] (20) COLLATE Chinese_PRC_CI_AS NULL ,
[goodsName] [varchar] (40) COLLATE Chinese_PRC_CI_AS NULL ,
[price] [decimal](18, 2) NULL ,
[amount] [decimal](18, 2) NULL ,
[money] [decimal](18, 2) NULL ,
[info] [varchar] (200) COLLATE Chinese_PRC_CI_AS NULL
) ON [PRIMARY]
GO
CREATE TABLE [dbo].[Goods] (
[id] [int] IDENTITY (1, 1) NOT NULL ,
[goodsId] [varchar] (20) COLLATE Chinese_PRC_CI_AS NULL ,
[name] [varchar] (30) COLLATE Chinese_PRC_CI_AS NULL ,
[type] [varchar] (10) COLLATE Chinese_PRC_CI_AS NULL ,
[warehouseId] [varchar] (20) COLLATE Chinese_PRC_CI_AS NULL ,
[leafe] [varchar] (10) COLLATE Chinese_PRC_CI_AS NULL ,
[specifi] [varchar] (20) COLLATE Chinese_PRC_CI_AS NULL ,
[origin] [varchar] (40) COLLATE Chinese_PRC_CI_AS NULL ,
[units] [varchar] (20) COLLATE Chinese_PRC_CI_AS NULL ,
[price] [decimal](18, 2) NULL ,
[amount] [decimal](18, 2) NULL ,
[info] [varchar] (200) COLLATE Chinese_PRC_CI_AS NULL
) ON [PRIMARY]
GO
CREATE TABLE [dbo].[Liquide] (
[id] [int] IDENTITY (1, 1) NOT NULL ,
[liquideId] [varchar] (20) COLLATE Chinese_PRC_CI_AS NULL ,
[liquideTime] [datetime] NULL ,
[customerId] [varchar] (20) COLLATE Chinese_PRC_CI_AS NULL ,
[customerName] [varchar] (50) COLLATE Chinese_PRC_CI_AS NULL ,
[documentId] [varchar] (20) COLLATE Chinese_PRC_CI_AS NULL ,
[type] [varchar] (10) COLLATE Chinese_PRC_CI_AS NULL ,
[money] [decimal](18, 2) NULL ,
[userId] [varchar] (20) COLLATE Chinese_PRC_CI_AS NULL ,
[info] [varchar] (200) COLLATE Chinese_PRC_CI_AS NULL
) ON [PRIMARY]
GO
CREATE TABLE [dbo].[Pools] (
[id] [int] IDENTITY (1, 1) NOT NULL ,
[poolsId] [varchar] (20) COLLATE Chinese_PRC_CI_AS NULL ,
[poolsTime] [datetime] NULL ,
[customerId] [varchar] (20) COLLATE Chinese_PRC_CI_AS NULL ,
[warehouseId] [varchar] (20) COLLATE Chinese_PRC_CI_AS NULL ,
[allAmount] [decimal](18, 2) NULL ,
[allMoney] [decimal](18, 2) NULL ,
[receive] [decimal](18, 2) NULL ,
[status] [varchar] (10) COLLATE Chinese_PRC_CI_AS NULL ,
[userId] [varchar] (20) COLLATE Chinese_PRC_CI_AS NULL ,
[info] [varchar] (200) COLLATE Chinese_PRC_CI_AS NULL
) ON [PRIMARY]
GO
CREATE TABLE [dbo].[PoolsDetail] (
[id] [int] IDENTITY (1, 1) NOT NULL ,
[poolsId] [varchar] (20) COLLATE Chinese_PRC_CI_AS NULL ,
[goodsId] [varchar] (20) COLLATE Chinese_PRC_CI_AS NULL ,
[goodsName] [varchar] (40) COLLATE Chinese_PRC_CI_AS NULL ,
[price] [decimal](18, 2) NULL ,
[amount] [decimal](18, 2) NULL ,
[money] [decimal](18, 2) NULL ,
[info] [varchar] (200) COLLATE Chinese_PRC_CI_AS NULL
) ON [PRIMARY]
GO
CREATE TABLE [dbo].[Users] (
[id] [int] IDENTITY (1, 1) NOT NULL ,
[userId] [varchar] (20) COLLATE Chinese_PRC_CI_AS NULL ,
[userName] [varchar] (20) COLLATE Chinese_PRC_CI_AS NULL ,
[password] [varchar] (20) COLLATE Chinese_PRC_CI_AS NULL ,
[role] [varchar] (20) COLLATE Chinese_PRC_CI_AS NULL ,
[info] [varchar] (200) COLLATE Chinese_PRC_CI_AS NULL
) ON [PRIMARY]
GO
CREATE TABLE [dbo].[WareHouse] (
[id] [int] IDENTITY (1, 1) NOT NULL ,
[warehouseId] [varchar] (20) COLLATE Chinese_PRC_CI_AS NULL ,
[name] [varchar] (20) COLLATE Chinese_PRC_CI_AS NULL ,
[allAmount] [decimal](18, 2) NULL ,
[contact] [varchar] (20) COLLATE Chinese_PRC_CI_AS NULL ,
[phone] [varchar] (20) COLLATE Chinese_PRC_CI_AS NULL ,
[address] [varchar] (50) COLLATE Chinese_PRC_CI_AS NULL ,
[info] [varchar] (200) COLLATE Chinese_PRC_CI_AS NULL
) ON [PRIMARY]
GO
为了便于后面的程序开发和测试,在此先在操作员信息表USERS里添加几条不同角色的用户信息记录,记录如表26-10所示。
表26-10 在操作员信息表USERS里添加记录
26.4.1 页面设计
页面是系统与用户进行信息交互的最有效途径。向客户展示信息,获取用户的输入信息都是通过页面来完成的。在详细设计里,首先进行页面的设计,以便对系统先有个感性的认识。
经过前面的需求分析,OA系统进销存模块主要是商品的进货入库、销售出库和库存管理等。由于存在商品的进货与销售,所以还需要简单的财务管理功能。财务上主要是对商品进货时应付款项的管理和对商品销售时应收款项的管理,以及款项清算支付管理等。由于进销存模块主要实现了三块功能:基本信息管理、进销存管理和财务管理,所以对于不同的功能,要分配不同角色的操作人员来进行操作。三个模块对应三种不同角色:系统管理员(Admin)、仓库管理员(HouseUser)和财务人员(AccountUser)。因此在系统首页就需要提供登录窗口,不同角色的人员登录进去后,分别可以操作对应的功能。首页登录相关操作功能的页面流程如图26-4所示。
图26-4 登录相关操作功能的页面过程图
不同身份登录进去之后,操作不同的模块。如果是以"Admin"身份登录,则进入基本信息管理模块。基本信息管理里的页面流程如图26-5所示。
图26-5 基本信息管理的页面过程图
如果是以"HouseUser"身份登录,则进入进销存管理模块。进销存管理里的页面流程如图26-6所示。
如果是以"AccountUser"身份登录,则进入财务管理模块。财务管理里的页面流程如图26-7所示。
图26-6 进销存管理的页面过程图 图26-7 财务管理的页面过程图
从上面各个页面的过程图可以看出,需要一些相应的页面来显示信息和收集信息,需要的页面如表26-11所示。
表26-11 需要的页面描述信息
在表26-11中,只是列出了其主要的页面,虽然这几个页面已经可以满足需求,但还可以增加别的页面。在此只是为了说明方法和过程,所以就忽略了一些无关紧要的页面。从表26-11中,可以知道各个页面之间的关联关系,它们之间的访问关系如图26-8所示。
图26-8 页面之间的访问关系