http://www.educity.cn/jiaocheng/j9415.html
JSP程序员常用的技术
第1章 JSP及其相关技术导航
【本章专家知识导学】
JSP是一种编程语言,也是一种动态网页开发技术,要用它完成实践项目工程的开发,需要掌握的知识点比较多。为了让读者对JSP这一开发技术的知识体系有个全面、清晰的了解,为后续的学习打下基础,本章将首先讲述作为一名JSP程序员应该掌握的技术知识体系和本书的内容安排。接着,对JSP技术进行了简要的介绍,使读者了解JSP技术的功能和优势。读者在读完本章后,应当能够了解要成为一个优秀的JSP程序员所要掌握的知识内容,并对JSP技术的要点有了一个整体性的认识,从而在以后的学习中做到有的放矢,完善自己的知识积累。
1.1 JSP程序员常用的技术
如今,开发Web应用程序的技术已经变得相对成熟与复杂了。构建一个Web应用程序不仅仅需要基本的HTML知识,还需要开发者对数据库访问、脚本语言和Web应用服务器管理等方面的知识有所了解,下面介绍JSP程序员常用的一些技术。
1.HTML语言
HTML语言(Hypertext Marked Language,超文本标记语言)是一种用来表示网页中的信息的符号标记语言。通过使用HTML可以在Web页面中加入图片、声音、动画、视频等内容,从一个文件跳转到另一个文件,*链接。用HTML编写的超文本文档称为HTML文档,它能独立于各种操作系统平台(如UNIX、Windows等)。自1990年以来HTML就一直被用作Internet上的信息表示语言,它是Web程序员需要掌握的基础知识。
JSP技术是一种主流的开发动态网页的技术,JSP程序员理所当然应熟悉HTML。虽然现在制作网页的可视化开发工具很多,功能也强大,例如Dreamweaver、FrontPage,可以自动生成HTML文件中的部分代码,但是在开发过程中,很多地方仍然要求开发人员手工修改HTML代码。所以,作为一个合格的JSP程序员必须要熟练掌握HTML。
2.JavaScript脚本语言
HTML仅仅是一种标记语言,用它可作的事是有限的。用HTML创建的网页是静态的,这种网页不能处理和应答用户的客户端活动。例如,Web页面可能要求用户必须输入用户名、密码等信息,如果用户没有输入这些信息,网页要给出提示信息。如果为了核对用户输入是否为空,把数据送到服务器处理,然后服务器再返回处理结果,这样势必导致大量的网络通讯量。如果这些工作可以在客户端得到处理,将大大降低服务器的负担和网络的通讯量。正是Web网页可交互性的需要导致了脚本语言的出现。脚本语言使网页变成了可交互式的,它允许开发者获取客户端的每个事件,但无需与服务器交互。所以,掌握一种脚本语言成为JSP程序员的必备技能。
推荐读者使用目前最流行的脚本语言JavaScript。JavaScript是一种独立的编程语言,它与Java没有任何关系。JavaScript与Java的区别比较大。首先,JavaScript是一种解释性语言,用JavaScript语言写出的脚本不用编译就可在客户端浏览器上执行,而Java语言编写的程序在执行前是要被编译的。其次,如JavaScript这样的解释语言没有复杂的语法和规则,因此比Java这种被编译语言更容易学习。JavaScript编写的脚本都是集成在HTML当中,用于处理客户端的交互活动,而且是不可扩展的,而Java应用不与HTML直接集成。总之,JavaScript语言与Java语言是性质完全不同的两种语言,两者使用的领域范围不同,两者所能实现的功能也有区别。
3.Java语言基础
Java是JSP的基础,要学习JSP技术,Java基础是必不可少的。在用JSP技术编写Web页面时,很多时候要用到Java语言和Java编程的思想,所以如果读者想深入学习JSP技术,就必须对Java语言有深刻的理解。
Java是一种简单易用、完全面向对象、具有平台无关性、安全可靠的主要面向Internet的编程语言。自它问世以来,Java的快速发展和广泛应用已经让整个Web世界发生了翻天覆地的变化。随着Java Servlet和Java Server Page技术的推出,Java开发工具的功能不断强大,Web服务器软件功能的增强,Java已成为主流的软件开发语言之一。
4. SQL和JDBC
如今,大多数Web应用程序需要访问关系数据库中的数据。作为一个JSP程序员,你需要知道如何存储、得到并操作数据库中的数据。有时候,也需要设计数据库,构建数据库中的表和其他结构。SQL(Strutured Query Language,结构化查询语言)就是用来操作数据库中数据的语言。程序员通常需要编写SQL语句(常常是动态的),把它们传送到数据库服务器,再由数据库服务器执行SQL语句并返回SQL语句执行的结果。
Java语言中需要使用JDBC来帮助Web应用程序和数据库服务器进行通信。JDBC是用于执行SQL语句的Java应用程序接口,由一组用Java语言编写的类与接口组成,在JSP中将使用JDBC来访问数据库。JDBC是一种规范,它让各数据库厂商为Java程序员提供标准的数据库访问类和接口,这样就使得独立于数据库管理系统的Java应用程序统一了开发接口,便于模块化开发。在Windows操作系统中,还可以使用JDBC-ODBC桥驱动程序,这样只要是能够使用ODBC访问的数据库系统,也就能够使用JDBC访问了。
5.Web 服务器管理和应用程序的部署
服务器是对Web浏览器检索信息的请求做出响应,进而将HTML文档回传到客户机的浏览器。JSP页面和Java Servlet必须在特定的Web服务器中运行。因此,你至少需要知道如何配置Web服务器,以及如何为测试、生产运行应用程序而部署相关的Web资源。例如,如果运用Tomcat,你需要了解怎样安装配置它,需要了解如何映射配置文件(server.xml)中相关的应用程序,使Tomcat知道如何调用JSP页面、Servlet组件等资源。
推荐读者使用现在最流行的Web服务器中间件软件Tomcat、Weblogic或Websphere,其中Tomcat是开源免费的,读者可以从http://www.apache.org网站下免费下载得到,本书也将使用Tomcat作为Web服务器端的中间件软件。
6.XML
XML是计算机技术领域中的后起之秀,由World Wide Consortium在1996年开发,现在已经是用于数据交换和可扩展数据结构的一个广泛的、公认的标准了。JSP在其发展过程中也越来越强调XML的语言格式,XML在Java Web开发中扮演着越来越重要的角色。为什么要用XML的相容语言来架构JSP呢?因为作为XML文档的JSP将会得到很多好处,例如:
一个标准的XML相容的JSP语法将有助于JSP的开发。
JSP文件的XML语法使得JSP文件的内容很容易被组织和管理。
可以使用XML的开发和分析工具来开发和分析JSP,仅仅需要更换DTD文件就可以升级到最新版本的JSP。
XML格式统一的语法更容易学习和使用。
7.Java Servlet
Java Servlet是JSP技术的基础,而且大型的Web 应用程序的开发常使用Java Servlet和JSP结合来实现。
Java Servlet其实和传统的CGI程序和ISAPI、NSAPI等Web 程序开发工具的作用是相同的,在使用Java Servlet以后,用户不必再使用效率相对低下的CGI技术了,也不必使用只能在某个固定Web 服务器平台运行的API方式来动态生成Web 页面。许多Web 服务器都支持Servlet,即使不直接支持Servlet的Web 服务器也可以通过附加的应用服务器和模块来支持Servlet。得益于Java的跨平台的特性,Servlet也是平台无关的,实际上只要符合Java Servlet规范,Servlet是完全平台无关且是Web 服务器无关的。由于Java Servlet内部是以线程方式提供服务,不必对于每个请求都启动一个进程,并且利用多线程机制可以同时为多个请求服务,因此Java Servlet效率非常高。
但Java Servlet也不是没有缺点,和传统的CGI、ISAPI、NSAPI方式相同,Java Servlet是利用输出HTML语句来实现动态网页的,如果用Java Servlet来开发整个网站,动态部分和静态页面的整合过程会给程序员带来相当大的工作量,对程序员要掌握的知识水平和深度也要求比较高,这就是为什么SUN还要推出JSP的原因。JSP编写起来简单多了,但最终在执行时会先由Web容器先转换成Java Servlet,再由Web容器编译执行,所以一个JSP页和一个Java Servlet是一一对应的。
8.JavaBean和JSTL
在复杂的Web应用程序中,JSP页面只用于显示数据,而JavaBean和标签用来实现业务逻辑。
什么是JavaBean?
JavaBean就是Java中的可重用组件。
ASP.NET通过COM来扩充复杂的功能,如文件上载、发送email等业务处理功能或复杂的计算功能,可以将它们分离出来成为独立的可重复利用的模块。
JSP通过JavaBean实现了同样的功能扩充。JSP为在Web 应用中集成JavaBean组件提供了完善的支持。这种支持不仅能缩短开发时间(可以直接利用经测试和可信任的已有组件,避免了重复开发),也为JSP应用带来了更多的可伸缩性。JavaBean组件可以用来执行复杂的计算任务,负责与数据库的交互等。
和传统的ASP或PHP页面相比,JSP页面将会是非常简洁的,由于JavaBean开发起来简单,又可以利用Java语言的强大功能,许多动态页面处理的过程实际上被封装到了JavaBean中。
什么是JSTL呢?JSTL(JSP Standard Tag Libraries,JSP标准标签库)就是存储了可复用代码的标准标签库。为了提高应用程序的开发效率,就可以使用JSTL中已有的标签。此外,通过扩展程序员也可以编写自已的标签;一些框架技术也提供了一些用在JSP页面中的标签,如Struts提供的Struts HTML标签。
1990年11月,Tim Berners-Lee在自己编写的图形化Web浏览器“WorldWideWeb”上看到了最早的Web页面。1991年,CERN(European Particle Physics Laboratory,欧洲粒子物理研究中心)正式发布了Web技术标准。目前,与Web相关的各种技术标准大多由著名的W3C组织(World Wide Web Consortium)进行管理和维护。
Web服务器端的开发技术是由静态向动态逐渐发展、完善起来的。最早的Web服务器简单地响应浏览器发来地HTTP请求,并将存储在服务器上的HTML文件返回给浏览器。当时只有一种名为SSI(Server Side Includes)的技术可以让Web服务器在返回HTML文件前,更新HTML文件的某些内容,但其功能非常有限。第一种真正使服务器可以动态生成HTML页面的技术是CGI(Common Gateway Interface,公共网关接口)。1993年,CGI1.0的标准草案由NCSA(National Center for Supercomputing Application)提出;1995年,NCSA又开始制定CGI1.1标准;1997年,NCSA又开始讨论CGI1.2标准。CGI技术允许服务器端的应用程序根据客户端的请求动态生成HTML页面,这使客户端和服务端的动态信息交换成为了可能。随着CGI技术的普及,聊天室、论坛、电子商务、信息查询、全文检索等Web应用蓬勃发展起来,人们终于可以享受到信息检索、信息交换、信息处理等更为便捷的信息服务了。
早期的CGI程序大多是编译后的可执行程序,其编程语言可以是C、C++、Pascal等任何通用的程序设计语言。为了简化CGI程序的修改、编译和发布过程,人们开始探寻用脚本语言实现CGI应用的可行方式。在此方面,不能不提的是Larry Wall于1987年发明的Perl语言。Perl语言结合了C语言的高效以及SH、AWK等脚本语言的便捷,似乎天生就适合于编写CGI程序。1995年,第一个用Perl语言编写的CGI程序问世。很快,Perl在CGI编程领域的风头就盖过了它的前辈C语言。随后,Python等著名的脚本语言也陆续加入了CGI编程语言的行列。
1994年,Rasmus Lerdorf发明了专用于Web服务器端编程的PHP(Personal Home Page Tools)语言。与以往的CGI程序不同,PHP语言将HTML代码和PHP指令合成为完整的服务端动态页面,Web应用的开发者可以用一种更加简便、快捷的方式实现动态Web功能。1996年,Microsoft借鉴PHP的思想,在其Web服务器IIS3.0中引入了ASP技术。ASP使用的脚本语言编程时使用的是我们熟悉的VBScript和JavaScript。借助Microsoft Visual Studio等开发工具在市场上的成功,ASP迅速成为Windows系统下Web服务端的主流开发技术。
当然,以Sun公司为首的Java阵营也不会示弱。1997年,Servlet技术问世,1999年,JSP技术诞生。Servlet、JavaBean技术和JSP的组合让Java开发者同时拥有了类似CGI程序的集中处理功能和类似PHP的HTML嵌入功能,此外,Java的运行时编译技术也大大提高了Servlet和JSP的执行效率,这正是Servlet和JSP被后来的J2EE平台吸纳为核心技术的原因之一。
作为一种动态网页表现层的开发技术,JSP提供了Java Servlet 的所有好处,并且,当与一个JavaBean类结合在一起时,提供了一种使内容和显示逻辑分开的简单方式。分开内容和显示逻辑的好处是,更新页面外观的人员不必懂得Java代码,而编写JavaBean类代码的人员也不必是设计网页的行家里手,就可以用带JavaBean类的JSP页面来定义Web 模板,以建立一个由具有相似的外观的页面组成的网站。JavaBean类完成数据处理逻辑,这样在页面模板中就可以只书写少量的Java代码,这意味着这些模板可以由一个HTML 编写人员来维护。当然,也可以利用Java Servlet来控制网站的逻辑,通过Java Servlet调用JSP文件的方式来将网站的逻辑和内容分离。
然而,Java Servlet最适用于不需要频繁修改的网页,而JSP页面则通过以显示为中心的描述性的方法将动态内容和逻辑结合在一起。通过JSP页面还可以使用定制标记或者Scriptlet,而不是使用JavaBean类来将内容与应用逻辑结合起来。定制标记被打包到一个标记库中,并被引入到一个JSP页面中。Scriptlet是直接嵌入在JSP页面中的Java代码段。
在JSP引擎中,JSP页面在执行时是编译式,而不是解释式的。解释式的动态网页开发工具如ASP、PHP等由于效率不高、编写不便等原因已经满足不了当前大型电子商务应用的需要了,传统的开发技术都在向编译执行的方式改变,如ASP→ASP.NET。JSP页面被编译为Servlet的Java源文件,再经过Java编译器编译为Servlet的class文件。在JSP文件转译为Servlet以后,每次客户机(通常是用户的Web 浏览器)向服务器请求这一个JSP文件的时候,服务器将检查自上次编译后JSP文件是否有改变,如果没有改变,就直接执行Servlet,而不用再重新编译,其效率是相当高的。一般地,Web服务器还可以作出设置,使JSP文件在第一个用户访问之前就预先编译好,这样执行的效率就更高了。
和传统的CGI相比较,JSP有相当的优势。首先,在速度上对于CGI来说,每一个访问就需要新增加一个进程来处理,进程不断地建立和销毁对于作为Web 服务器的计算机将是不小的负担。JSP可以用线程的方式来应对客户端的访问,这样需要消耗的系统资源更小。JSP是专门为Web开发而设计的,其目的是为了建立基于Web的应用程序,包含了一整套的规范和工具。
和ISAPI和NSAPI相比较,JSP的开发速度要快得多,开发难度也要小得多。而且ISAPI和NSAPI这种和Web 服务器过于紧密结合的技术在使用时的一旦出现错误,有可能导致Web 服务器崩溃,而JSP是线程安全的,且没有诸如C语言中的指针之类的操作内存的机制,因此编写出来的程序显得更加安全和可靠。
JSP的竞争对手是ASP.NET和PHP,在Web 技术方面ASP.NET、PHP和JSP的比较见表1-1。
表1-1 ASP.NET、JSP、PHP比较
1. Web 服务器和运行平台
ASP.NET主要应用于Windows平台,在添加组件后也可以用于Linux平台。尽管有第三方的插件号称可以在UNIX下使用ASP.NET,但ASP.NET经常需要使用COM组件封装已有的程序模块,而UNIX平台并不支持COM。
JSP得益于Java的跨平台性,它可以在许多平台下使用。
Apache Web Server是世界上占有率最高的Web 服务器产品,可以在包括SUN Solaris、IBM AIX、Linux和Windows在内的许多操作系统下运行。Apache Web Server下JSP的实现可以通过免费的Apache Jserv 和GNUJSP、Jakarta-Tomcat实现,也可以使用商业的JRUN (LiveSoftware)、WebLogic(BEA)、Websphere(IBM)来实现。
还可以使用应用服务器添加JSP支持的Netscape Enterprise Server及由之发展而来的可以直接支持JSP的iPlanet Web Server等等。
PHP本身就对各种操作系统和Web 服务器做了支持,PHP目前可以作为Apache的一个附加模块直接编译进入Apache中去,由于Apache支持多种操作系统,PHP相应地也就可以在各种操作系统上实现。PHP也可以CGI方式或ISAPI方式插入到IIS或PWS中去。
2. 组件技术
ASP.NET和JSP对组件技术的支持已经比较完善了,而PHP直到前不久才开始支持COM,但支持能力还不够强,如果PHP不能在将来完善对组件技术的支持,在大型Web 应用程序方面将很难与JSP和ASP竞争。但由于PHP技术本身的易学易用,加上众多的函数支持和开放源代码的特性,在中小型Web 站点的开发上,PHP还是会占有一席之地的。
JSP本身相对于ASP.NET和PHP并没有明显的优势,JSP的强大是因为其后面有强大的Java技术做支持。包括JavaBean和EJB技术在内的J2EE技术体系是JSP强大生命力的所在。
有理由认为,在将来的Web 开发中,中小型站点将出现JSP、ASP.NET和PHP三分天下的局面,但是对于大型的电子商务站点,JSP与ASP.NET技术将成为首选。
JSP作为J2EE的一部分,既可以用于开发小型的Web 站点、也可以用于开发大型的、企业级的应用系统,本节将讲述对于不同规模的Web 系统,使用JSP进行开发的不同方式。
1.直接使用JSP
对于最小型的Web 站点,可以直接使用JSP来构建动态网页,这种站点最为简单,所需要的仅仅是简单的留言板、动态日期等基本的功能。对于这种开发模式,一般可以将所有的动态处理部分都放置在JSP的Scriptlet中,这有些类似于使用PHP或ASP开发动态网页。
2.JSP+JavaBean
中型站点面对的是数据库查询、用户管理和少量的商业业务逻辑。对于这种站点,不能将所有的程序都编写在JSP页面中。利用JavaBean,将很容易完成如数据库连接、用户登录与注销、购物车等商业业务逻辑封装的任务。如:将常用的数据库连接写为一个JavaBean,既方便了使用,又可以使JSP文件简单而清晰,通过封装,还可以防止一般的开发人员直接获得数据库的控制权。
3.JSP+JavaBean+Servlet
无论用ASP还是PHP开发动态网站,长期以来都有一个困扰着的问题,就是网站的逻辑关系和网站的显示页面不容易分开。常常可以看见一些夹杂着if......then......、case select或是if{......}和大量显示用的HTML代码的ASP、PHP页面,即使是有着良好的程序写作习惯的程序员,其作品也几乎无法阅读。另一方面,动态Web的开发人员也在抱怨,将网站美工设计的静态页面和动态程序合并的过程是一个异常痛苦的过程。
如何解决这个问题呢?有了JSP后,表现数据的任务由JSP完成,而Servlet不再担负动态页面生成的任务,却开始担负起决定整个网站逻辑流程的任务。在逻辑关系异常复杂的网站中,借助于Servlet和JSP良好的交互关系和JavaBean的协助,完全可以将网站的整个逻辑结构放在Servlet中,而将动态页面的输出放在JSP页面中来完成。在这种开发方式中,一个网站可以有一个或几个核心的Servlet来处理网站的逻辑,通过调用JSP页面来完成客户端(通常是Web浏览器)的请求。
目前JSP技术已经成为了开发Web应用的主流技术,在国内外得到了广泛的应用。本章主要是对JSP的知识体系和技术特点作概括性的介绍,使读者对JSP有一个整体的了解。
JSP技术不是一项孤立的技术,如果仅仅掌握了JSP技术本身将难以满足实际开发的需要。想成为一名优秀的JSP开发人员,必须掌握与JSP技术密切联系的HTML、数据库开发、应用服务器的安装与配置、XML等知识。
JSP、ASP.NET和PHP是当今主流的Web开发技术。JSP与ASP.NET和PHP相比有许多优势,例如跨平台、可以利用J2EE分布式计算能力、使业务逻辑与页面分离。另外,JSP开发Web应用程序时主要有3种方式,分别是直接使用JSP、JSP+JavaBean、JSP+JavaBean+Servlet。
第2章 开发环境的安装与配置
【本章专家知识导学】
开发环境的安装与配置是学习jsp程序开发的第一步。本章将讲述目前比较流行开发环境的基础知识以及安装、配置方法,诸如JDK、Eclipse等。读者要融会贯通地掌握它,如果已经比较熟悉的读者可跳过本章的学习。Eclipse和Tomcat的安装与配置是本章的重点,希望读者能熟练掌握。
本章还将引入配置管理工具和中间件的概念。软件配置用来保证所有配置项的完整性和可跟踪性。配置管理是对工作成果的一种有效保护,对复杂、大型的软件项目开发具有很强的实用意义。
2.1 Java开发工具
本节主要介绍JDK和Eclipse。JDK是开发Java程序所需要的基本开发工具,Eclipse是目流程的Java集成开发工具。
Sun公司在推出Java语言的同时,推出了一套开发工具JDK,JDK提供了Java开发常用的类库。JDK开发工具有Solaris、Windows NT、Windows 95等多种版本,本节介绍JDK1.5版,包括javac、ja-va、jdb、javah、javap、javadoc和appletview等工具,涵盖了对Java源文件的编译,执行Java字节码文件等工具。
读者可以从Sun公司的网站http://www.sun.com上免费下载得到JDK的安装文件,也可以从如下的网址下载得到JDK1.5:
http://jvm.cn/soft/jdk-1_5_0_08-windows-i586-p.exe
javac,即Java语言编译器,是JDK提供的重要工具之一,能够把由Java语言书写的程序编译成字节代码。
(1)javac使用的语法格式
javac [选项] 文件名.java
(2)javac的功能
javac命令把Java语言源码编译成字节码,字节码文件的扩展名为.class。Java源程序文件名后缀必须是java。javac编译器对每一个Java源文件中定义的类,生成一个保存编译结果字节码的文件,其后缀为class。如果程序中需要引用另一个类,则需要使用classpath环境变量指明存放路径。如果在源程序中引用了一个在该目录下其它文件中都没定义的类,则javac搜寻classpath指明的类路径。
【专家提示】如果没有指定-classpath参数,系统总是把系统变量CLASSPATH中设置的类路径附加到类路径中。
(3)语法中有关[选项]的说明
-classpath类路径:指定javac搜寻类的路径,覆盖CLASSPATH环境变量指定的类路径。目录名由冒号(:)分开,如:home/awh/classes:/usr/local/java/classes。
-d 目录:指定存放编译结果的类根目录。
-g :生成调试表。调试表包含调试工具使用的行号和局部变量信息。缺省时只有行号信息。
-nowarn:关闭警告错误。使用该选项后编译器不生成任何警告错误。
-O:通过嵌入静态、final、私用方法优化编译代码。注意,使用该选项后结果文件的大小会增加。
-verbose:使编译器和链接器显示出正被编译的文件名和正被装载的类名。
2001年11月IBM出资4千万美金开发的软件开放源码项目——Eclipse问世。Eclipse 是替代IBM Visual Age for Java(以下简称IVJ)的下一代IDE开发环境,但它未来的目标不仅仅是成为专门开发Java程序的IDE环境,还能通过开发插件,扩展到任何语言的开发。目前,Eclipse已经开始提供C语言开发的功能插件。Eclipse是一个开放源代码的项目,也就是说未来只要有人需要,就会有建立在Eclipse之上的COBOL、Perl、Python等语言的开发插件出现。
Eclipse是开放源代码的项目,读者可以到http://www.eclipse.org去免费下载Eclipse的最新版本,这个网站上提供了几个下载版本:Release,Stable Build,Integration Build和Nightly Build,建议下载Release或Stable版本。Eclipse本身是用Java语言编写,但下载的压缩包中并不包含Java运行环境,需要用户自己另行安装JRE。
图2-1 Eclipse显示的缺省界面
下面将分别对Eclipse的各种特性作简单介绍,包括:文件存放,开发环境,编译与运行,版本管理,使用插件。
(1)文件存放
Eclipse把所有的源代码都存储到一个reponsitory库文件中,想要得到文本格式的源代码必须用Export功能从reponsitory中导出以文本方式保存的源代码。安装Eclipse之后,在安装路径的下一层路径中会有一个叫workspace的文件夹。每当在Eclipse中新生成一个项目,缺省情况下都会在workspace中产生和项目同名的文件夹以存放该项目所用到的全部文件。可以用Windows资源管理器直接访问或维护这些文件。
将已有的文件加入到一个项目中目前有三种方式:第一种是用IDE的“File”菜单中的“Import”功能将文件导入到项目中。第二种是从Windows的资源管理器中直接拖动文件到项目中。第三种就是直接将文件拷贝到项目文件夹中,然后在Eclipse的资源浏览窗口中选择项目或文件夹并执行从本地刷新功能(Refresh from locate)。需要说明的一点是,项目文件夹可以放在计算机的任何位置,并且可以在Eclipse中用新建项目的方法将项目路径指定到已经存在的项目文件夹,然后在Eclipse中刷新即可。
(2)Eclipse开发环境
Eclipse的开发环境被称为Workbench,它主要由三个部分组成:视图(Perspective),编辑窗口(Editor)和观察窗口(View)。
图2-2 关系结构略图
文件的显示和编辑包含在编辑窗口里。缺省情况下打开的多个文件是以标签(TagTable)方式在同一个窗口中排列,可以用拖动方式将这些文件排列成各种布局。
文件被加入到项目中后,在资源浏览或Java包浏览窗口双击文件,Eclipse会试图打开这个文件:其中Eclipse内嵌的编辑器能缺省打开一些文件,如*.java,*.txt等等。如果是其它类型的文件,Eclipse会调用操作系统相应的缺省编辑器打开,如word文档。例如在Eclipse项目中双击HTML文件时,可能希望是用Notepad打开,而不是用系统缺省的IE浏览器打开。实现的方法是打开菜单栏中的“Windows”→“Preferences”对话框,之后在对话框中选择“WorkBench”→“File Associations”,然后添加文件类型,再为其指定编辑器即可。
浏览窗口和Java浏览窗口是观察窗口核心部分。后者用来浏览项目中的Java包,包中的类,类中的变量和方法等信息。类中的编译出错信息可以在任务窗口中查到,同时它也可以成为名符其实的任务窗口:向其中添加新的任务描述信息,来跟踪项目的进度。控制台则主要用来显示程序的输出信息。
一个视图包括一个或多个编辑窗口和观察窗口。视图是Eclipse的最灵活的部分,可以自定义每个视图中包含的观察窗口种类,也可以自定义一个新视图。这些功能都被包括在"Perspective" 菜单中。在Eclipse的Java开发环境中提供了几种缺省视图,如资源视图(Resource Perspective),Java视图(Java Perspective),调试视图(Debug Perspective),团队视图(Team Perspective)等等。每一种视图都对应不同种类的观察窗口。可以从菜单栏中的“Windows”→“Show View”看到该视图对应的观察窗口。
(3)编译与运行
在Java视图中,工具栏中有两个按钮如图2-3所示,分别用来进行调试和运行程序。由于安装的插件不同Eclipse可能会存在多种运行和调试程序的方式,为了确定当前项目用那一种方式运行,需要在“Run”菜单中设置“Run As…”或“Debug As…”选项。通常我们需要用的是“Java Applicantion”方式。在这种方式下,如果当前位置是包含main()方法的Java程序,点击调试或运行按钮就会立即开始执行调试或运行功能。如果当前位置是在包或项目上,Eclipse会搜索出当前位置所包含的所有可执行程序,然后由程序员自己选择运行哪一个。
图2-3 Eclipse的工具栏中的运行与调试按钮
(4)版本管理
Eclipse的版本管理分为个人(或称为本地)和团队两种。
Eclipse提供了强大的个人版本管理机制,每一次被保存的更改都可以得到恢复。而且可以精确到每一个方法的版本恢复。操作也十分方便,在任何一个能看到所要操作文件的观察窗口中,例如资源浏览窗口,选中该文件,点击右鼠标键,选择“Compare with”或“Replace with”,按照你的需求找到相应的版本就可以了。强大的个人版本管理功能为程序员提供了更多的安全保障。
Eclipse缺省为版本管理工具CVS提供了接口,可以非常方便的连接到CVS服务器上。CVS的指示我们后面还会遇到。通过CVS版本管理,Eclipse为团队开发提供良好的环境。要连接CVS服务器需要先打开团队视图(Team Perspective),然后在Reponsitories观察窗口中点击鼠标右键并选择新建(New),在打开的对话框中可以填入要连接的CVS库所需要的信息,如CVS服务器类型,还要填入用户名,主机名,密码,reponsitory地址等信息。
(5)使用插件
使用插件可以丰富Eclipse的功能。下面将介绍如何应用插件来嵌入Tomcat服务器。这个插件是一家叫sysdeo的公司开发的,非常小巧。读者可以到http://www.sysdeo.com/eclipse/tomcatPlugin.html去免费下载。另外,这个插件只支持Tomat4.0以上的版本,读者可以在www.apache.org得到Tomcat的最新版本。
只需将下载的zip文件解压缩,并将其中的plgins目录中的内容拷贝到Eclipse的安装路径的plugins目录下面,然后重新启动Eclipse。启动后在菜单栏上选择“Windows”“Customize Perspective…”菜单,在打开的对话框中选中“Other Tomcat”。之后马上会发现Eclipse有了两处变化:菜单栏中多了一个Tomcat选项,工具栏中多了两个按钮,如图2-3所示。
图2-3 Eclipse 界面
CVS是目前比较流行的一款优秀的版本管理与控制工具,是用来管理其它日常文档(如word工作文档之类)的一个强有力的工具。WinCVS是CVS的一个客户端软件,它运行在Windows上。企业内部可以采用Linux/Unix/Windows做服务器,用Windows做客户端,所以WinCVS与CVS服务器是目前应用最广泛的版本控制与管理的组合。下面主要介绍WinCVS的日常操作。
(1)第一步:安装Wincvs
执行setup.exe按提示完成安装。.安装完毕后运行wincvs。
(2)第二步:配置WinCVS参数
“Preferences”→“ General”:普通参数设置。
图2-4 Wincvs 配置界面
Authentication:验证方式,CVS默认采用pserver。
Path:CVS服务器的路径,就是Repository(仓库)。
Host address:CVS服务器的IP地址或者域名。
User name:用户名。
CVSROOT:CVSROOT,由上面4项生成的字符串,用于连接服务器。
“Preferences”→“Globals”:全局参数设置。
checkout read-only: 检出只读,wincvs默认导出文件为只读状态。去掉该选项。
Prune(remove)empty directories剪除(删除)空目录,去掉该选项。
“Preferences”→“WinCVS”:WinCVS参数设置。
图2-5 WinCVS参数设置
(3)第三步:登录。
图2-6 登陆界面
正常登录后即可开始使用CVS了。
Tomcat是一个免费的开源的JSP容器,它是Apache基金会的一个核心项目,由Apache、Sun和其它一些公司及个人共同开发而成。由于有了Sun的参与和支持,最新的Servlet和JSP规范总能在Tomcat中得到体现。
在Tomcat中,应用程序的部署很简单,只需将你的war文件(Java Web应用系统的包文件)放到Tomcat的webapp目录下,Tomcat会自动检测到这个文件,并将其解压。在浏览器中访问这个应用的JSP文件时,通常第一次会慢一点,因为Tomcat要将JSP转化为Servlet文件,然后编译。编译以后再次访问将会很快了,因为无需再行编译了。另外Tomcat也提供了一个应用—manager,访问这个应用需要用户名和密码,用户名和密码存储在一个xml文件中。通过manager这个应用,可以远程以Web方式部署和撤销应用。
Tomcat不仅仅只是一个JSP容器,也具有传统的Web服务器的功能:处理Html页面。但是与Apache相比,它的处理静态HTML的能力就不如Apache。可以将Tomcat和Apache集成到一块,让Apache处理静态HTML,而让Tomcat来处理JSP和Servlet。
Tomcat也提供其它的一些特征,如与SSL集成到一块,实现安全传输。还有Tomcat也提供JNDI支持。然而Tomcat只是一个轻量级的Web服务器,并不象诸如Weblogic之类的J2EE应用服务器功能那么强大。通常所说的J2EE应用服务器(如WebLogic)与Tomcat又有何区别呢?应用服务器提供更多的J2EE特性,如EJB,JMS,JAAS等,同时也支持JSP和Servlet,而Tomcat则功能没有那么强大,它不提供EJB等支持。但Tomcat如果与JBOSS(一个开源的应用服务器)集成到一块,也可以实现J2EE的许多功能。在很多中小型应用场合中不需要采用EJB等复杂的技术,JSP和Servlet的组合已经能够胜任,这时如果采用J2EE应用服务器就没有必要了。
WebSphere是IBM的一套软件产品,包括WebSphere应用服务器,WebSphere Studio和 WebSphere Performance Pack。WebSphere可在30多种操作系统平台上运作,除计算机外,还可用于PDA、信息家电等产品,跨平台能力较强。WebSphere目前在全球已有超过35000家企业采用。近期,IBM力推的中间件(middleware)平台WebSphere将推出新版本,预计将进一步提升IBM在应用服务器市场上的份额,对市场龙头BEA Systems构成威胁。目前WebSphere在全球应用服务器产品中排名第二,仅次于BEA Systems的WebLogic,但在亚太区已排名第一。该产品包括一个基于 Java 的 Servlet 引擎,独立于 Web 服务器和它所基于的操作系统。WebSphere应用服务器提供了服务器插件的选项,与大多数流行的应用程序设计接口(API)兼容。
WebSphere由于面向专业人员,要掌握它的管理与使用有一定的难度。此外,WebSphere本身需要占用2G多的磁盘空间,需要256M以上内存支持,系统配置相对要求较高。
(1)安装JDK
从http://www.sun.com或从如下地址可下载得到JDK1.5:
http://jvm.cn/soft/jdk-1_5_0_08-windows-i586-p.exe
双击jdk-1_5_0_08-windows-i586-p.exe即会开始安装,界面如图2-7所示。安装过程中,还会提示安装JRE1.5,这里单击安装JRE1.5界面中的“更改…”按钮,更改JRE1.5的安装目录,这里更改为“c:\jre15”,再单击“下一步”按钮会继续安装,直至安装完毕。JDK1.5的安装目录与JRE1.5的安装目录要设置一样,否则可能会互相冲突,而导致Web应用服务软件Tomcat5.5在启动时不能识别JDK的版本而无法启动。
图2-7 JDK安装界面
在Windows操作系统的桌面上使用鼠标右击“我的电脑”,打开“属性”对话框,在“高级”选项卡上单击“环境变量”,打开“环境变量”设置对话框,进行如下操作:
系统变量->新建->变量名:JAVA_HOME(JDK的安装目录,如“c:\jre15” )。
系统变量->新建->变量名:CLASSPATH(类的路径,如“.;%JAVA_HOME%\lib”)。
系统变量->编辑->变量名:Path(寻找应用程序的目录,可以在最前面加入“%JAVA_HOME%\bin;”)。
【专家提示】CLASSPATH中有一个英文句号“.”后跟一个分号,表示当前路径的意。以上变量中,如果变量名已经存在则无需新建,编辑变量的值即可。
(2)测试JDK安装是否正确
进入命令窗口(在Windows桌面上单击“开始”“运行”,输入cmd进入命令窗口(Windows95/98下输入command)。
执行如下的命令:
javac -version
窗口会回显如图2-8的信息:
图2-8 查看JDK的版本信息
如果能显示JDK的版本信息(不一定与以上文字完全相同),则说明JDK环境安装成功。
(1)安装Tomcat
从http://tomcat.apache.org上可以下载到Tomcat的安装程序、源代码及相关的文档,本书使用的Tomcat5.5。Tomcat不能单独使用,安装之前必须先行安装JDK。在如图2-9所示的网站,在左边的Download区中点击Tomcat版本的超链接可以下载相应版本的Tomcat。
图2-9 Tomcat的下载地址
得到Tomcat5.5的压缩文件后,将其用解压缩软件,如WinZip或WinRar,解压至指定的目录,这里我们解压至d:\tomcat55,实际应用可根据需要而定。
Tomcat5.5发布的程序版本有三种:zip版、tar.gz版和Windows Executable版。在Windows操作系统中,Windows Executable版本的安装可根据向导提示安装,不再叙述;zip版无须安装,直接拷贝解压缩目录下的所有文件至指定目录即可。这里推荐使用zip版,因为Windows Executable版虽然安装简单,但在安装过程中会修改系统的注册表,当经过多次安装Tomcat后,会出现一些不可预料的错误;而zip版无需设置,解压即可使用。
执行Tomcat安装目录bin子目录下的startup.bat程序就可启动Tomcat5.5服务器。结果如图2-10所示。
图2-10 运行Tomcat5.5
(2)测试是否安装成功
接下来,测试一下Tomcat服务器。打开浏览器,在地址栏中输入http://localhost:8080或http://127.0.0.1:8080,127.0.0.1与localhost均代表本机,会出现如图2-11所示的页面。
图2-11 测试Tomcat5.5
(3)配置Tomcat
Tomcat默认的Web服务端口号是8080,IE浏览器默认的HTTP服务端口号是80,如果将Tomcat的Web服务端口号改为80,则在访问服务器时不必再输入端口号了。如果是在系统自带了IIS(Windows 2000 server、Windows XP等操作系统自带的支持ASP技术的Web服务器软件),要注意IIS默认的HTTP服务端口号也是80,容易产生冲突。当然读者也可以把Tomcat的Web服务端口号改为你所要设置的数值。下面讲解怎么更改Tomcat的Web服务端口号。
在Tomcat的安装目录中有个叫config的子目录,进入后打开其中的server.xml文件,可以使用任一文本编辑器,一般使用记事本即可。在server.xml找到如下的一段文字。
<!-- Define a non-SSL HTTP/1.1 Connector on port 8080 -->
<Connector port="8080" maxHttpHeaderSize="8192"
maxThreads="150" minSpareThreads="25" maxSpareThreads="75"
enableLookups="false" redirectPort="8443" acceptCount="100"
connectionTimeout="20000" disableUploadTimeout="true" />
<!-- Note : To disable connection timeouts, set connectionTimeout value
to 0 -->
如果觉得目视查找比较困难,可用记事本“编辑”菜单下的“查找”工具,查找“8080”,以进行定位查找,很快就可以找到上述的文字。将这段文字中的port=“8080”更改为port=“80”即可。重新启动Tomcat服务器,再次测试Tomcat服务器时将不需要输入端口号。
虚拟目录是针对提供Web服务的目录而言的。可以把硬盘中的一个目录映射成Web服务目录的一个子目录,但它事实上并不在Web服务目录中,这样用户访问这个目录时实际*问的并非是Web服务目录中的文件,但给他的感觉就好象是Web服务目录中的文件,这个被映射成Web服务目录的子目录就称为虚拟目录。
使用虚拟目录可以提高Web服务器的安全,即便是Web服务器被攻破,黑客也无法得到程序的源代码;此外,当Web服务目录的磁盘空间不够时,可以用配备在其它磁盘上的虚拟目录来解决这个问题。
在Tomcat5.5中配置虚拟目录的方法是编写一个xml文件,内容如下:
<Context path="/jsp" docBase="e:/jsp" debug="0"
reloadable="true" crossContext="true">
</Context>
其中“path="/jsp"”表示配置的虚拟目录的名称,“docBase="e:/jsp"”是虚拟目录指向的事实目录。将此文件保存于Tomcat5.5安装目录的“conf/Catalina/localhost”文件夹下,文件名为jsp.xml。保存后,Tomcat5.5会自动更新配置。
【专家提示】配置虚拟目录的xml文件的命名规则是:虚拟目录的名称为xml文件的文件名,如此处,虚拟目录的名称为“/jsp”,则xml文件名称为jsp.xml。
第3章 JSP语法
【本章专家知识导学】
JSP是建立在Java语言基础上的一种Web程序设计语言,具有自己特有的用法和指令。本章首先介绍JSP页面的程序结构,然后讲述JSP程序中经常用到基本的面向对象Java语言程序基础知识,最后讲在JSP中特有的JSP指令和JSP动作指令。
通过本章的学习,应当深入了解JSP语法,并能灵活运用JSP语法编写简单的JSP页面和其中的Java程序。
3.1 JSP程序的结构
JSP页面的文件名以“.jsp”为后缀,在一个JSP页面中,除了基本的HTML语言元素外,主要还包含三种基本元素:JSP指令、JSP动作指令和JSP代码。JSP指令用于告诉JSP的Web引擎(如Tomcat)需要在编译时做什么动作,比如引入一个其它的类,设置JSP页面使用什么语言编码等。JSP动作指令则是在JSP页面被请求时动态执行的,比如可以根据某个条件动态跳转到另外一个页面。JSP代码指的就是嵌入在JSP页面中的Java代码,即Java程序片,它通常包含JSP表达式、变量和方法的声明以及Java语句。所有HTML文件可以在浏览器中直接打开查看运行效果,但是JSP文件必须先放到Web服务器中,然后通过HTTP的方式访问。
【例3-1】一个简单的JSP程序结构示例
3-1.jsp
<%@ page contentType="text/html;charset=gb2312" %>
<html>
<head><title>
一个简单的JSP程序结构示例
</title></head>
<body>
生成并显示一个从0~9的字符串:
<%! String str="0"; %>
<% for (int i=1; i <10; i++) {
str = str + i;
}
%>
<p> <%= str %>
</p>
</body>
</html>
这个简单JSP页面的基本结构程序包含了三部分:普通的HTML标记、JSP指令和JSP代码。通过一个for语句生成并显示一个从0~9的字符串,程序的执行结果如图3-1所示。
图3-1 一个简单的JSP程序结构示的运行结果
JSP表达式包含一个符合JSP语法的表达式,JSP表达式使用的方法如下:
<%=表达式%>
其中:“<%=”和“%>”之间为JSP表达式,一对表达式标志“<%=”和“%>”中只能有一个表达式。例如:
<%="Hello World!" %>
<%= 1+1 %>
表达式中的变量需要在脚本程序中事先声明,表达式运行后的结果会被自动地转化为字符串,然后插入到这个表达式在JSP文件中的位置,显示在网页上。因为这个表达式的值自动地转化为字符串,所以能在一行文本中插入这个表达式,Web引擎遇到“<%=”符号后,就会把“<%=”和“%>”中的内容作为表达式来解析。
【专家提示】JSP表达式标志不能用一个分号“;”来作为表达式的结束符,但是同样的表达式用在Java脚本代码中就需要以分号来结尾了。
Java脚本代码(Java Scriptlet)是指在JSP页面中的“<%”和“%>”之间插入的Java程序代码。Java脚本代码使用的语法格式如下:
<% Java脚本代码%>
其中“<%”和“%>”之间为Java脚本代码。
“<%”和“%>”是Java脚本代码标志。Java脚本代码中可以包含多个JSP语句、方法、变量、表达式,它可以访问JSP对象、操作数据库、使用Java类库中的类等,利用这些可以在JSP页面内实现较复杂的逻辑功能。有了Java脚本代码,便能做以下的事:
(1)声明将要用到的变量或方法。
(2)编写JSP表达式。
(3)使用任何隐含的对象和任何用<jsp:useBean>声明过的对象。
(4)编写Java语句。
Java脚本代码由服务器负责把它转化成对应的Servlet的Service方法。当JSP服务器收到客户的请求时,Scriptlet就会被执行,如果Java脚本代码中有显示的内容(即向浏览器页面的输出),这些显示的内容就被输出到out对象流中,形成网页的动态部分。
【例3-2】JSP脚本代码示例
3-2.jsp
<%@ page contentType="text/html; charset=gb2312" %>
<%@page language="java" %>
<html>
<head><title> JSP脚本代码示例</title>
</head>
<body>
<h3> Java Scriptlet</h3>
<%
for (int i =1;i <=5;i ++)
{
%>
<p>
i=<%= i %>
</p>
<%
}
%>
</body>
</html>
例3-2是使用循环语句在网页中显示i值(1 2 3 4 5)的JSP页面代码,其中的一个脚本代码段如:<% for (int i =1;i <=5;i ++) { %>。
它不是完整的Java代码语句,但是它和随后的其他脚本代码段一起构成完整的Java脚本代码语句。不同的脚本代码段之间可以按需要添加HTML文本。其运行结果如图3-2。
图3-2 JSP脚本代码示例运行结果
声明变量或方法的语法格式:
<%!声明变量或方法的代码%>
其中:“<%!”和“%>”之间为Java脚本程序声明的变量或方法。声明的代码不产生输出,而只声明了供访问的方法和变量,因此通常要将其和Java表达式、Java脚本程序结合使用。
声明的代码用于声明目标Servlet类的方法和变量(在处理HTTP请求的“service”方法之外)。在程序中也可以一句声明语句声明多个变量和方法,只要以“;”结尾就行。当然这些声明语句要合乎Java语法。
【专家提示】当声明方法或变量时,要注意以下的一些规则:
(1)声明必须以“;”结尾(Java脚本有同样的规则,但是表达式就不同了)。
(2)可以直接在<%@page%>语句中使用声明语句,包含已经声明了的变量和方法,不需要对它们重新声明。
(3)一个声明仅在一个页面中有效。如果想每个页面都用到一些声明,最好把它们写成一个单独的文件,然后用<%@include%>或<jsp:include>语句包含进来。
【例3-3】Java程序片综合应用示例—一个简单的计数器
3-3.jsp
<%@ page contentType="text/html;charset=gb2312"%>
<html>
<body>
<%!int counter=0;
synchronized void counterFunction()
{
counter++;
}
%>
<%counterFunction();%>
网站计数器<br>
您是第<%=counter%>位访问者
</body>
</html>
本例的代码是一个简单的计数器程序,它先后声明了一个计数器变量和计数的方法。计数器变量在客户间共享,直到服务器关闭;而计数方法只做简单的加1处理。Synchronized保留字表示串行化处理,即表示counterFunction()方法在某个时刻只能由一个语句调用。上述程序的其运行的结果如图3-3所示。
图3-3 Java程序片综合应用示例运行结果
Java语言标识符用来表示变量、方法或类等的名字。Java语言标识符的组成规则为:
(1)标识符必须以字母、下划线(_)或美元符($)开头,
(2)标识符第一个字符后面可以跟任意数目的字母、数字、下划线(_)或美元符($)。标识符的长度没有限制。
(3)在声明和使用标识符时需要注意,Java语言是大小写敏感的。
(4)标识符的命名应遵循Java编码惯例,并且应使标识符能从字面上反映出它所代表的变量或类型的用途,但不能是关键字。所有Java关键字都是小写的,true、false、null等都不是Java关键字;goto和const 虽然从未使用,但也作被为Java关键字保留;true、false、null虽用做专门用途,但不是Java关键字。
Java语言的关键字包括9大类:
(1)原始数据类型:byte、short、int、long、float、double、char、boolean。
(2)循环关键字:do、while、for、break、continue。
(3)分支关键字:if、else、switch、case、default、break。
(4)方法、变量和类修饰符:private、public、protected、final、static、abstract、synchronized、volatile、strictfp。
(5)异常处理:try、catch、finally、throw、throws。
(6)对象相关关键字:new、extends、implements、class、instanceof、this、super。
(7)字面值常量:false、true、null。
(8)方法相关关键字:return、void。
(9)包相关关键字:package、import。
(1)常量和变量
常量是指在程序运行过程中其值不变的量。常量有字面常量和符号常量两种。常量在表达式中用文字串表示,它区分不同类型,如整型常量123、-15,实型常量12.1f,字符常量' x ',布尔常量true,字符串类型常量"Test"。而符号常量声明的一般格式如下:
<final> <数据类型> <符号常量标识符>=<常量值>;
例如:final double PI=3.141593;
final int COUNT=1000;
变量是用来存放指定类型的数据,其值在程序运行过程中是可变的。按变量声明的位置Java的变量分为两种:类成员变量和局部变量。变量声明的一般格式如下:
<数据类型> <变量标识符>=<值>
例如 double x=1.2345;
(2)整型数据
Java整型数据多为十进制数形式,也可为八进制或十六进制形式。Java整型数都为带符号数。整型缺省为int型,若为长整型需在数据后加字母l或L。
按照长度分为:byte、short、int、long。int类型最为常用,可满足大多数场合应用的需要,如果需要更大的整数就可以使用long类型。byte类型范围很小,数据范围在-255到+255之间。short类型很少用,限制数据的存储为先高字节,后低字节,有时会引发错误,不提倡使用。
例如:byte b; short s;
int i; long l;
(3)实型数据
实型用十进制数形式表示,由数字和小数点组成,例如:3.25。也可以用科学计数法形式表示,例如,123E-3。数后加f或F为float,加d或D为double,没有后缀修饰的则缺省为double类型。
实型变量按长度分为:float和double。双精度浮点型double比单精度浮点型float的精度更高,表示数据的范围更大,详见表3-1。
例如,float c;
double d;
(4)字符型数据
字符型常量是用单引号括起来的一个字符,如:'J'、'*'。Java中的字符型数据是16位的Unicode字符,汉字和英文字母占的内存空间相同。如“JAVA你好”共12个字节。
变量的声明如下:
char ch='c';
Java中有以反斜杠(\)开头的字符,反斜杠将其后面的字符转变为另外的含义,称为转义字符。
表3-2 转义字符
【专家提示】字符串常量是使用双引号括起来的字符序列,注意,最后字符不是“\0”,这与C语言中不同。例如:"Let’s learn Java! "。Java中的字符串变量是作为对象来处理的,通过String和StringBuffer类的构造方法来声明,后续内容中还会有详细的介绍。
(5)布尔型数据
布尔型常量值只有2个:true和false。布尔型变量为boolean类型,取值为true或false。
例如:
boolean b=true;
(6)类型转换
整型、实型、字符型数据可以混合运算。运算过程中,不同类型的数据会自动转换为同一类型,然后进行运算。自动类型转换的规则是,低优先级的数据自动会转换为高优先级的数据。
而优先级高的数据类型要转换成优先级低的数据类型,需要用到强制类型转换。其一般形式为:
(类型名)表达式 或
类型名(表达式)
例如:double d=3.14159d;
int a=(int)d;
【例3-4】简单数据类型应用示例
3-4.jsp
<%@ page contentType="text/html;charset=gb2312"%><!--JSP指令标签-->
<%@ page import="java.util.*"%> <!--JSP指令标签-->
<html><!--HTML标记符-->
<body>
<%//以下为Java程序片
boolean booleanTemp=true;
byte byteTemp=100;
char charTemp='x';
int intTemp=234;
short shortTemp=456;
long longTemp=12345678;
int changlong=(int)longTemp;
float floatTemp=1.234F;
double changfloat=floatTemp;
double doubleTemp=1.23E-8;
out.println("布尔变量booleanTemp值为:"+booleanTemp+"<br>");
out.println("字符型变量charTemp值为:"+charTemp+"<br>");
out.println("整型变量intTemp值为:"+intTemp+"<br>");
out.println("短整型变量shortTemp值为:"+shortTemp+"<br>");
out.println("字节型变量byteTemp值为:"+byteTemp+"<br>");
out.println("长整型变量longTemp值为:"+longTemp+"<br>");
out.println("强制类型转化changlong 值为:"+changlong+"<br>");
out.println("单精度型变量floatTemp值为:"+floatTemp+"<br>");
out.println("自动类型changfloat值为:"+changfloat+"<br>");
out.println("双精度型变量doubleTemp值为:"+doubleTemp+"<br>");
%>
</body><!--HTML标记符-->
</html>
</body>
</html>
简单数据类型应用示例程序声明了几个简单数据类型,对其中的几个进行了自动转换和强制转化,并输出所有的值。其运行结果如图3-5所示。
图3-5 简单数据类型应用示例运行结果
数组是相同类型的数据元素按顺序组成的一种复合数据类型,元素在数组中的相对位置由下标来指定。数组中的每个元素通过数组名加下标进行引用。
(1)一维数组
一维数组的声明格式如下:
数组类型 数组名[ ] ;
或
数组类型[ ] 数组名;
数组类型可以是Java中的任何数据类型。数组名必须符合标识符声明规则。“[]”指明该变量是一个数组类型的变量,可放到数组名后面,也可放到数组名前。
在说明数组时,如果不直接指出数组中元素的个数(即数组长度),则在数组说明之后尚不能立即被访问,还需要使用new操作来构造数组,为数组元素分配内存空间,同时对数组元素进行初始化。其格式如下:
数组名=new类型[数组长度];
如:
int student[ ];
student=new int[10];
或
int student[]=new int[10];
数组初始化就是为数组元素指定初始值。用new关键字为一个数组分配内存空间后,系统将为每个数组元素都赋予一个缺省的初值,所有数值型数组元素的初值为0,字符型数组元素的初值为一个不可见的ISO控制符(\u000),布尔型数组元素的初值为false,字符串数组和所有其他对象数组在构造时元素的初值为null。数组一旦创建之后,就不能再改变其长度。但在许多情况下,并不希望数组的初始值为默认值,此时,就需要用赋值语句来对数组的每个元素赋值进行初始化。
数组的初始化还有一种方式是像初始化简单类型一样自动初始化数组,即在声明数组的同时进行初始化,这个时候不需要使用new关键字。例如
int a[ ]={1,2,3,4,5};
【专家提示】注意数组的下标的取值范围从0开始,一直到“数组的长度减1”。例如
int a[]=new int[10];
int b=a[0]+a[9];
数组下标为从0到9。如果调用了a[10],程序运行时将提示越界错误:
java.lang.ArrayIndexOutOfBoundsException
(2)二维数组
在Java中并不直接支持多维数组,所以,多维数组的声明是通过对一维数组的嵌套形式声明来实现的,即用“数组的数组”来声明多维数组。例如,二维数组为一个特殊的一维数组,其每个元素又是一个一维数组。二维数组的声明、初始化和引用与一维数组基本相同,只是二维数组比一维数组多了一个下标。例如:
int a[][]=new int[3][2];
int b[][]={{1},{2,3},{4,5,6}};
数组a被声明为一个3行2列的二维整数数组,并分配存储空间,所有的数据元素都初始化为0;数组b声明了一个3行3列的二维整数数组,并同时给每个数据元素赋初值。
【例3-5】数组应用示例
3-5.jsp
<%@ page contentType="text/html; charset=gb2312" %>
<html>
<head>
<title>
数组应用示例
</title>
</head>
<body>
<%
int a[][]={{1,2,3},{4,5,6},{7,8,9},{10,11,12}};
int b[][]=new int[3][4];
int i,j'
System.out.println("数组a各元素的值为:");
for (i=0;i<4;i++){
for(j=;j<3;j++)
System.out.print(a[i][j]+"\t");
System.out.println();
}
for(i=0;i<4;i++)
for(j=0;j<3;j++)
b[j][i]=a[i][j]; //矩阵转置
System.out.println("数组b各元素的值为:");
for(i=0;i<=3;i++){
for(j=0;j<4;j++)
System.out.print(b[i][j]+"\t");
System.out.println();
}
%>
<html>
<body>
上述代码声明了一个二维整数数组a,输出数组a的所有元素,然后把数组a转置成数组b,并输出数组b的所有元素。程序在浏览器中的运行结果如图3-6所示。
图3-6 数组应用示例运行结果
算术运算符用于对整型数和实型数的运算。如:+(加,取正值)、-(减,取负值)、++ (自加)、--(自减)以及+(加)、-(减)、*(乘)、/(除)、%(取余数或取模)。
【专家提示】其中运算符++、--可以位于操作数的前面,如++x 或--x,也可以位于操作数的后面,如x++、x--。无论运算符++、--位于操作数的前面或后面,操作数完成其运算并把结果赋于操作数变量。但是注意,如把++x或x++整体参加表达式运算时,表达式的运算结果可能不同。
后缀++:变量首先进行操作再自身进行加1运算。y= x++等价于:
y= x;
x=x+1;
例如:
int a=2;
int b=a++;
运算结果:a=3,b=2
前缀++:变量自身首先加然后再进行操作。y=++x等价于:
x=x+1;
y= x;
例如:
int a=2;
int b=++a;
运算结果:a=3,b=3
【例3-6】算术运算符应用示例
3-6.jsp
<%@ page contentType="text/html; charset=gb2312" %>
<html>
<head><title>算术运算符应用示例</title></head>
<body>
<%
int a=10;
out.println("a="+a+"<br>");
int b=++a;
out.println("a="+a+"<br>");
out.println("b="+b+"<br>");
int c=a++;
out.println("a="+a+"<br>");
out.println("c="+c+"<br>");
int d=--a;
out.println("a="+a+"<br>");
out.println("d="+d+"<br>");
int e=a--;
out.println("a="+a+"<br>");
out.println("e="+e+"<br>");
%>
</body>
</html>
上述程序代码对运算符++、--的前缀和后缀算术符的运算结果进行了比较,其运行结果如图3-7所示。
图3-7 算术运算符应用示例运行结果
布尔逻辑运算符有六个,它们是:!(非)、&(与)、&&(简洁与)、|(或)、||(简洁或)、^(异或)。这些运算符要求的操作数和结果值都是布尔型。
【专家提示】注意区别简洁与&&(或||)和非简洁与&(或|):&&,如果其前面的表达式为假,则不再计算其后面的表达式,整个表达式为false;而&,不管其前面的表达式为什么,都计算其后面表达式。例如,
int a=6,b=8,c=10,d=12;
boolean x=++a>b++&&c++>d--;
(a=7,b=9,c=10,d=12,x=false)
boolean x=++a>b++&c++>d--;
(a=7,b=9,c=11,d=11,x=false)
两个表达式x都为false,但它们最后的c、d的值不一样。
赋值运算符“=”用来把右边表达式的值赋给左边的变量,即将右边表达式的值存放在变量名所表示的存储单元中,这样的语句又叫赋值语句。赋值运算符使用的语法格式如下:
变量名=表达式;
【专家提示】赋值运算符“=”与数学的等号含义不同。
赋值运算符还包括很多变体,如+=,-=,*=,/=,%=等。
例如,int a=b=c=3;
a+=b;
等价于:
a=a+b;
包括if-else和 switch两种形式。
(1)if-else 结构
If-else 结构的控制逻辑如图3-8。语句的执行过程是:首先计算布尔表达式,若布尔表达式的值为true,则程序执行语句1,否则执行语句2,然后执行if语句的后续语句。
图3-8 if-else语句的流程图
if-else 结构选择控制语句其格式为:
if (条件) {
语句1;
}else{
语句2;
}
【专家提示】在使用if-else 结构选择控制语句应该注意:
else子句不能作为语句单独使用,它必须是if语句的一部分,与if配对使用。
语句1、语句2后一定要有“;”号。
语句1和语句2可以是复合语句。
if-else 结构选择控制语句可以嵌套使用,这时要注意花括号“{}”匹配问题,当分支较多时建议采用switch 结构,这样程序代码可读性更强一些。
(2)switch 结构
switch语句根据表达式的结果来执行多个可能操作中的一个,其控制逻辑如图3-9所示。语句的执行过程是:switch语句将表达式的值依次与每个case子句中的常量值相比较。如果匹配成功,则执行该case子句中常量值后的语句;如果没有找到,则执行default语句,直到遇到break语句为止退出switch语句。
图3-9 switch语句流程图
switch语句的语法形式如下:
switch (表达式){
case 常量1:
语句1
[break;]
case 常量2:
语句2
[break;]
…
case 常量n:
语句n
[break;]
[default: ]
缺省处理语句
[break;]
}
switch语句中的每个“case 常量n:”称为一个case子句,代表一个case分支的入口。
switch语句中表达式必须为byte,short,int或char类型,常量值必须是与表达式类型兼容的特定的一个常量,不允许有重复的case值。
通过if-else语句可以实现switch语句所有的功能。但通常使用switch语句更简练,且可读性强,程序的执行效率也高。
if-else语句可以基于一个范围内的值或一个条件来进行不同的操作,但switch语句中的每个case子句都必须对应一个单值。
【例3-7】选择控制应用示例
3-7.jsp
<%@ page contentType="text/html; charset=gb2312" %>
<html>
<head><title>
选择控制应用示例
</title></head>
<body>
2003年6月有多少天?
<%
int month = 6;
int year = 2003;
int numDays = 0;
switch (month) {
case 1:
case 3:
case 5:
case 7:
case 8:
case 10:
case 12:
numDays = 31;
break;
case 4:
case 6:
case 9:
case 11:
numDays = 30;
break;
case 2:
if((year%4 ==0)&&(year%100 ==0)||(year%400==0))
numDays = 29;
else
numDays = 28;
break;
}
out.print("<br/>");
out.println("答:2003年6月有 "+ numDays+"天。");
%>
</body>
</html>
上述程序代码通过switch语句根据给的年、月,输出该月的天数。其程序运行结果如图3-10所示。
图3-10 选择控制应用示例运行结果
循环语句的作用是反复执行同一段代码直到满足结束条件。许多问题中需要用到循环控制。Java循环语句有while、do-while和for三种。
(1)while 循环
while语句形式如下:
while (布尔表达式) <语句>;
其中:
while是while语句的关键字;布尔表达式是循环条件;语句为循环体,当循环体为多个语句时构成复合语句。
while语句执行的过程为:首先判断布尔表达式的值,当布尔表达式的值为true,则执行循环体,然后再判断条件,直到布尔表达式的值为false,停止执行语句。
【专家提示】使用while语句应注意以下两点:
(1)该语句是先判断后执行,若一开始条件就不成立,则不执行循环体。
(2)在循环体内一定要有改变条件的语句,否则是死循环。
while语句的流程图如图3-11所示。
图3-11 while语句的流程图
(2)do-while循环
Java还提供了另一个与while语句类似的语句——do-while语句。do-while语句的语法形式如下:
do
语句;
while(布尔表达式);
do-while语句执行的过程为:先执行一次循环体中的语句,然后测试布尔表达式的值,如果布尔表达式的值为true,则继续执行循环体。do-while语句将不断地测试布尔表达式的值并执行循环体中的内容,直到布尔表达式的值为false为止。do-while语句的流程图如图3-12所示。
图3-12 do-while 语句的流程图
do-while语句和while语句的不同之处:do-while语句总是先进入循环,然后检测条件,再决定是否继续循环,而while语句是先测试条件,再判定是否进入循环。所以,用do-while语句时,循环体至少执行一次。
(3)for 循环
图3-13 for语句的流程图
for语句是循环的另一种表示形式。for语句的语法形式为:
for(变量初始化表达式1;条件表达式2;对变量的值作递增或递减处理) {
语句组;
}
for是for语句的关键字,语句组为for语句的循环体。
for语句中循环控制变量必须是有序类型,常用的有整型、字符型、布尔型。循环控制变量初值和终值通常是与控制变量类型相一致的一个常量,也可以是表达式。循环次数由初值和终值决定。
for语句的执行过程如图3-13:
(1)按表达式1将初值赋给循环控制变量。
(2)按表达式2判断循环是否成立,即判断控制变量的值是否越过终值(未越过终值为条件成立,越过终值为条件不成立),若条件不成立,则转步骤(6)。
(3)若条件成立,则执行循环体。
(4)按表达式3修改控制变量。对于递增型为原控制变量值的后续;对于递减型为原控制变量值的前导。
(5)返回步骤(2)。
(6)结束循环。
【例3-8】循环应用综合示例
3-8.jsp
<%@ page contentType="text/html; charset=gb2312" %>
<html>
<head><title>循环应用示例
</title></head>
<body>
<%
int sum,i;
i = 1;
sum = 0;
while (i<=2000) {
if(i%2!=0)
sum+=i;
i++;
}
out.println("用while循环求2000内的奇数和:" +sum);
out.print("<br/>");
i = 1;
sum = 0;
do{
sum+=i;
i+=2;
}while (i<=2000);
out.println("用do-while循环求2000内的奇数和:"+sum);
out.print("<br/>");
for(sum=0,i=1;i<=2000;i+=2)
sum+=i;
out.println("用for循环求2000内的奇数和:"+sum);
out.print("<br/>");
%>
</body>
</html>
上述程序代码用while、do-while和for语句分别求2000内的奇数的和。从代码上看while和do-while语句查不多,只是do-while至少会执行一次,而while却不一定。for语句使用简单明了,使用灵活,经常被程序员所使用。其程序运行结果如图3-14所示。
图3-14 选择控制应用示例运行结果
类是一种复杂的数据类型,它描述了一类对象的属性和行为。类是对象的抽象,对象是类的实例化,要进行面向对象Java程序设计,先要定义类,然后创建对象,再使用对象。来看下面的代码示例。
Class MyDateClass {
private int day;
private int month;
private int year;
public MyDateClass(){
day = 1;
month = 1;
year = 1900;
}
public MyDateClass(int d,int m,int y){
day = d;
month = m;
year = y;
}
public void display(){
System.out.println(day + "-" + month + "-" + year);
}
public static void main(String args[]){
MyClass m1 = new MyDateClass();
MyClass m2 = new MyDateClass(25,12,2005);
m1.display();
System.out.println("m2 是 "+m2.year+"年");
m2.display();
}
}
类的定义要使用关键词class,类体包括数据和操作两部分。数据是类的成员变量,表示对象的属性;操作是类的成员方法,表示对象的行为。在上面的例子中定义了一个MyDateClass类,有day、month、year3个数据,以及MyDateClass()和display()方法,在main(String args[])方法中通过new声明和创建了两个实例对象m1、m2。在Java中所有的对象的内存空间都是动态分配的,所以要用new运算符声明和创建它们。对象的使用通过运算符“.”可以实现对象的成员变量和成员方法的使用,m1.display()和m2.display()是对成员方法的调用,而m2.year是对成员变量的访问。
从MyDateClass()、display()和main(String args[])方法,可以看出类的方法代码编写格式为:
返回的数据类型 方法名 ([参数列表]){
//方法体,实现对象的行为的代码
}
其中MyDateClass()和MyDateClass(int d,int m,int y)两个方法与类MyDateClass同名,称之为构造函数。构造函数名称与类名称相同,构造函数没有返回类型,其任务是在创建对象时初始化其内部状态。如果类中没有定义构造函数,则表示默认有一个无参数构造方法。MyDateClass()和MyDateClass(int d,int m,int y)两个方法同名,只是参数列表中参数个数和类型不同,这就是所谓的方法重载。
被继承的类称为父类,继承父类的类称为子类。执行继承时,子类将获得父类的属性,并具有自身特有的属性。extends关键字用于继承类,声明一个继承父类的类的通常形式如下:
class 子类名 extends 父类名 {
//子类程序代码
}
如果类的声明中在没有extends,则默认父类为Object。Java语言中子类只能有一个父类,具有单继承性,要用到多继承时可以通过后文中讲解的接口来实现。
class A{
int value = 3;
int getValue(){return value;}
}
class B extends A{
int value = 6;
int getValue() {return value;}
int getValue2(){return super.value;}
int getValue3(){return getValue();}
int getValue4(){return super.getValue();}
}
A类是父类,B类是子类,B类继承了A类的数据和方法,同时也增加了一个数据和四个方法。在B类中还用到了关键字super。super指这个对象的父类,关键字super可用于访问父类中定义的属性,调用父类中定义的成员方法,在子类构造方法中调用父类的构造方法。以上程序表示使用父类的构造方法生成实例,super必须是子类构造方法的第一条语句。
用abstract关键字来修饰一个类时,该类叫做抽象类;抽象类必须被继承。抽象类不能被直接实例化,它只能作为其它类的超类,这一点与最终类(final类)正好相反。
用abstract来修饰一个方法时,该方法叫做抽象方法。抽象方法只有声明,不能有实现。抽象方法必须被重写。定义了抽象方法的类必须是抽象类。下面是一个抽象类的例子。
abstract class Shape{
abstract float area( );
}
class Circle extends Shape{
public float r;
Circle(float r){
this.r = r; //this指“这个对象的”
}
public float area( ){
return 3.14*r*r;
}
}
抽象类Shape中声明了一个抽象方法area( ),该方法在子类中实现。
Java提供了丰富的标准类来帮助程序员更方便快捷地编写程序,这些标准类组成了类包,主要有:
(1)java.lang
本类包中的类构成Java语言核心的类,任何java程序都将自动引入这个包。其中的类包括:
Object类:java中最原始、最重要的类,每个java类都是它的子类,它实现了每个类都必须具有的基本方法。
基本数据类型的包装类:Boolean、Character、Number、Double、Float、Integer、Long。
String类:字符串类。
Math类:数学函数的集合。
执行线程:Thread类、ThreadGroup类、Runable接口。
异常和错误:Exception类、Error类、Throwable接口。
运行环境相关类:还可以通过Runtime类和System类访问外部系统环境。System类的两个常用功能就是访问标准输入/输出流和错误流、退出程序。
其他类:接口Cloneable、运行时的类等。
(2)java.applet
java.applet类包提供了Applet的运行机制以及一些编写Applet非常有用的方法。
(3)java.awt
此本类包是各种窗口环境的统一界面AWT(Abstract Windows Toolkit,即抽象窗口工具包),其中的类使得程序员创建诸如窗口、菜单、滚动条、文本区、按钮以及复选框等图形用户界面(GUI)的元素变得非常容易。
(4)java.awt.image
该类包中的类能够以独立于设备的方式加载并过滤位图图象。
(5)java.awt.peer
java.awt.peer是全部awt组件的对等对象接口的集合法,awt使用这些方法来实现GUI,而不必关心是何种机器或操作系统。
(6)java.io
Java的输入/输出模式是完全建立在流的基础之上的。流是一种字节从一个地方到另一个地方的单向流动,可以把流附加于文件、管道和通信链路等。java.io类包中定义的许多种流类通过继承的方式进行组织,其中也包括一些用来访问本地文件系统上的文件的流类。
(7)java.net
java.net类包用来完成与网络相关的功能:URL、WWW连接以及更为通用的Socket网络通信。
(8)java.util
java.util类包包含了一些实用类和有用的数据结构,如字典(Dictionary)、散列表(Hashtable)、堆栈(Stack)、向量(Vectro)以及枚举类(Enumeration)等。
所谓包装类,就是可以直接将简单类型的变量表示为一个类。Java共有六个包装类,分别是Boolean、Character、Integer、Long、Float和Double,从字面上我们就可以看出它们分别对应于简单数据类型中的 boolean、char、int、long、float和double。
简单类型的变量转换为相应的包装类,可以利用包装类的构造函数。即:
Boolean(boolean value)、Character(char value)、Integer(int value)、Long(long value)、Float(float value)、Double(double value)
而在各个包装类中,总有形为××Value()的方法,来得到其对应的简单类型数据。利用这种方法,也可以实现不同数值型变量间的转换,例如,对于一个双精度实型类,intValue()可以得到其对应的整型变量,而doubleValue()可以得到其对应的双精度实型变量。
在进行简单数据类型之间的转换(自动转换或强制转换)时,总是可以利用包装类进行中间过渡。一般情况下,首先声明一个变量,然后生成一个对应的包装类,就可以利用包装类的各种方法进行类型转换了。例如,当希望把float型转换为double型时:
float f1=100.00f;
Float F1=new Float(f1);
double d1=F1.doubleValue();
【例3-9】包装类示例
3-9.jsp
<%@ page contentType="text/html;charset=gb2312"%><!--JSP指令标签-->
<%@ page import="java.util.*"%> <!--JSP指令标签-->
<html><!--HTML标记符-->
<body>
<%//以下为Java程序片
boolean booleanTemp=true;
Boolean BooleanTemp=new Boolean(false);
byte byteTemp=80;
Byte ByteTemp=new Byte(byteTemp);
char charTemp='c';
Character CharacterTemp=new Character(charTemp);
int intTemp=234;
Integer IntegerTemp=new Integer(intTemp);
short shortTemp=235;
Short ShortTemp=new Short(shortTemp);
long longTemp=1234567;
Long LongTemp=new Long(longTemp);
float floatTemp=1.234F;
Float FloatTemp=new Float(floatTemp);
double doubleTemp=1.23E-8;
Double DoubleTemp=new Double(doubleTemp);
out.println("布尔型包装对象BooleanTemp值为:"+BooleanTemp.booleanValue()+"<br>");
out.println("字符型包装对象CharacterTemp值为:"+CharacterTemp.charValue()+"<br>");
out.println("整型变包装对象IntegerTemp值为:"+IntegerTemp.intValue()+"<br>");
out.println("短整型包装对象ShortTemp值为:"+ShortTemp.shortValue()+"<br>");
out.println("字节型包装对象ByteTemp值为:"+ByteTemp.byteValue()+"<br>");
out.println("长整型包装对象LongTemp值为:"+LongTemp.longValue()+"<br>");
out.println("单精度型包装对象FloatTemp值为:"+FloatTemp.floatValue()+"<br>");
out.println("双精度型包装对象doubleTemp值为:"+doubleTemp+"<br>");
%>
</body><!--HTML标记符-->
</html>
上述代码主要应用了包装类进行数据转换,其运行的结果如图3-15所示。
图3-15 包装类应用示例
String类用来表示字符串常量,用它创建的每个对象都是字符常量,一经建立就不能修改。创建String的一个对象并进行初始化,需要调用类String的构造方法,主要有以下创造方法:
String(String s):通过String对象或字符串常量传递给构造方法。
String(char value[]):将整个字符数组赋给String构造方法。
String(char value[],int offset,int count):将字符数组一部分赋给String构造方法,offset 为起始下标,count为字符数组从offset开始的字符个数。
String类的方法比较多,主要有以下几种:
(1)求字符串长度
public int length( ):返回字符串长度。
(2)字符串比较
public boolean equals(Object anObject):比较两个字符串对象,相等返回true,反之,返回false。
public int compareTo(String s):比较两个字符串的字典顺序,相等返回0,s大于当前串返回一个负值,s小于当前串返回一个正值。
public boolean startsWith(String prefix):从当前字符串的起始位置开始寻找字符串prefix。看当前字符串是否以prefix开头,如发现匹配,返回true,否则,返回false。
public boolean endsWith(String suffix):如当前字符串的结尾子串与suffix匹配,返回true,否则,返回false。
boolean regionMatches(int toffset,String other,int ooffset,int len):从当前字符串位置toffset开始寻找字符串other中起始位置为ooffset、长度为len的子串。如发现匹配,返回true,否则,返回false。
(3)字符串的检索和求子串
public char charAt(int index):返回当前字符串位置index处的字符。
public int indexOf(String str):在当前字符串中寻找与str匹配的子串,返回首次匹配的起始下标值,无匹配返回-1。
public String substring(int beginIndex,int endIndex):在当前字符串中,求从起始位置beginIndex到结束位置endIndex的子串。
【例3-10】String类字符串的检索和求子串应用示例
3-10.jsp
<%@ page contentType="text/html; charset=gb2312" %>
<html>
<head>
<title>String类字符串的检索和子串应用示例</title>
</head>
<body>
<%
String str="I like JSP programming!";
int il=str.indexOf('J');
String s1=str.substring(il);
String s2=str.substring(il,il+4);
int i2=str.lastIndexOf('J');
String s3=str.substring(i2-5);
out.println("s1="+s1);
out.print("<br/>");
out.println("s2="+s2);
out.print("<br/>");
out.println("s3="+s3);
%>
</body>
</html>
上述程序代码应用了String类的检索方法和求子串的方法,修改后的字符串存储在新的String类字符串对象中。该程序运行结果如图3-17所示。
图3-17 String类字符串的检索和求子串应用示例运行结果
(4)字符串的修改
public String toLowerCase():将当前字符串中的字符全部转换为小写形式。
public String toUpperCase():将当前字符串中的字符全部转换为大写形式。
public String replace(char oldChar,char newChar):将字符newChar替换当前字符串中所有的字符oldChar,并返回替换后的新字符串。
public String concat(String str):将当前字符串与str连接,返回连接后的字符串。
(5)字符串类与其他类型的转换
public Static String valueOf(type variable) 把variable转换为字符串。
public String toString():把一个对象转化为String类型。
【例3-11】String类字符串的综合应用示例
3-11.jsp
<%@ page contentType="text/html; charset=gb2312" %>
<html>
<head>
<title>
String类字符串的综合应用示例
</title>
</head>
<body>
<%
String s1="Hello Java!";
String s2="GOOD!";
out.println("字符串s1为:"+s1);
out.print("<br/>");
out.println("字符串s1的长度为:"+s1.length());
out.print("<br/>");
out.println("字符串s1的大写形式为:"+s1.toUpperCase());
out.print("<br/>");
out.println("字符串s2的小写形式为:"+s2.toLowerCase());
out.print("<br/>");
for(int i=0; i<s1.length(); i++){
out.println("s1中的第"+ i +"个字符是:"+s1.charAt(i));
out.print("<br/>");
}
out.println("s1+s2="+s1+s2);
out.print("<br/>");
if(s1.compareTo(s2)= =0)
out.println("字符串s1与s2相等");
else
out.println("字符串s1与s2不相等");
out.print("<br/>");
if(s1.indexOf(s2)!=-1){
out.println("字符串s2是s1的子串");
out.print("<br/>");
out.println("字符串s2在s1中的位置是:"+s1.indexOf(s2));
}
else
out.println("字符串s2不是s1的子串");
out.print("<br/>");
out.println("经过上述操作:");
out.print("<br/>");
out.println("字符串s1仍然为:"+s1);
out.print("<br/>");
out.println("字符串s2仍然为:"+s2);
%>
</body>
</html>
上述程序代码使用String类的修改方法对原有的字符串进行修改,修改后的字符串存储在新的String类字符串对象中,原有的字符串对象不变。其程序运行结果图3-18所示。
图3-18 String类字符串的综合应用示例
String类的实例用于表示不能改变的静态字符串,StringBuffer类的实例却用来代表动态可变的字符串。StringBuffer类还可以用于创建String类,StringBuffer一旦建立,即可用toString()方法将其转换为String类。
StringBuffer类的构造函数有以下的形式:
(1)StringBuffer():创建默认的空字符串对象,缓冲区大小为16个字符。
(2)StringBuffer(int len):建立缓冲区大小为len的字符串对象。
(3)StringBuffer(String str):创建一个初始化为str的字符串对象。
StringBuffer类的主要方法有如下的几类:
(1)分配/获取字符串的长度
void setLength(int newLength):指定字符串的长度。
int length():返回缓冲区的字符个数,即字符串的长度。
(2)分配/获取字符串的容量
void ensureCapacity(int minCapacity):分配字符缓冲区的大小,分配的字符缓冲区的容量至少为指定的值minCapacity。如果当前的缓冲区容量小于minCapacity,则应该分配较大的缓冲区。
int capacity():返回缓冲区剩余空间。
【专家提示】长度length和容量capacity是两个不同的概念,长度是指StringBuffer类对象中包含字符的个数,而容量是只缓冲区的大小。
(3)字符串的检索和子串
void getChars(int srcBegin,int srcEnd,char[] dst,int dstBegin):将StringBuffer对象字符串中的字符复制到目标数组中去。
public String substring(int beginIndex,int endIndex):当前StringBuffer类字符串中,求从起始位置beginIndex到结束位置endIndex的子串。
(4)字符串的修改
public synchronized StringBuffer append(type variable):把variable转换为字符串,然后与当前字符串连接。
StringBuffer insert(int index,String str):将一个字符串str从当前字符串的index位置开始插入到当前StringBuffer类字符串对象中。
StringBuffer delete(int starts,int end):当前StringBuffer类字符串从starts开始到end-1位置删除子字符串,并返回当前StringBuffer类字符串。
StringBuffer deleteCharAt(int index):删除当前StringBuffer类字符串index处的字符。
StringBuffer reverse():对StringBuffer类字符串进行翻转。
StringBuffer replace(int starts,int end,String str):将字符串str替代当前缓冲字符串的starts开始到end-1位置的子字符串。
void setCharAt(int index,char ch)设置指定位置index处的字符为ch。
(5)字符串类型转换
toString():把StringBuffer转换为字符串String。
【例3-12】Stringbuffer类字符串的综合应用示例
3-12.jsp
<%@ page contentType="text/html; charset=gb2312" %>
<html>
<head>
<title>
Stringbuffer类字符串的综合应用示例
</title>
</head>
<body>
<%
StringBuffer s=new StringBuffer("Hello");
out.println("s:"+s);
out.print("<br/>");
out.println("字符串s的长度:" +s.length());
out.print("<br/>");
out.println("字符串s的容量:"+ s.capacity());
out.print("<br/>");
s.append("Stringbuffer");
out.println("向字符串s中加入了字符串Stringbuffer:"+s);
out.print("<br/>");
out.println("字符串s的位置4-5的子串:"+s.substring(3,5));
out.print("<br/>");
out.println("字符串s的第6个字符:"+s.charAt(5));
out.print("<br/>");
out.println("将字符串的位置6-15的子串替换为java:"+s.replace(5, 16, "java"));
%>
<html>
<body>
上述程序代码首先创建初始化了的StringBuffer对象s,然后显示这个字符串,求其容量和长度,然后在字符串s后追加一个字符串“lo”,并用一个新的字符串替代s的子字符串。其运行结果如图3-19所示。
图3-19 Stringbuffer类字符串的综合应用示例运行结果
在Java中日期型数据处理用Date类,即日期类。Date类构造函数的常用的有四种形式:
(1)Date()
(2)Date(int year, int month, int date):以int型表示年、月、日。
(3)Date(int year, int month, int date, int hrs, int min):以int型表示年、月、日、时、分。
(4)Date(int year, int month, int date, int hrs, int min, int sec):以int型表示年、月、日、时、分、秒。
在长整型和Date类之间有一个对应关系,就是将一个时间表示为距离格林尼治标准时间1970年1月1日0时0分0秒的毫秒数。对于这种对应关系,Date类也有其相应的构造函数:Date(long date)。
获取Date类中的年、月、日、时、分、秒以及星期读者可以使用Date类的getYear()、getMonth()、getDate()、getHours()、getMinutes()、getSeconds()、getDay()方法。
而Date类的getTime()方法可以得到前文中所说的一个时间对应的长整型数,与包装类一样。此外,Date类也有一个toString()方法可以将其转换为String类。
【例3-13】日期型数据处理示例
3-13.jsp
<%@ page import="java.util.*" %>
<%@ page contentType="text/html; charset=gb2312" %>
<HTML>
<head><title>日期型数据处理示例</title></head>
<BODY>
你好,今天是
<%
Date today=new Date();
%>
<%=today.getDate()%>号,
星期<%=today.getDay()%>
<h1>现在是北京时间: <%=today.toString() %>
</h1>
</BODY>
</HTML>
上述程序代码创建一个Date类的对象,用getDate()方法获取对象的时间,并getDay()求当天的日期。其程序运行结果如图3-20所示。
图3-20 日期型数据处理示例运行结果
“<%@”和“%>”之间为JSP指令,常用下列三种指令:
<%@ page import="java.util.*" language="java" session="true" %>
<%@include file="包含文件的URL"%>
<%@taglib uri="URIToTagLibrary" prefix="tagPrefix"%>
其中:JSP指令的关键词page、include、taglib赋予指令不同类型的语义。每个JSP指令的关键词后面是“KEY=VALUE”形式的值对,其中KEY是属性名,VALUE是字符串类型的属性值。这里我们介绍最为常用的page和nclude指令,taglib指令在后面的章节还会进行讲解。
page指令用于定义JSP页面中的全局属性。<%@page%>指令作用于整个JSP页面,包括被静态包含的文件,但是<%@page%>指令不能作用于被动态包含的文件。page指令语法格式如下:
<%page [language="java"] [extends="package.class"]
[import="{package.class │package.*}, … …"] [session="true │false"]
[buffer="none│8kb│sizekb"] [isThreadSafe="true │false"]
[info="text"] [errorPage="relativeURL"]
[contentType="mimeType[;charset=characterSet]"│"text/html;charset=ISO-8859-1"]
[isErrouPage="true│false"]
%>
在一个页面中可以使用多个<%@page%>指令,除import属性以外的其它属性只能设置一次。页面中的import属性和Java中的import语句类似,可以多次设置。JSP页面指令是一种功能比较复杂的指令,这主要与它所要支持的繁多的属性有关。下面对page指令中可以包含的各个属性进行详细说明。
(1)language
language属性告诉服务器在文件中将采用哪种编程语言作为脚本语言,默认为Java。
(2)extends
extends 属性定义了由JSP 页面产生的servlet 的父类。一般来说,这个属性不会用到,只有需要实现一些特殊功能时才会用到。
(3)import
import属性用来声明输入引用的Java包(package)或(class) 。这是唯一可以在同一个JSP页面中重复出现的属性设置。
import属性的值是一系列用逗号分开的列表,指明想要引入的包和类。例如:
<%@ page import="java.util.*" %>
可以在程序中引入多个需要的包和类,包和类之间用逗号分隔。例如:
<%@ page import="java.util.*,java.io.*" %>
也可以将需要引入的多个包和类写在多个page指令中。例如,上面的语句等价于:
<%@ page import="java.util.*" %>
<%@ page import=" java.io.*" %>
当使用import引入了包和类后,就可以使用与这些包和类相关的方法和对象了。
【专家提示】java.lang.*、javax.servlet.*、javax.servlet.jsp.*和javax.servlet.http.*包已经作为缺省值被JSP引入,所以不需要在网页中再次引入。
(4)session
session 属性表示当前页面是否加入会话期间的管理,其缺省值为true, 表明内建对象session存在(如果没有session对象,则会新建一个),由session对象来管理会话。如果设session属性值为false,则内建对象session不存在,这样页面中任何使用到session 的语句都会产生编译错误。
(5)buffer
buffer决定输出流(out 对象)是否需要缓冲区,缺省值是8KB, 也可以设置为none(没有缓冲区)或所指定大小的缓冲区,例如:
<%@page buffer ="12kb" %>
上面的语句指定输出流缓冲区为12KB。
buffer 属性通常与autoFlush属性配合使用。
(6)autoFlush
autoFlush属性用于指定是否自动刷新输出缓冲,如果设成true,则当输出缓冲区满的时候,自动刷新缓冲区而不是抛出一个异常,缺省值为true。
【专家提示】同时设置autoFlush为false和buffer为none是不合法的,编译时将会出错。
(7)isThreadSafe
isThreadSafe属性指示JSP 引擎,网页在处理对象存取时是否要引入ThreadSafe( 线程保护)机制,缺省值为true,此时如果多个客户向JSP 引擎发送请求,就可以同时被处理。这时JSP 程序员要处理同步时的共享状态,以保证同步确实是安全的。如果isThreadSafe 被设成false,则采用单线程模式控制客户端访问该页面。
(8)info
info属性用于设置页面的说明信息文字,可以通过页面的Servlet.getServletInfo() 方法访问该信息。例如:
<%@page info="JSP 教程" %>
(9)ErrorPage
ErrorPage 属性用于指示一个JSP 文件的相对路径,以便在页面出错时,转到这个JSP 文件来进行处理。相应的,需要将这个JSP 文件的isErrorPage 属性设为true。
当ErrorPage 属性被设置后,JSP 网页中的异常仍然会产生,只不过此时捕捉到的异常将不由当前网页进行处理,而是由 ErrorPage 属性所指定的网页去进行处理。
(10)isErrorPage
isErrorPage 属性指示一个页面是否为错误处理页面。设置为true 时,在这个JSP 页面中的内建对象exception 将被定义,其值将被设定为呼叫此页面的JSP 页面的错误对象,以处理该页面所产生的错误。isErrorPage 属性缺省值为false,此时将不能使用内建对象exception来处理异常,否则将产生编译错误。
(11)contentType
contentType属性用于设置JSP 文件和最终文件的MIME 类型和字符集的类型。这一项必须在文件的顶部。可用的MIME类型有text/plain、text/html、text/html 和image/gif、image/jpeg等。contentType属性缺省值为“text/html;charset = ISO8859_1”,为了让JSP页面可以正常地显示中文需要设置为“text/html;charset = GBK”或“text/html;charset =gb2312”。
include指令指定在JSP文件中包含的一个静态的文件,即在JSP文件被编译时需要插入的文本或代码。include指令语法格式如下:
<%@include file="relativeURL"% >
如果这个路径以“/”开头,那么这个路径主要参照JSP应用的上下文关系路径,如果路径是以文件名或目录名开头,那么这个路径就是正在使用JSP文件的当前路径。
<%@include %>指令将会在JSP编译时输入一个包含文本或代码的文件,当使用 <%@include %> 指令时,包含的过程是静态的。静态的包含就是指这个被包含的文件将会被导入到JSP文件中去,这个被包含的文件可以是JSP文件、HTML 文件、文本文件。如果被包含的是JSP文件,这个被包含JSP的文件的代码将会被执行。
【专家提示】包含文件可以是html文件、jsp文件、文本文件、或者只是一段Java代码,但是注意在这个包含文件中不能使用<html>、</html>、<body>、</body>标志,因为这将会影响在原 JSP文件中同样的标志,这样做有时会导致错误。
在JSP页面中,包含指令最常见的用途就要数网页导航条了,由于一个网站的主导航条栏往往是统一的,所以主导航栏一般被写成一张单独的网页,然后通过框架结构与其他网页拼接。由于框架可能带来的诸多的不便。如用户难以对框架内网页做书签等,包含指令就成了实现导航栏的最佳机制。
【例3-14】include指令应用示例
3-14.jsp
<%@page contentType="text/html;charset=GB2312"%>
<html>
<head>
<title>JSP指令示例</title>
</head>
<body>
<h1>**金字塔**</h1>
<%@ include file="Ta.jsp" %>
</body>
</html>
Ta.jsp源程序如下,功能是输出一个数字构成的金字塔。
<% out.print("<pre>");
int i=7,j,k;
for(j=1;j<i;j++){
for(k=0;k<2*(i-j-1);k++)
out.print(" ");
for(k=0;k<j;k++)
out.print(j+" ");
%>
<P></P>
<%
}
out.print("</pre>");
%>
这里将两个程序放在同一目录下面,3-14.jsp包含Ta.jsp,实现了静态文件的插入。include指令应用示例运行结果如图3-21所示。
图3-21 include指令应用示例
第4章 隐含对象解析
【本章专家知识导学】
JSP提供了一些隐含对象,以方便在JSP页面中快速方便地编写程序,这些对象不必再用类来声明建立类的实例,可以直接使用。本章旨在引导读者学习运用request、response、session、application、pageContext、out、page和pageContext等这些隐含的对象来进行JSP编程。
4.1 隐含对象概述
JSP隐含对象是Web容器加载的一组类的实例,它不同于一般的Java类的实例那样要用“new”关键字去获取实例,而是可以直接在JSP页面使用的对象。
JSP提供的9个隐含对象分为4个主要类别,如图7-1所示:
与“I/O”(input/output,输入/输出)有关的隐含对象:用于控制页面的输入和输出,包含request、response、out。
JSP执行时,提供有关上下文(Context)有关的隐含对象:用于控制JSP页面之间的信息传递,包含session、application、pageContext。
与Servlet有关的隐含对象:提供有关页面环境的信息,包含page、config。
与Error有关的隐含对象:处理页面中的错误的exception。
图 7-1 JSP隐含对象
request对象包含所有请求信息,如请求的来源、请求头信息、cookies和请求相关的参数值等等。在JSP网页中,request对象是实现了javax.servlet.http.HttpServletRequest接口的实例。
request对象主要用于取得客户在表单中提交的数据信息。request对象常用的取得客户表单中提交的数据信息的方法有:
String getParameter(String name):根据页面表单域的名称取得请求页面提交到服务端的数据。例如,在requestDemo.jsp页面中有一个输入用户名称的文本框,它的名称是“name”,页面提交请求到页面requestDemo1.jsp,那么在requestDemo1.jsp中可以利用语句request.getParameter("name")来获取用户输入的用户名。
String[] getParameterValues(String name):获取页面请求中一个表单域对应多个值的用户请求数据。例如,在requestDemo.jsp页面中有一个兴趣爱好选择的复选框,用户可以选择多项内容,复选框的名称都是“interest”,它提交请求到页面requestDemo1.jsp,那么在requestDemo1.jsp中可以利用语句request.getParameterValues("interest")来获取用户选择的多个兴趣爱好。
public void setCharacterEncoding(java.lang.String env):设置请求对象的字符集编码,此方法必须在取得request对象参数的值前调用。
【例4-1】取得请求中的参数值
requestDemo.jsp
<%@ page contentType="text/html; charset=GBK" %>
<html>
<head>
<title>
请求参数输入页面
</title>
</head>
<body bgcolor="#ffffff">
<h1>
请求参数输入页面
</h1>
<form action="requestDemo1.jsp" method="POST">
<table border="1">
<tr>
<td>用户名:</td>
<td><input type="text" name="name"/></td>
</tr>
<tr>
<td>性别:</td>
<td>
<input type="radio" name="sex" value="男"/>男
<input type="radio" name="sex" value="女"/>女
</td>
</tr>
<tr>
<td>兴趣爱好:</td>
<td>
<input type="checkbox" name="interest" value="上网"/>上网
<input type="checkbox" name="interest" value="旅游"/>旅游
<input type="checkbox" name="interest" value="阅读"/>阅读
</td>
</tr>
</table>
<input type="submit" name="submit" value="提交"/>
</form>
</body>
</html>
requestDemo1.jsp
<%@ page contentType="text/html; charset=GBK" %>
<%@ page import="java.util.*" %>
<html>
<head>
<title>
接收请求参数
</title>
</head>
<body bgcolor="#ffffff">
<h1>
接收客户端请求数据
</h1>
<%
request.setCharacterEncoding("GBK");
String name=request.getParameter("name");
String sex=request.getParameter("sex");
String[] interest=request.getParameterValues("interest");
%>
<table border="1">
<tr>
<td>用户名:</td>
<td><%=name%></td>
</tr>
<tr>
<td>性别:</td>
<td><%=sex%></td>
</tr>
<tr>
<td>兴趣爱好:</td>
<td>
<%
for (int i = 0; i < interest.length; i++) {
out.print(interest[i]);
out.print(",");
}
%>
</td>
</tr>
</table>
</body>
</html>
requestDemo.jsp运行结果如下图所示。
图4-1 requestDemo.jsp显示结果
输入用户名、性别和兴趣爱好等信息后,点击“提交”按钮后将数据信息提交到requestDemo1.jsp页面,运行结果如下图所示。
图4-2 requestDemo1.jsp显示结果
【专家提示】requestDemo1.jsp在接收处理中文信息时,采用了方法request.setCharacterEncoding("GBK")进行编码转换,但这种方式只是当requestDemo.jsp表单提交方式为method="post"方式才有效。如果表单的提交方式为method="get"方式requestDemo1.jsp代码应该改写为如下所示。
requestDemo1.jsp
<%@ page contentType="text/html; charset=GBK" %>
<%@ page import="java.util.*" %>
<html>
<head>
<title>
接收请求参数
</title>
</head>
<body bgcolor="#ffffff">
<h1>
接收客户端请求数据
</h1>
<%
//request.setCharacterEncoding("GBK");
String name=request.getParameter("name");
String sex=request.getParameter("sex");
String[] interest=request.getParameterValues("interest");
//对GET请求的额外处理
name=new String(name.getBytes("ISO-8859-1"),"GBK");
sex=new String(sex.getBytes("ISO-8859-1"),"GBK");
for (int i = 0; i < interest.length; i++) {
interest[i]=new String(interest[i].getBytes("ISO-8859-1"),"GBK");
}
%>
<table border="1">
<tr>
<td>用户名:</td>
<td><%=name%></td>
</tr>
<tr>
<td>性别:</td>
<td><%=sex%></td>
</tr>
<tr>
<td>兴趣爱好:</td>
<td>
<%
for (int i = 0; i < interest.length; i++) {
out.print(interest[i]);
out.print(",");
}
%>
</td>
</tr>
</table>
</body>
</html>
参见代码中的如下语句:
String name=request.getParameter("name");
name=new String(name.getBytes("ISO-8859-1"),"GBK");
这两句就将字符的编码方式作了转换,这样中文字符就可以正确地显示了。
有时需要访问一组网络资源(如其他Servlet和JSP页面)以满足客户端请求,JSP可以通过请求URL对Web服务器上的活动资源作出请求。Servlet API规范支持在服务器中运行的Servlet直接访问服务器的资源。JSP和Servlet可以将请求转发到其他资源,或包括资源在其响应中创建的响应。属于同一个Web应用程序的JSP或Servlet可以使用javax.servlet.RequestDispatcher接口的forward和include方法来共享数据
有时需要访问一组网络资源(如其他Servlet和JSP页面)以满足客户端请求,JSP可以通过请求URL对Web服务器上的活动资源作出请求。Servlet API规范支持在服务器中运行的Servlet直接访问服务器的资源。JSP和Servlet可以将请求转发到其他资源,或包括资源在其响应中创建的响应。属于同一个Web应用程序的JSP或Servlet可以使用javax.servlet.RequestDispatcher接口的forward和include方法来共享数据。
RequestDispatcher getRequestDispatcher(String path):取得一个相对于当前路径的请求转发器,以便于请求转发。
void forward(SerletRequest request,ServletResponse response):用于将请求从一个JSP或Servlet转发到同一服务器上的另一个JSP或Servlet。
void include(SerletRequest request,ServletResponse response):用于包括另一个Servlet的内容。
【例4-2】利用forward()方法作转移控制
接下来介绍使用forward()在JSP或Servlet间转移控制的方法。将例4-1的requestDemo.jsp更改代码为requestDemo2.jsp。将requestDemo1.jsp修改代码保存为requestDemo3.jsp,新生成一个dispatcherDemo.jsp。代码分别如下:
requestDemo2.jsp
<%@ page contentType="text/html; charset=GBK" %>
<html>
<head>
<title>
请求参数输入页面
</title>
</head>
<body bgcolor="#ffffff">
<h1>
请求参数输入页面
</h1>
<form action="requestDemo3.jsp" method="POST">
<table border="1">
<tr>
<td>用户名:</td>
<td><input type="text" name="name"/></td>
</tr>
<tr>
<td>性别:</td>
<td>
<input type="radio" name="sex" value="男"/>男
<input type="radio" name="sex" value="女"/>女
</td>
</tr>
<tr>
<td>兴趣爱好:</td>
<td>
<input type="checkbox" name="interest" value="上网"/>上网
<input type="checkbox" name="interest" value="旅游"/>旅游
<input type="checkbox" name="interest" value="阅读"/>阅读
</td>
</tr>
</table>
<input type="submit" name="submit" value="提交"/>
</form>
</body>
</html>
requestDemo3.jsp
<%@ page contentType="text/html; charset=GBK" %>
<%@ page import="java.util.*" %>
<%@ page import="javax.servlet.*" %>
<html>
<head>
<title>
接收请求参数
</title>
</head>
<body bgcolor="#ffffff">
<h1>
接收客户端请求数据
</h1>
<%
request.setCharacterEncoding("GBK");
String name=request.getParameter("name");
String sex=request.getParameter("sex");
String[] interest=request.getParameterValues("interest");
%>
<table border="1">
<tr>
<td>用户名:</td>
<td><%=name%></td>
</tr>
<tr>
<td>性别:</td>
<td><%=sex%></td>
</tr>
<tr>
<td>兴趣爱好:</td>
<td>
<%
for (int i = 0; i < interest.length; i++) {
out.print(interest[i]);
out.print(",");
}
%>
</td>
</tr>
</table>
<%
//转发之前不能使用out.flush()或out.close()向客户端提交响应
//forward转发
RequestDispatcher rd=request.getRequestDispatcher("dispatcherDemo.jsp");
rd.forward(request,response);
%>
</body>
</html>
requestDemo3.jsp页面在接收requestDemo2.jsp页面的请求参数后进行forward转发至dispatcherDemo.jsp页面。
dispatcherDemo.jsp
<%@ page contentType="text/html; charset=GBK" %>
<html>
<head>
<title>
dispatcher转发结果显示页面
</title>
</head>
<body bgcolor="#ffffff">
<h1>
dispatcher转发结果显示页面
</h1>
name参数的值是:<%=request.getParameter("name")%>
</body>
</html>
dispatcherDemo.jsp继续从request对象中取得客户端请求参数“name”。requestDemo2.jsp运行显示结果如图4-3所示。
图4-3 requestDemo2运行显示结果
填写数据点击“提交”按钮后,requestDemo3.jsp运行显示结果如图4-4所示。
图4-4 requestDemo3.jsp运行显示结果
requestDemo3.jsp并没有显示我们代码编写显示请求数据的表格,仅仅只显示dispactherDemo.jsp页面的内容。读者可以注意到IE地址栏URL并没有发生改变用户虽然请求的网络资源是requestDemo3.jsp,但实际上给客户端的响应是由dispatcherDemo.jsp呈现的,说明页面响应控制权已经完全移交给dispactherDemo.jsp。
【专家提示】调用forward()方法时必须注意下列两点:
1.调用forward()方法后,原先存放在HttpResponse对象中的内容将会自动被清除,在此之前requestDemo3.jsp再多的输出也不会在页面显示输出。
2.在HTTP回应被“确认”(committed)以前才能调用forward()方法(这里的“确认”是指将HTTP回应的内容主体送回用户端),如果在代码注释处所说明的使用out.flush()或out.close()方法,否则将拋出IllegalStateException异常。
【例4-3】用include()在一个JSP包含另一个JSP或Servlet的响应输出
将例4-2中的requestDemo3.jsp改写成如下的代码。
requestDemo3.jsp
<%@ page contentType="text/html; charset=GBK"%>
<%@ page import="java.util.*"%>
<%@ page import="javax.servlet.*"%>
<html>
<head>
<title>接收请求参数</title>
</head>
<body bgcolor="#ffffff">
<h1>接收客户端请求数据</h1>
<%
request.setCharacterEncoding("GBK");
String name = request.getParameter("name");
String sex = request.getParameter("sex");
String[] interest = request.getParameterValues("interest");
%>
<table border="1">
<tr>
<td>用户名:</td>
<td><%=name%></td>
</tr>
<tr>
<td>性别:</td>
<td><%=sex%></td>
</tr>
<tr>
<td>兴趣爱好:</td>
<td>
<%
for (int i = 0; i < interest.length; i++) {
out.print(interest[i]);
out.print(",");
}
%>
</td>
</tr>
</table>
<%
//forward转发
//RequestDispatcher rd=request.getRequestDispatcher("dispatcherDemo.jsp");
//rd.forward(request,response);
//include转发
out.flush();
RequestDispatcher rd = request
.getRequestDispatcher("dispatcherDemo.jsp");
rd.include(request, response);
%>
<h2>转发后的页面输出:我还是有主权的</h2>
</body>
</html>
图4-5 requestDemo2运行显示结果
填写数据点击“提交”按钮后,requestDemo3.jsp运行显示结果如图所示。
图4-6 requestDemo3运行显示结果
RequestDispatcher接口的include()方法与forward()方法非常类似,惟一的不同在于:利用include()方法将HTTP请求转送给其他JSP页面后,被调用的JSP页面虽然可以处理这个HTTP请求,但是最后的主导权仍然是在原来的JSP页面,如requestDemo3.jsp输出处理requestDemo2.jsp请求数据信息结果后,显示dispatcherDemo.jsp页面要求输出的内容,最后仍然由requestDemo3.jsp掌握主动输出权。换言之,被调用的JSP页面如果产生任何HTTP回应,将会并入原来的HttpResponse对象。
request对象还有其他一些的常用方法来取得客户端请求头中的一些详细信息,如:
String getRemoteAddr() 取得客户端访问IP。
String getRequestURL() 取得客户端请求的URL串。
Enumeration getHeaderNames() 取得所有的请求头名。
String getHeader(String head) 取得指定的请求头信息的值。
其它更多方法使用详情请查看API帮助。
【例4-4】得到请求头信息
接下一起来编写一个例子演示request对象取得请求头信息的几个方法的使用,为了简单起见我们直接修改requestDemo3.jsp的代码。
requestDemo3.jsp
<%@ page contentType="text/html; charset=GBK"%>
<%@ page import="java.util.*"%>
<%@ page import="javax.servlet.*"%>
<html>
<head>
<title>接收请求参数</title>
</head>
<body bgcolor="#ffffff">
<h1>接收客户端请求数据</h1>
<%
request.setCharacterEncoding("GBK");
String name = request.getParameter("name");
String sex = request.getParameter("sex");
String[] interest = request.getParameterValues("interest");
%>
<table border="1">
<tr>
<td>用户名:</td>
<td><%=name%></td>
</tr>
<tr>
<td>性别:</td>
<td><%=sex%></td>
</tr>
<tr>
<td>兴趣爱好:</td>
<td>
<%
for (int i = 0; i < interest.length; i++) {
out.print(interest[i]);
out.print(",");
}
%>
</td>
</tr>
</table>
<%
//forward转发
//RequestDispatcher rd=request.getRequestDispatcher("dispatcherDemo.jsp");
//rd.forward(request,response);
//include转发
out.flush();
RequestDispatcher rd = request
.getRequestDispatcher("dispatcherDemo.jsp");
rd.include(request, response);
%>
<h2>转发后的页面输出:我还是有主权的</h2>
<b>HTTP请求方式:</b><%=request.getMethod()%><br>
<b>客户端IP:</b><%=request.getRemoteAddr()%><br>
<b>请求的URL是:</b><%=request.getRequestURL()%><br>
<b>更多关于请求对象的信息:<br>
<%
Enumeration em=request.getHeaderNames();
while(em.hasMoreElements()){
String header=(String)em.nextElement();
out.print("<b>");
out.print(header);
out.print(":<b>");
out.print(request.getHeader(header)+"<br>");
}
%>
</body>
</html>
运行显示结果如图所示。
图4-7 requestDemo3.jsp运行显示结果
response对象主要将JSP处理数据后的结果传回到客户端。response对象是实现javax.servlet.http.HttpServletResponse接口。response的方法有很多,下面列出几个常用的方法。
void setContentType (String name) 设置作为响应生成的内容的类型和字符编码。
void sendRedirect (String name) 发送一个响应给浏览器,指示其应请求另一个URL。
void setHeader(String name,String value) 设置HTTP响应的参数使客户自动产生相应的动作。
设定response对象的contentType属性可以指定JSP页面生成响应MIME的类型和JSP文件的字符编码方式,它们都是最先传送给客户端。图4-7中的accept描述指明当前客户端浏览器所能支持的MIME类型。
【专家提示】MIME的含义是“多用途的网际邮件扩充协议”。它不是一种邮件传输协议,相反,它定义传输的内容:消息的格式、附件等。许多文档都定义了MIME协议,包含:RFC 822、RFC 2045、RFC 2046和RFC 2047。作为一般程序员,一般不需要担心这些格式。但是,这些格式确实存在,并为您的程序所用。例如:text/plain表示输出内容是未格式化的文本文档(txt)格式的, 当MIME的包头是text/plain时,浏览器将直接显示而不关心它的什么字体,颜色之类的参数;image/gif或image/jpeg内容将以图片的形式显示。
【例4-5】将JSP页面显示成Excel表单
一起来看看如何通过设置response对象的contentType属性将一个JSP页面显示成为一个EXCEL表单的形式。
responseExcel.jsp
<html>
<head>
<title>显示成为EXCEL报表</title>
</head>
<body>
<h1>
将HTML表格数据,显示成为EXCEL表格
</h1>
<%
response.setContentType("application/vnd.ms-excel");
%>
<table border="1">
<tr>
<td>用户名</td>
<td>李四</td>
</tr>
<tr>
<td>性别</td>
<td>女</td>
</tr>
<tr>
<td>兴趣</td>
<td>
上网,阅读,旅游
</td>
</tr>
</table>
</body>
</html>
responseExcel.jsp运行结果如图4-8所示。
图4-8 IE浏览器的提示框
点击“打开”按钮,运行结果如图4-9所示。
图4-9 页面显示结果
【例4-6】生成图形验证码
本例通过设置response对象的contentType属性为“image/jpeg”以图片的形式显示一个随机验证码。login.jsp和verifycode.jsp代码分别如下。
login.jsp
<%@ page contentType="text/html; charset=GBK" %>
<html><head>
<title>用户登录页面</title>
</head>
<body bgcolor="#ffffff">
<h1>
请输入用户登录信息
</h1>
<form name="userinfo" action="checklogin.jsp"
method="POST" onsubmit="return(checkinfo())">
<table border="1">
<tr>
<td>用户名:</td>
<td><input type="text" name="username"/></td>
<td><a href="register.jsp">用户注册</a></td>
</tr>
<tr>
<td>密码:</td>
<td><input type="password" name="password"/></td>
<td><a href="forgetpassword.jsp">忘记密码了</a></td>
</tr>
<tr>
<td>验证码:</td>
<td><input type="text" name="verifycode"/></td>
<td><img alt="" src="verifycode.jsp" /></td>
</tr>
<tr>
<td><input type="submit" value="登录"/></td>
<td><input type="reset" value="重置"/></td>
<td></td>
</tr>
</table>
</form>
<script >
function checkinfo(){
var oObject = document.all.item("userinfo");
for (i = 0; i < oObject.length; i++){
if (oObject(i).value.length==0) {
alert("必填项的值不能为空");
oObject(i).focus();
return false;
}
}
return true;
}
</script>
</body>
</html>
verifycode.jsp
<%@ page import="java.awt.*,java.awt.image.*,java.util.*,javax.imageio.*" %>
<%@ page import="java.io.OutputStream" %>
<%
response.setContentType("image/jpeg");
response.addHeader("Content-Disposition","attachment;filename=verifycode.jpg" );
//定义一个整型变量用于存放生成的随机数
int icode=0;
//在内存中生成一个图片以及宽、高、类型
BufferedImage image=new BufferedImage(50,16,BufferedImage.TYPE_INT_RGB);
//生成一个2D的图形
Graphics g =image.getGraphics();
//设置图形为白色
g.setColor(Color.white);
//填充图象
g.fillRect(0,0,50,16);
//新建一个随机对象
Random random=new Random();
//取出4位整数
while(icode<=1000){
icode=random.nextInt(10000);
};
//把随机整数转换成字符串
String scode=icode+"";
//将生成随机校验码存入session中
session.setAttribute("verifycode",scode);
//设置图形的颜色为黑色
g.setColor(Color.BLACK);
//把生成的随机数做为字符串写到图形中
g.drawString(scode,12,12);
//从response.getOutputStream()得到一个输出流对象
ServletOutputStream os=response.getOutputStream();
//输出到页面(不知道我的理解是否正确)
ImageIO.write(image,"JPEG",os);
//关闭输出流对象
os.flush();
os.close();%>
login.jsp和verifycode.jsp的运行结果如图所示。
图4-10 login.jsp运行显示结果
第 4 章:隐含对象解析作者:邓子云,赫斌等 来源:希赛网 2014年03月03日
response对象:页面重定向技术
用response.sendRedirect()方法可以将页面重定向至其它页面,重定向后在浏览器地址栏上会出现重定向页面的URL。
【例4-7】页面重定向
继续使用例4-6中的login.jsp,用户在输入用户名和密码后将数据提交至checklogin.jsp页面,checklogin.jsp检查密码是否等于“000000”,如果等于则显示用户登录成功,否则跳转至前面示例的dispatcherDemo.jsp页面。
checklogin.jsp
<%@ page contentType="text/html; charset=GBK" %>
<html>
<head>
<title>responseDemo</title>
</head>
<body bgcolor="#ffffff">
<h1>
用户登录信息
</h1>
<%
String name=request.getParameter("name");
String password=request.getParameter("password");
if (password.equalsIgnoreCase("000000")){
%>
用户登录成功
<%}else{
response.sendRedirect("dispatcherDemo.jsp");
}
%>
</body>
</html>
首先运行login.jsp,如图-11所示。
图4-11 login.jsp运行显示结果
输入用户名“orion”、密码“000000”和验证码“2071”点击“登录”按钮提交至checklogin.jsp页面运行结果如图4-12所示。
图4-12 checklogin.jsp运行显示结果
如果输入密码不等于“000000”时,checklogin.jsp将执行如下语句:
response.sendRedirect("dispatcherDemo.jsp");
显示结果如图4-13所示。
用response.sendRedirect()方法可以将页面重定向至其它页面,重定向后在浏览器地址栏上会出现重定向页面的URL。
【例4-7】页面重定向
继续使用例4-6中的login.jsp,用户在输入用户名和密码后将数据提交至checklogin.jsp页面,checklogin.jsp检查密码是否等于“000000”,如果等于则显示用户登录成功,否则跳转至前面示例的dispatcherDemo.jsp页面。
checklogin.jsp
<%@ page contentType="text/html; charset=GBK" %>
<html>
<head>
<title>responseDemo</title>
</head>
<body bgcolor="#ffffff">
<h1>
用户登录信息
</h1>
<%
String name=request.getParameter("name");
String password=request.getParameter("password");
if (password.equalsIgnoreCase("000000")){
%>
用户登录成功
<%}else{
response.sendRedirect("dispatcherDemo.jsp");
}
%>
</body>
</html>
首先运行login.jsp,如图-11所示。
图4-11 login.jsp运行显示结果
输入用户名“orion”、密码“000000”和验证码“2071”点击“登录”按钮提交至checklogin.jsp页面运行结果如图4-12所示。
图4-12 checklogin.jsp运行显示结果
如果输入密码不等于“000000”时,checklogin.jsp将执行如下语句:
response.sendRedirect("dispatcherDemo.jsp");
显示结果如图4-13所示。
图4-13 dispatcherDemo.jsp运行显示结果
从运行结果来看IE的地址栏发生了改变已经完全指向了
http://localhost:8080/implict_object/dispatcherDemo.jsp
说明是从客户端重新发送了一次请求到服务器,所以dispatcherDemo.jsp无法从request对象中再次取得用户名参数。
【思考1】思考RequestDispatcher.forward()和response.sendRedirect()两者有什么区别?编写程序时使用哪个更好?
通过response.setHeader()方法可以设置HTTP响应的头信息参数使客户端能自动产生相应的动作。
【例4-8】限时自动重定向
接下来将实现一个JSP中实现在某页面停留若干秒后,自动重定向到另一页面的功能。实际上HTML的使用中也有类似的功能:
<meta http-equiv="refresh" content="300; url=target.jsp">
它的含义是:在5分钟之后,正在浏览的页面将会自动变为target.html。代码中的300为刷新的延迟时间,以秒为单位。targer.jsp为你想转向的目标页,若为本页则为自动刷新本页。可以通过setHeader来实现某页面停留若干秒后,自动重定向到另一页面,关键代码如下:
String content=stayTime+";URL="+URL;
response.setHeader("REFRESH",content);
refreshDemo.jsp
<%@ page contentType="text/html; charset=GBK" %>
<html>
<head>
<title>refreshDemo</title>
</head>
<body bgcolor="#ffffff">
<h1>
自动刷新示例
</h1>
<%
System.out.println(new java.util.Date()+ "刷新一次了");
String content=5+";URL=refreshDemo.jsp";
response.setHeader("REFRESH",content);
%>
</body>
</html>
refreshDemo.jsp设置的是每隔5秒刷新一次,运行结果显示如图4-14所示。
图4-14 refreshDemo.jsp运行显示结果
图4-15 运行refreshDemo.jsp时Tomcat的控制台输出
out对象能把结果输出到网页上。通常我们最常使用out.println(String name)和out.print(String name),它们两者最大的差别在于println()在输出的数据后面会自动加上换行的符号;而println()不会在数据后自动换行。但这种区别对于HTML网页输出在IE浏览中显示没有任何差别的,如果用记事本查看HTML源码时可以看到是有差别的。
out对象除了这两种最常使用方法之外,它还有一些方法,这些方法主要是用来控制管理输出的缓冲区(buffer)和输出流(output stream)。
int getBufferSize():取得目前缓冲区的大小(KB)。
int getRemaining():取得目前使用后还剩下的缓冲区大小(KB)。
void clearBuffer():清除输出缓冲区的内容。
void close():关闭输出流,清除所有的内容。
【例4-9】使用out对象
outDemo.jsp
<%@ page contentType="text/html; charset=GBK"%>
<html>
<head>
<title>outDemo.jsp</title>
</head>
<body>
<h2>JSP out对象示例</h2>
<%
out.println("欢迎来到 JSP: 隐含对象<br>");
out.print("欢迎来到 JSP: 隐含对象<br>");
int BufferSize=out.getBufferSize();
int Availabel=out.getRemaining();
int Used=BufferSize-Availabel;
%>
缓冲区大小:<%= BufferSize%><br>
可以使用大小:<%= Availabel%><br>
被使用了:<%=Used %><br>
</body>
</html>
outDemo.jsp运行显示结果如图4-16所示。
图4-16 outDemo.jsp运行显示结果
session对象表示用户的会话状况,用此项机制可以轻易识别每一个用户,能保存和跟踪用户与服务器之间的会话状态。例如,购物车最常使用session的概念,当用户把商品放入购物车时,它再去添加另外的商品到购物车时,原先选购的商品仍然在购物车内,而且用户不用反复去做身份验证。但如果用户关闭Web浏览器,则会断开与服务器的会话。
session对象存储有关用户会话的所有信息。session对象用于在程序的网页之间跳转时,存储有关会话的信息,如图4-17所示。
图4-17 会话跟踪原理图
session对象常用的方法如下。
void setAttribute(String name,Object value):以名称/值的方式,将一个对象的值存放到session中。
void getAttribute(String name):根据名称去获取session中存放对象的值。
【例4-10】校验验证码
接下来演示如何从session对象中得验证码,并比较用户输入的验证码是否与session对象中保存的验证码相等。一起来修改例4-7中的checklogin.jsp文件代码。
checklogin.jsp
<%@ page contentType="text/html; charset=GBK" %>
<html>
<head>
<title>responseDemo</title>
</head>
<body bgcolor="#ffffff">
<h1>
用户登录信息
</h1>
<%
String name=request.getParameter("name");
String password=request.getParameter("password");
if (password.equalsIgnoreCase("000000")){
//从Session中取得验证码的值
String verifycode=(String)session.getAttribute("verifycode");
//从客户端取用户提交过来验证码
String verifycode1=request.getParameter("verifycode");
if (verifycode.equalsIgnoreCase(verifycode1)){
%>
用户登录成功,客户端输入验证码为:<%= verifycode1%>
<%}
}else{
response.sendRedirect("dispatcherDemo.jsp");
}
%>
</body>
</html>
接下来看看来效果。login.jsp的运行效果如图4-18所示。
图4-18 login.jsp运行显示结果
输入用户为“orion”,密码为“000000”和验证码为“4762”点击“登录”按钮后运行结果如图所示。
图4-19 从会话中取得验证码的显示
application对象的作用范围比session更大,不仅仅是在同一个浏览器窗口中,而是作用于整个应用程序,所有客户端浏览器窗口都可以共享对象,它从服务器启动开始就在,直到服务器关闭为止。application对象常用的方法如下。
String getServerInfo():取得容器的名称和版本。
void setAttribute(String name,Object value):以名称/值的方式,将一个对象的值存放到application中。
void getAttribute(String name):根据名称去获取application中存放对象的值。
pageContext对象使用户可以访问页面作用域中定义的所有隐含对象。pageContext对象提供方法以访问隐含对象在页面上定义的所有属性,它的作用范围仅仅在页面内。
pageContext对象最常用的方法如下。
void setAttribute(String name,Object value):以名称/值的方式,将一个对象的值存放到pageContext中。
void getAttribute(String name):根据名称去获取pageContext中存放对象的值。
这两个方法的使用与session,application类似,下面举一个例子来区别pageContext、session、application这3个对象作用范围的区别。
【例4-11】计数器
下面将分别使用session、application和pageContext对象显示会话计数、应用程序计数和页面计数。
Count.java
package implictobject;
public class Count {
//定义计数器
private int count;
//返回计数器的当前值
public int getCount(){
return ++count;
}
}
countDemo.jsp
<%@ page contentType="text/html; charset=GBK" %>
<%@ page import="implictobject.*" %>
<html>
<head>
<title>countDemo</title>
</head>
<body bgcolor="#ffffff">
<h1>
多种计数器示例
</h1>
<%
//判断是否存在计数器对象,不存在则新建
if(pageContext.getAttribute("count")==null)
pageContext.setAttribute("count",new Count());
if(session.getAttribute("count")==null)
session.setAttribute("count",new Count());
if(application.getAttribute("count")==null)
application.setAttribute("count",new Count());
%>
<h2>
页面计数器:
<%=((Count)pageContext.getAttribute("count")).getCount() %>
</h2>
<h2>
会话计数器:
<%=((Count)session.getAttribute("count")).getCount() %>
</h2>
<h2>
应用计数器:
<%=((Count)application.getAttribute("count")).getCount() %>
</h2>
</body>
</html>
countDemo.jsp运行结果如图4-20所示。
图4-20 countDemo.jsp运行结果
多刷新几次页面可以发现页面计数器始终为1,会话计数器和应用计数器实现了结果累加。按如图所示的操作新建一个浏览器窗口看看countDemo.jsp输出的结果是否有变化。
看看新建浏览器窗口的输出,如下图所示。
图4-21 新建浏览器窗口后countDemo.jsp运行结果
可以看出application(应用计数器)完全是依赖服务器存在的,session(会话计数器)依赖窗口会话,pageContext(页面计数器)只依赖页面本身。
page对象提供对网页上定义的所有对象的访问。page对象表示页面本身,它是java.lang.Object类的一个实例。为JSP页面创建的Servlet类定义变量和方法,这些变量和方法包含页面的信息。使用page对象可以访问所有这些变量和方法。page对象的用法如下。
<%@page info="我的信息" contentType="text/html;charset=GBK" %>
<html>
<body>
<%=((javax.servlet.jsp.HttpJspPage)page).getServletInfo()%>
</body>
</html>
不过,page对象较少在JSP开发中使用,一般使用前面学过的page指令即可。
最后一类的隐含对象只有一个成员:exception对象。当JSP网页有错误时会产生异常,而exception对象就来针对这个异常做处理。exception对象并不是在每一个JSP网页中都能够使用。若要使用exception对象时,必须在page指令中设定。<%@page isErrorPage="true"%>才能使用,不然在编译时会产生错误。
【例4-12】使用exception对象
MakerError.jsp
<%@ page contentType="text/html; charset=GBK" %>
<%@ page errorPage="ErrorPage.jsp" %>
<html>
<head>
<title>产生错误页面</title>
</head>
<body bgcolor="#ffffff">
<h1>
产生被零整除错误的页面,如果没有发生跳转说明页面正确运行
</h1>
<%
int i=8,j=0;
out.print("i/j的正确结果:");
out.print(i/j);
%>
</body>
</html>
ErrorPage.jsp
<%@ page contentType="text/html; charset=GBK" %>
<%@page isErrorPage="true" %>
<%@page import="java.io.PrintWriter"%>
<html>
<head>
<title>错误处理页面</title>
</head>
<body bgcolor="#ffffff">
<h1>
程序运行发生了如下错误:
</h1>
<font color="red">
错误原因是:<%=exception.getMessage()%>
</font>
<br>
<font color="red">
错误跟踪:<%exception.printStackTrace(new PrintWriter(out));%>
</font>
</body>
</html>
MakerError.jsp会产生一个除零异常将被自动转给ErrorPage.jsp处理,因为<%@page isErrorPage="true" %>所以能读取exception对象中的错误信息。运行结果如图4-22所示。
图4-22 错误处理页面运行结果
【专家提示】程序代码中打印异常追踪信息使用了printStackTrace(new java.io.PrintWriter(out)),其中 printStackTrace()的参数要为PrintWriter而不是JspWriter。
JSP隐含对象分为I/O有关对象、Context有关对象、Servlet有关对象、Error有关对象4类。
I/O有关对象包括request、response和out;Context有关对象包括session、application和pageContext;Servlet对象包括config和page;Error有关对象包括exception。
request对象通过getParameter()和getParameterValues()方法去获取表单请求数据;response对象通过sendRedirect()方法去实现重定向;out方法通过print()方法去实现页面输出。
session、application和pageContext对象通过setAttribute()和getAttribute()方法去设置和获取属性值。
【思考1】思考requestDispatcher.forward()和response.sendRedirect()两者的区别,以及平时在程序开发中如何使用它们?
答:requestDispatcher.forward()是容器中控制权的转向,在客户端浏览器地址栏中不会显示出转向后的地址;而response.sendRedirect()则是完全的跳转,浏览器将会得到跳转的地址,并重新发送请求链接。这样,从浏览器的地址栏中可以看到跳转后的链接地址。前者更加高效,在前者可以满足需要时,尽量使用requestDispatcher.forward()方法。
第5章 Servlet开发
【本章专家知识导学】
众所周知JSP页面最终在服务端是被编译成Servlet的形式运行的,要对JSP有深入的了解,Servlet是最基本的内容,只要能够把Servlet学好,就更能够理解JSP技术底层运作的方式。
本章旨在引导读者学习什么是Servlet以及客户端和服务器之间的数据传输是如何发生的。通过学习读者还能够掌握HttpServlet类的方法,用于开发Servlet的Servlet生命周期、基本接口、类和方法的概念。读者将能够较全面地使用Servlet API进行开发。
5.1 什么是Servlet
服务器是一台设备,它为网络上的不同设备(客户端)的请求提供信息。例如,文件服务器提供有关文件的共享信息。最初,通过编写CGI(Common Gateway Interface,通用网关接口)程序来实现数据在Web上的传输,这类程序是使用如Perl这样的语言编写的。但是,对于客户端作出的每个请求,必须创建CGI程序的一个新的实例,这将占用大量内存导致系统运行效率不高。因此为了解决这个问题,1997年3月 Sun Microsystems公司所组成的JavaSoft部门推出了Servlet技术替代当时的CGI之类的产品。Java Servlet是一个专门用于编写网络服务器应用程序的Java组件。所有基于Java的服务器端编程都是构建在Servlet之上的。Servlet具有如下优点:
可移植性
由于Servlet是用Java编写的,所以它与生俱来就有跨平台的特性,因此Servlet程序的设计完全和平台是无关的,同样的Servlet完全可以在Apache,IIS等不同Web服务器上执行,不管底层的操作系统是Windows,Solaris,Mac,Linux还是其他的能支持Java的操作系统。
高效率
Servlet是跟普通的Java程序一样,是被编译成字节码后由JVM执行的。相比传统的CGI,尽管CGI是用本地代码直接执行的,但是由于每次客户端发出请求,服务器必须启动一个新的程序来处理请求,这就把高负载强加给了服务器资源,尤其如果CGI使用脚本语言编写时,如perl语言,服务器还必须启动语言解释程序,程序越多,占用的内存就越多,消耗CPU资源也越多,严重影响系统性能。
Servlet运行于Servlet引擎管理的Java虚拟机中,被来自客户机的请求所唤醒,与CGI不同的是,在虚拟机中只要装载一个Servlet就能够处理新的请求,每个新请求使用内存中那个Servlet的相同副本,所以效率比CGI要高。其它采用服务器端的脚本,如ASP,PHP,语言解释程序是内置程序,因此可以加快服务器的运行,但是效率还是比不上准编译的Servlet。实际的使用也已经证明,Servlet是效率很高的服务器端程序,很适合用来开发Web服务器应用程序。
【专家提示】使用过CGI的读者一定知道CGI程序的作用,Servlet要实现的功能和CGI是一样的,只是实现的时候更为方便,效率更高。如果读者对以上概念不是很清楚,也不必着急,通过学习下面的内容,有了感性的认识,再回来看看,一定会有更大的收获。
扩展性
Java Servlet有着十分广泛的应用。不仅能简单的处理客户端的请求,借助Java的强大的功能,使用Servlet还可以实现大量的服务器端的管理维护功能,以及各种特殊的任务,比如,并发处理多个请求,转送请求,代理服务等。
Servlet容器将Servlet动态地加载到服务器上。以HTTP Servlet为例,HTTP Servlet使用HTTP请求和HTTP响应标题与客户端进交互。因此,Servlet容器支持请求和响应所用的HTTP协议。Servlet应用程序的工作原理如图 5-1所示。
图5-1 Servlet工作原理
客户端对Servlet的请求首先会被Web服务器接收,Web服务器将客户的HTTP请求提交给Servlet容器,Servlet容器调用相应的Servlet,Servlet作出的响应传递到Servlet容器,并进而由WEB服务器将响应传输给客户端。WEB服务器提供静态内容并将所有客户端对Servlet作出的请求传递到Servlet容器。
Servlet是实现javax.servlet.Servlet接口的对象。大多数Servlet通过从GenericServlet或HttpServlet类进行扩展来实现。Servlet API包含于两个包中,即javax.servlet和javax.servlet.http。
(1)javax.servlet包
图5-2 javax.Servlet包
ServletInputStream类:用于从客户端读取二进制数据。
ServletOutputStream类:向客户端发送二进制数据。
GenericServlet类:抽象类,定义一个通用的,独立于底层协议的Servlet。
ServletRequest接口:定义一个对象封装客户向Servlet发关的请求信息。
ServletResponse接口:定义一个对象辅助Servlet将请求的响应信息发送给客户端。
ServletContext接口:定义Servlet使用方法以获取其容器的信息。
ServletConfig接口:定义了在Servlet初始化的过程中由Servlet容器传递给Servlet的配置信息对象。
Servlet接口:定义所有Servlet必须实现的方法。
(2)javax.servlet.http包
图5-3 javax.servlet.http
HttpServletRequest接口:扩展ServletRequest接口,为HTTP Servlet提供HTTP请求信息。
HttpServletResponse接口:扩展ServletResponse接口,提供HTTP特定的发送响应的功能。
HttpSession接口:用于标识客户端并存储有送客户端的信息。
HttpSessionAttributeListener接口:实现这个侦听接口用于获取会话属性列表改变的通知。
HttpServlet类:扩展GenericServlet的抽象类。用于扩展创建Http Servlet。
Cookie类:创建一个Cookie,用于存储Servlet发送给客户端的消息。
为了运行Servlet,首先当然需要一个JVM来提供对Java的基本支持,一般需要安装JRE(Java Runtime Environment)或JDK(Java Develop Kit,JRE是其一个子集)。其次我们需要Servlet API的支持,一般的Servlet引擎都自带Servlet API,只要我们安装Servlet引擎,或直接支持Servlet的Web服务器之后便会自动安装上Servlet相关的程序包。本章使用本书第二章“开发环境的安装与配置”中所讲述的Tomcat服务器为Servlet运行环境。
【专家提示】典型的Servlet运行环境有JSWDK,Tomcat,Resin等,这几个都是免费的软件,适合用来学习Servlet和JSP。它们都自带一个简单的HTTP Server,只需简单配置即可投入使用,也可以把它们绑定到常用的Web服务器上,如Apache,IIS等,提供小规模的Web服务。还有一些商业的大中型的支持Servlet和JSP的Web服务器,如JRun,Web Sphere,Web Logic等等,配置比较复杂,并不适合初学者,但是功能较为强大,有条件的读者可以一试。
程序必须导入(import)javax.servlet.*、javaxservlet.http.*两个包。
所有Servlet都必须实现javax.servlet.Servlet接口(Interface),但是通常我们都会从javax.servlet.GenericServlet或javax.servlet.http.HttpServlet择一继承来实现。若写的Servlet程序和HTTP协议无关,那必须继承GenericServlet抽象类;若有关,就必须继承HttpServlet抽象类。目前我们绝大多数应用都是与HTTP协议有关,所以本书示例的Servlet继承HttpServlet抽象类。
【例5-1】一个简单的Servlet
firstServlet.java
package ch05;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.util.*;
public class FirstServlet extends HttpServlet {
private static final String CONTENT_TYPE = "text/html; charset=GBK";
//初始化
public void init() throws ServletException {
}
//处理HTTP请求
protected void service(HttpServletRequest request, HttpServletResponse response) throws
ServletException, IOException {
response.setContentType(CONTENT_TYPE);
PrintWriter out = response.getWriter();
out.println("<html>");
out.println("<head><title>FirstServlet</title></head>");
out.println("<body bgcolor=\"#ffffff\">");
out.println("<H1>这是我的第一个Servlet</H1>");
out.println("</body>");
out.println("</html>");
out.close();
}
//清除资源
public void destroy() {
}
}
Servlet可以利用HttpServletResponse接口的setContentType()方法来设定内容类型,我们要显示为HTML网页类型,因此,内容类型设为“text/html”,这是HTML网页的标准MIME类型值。之后,Servlet用getWrite()方法取得PrintWriter类型的out对象,它与PrintSteam类似,但是它能对Java的Unicode字符进行编码转换。最后,再利用out对象把字符串显示在网页上。
接下来看如何编译、部署Servlet程序。将servlet-api.jar(可以在{Tomcat_Install}\common\lib目录下找到)加入至CLASSPATH之中,使用您的IDE工具或直接使用javac来编译firstServlet.java。编译好firstServlet.java之后将其生成的class文件放置到相应的Web应用目录,如图 5-4所示。
图5-4 部署Servlet应用
接下再设定web.xml,如下:
<servlet>
<servlet-name>firstservlet</servlet-name>
<servlet-class>ch05.FirstServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>firstservlet</servlet-name>
<url-pattern>/firstservlet</url-pattern>
</servlet-mapping>
这样当客户端产生/firstservlet请求的时候,Tomcat就能把这个请求定向到ch05.firstServlet上,该Servlet就能正常运行了。
firstServlet.java的执行结果如图5-5所示。
图5-5 firstServlet.java的执行结果
跟客户端的Applet相似,Servlet也遵循严格的生命周期。Servlet的生命周期由Servlet容器控制,容器创建Servlet的实例。Servlet生命周期就是指Servlet实例在创建之后响应客户请求直至销毁的全过程。Servlet实例的创建取决于Servlet的首次调用。Servlet接口定义了Servlet生命周期的3个方法,分别说明如下。
init()。当Servlet第一次被装载时,Servlet引擎调用这个Servlet的init()方法,只调用一次。如果某个Sevlet需要特殊的初始化需要。那么Servlet编写人员可以重写该方法来执行初始化任务。这是一个可选的方法。如果某个Servlet不需要初始化,那么默认情况下将调用它父类的init()方法。系统保证,在init方法成功完成以前,是不会调用Servlet去处理任何请求的。
service()。这是Servlet最重要的方法,是真正处理请求的地方。对于每个请求,Servlet引擎将调用Servlet的service()方法,并把Servlet请求对象和Servlet响应对象最为参数传递给它。
destroy()。这是相对于init的可选方法,当Servlet即将被卸载时由Servlet引擎来调用,这个方法用来清除并释放在init()方法中所分配的资源。
Servlet的生命周期可以被归纳为以下几步:
(1) 装载Servlet,这一项操作一般是动态执行的。然而,Servlet通常会提供一个管理的选项,用于在Servlet启动时强制装载和初始化特定的Servlet。
(2) 服务器创建一个Servlet实例。
(3) 服务器调用Servlet的init()方法。
(4) 一个客户端请求到达Server。
(5) 服务器创建一个请求对象。
(6) 服务器创建一个响应对象。
(7) 服务器激活Servlet的servic()e方法,传递请求和响应对象作为参数。
(8) service()方法获得关于请求对象的信息,处理请求,访问其他资源,获得需要的信息。
(9) service()方法使用响应对象的方法。将响应传回Server,最终到达客户端。Service()方法可能激活其他方法以处理请求。如doGet(),doPost()或其他程序员自己开发的方法。
(10) 对于更多的客户端请求,Server创建新的请求和响应对象,仍然激活此servlet的service()方法,将这两个对象作为参数传递给它,如此重复以上的循环,但无需再次调用init方法,Servlet一般只初始化一次。
(11) 当Server不再需要Servlet时,比如当Server要关闭时,Server调用Servlet的destroy()方法。
Servlet生命周期的各个阶段如图5-6所示。
图5-6 Servlet生命周期
【例5-2】Servlet的生命周期
接下来将firstServlet.java示例代码修改演示Servlet生命周期的各种方法的调用。
firstServlet2.java
package ch05;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.util.*;
//Servlet生命周期演示
public class FirstServlet2 extends HttpServlet {
private static final String CONTENT_TYPE = "text/html; charset=GBK";
private String user;
//读取初始化参数
public void init() throws ServletException {
super.init();
user=this.getServletConfig().getInitParameter("user");
System.out.println("Servlet已经成功初始化");
}
//处理HTTP请求
protected void service(HttpServletRequest request, HttpServletResponse response) throws
ServletException, IOException {
response.setContentType(CONTENT_TYPE);
PrintWriter out = response.getWriter();
out.println("<html>");
out.println("<head><title>FirstServlet</title></head>");
out.println("<body bgcolor=\"#ffffff\">");
out.println("<H1>加载后初始化参数user="+user+"</H1>");
out.println("</body>");
out.println("</html>");
out.close();
System.out.println("Servlet的service方法被执行了一次");
}
//清除资源
public void destroy() {
System.out.println("Servlet实例已经被释放");
}
}
firstServlet2加载并实例化后init()方法读取web.xml中初始化参数并在系统控制台输出“Servlet已经成功初始化”的信息;service()方法接受客户端的请求在响应中输出init()方法中读取的初始化参数值随后在系统控制台输出“Servlet的service()方法被执行了一次”;容器在销毁Servlet实例之前会调用destroy()方法,destroy()方法给了Servlet机会,来清除所持有的资源(比如内存,文件处理和线程),本例仅仅是在系统控制台输出“Servlet实例已经被释放”。
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app 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" version="2.4">
<display-name>ch05</display-name>
<servlet>
<servlet-name>firstservlet</servlet-name>
<servlet-class>ch05.FirstServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>firstservlet</servlet-name>
<url-pattern>/firstservlet</url-pattern>
</servlet-mapping>
<!--Servlet生命周期示例-->
<servlet>
<servlet-name>firstservlet2</servlet-name>
<servlet-class>ch05.FirstServlet2</servlet-class>
<!--定义初始化参数-->
<init-param>
<param-name>user</param-name>
<param-value>jspdev</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>firstservlet2</servlet-name>
<url-pattern>/firstservlet2</url-pattern>
</servlet-mapping>
</web-app>
firstServlet2的执行结果如图 5-7所示。
图5-7 firstServlet2的执行结果
多点击几次“刷新”或按“F5”键并停止应用服务器,系统控制台显示结果如图 5-8所示。
图5-8 FirstServlet2运行时在系统控制台输出
可以看到创建Servlet实例并初始化(init()方法)只在第一次调用Servlet时执行,以后反复调用仅仅只执行service()方法。停止应用服务器(Tomcat)则调用了destroy()方法。
【专家提示】Servlet生命周期完全由Servlet容器掌握,当Servlet容器没有限定一个加载的Servlet能保存多长时间,因此,一个Servlet实例可能只在Servlet容器中存活几毫秒,或是其他更长的任意时间。因此示例中我们采用了停止Tomcat应用服务器的方式来测试destroy()方法。
HttpServlet作为一个抽象类用来创建用户自己的 HTTP Servlet。HttpServlet类扩展了GenericServlet类。HttpServlet类的子类必须至少重写以下方法中一个:
doGet():由服务器调用来处理客户端发出的GET请求。通过GenericServlet类的service()方法来调用此方法。重写GET方法还支持HTTP HEAD请求,该请求返回没有主体只标题字段的响应。提交响应之前,Servlet容器要编写标题,这是因为在HTTP中必须在发送响应主体之前发送标题。GET方法必须是安全的,如果客户端请求更改存储的数据,则必须使用其他的HTTP方法。如果请求的格式不正确,则doGet()方法返回HTTP“请求错误”消息。doGet()方法的语法为:
public void doGet(HttpServletRequest request,HttpServletResponse response) throws ServletException,IOException
其中,request是存储客户端请求的HttpServletRequest对象;response是包含服务器对客户端作出响应的HttpServletResponse对象。
doPost():服务器调用以允许Servlet处理客户端发出的POST请求。通过GenericServlet类的service()方法来调用此方法。HTTP POST方法用于通过Internet发送大量数据。提交响应之前,Servlet容器要编写标题,这是因为在HTTP中必须在发送响应主体之前发送标题。如果HTTP POST请求格式不正确,则doPost()方法会返回HTTP“请求错误”消息。doPost()方法的语法为:
public void doPost(HttpServletRequest request,HttpServletResponse response) throws ServletException,IOException
其中,request是存储客户端请求的HttpServletRequest对象;response是包含服务器对客户端作出响应的HttpServletResponse对象。
doHeader()用于处理HEADER请求。
doPut()用于处理PUT请求。
doDelete()用于处理DELETE请求。
HttpServletRequest接口
HttpServletRequest接口扩展ServletRequest接口并向HTTP Servlet提供信息。Servlet容器创建HttpServletRequest实现对象,并将其作为参数传递给Servlet的service()方法(service()方法再传递给doGet(),doPost()等)。
HttpServletRequest接口的常用方法如下。
getInputStream():返回客户端请求中的二进制数据,并将其存储在ServletInputStream对象中。
getParameter(String name):用于获取随请求信息一起发送的请求参数。
getContentLength():返回客户端发送的请求的实际长度,如果长度未知,则返回-1。
getMethod():返回用作出请求的HTTP方法的名称,如GET和POST。
HttpServletResponse接口
HttpServletResponse接口扩展ServletResponse接口,用于帮助向客户端发送响应。HttpServletResponse实现对象在创建后会作为参数传递至service()方法。
HttpServletResponse接口的常用方法如下。
getOutputStream():返回一个ServletOutputStream对象,它被用来向客户端发送二进制数据响应。
getWriter():返回将字符文本发送到客户端的PrintWriter类的对象。
setContentLength(int len):允许设置将作为响应发送的数据的长度。
【例5-3】Servlet与表单交互数据
接下来将在一个Servlet中演示对GET请求和POST请求的不同处理。
formServlet.java
package ch05;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.util.*;
public class FormServlet extends HttpServlet {
private static final String CONTENT_TYPE = "text/html; charset=GBK";
//初始化
public void init() throws ServletException {
}
//处理HTTP的GET请求
public void doGet(HttpServletRequest request, HttpServletResponse response) throws
ServletException, IOException {
response.setContentType(CONTENT_TYPE);
PrintWriter out = response.getWriter();
out.println("<html>");
out.println("<head><title>FormServlet</title></head>");
out.println("<body bgcolor=\"#ffffff\">");
out.println("<h3>GET请求被处理</h3>");
out.println("<form method='post' action=''>");
out.println("姓名:<input name='name' type='text'/><br>");
out.println("年龄:<input name='age' type='text'/><br>");
out.println("<input type='submit' value='提交'/><br>");
out.println("</form>");
out.println("</body>");
out.println("</html>");
out.close();
}
//处理HTTP的POST请求
public void doPost(HttpServletRequest request, HttpServletResponse response) throws
ServletException, IOException {
response.setContentType(CONTENT_TYPE);
PrintWriter out = response.getWriter();
request.setCharacterEncoding("GBK");
String name=request.getParameter("name");
String age=request.getParameter("age");
out.println("<html>");
out.println("<head><title>FormServlet</title></head>");
out.println("<body bgcolor=\"#ffffff\">");
out.println("<h3>POST请求被处理</h3>");
out.println("姓名:"+name+"<br>");
out.println("年龄:"+age+"<br>");
out.println("</body>");
out.println("</html>");
out.close();
}
//Clean up resources
public void destroy() {
}
}
formServlet对GET请求的处理时,生成一个HTML表单网页并指定的提交请求数据方式是POST方式,并再次向formServlet提交请求。对POST请求的处理时,接收请求参数显示HTML网页。
图5-9 FormServlet对GET请求的处理
图5-10 FormServlet对POST请求的处理
通过示例我们可以看到对于同一个Servlet对GET和POST请求的不同的处理方式。
【思考1】为什么程序中覆盖doGet()和doPost()等方法而不覆盖service()方法?
Servlet是允许运行在服务器上的Java代码,能响应用户的请求生成动态的内容;运行Servlet必须提供一个Servlet容器;Servlet API主要包含在两个包中,分别为javax.servlet和javax.servlet.http;通过继承GenericServlet抽象类或HttpServlet抽象类,可以编写Servlet。
Servlet的生命周期由Servlet容器完全掌控,Servlet接口定义了Servlet生命周期的3个方法,分别为init()、service()和destroy()。
继承HttpServlet抽象类的Servlet必须覆盖如下至少一个方法:doGet()、doPost()、doPut()、doDelete()、destroy()和getServletInfo()。
【思考1】为什么程序中覆盖doGet()和doPost()等方法而不覆盖service()方法?
答:当服务器接收到对 servlet 的请求时,服务器会产生一个新的线程调用 service()方法。Service()方法检查HTTP请求类型(GET、POST、PUT、DELETE 等)然后相应的调用doGet()、doPost()、doPut()、doDelete()等方法。如果servlet处理POST请求和GET请求方式相同,也可以尝试覆盖service()方法。但这样做并不理想,更好的做法在 doPost()方法中调用doGet()(或者反过来)。这种方法代码稍多了一些,但相比直接覆盖 service()方法它有几个优点:
1.这样做确保您以后可以在子类中添加对其他请求服务的支持,如doPut()、doTrace() 等。如果您直接覆盖了service()方法则就没有了这种可能性。
2.您可以通过实现getLastModified()方法来增加对修改日期的支持。如果您调用了 doGet()方法,标准service()方法会用getLastModified()方法设置header的最后修改日期然后对GET请求作出回应(包括以修改过的 header,If-Modified-Since header)。
第6章 JavaBean开发
【本章专家知识导学】
JavaBean是JSP开发中一种重要的组件开发技术,JSP程序员需要学会用JavaBean来封装各种程序中的处理逻辑,以提高程序的可复用程度和可读性,并学会在JSP页面中使用JavaBean组件。
本章从JavaBean的基本概念讲起,详细讲解了如何开发JavaBean,包括如何编写、编译、部署和使用JavaBean。
6.1 什么是JavaBean
可以把JavaBean简单地理解为一个Java类,这个类中封装了一段可以重复使用的Java代码。Sun公司把JavaBean定义为一个可重复使用的软件组件。为了方便使用,JavaBean往往被设计成是具有特定功能的,如生成一个数据库连接的JavaBean类、代表网上商场购物车的JavaBean类、计算某些复杂数字运算公式的类等。
JavaBean有两种,一种是带有可视化界面的JavBean,如代表窗口界面中的按钮的类、代码窗口界面中的下拉框的类等;另一种中没有可视代界面的JavaBean,在Web开发者常使用后者,因为Web系统的界面通过HTML、CSS等技术结合已能完成,集成显示在浏览器,配合以JavaScript语言还可以实现许多的特效,这比窗口界面的可视化应用程序界面具有更加丰富的表现力。
在Web开发中,使用JavaBean可以把各种程序逻辑功能与页面分离开来,以在Web页面中尽量少地出现Java代码,而大量的Java代码封装于JavaBean中,这样程序的可读性更强,可维护性也会更好一些,大多数情况下,修改代码只需修改JavaBean代码,而不必在冗长的Web页面中去寻找Java代码。
编写JavaBean,其实质上就是写一个Java类的源代码,因此可以使用许多的编辑工作来编写,最简单的就是使用记事本了。为了方便编写、调试、编译程序,笔者推荐使用Java IDE开发工具,如Eclipse、Jbuilder。在Eclipse中编写JavaBean时,编写了属性后,可以用向导方面为JavaBean类的属性生成getXxxx()和setXxxx()方法的代码,以降低手工书写代码出错的可能性;保存了JavaBean类后,Eclipse能为程序员自动编译类(当然这需要在Eclipse环境中作出设置),生成.class字节码文件,这样,程序员就不必再书写复杂的javac编译指令了。
JavaBean既然是一个类,那么它当然也就能够有属性和方法,编写JavaBean所要做的工作就是书写属性和方法的源代码。书写时程序员需要遵循如下的规则:
(1)属性的get()与set()方法
属性名为xxxx,则相应的得到属性值和设置属性值的方法为getXxxx()和setXxxx(),方法名中属性名的第一个字母为大写,具体形式如下:
public 属性数据类型 getXxxx()
public void setXxxx(属性数据类型 data)
参数data是设置赋与属性的值。
如果属性数据类型为布尔型,则形式如下:
public boolean isXxxx()
public boolean getXxxx()
public void setXxxx(boolean data)
isXxxx()方法和getXxxx()方法用于得到属性的值,setXxxx()方法用设置属性的值。
(2)访问属性的方法都设为public,即公有方法;如果有构造函数,则方法也为public型。
然而,以上2个规则并不是严格的,比如设置成员属性的值也并不一定要使用setXxxx()方法,读者也可以自行编写方法,在调用JavaBean注意使用就行了。因此,程序员也可以依自己的喜好来编写程序,不过遵循这2个规则将给其它程序带来一些方便。比如在使用<jsp:setProperty>指令来设置JavaBean的属性值,默认情况下就是调用的setXxxx()方法,使用<jsp:setProperty>指令来得到JavaBean的属性值时,默认情况下就是调用的getXxxx()方法。
编译JavaBean的方法和编译一个Java类的方法相同,使用javac命令。如果使用Eclipse这样的Java IDE开发工具,Eclipse还能为程序员自动编译JavaBean,其实Eclipse也是使用的javac命令来完成编译工作的。
打开“命令与提示符工具”(在Windows 9x/2000/XP中是在【开始】→【程序】→【附件】选项中),再切换到保存JavaBean类源文件(扩展名为.java)的目录下,输入如下命令:
javac 源文件名.java
如果编译通过就会在和JavaBean类源文件同一目录下生成同名的.class文件。
javac是一个编译工具,在JDK安装目录的bin子目录下有javac.exe文件,它是这个编译器的执行文件,也可以带一些参数来使用,形式如下:
javac [编译时的选项] 源文件名.java
“[]”中的内容表示可选项,也就是说编译时的选项可有可无。常用的编译时的选项有如下一些:
(1)-classpath path。设定编译时需要用到的Java类文件的路径,如果在系统变量CLASSPATH中已有则不必给出,参数path是类文件的路径。
(2)-d directory。设定编译生成的.class文件输入到哪一个目录。通常情况下,javac把生成的 .class文件放在 .java文件所在的目录中;如果使用-d参数,则可以指定javac将生成的 .class文件在其他目录中;参数directory是要放入的目录。
(3)-g。此选项在代码产生器中打开调试表,以后可凭此调试产生的字节代码。
(4)-nowarn。此选项禁止编译器产生警告。
(5)-o。告诉javac优化由内联的static、final以及privite成员函数所产生的代码。
(6)-verbose。告知Java显示出有关被编译的源文件和任何被调用类库的信息。
最为常用的是第1项和第2项,设定要用到的类和class文件输出的路径。
【例6-1】编译一个简单的JavaBean
编写一个JavaBean类,源代码如下:
book.java
package bean;
/**
* @author dengziyun
*/
public class book {
public long bookId;//书籍ID号
public String bookName;//书名
public String bookAbstract;//书的内容简介
public String bookAuthor;//书的作者
public String bookPublisher;//书的出版社
public float bookPrice;//书的价格
/**
* @return Returns the bookAuthor.
*/
public String getBookAuthor() {
return bookAuthor;
}
/**
* @param bookAuthor The bookAuthor to set.
*/
public void setBookAuthor(String bookAuthor) {
this.bookAuthor = new String(bookAuthor);
}
/**
* @return Returns the bookId.
*/
public long getBookId() {
return bookId;
}
/**
* @param bookId The bookId to set.
*/
public void setBookId(long bookId) {
this.bookId = bookId;
}
/**
* @return Returns the bookName.
*/
public String getBookName() {
return bookName;
}
/**
* @param bookName The bookName to set.
*/
public void setBookName(String bookName) {
this.bookName = new String(bookName);
}
/**
* @return Returns the bookPublisher.
*/
public String getBookPublisher() {
return bookPublisher;
}
/**
* @param bookPublisher The bookPublisher to set.
*/
public void setBookPublisher(String bookPublisher) {
this.bookPublisher = new String(bookPublisher);
}
/**
* @return Returns the bookAbstract.
*/
public String getBookAbstract() {
return bookAbstract;
}
/**
* @param bookAbstract The bookAbstract to set.
*/
public void setBookAbstract(String bookAbstract) {
this.bookAbstract = new String(bookAbstract);
}
/**
* @return Returns the bookPrice.
*/
public float getBookPrice() {
return bookPrice;
}
/**
* @param bookPrice The bookPrice to set.
*/
public void setBookPrice(float bookPrice) {
this.bookPrice = bookPrice;
}
}
这个类为书籍类,他的一个对象代表一本书。book类有6个属性,bookId为书籍的ID号,bookName为书名,bookAbstract为书的内容简介,bookAuthor为作者名称,bookPublisher为出版社名称,bookPrice为书的价格。这个类比较常见的用处就是在在线书店系统中,用来代表书籍,封装数据库中的书籍表的记录。
针对书籍类的每一个属性都有相应的getXxxx()方法和setXxxx()方法,以便于JSP页面在使用JavaBean时,可以直接设置JavaBean的属性。
进入命令窗口,将当前目录切换到Java源文件所在的目录,本处为“D:\eclipse\workspace\javabean\src\bean”。如果系统变量PATH中没有设置JDK的bin目录,请加入。本例中设置JDK的bin目录到PATH变量中的命令为:
set path=%path%;d:\jdk5\bin
使用“%path%;”的目的是为了不影响PATH变量中原有的设置;本处因为JDK1.5安装在“d:\jdk5”目录中,故设置的路径为“d:\jdk5\bin”,读者需要根据自己机器上的安装情况改变这个设置。
【专家提示】在命令窗口中设置变量PATH的值,并不会影响到系统变量的值,而只是对当前窗口中的对话在效,一旦窗口关闭即会失效,因此建议读者把JDK的bin目录设置到系统变量PATH中。设置系统变量可以通过“我的电脑”“属性”对话框中的“系统变量”选项卡来设置。
再用如下的命令编译book.java:
javac book.java
【专家提示】book.java属于bean包,在Java中包对应着操作系统的一个目录,编译时,需要把当前目录切换到book.java所在的目录,即Java源文件所在最底层的包的目录。
编译的情况如图6-1所示。
图6-1 编译book.java
从图6-1也可以看出,在book.java源文件的同一目录下生成了编译后的字节码文件book.class。
当编写的类较多时,程序员常常会把类按功能类型,或按类与类之间的紧密程度,把属于同一功能模块或互相之间调用关系比较紧密的类放在同一个包中,如果类的体系比较庞大,还会将包再分成若干个层次,形成一个树形的架构,这就象操作系统文件夹的结构,事实上类的源代码和编译后的字节码文件最终的表现形式也就是操作系统的文件夹结构,按包的层次进行组织。
如果在一个Web系统中需要引用许多个类,如果要将这些类一一配置到系统变量CLASSPATH中,要么是CLASSPATH变量的配置参数值会相当冗长,且程序员要作的工作量也比较大,稍一不慎还会出错。为此,可以考虑将所有要用到的类打成一个jar文件(或称为jar包),再在CLASSPATH中引用这个jar文件,就可以使用这个jar文件中的所有类了。
那么jar文件到底是什么呢?一个jar文件是一个压缩包,可以包含一组类及其相关的资源,甚至是声音和图像文件,把这些文件组成统一的一个文件——jar文件。有了jar文件,其中包含的类就不必一个一个地部署,可以作为一个jar文件一次部署,也可以在网上方便下载,因为下载一个文件比下载多个文件方便得多。
将资源打包成jar文件的jar工具使用语法如下:
jar 打包时的选项 [打包后的jar文件名称.jar] 要打包的文件
打包时的选项比较常用的有:
c——创建一个新的存档文件。
f——文件列表中第一个文件的名称为要创建的或要访问的包文件的名称。
m——文件列表中的第一个文件是外部清单文件名。
t——jar文件的内容应制成表格。
u——更新已存在的jar文件。
v——当jar工具执行时显示详细信息。
X——从jar文件中展开文件。
这些选项可以组合使用,如cvf。
“要打包的文件”参数中可以使用通配符,如果是所有文件用“*”,如果是以JPEG为扩展的则用*.JPEG。如果要将当前目录下的所有.class文件打包成class.jar,可使用如下的命令:
jar cf class.jar *.class
jar工具也可以用于将jar文件解包。列出class.jar包中的文件命令如下:
jar tf class.jar
展开class.jar文件,并释放在当前目录下:
jar xf class.jar
除了jar工具以外,还有一种简便的方法可以将资源打包成jar文件,以及将jar文件解包,那就是使用WinRar或WinZip这样的解压缩工具。
将资源打包成jar文件的方法是:用WinRar或WinZip将资源打包成.zip压缩文件,或将.zip文件的扩展名改为.jar。
将jar文件解包的方法是:将jar文件的扩展名改为.zip,再用WinRar或WinZip解压缩。
部署JavaBean编译后的.class文件,需要先考虑这个JavaBean是要让Web站点中的所有Web应用都能够使用,还是让Web站点中的某一个Web应用能够使用,两者的部署方法不同。
如果要让Web站点中的所有Web应用都能够使用,应当把类文件拷贝到Tomcat5安装目录的“common/classes”目录中,如果类属于某个包则应当把整个包都拷贝过去。
如果仅仅是想让Web站点中的某一个Web应用能够使用,则应当把类文件拷贝到这个Web应用的“WEB-INF/classes”(注意区分大小写)目录中,“WEB-INF”是每个Web应用都必须要有的子目录。如果类属于某个包则应当把整个包都拷贝过去。
与部署JavaBean类似,部署jar文件同样有两种情况,一种是对Web服务器中的JSP页面都有效;另一种仅对当前应用有效。
如果要让Web服务器中所有的JSP页面都可以使用要部署的jar文件,则可以把打包后的jar文件拷贝到Tomcat 5安装目录的“common/lib”或“lib”子目录下,但需要重启Tomcat服务器。这样无需作出配置,JSP页面即可使用这个jar文件了。
如果只对当前的应用有效,则须在当前应用的WEB-INF子目录中建立一个新的子目录lib,并把jar文件拷贝过来。
【专家提示】其实,并不必把jar文件拷贝到这些指定的目录中,也可以是放在Web服务器中的任意之外,但必须在CLASSPATH系统变量中引用。
部署好JavaBean后就可以JSP中使用了。要在JSP页面中使用JavaBean,首先要用<%@page%>指令引入要使用到的类。如,要使用本章前文中编译后的book类,可使用如下的<%@page%>指令:
<%@page import="bean.book"%>
其中,book是要导入的类名,如果还有包,可在类名前加包名,形如“包名.类名”。
导入后即可以JSP页面中的Java代码中使用这个JavaBean类了。
JSP提供了指令标签useBean简单而又方便地使用JavaBean。这个指令标签的语法如下:
<jsp:useBean id="给JavaBean实例取的名称" class="Bean类名"
scope="JavaBean实例的有效范围"></jsp:useBean>
或
<jsp:useBean id="给JavaBean实例取的名称" class="JavaBean类名"
scope=" JavaBean实例的有效范围"/>
其中,id的设置可由用户任意给定;class为JavaBean类名,如果类之上还有包,则此参数用形如“包名.类名”的形式;scope有四种不同的取值范围,分别解说如下:
scope设为page,表示分配给每个客户的JavaBean不同,有效范围仅对当前的JSP页面有效,如果关闭此JSP页面,相应的分配给此客户的JavaBean将被取消。
scope设为session表示分配给每个客户的JavaBean不同,但在同一个客户打开的多个JSP页面,即一次会话其间,是同一个JavaBean。如果在同一客户的不同JSP页面中声明了相同id的JavaBean,且范围仍为scope,若更改此JavaBean的成员变量值,其他页面中此id的Bean的成员变量值也会被改变。当客户打开服务器上的所有网页都被关闭时,对应客户的这一次会话中的JavaBean也被取消。
scope设为request表示分配给每个客户的JavaBean不同,且有效范围在request期间,即在请求与被请求页面之间共享JavaBean。当对请求作出响应后,JavaBean就会被取消。
scope设为application表示在服务器的所有客户之间共享JavaBean。当一个客户改变了成员变量的值时,另一个客户的此JavaBean的同一个成员变量值也会被改变。当服务器关闭时JavaBean才会被取消。
【例6-2】使用JavaBean
假设前面编写的类book位于包javabean下,经编译通过后已按本节中部署class文件或部署jar文件的方法部署好,再来看在JSP页面中如何使用它。
useBeanBook.jsp
<%@ page contentType="text/html;charset=gb2312"%>
<%@ page import="bean.book"%>
<html>
<head>
<title>第一个JSP页面</title>
</head>
<body>
<%//------用jsp:useBean指令使用JavaBean------
out.println("用jsp:userBean指令使用JavaBean<hr>");
%>
<jsp:useBean id="csaiBookZYS" class="bean.book" scope="page">
<jsp:setProperty name="csaiBookZYS" property="bookId" value="2"/>
<jsp:setProperty name="csaiBookZYS"
property="bookName" value="系统分析师技术指南"/>
<jsp:setProperty name="csaiBookZYS"
property="bookAuthor" value="张友生"/>
<jsp:setProperty name="csaiBookZYS"
property="bookPublisher" value="清华大学出版社"/>
<jsp:setProperty name="csaiBookZYS"
property="bookPrice" value="45"/>
</jsp:useBean>
<%
out.println("书籍ID号:"+csaiBookZYS.bookId+"<br>");
out.println("书名:"+csaiBookZYS.bookName+"<br>");
out.println("作者:"+csaiBookZYS.bookAuthor+"<br>");
out.println("出版社:"+csaiBookZYS.bookPublisher+"<br>");
out.println("定价:"+csaiBookZYS.bookPrice);
%>
<jsp:getProperty name="csaiBookZYS" property="bookPrice"/>
<%//------在Java程序片中使用JavaBean------
book csaiBookDZY=new book();
csaiBookDZY.bookId=1;
csaiBookDZY.bookName="精通J2EE网络编程";
csaiBookDZY.bookAuthor="邓子云";
csaiBookDZY.bookPublisher="清华大学出版社";
csaiBookDZY.bookPrice=49;
out.println("<br>在Java程序片中使用JavaBean");
out.println("<hr>书籍ID号:"+csaiBookDZY.bookId+"<br>");
out.println("书名:"+csaiBookDZY.bookName+"<br>");
out.println("作者:"+csaiBookDZY.bookAuthor+"<br>");
out.println("出版社:"+csaiBookDZY.bookPublisher+"<br>");
out.println("定价:"+csaiBookDZY.bookPrice+"<br>");
%>
</body>
</html>
程序的运行效果如图6-2所示。
图6-2 使用JavaBean
程序中通过两种方法来使用JavaBean,一种是通过<jsp:useBean>指令来使用;另一种是直接在Java程序片中使用。为了减少Java代码量,建议程序员尽量使用JSP指令,以增强JSP页面中代码的可读性。
从例6-2可以看出,使用setXxxx()方法和getXxxx()方法能设置和得到JavaBean的属性值,也可以用动作标签setProperty与getProperty来修改与获取JavaBean的属性值。
getProperty标签获得JavaBean的属性值,并将这个属性值以字符串的形式显示出来。getProperty的使用语法格式如下:
<jsp:getProperty name="JavaBean的名称" property="JavaBean属性的名称"/>
或
<jsp:getProperty name="JavaBean的名称" property="JavaBean属性的名称">
<jsp:getProperty>
setProperty标签设置JavaBean的属性值用于设置JavaBean的属性值。setProperty标签的使用语法有如下4种:
<jsp:setProperty name="bean的名称" property="*"/>
<jsp:setProperty name="bean的名称" property="属性名称"/>
<jsp:setProperty name="bean的名称" property="属性名称" param="参数名称"/>
<jsp:setProperty name="bean的名称" property="属性名称" value="属性值"/>
在第一种语法格式中,property="*",应用这种格式要求bean属性的名称与类型要和request对象中参数名称与类型一致,以此用bean中的属性来接收客户输入的数据,系统会根据名称来自动匹配。如果类型不一致,会根据bean中的类型进行转换。第二种语法格式则只设置其中匹配的一个bean的属性。第三种语法格式根据指定的reaquest对象中的参数与属性匹配。第四种语法格式用来给bean的属性赋值,属性值的数据类型要与属性的数据类型一致,否则程序会出错,因此要进行数据类型的转换。
setProperty动作指令可以在useBean动作指令中使用,也可在声明了useBean后使用,但不能在声明之前使用。与userBean动作指令结合使用的语法格式如下:
<jsp:useBean id="bean的名称" scope="有效范围" class="包名.类名">
<jsp:setProperty name="bean的名称" property="属性名称" value="属性值"/>
… …
<jsp:setProperty name="bean的名称" property="属性名称" value="属性值"/>
</jsp:useBean>
【专家提示】由于用户输入的数据往往不规范,数据类型的转换可能会出错,因此在程序中要及时捕获并报告错误,以增强程序的健壮性。
【专家提示】在同一个setProperty动作指令中不能同时存在param和value参数。
还一种方法,可以通过表单来设置JavaBean的属性值,但这种方法要求HTML表单输入项的名字要与JavaBean属性的名字相同,这样服务器引擎会自动进行匹配把字符串转换为相应JavaBean属性的数据类型数据。使用如下的语法格式:
<jsp:getProperty name="JavaBean的名称" property="*"/>
【例6-3】用HTML表单设置JavaBean的属性值
htmlJavaBean.jsp
<%@ page contentType="text/html;charset=GB2312" %>
<%!
public String codeToString(String str){//处理中文字符串的函数
String s=str;
try{
byte tempB[]=s.getBytes("ISO-8859-1");
s=new String(tempB);
return s;
}catch(Exception e){
return s;
}
}
%>
<jsp:useBean id="csaiBookZYS" class="bean.book" scope="page">
</jsp:useBean>
<jsp:setProperty name="csaiBookZYS" property="*"/>
<html>
<head>
<title>用HTML表单设置JavaBean的属性</title>
</head>
<body>
<div align="center">
<center>
<%
out.println("书籍ID号:"+csaiBookZYS.bookId+"<br>");
out.println("书名:"+codeToString(csaiBookZYS.bookName)+"<br>");
out.println("作者:"+codeToString(csaiBookZYS.bookAuthor)+"<br>");
out.println("出版社:"+codeToString(csaiBookZYS.bookPublisher)+"<br>");
out.println("定价:"+csaiBookZYS.bookPrice);
%>
<table border="1" width="46%">
<form name="form1" action="" method="post">
<tr>
<td width="44%">请输入书籍ID号:</td>
<td width="56%"> <input type="text" name="bookId" size="20"></td>
</tr>
<tr>
<td width="44%">请输入书名:</td>
<td width="56%"> <input type="text" name="bookName" size="20"></td>
</tr>
<tr>
<td width="44%">请输入作者名字:</td>
<td width="56%"> <input type="text" name="bookAuthor" size="20"></td>
</tr>
<tr>
<td width="44%">请输入出版社名称:</td>
<td width="56%"> <input type="text" name="bookPublisher" size="20"></td>
</tr>
<tr>
<td width="44%">请输入此书的定价:</td>
<td width="56%"> <input type="text" name="bookPrice" size="20"></td>
</tr>
<tr>
<td width="100%" colspan="2">
<p align="center"><input type="submit" name="T1" size="20" value="提交">
<input type="reset" name="T1" size="20" value="重置"> </td>
</tr>
</form>
</table>
</center>
</div>
</body>
</html>
程序中,表单的action属性值为空,表示数据提交到本页面。表单中的输入框的名称与JavaBean属性的名称一一对应起来了,因此可以只用如下的一句语句即可将提交过来的数据设置为JavaBean的属性值:
<jsp:setProperty name="csaiBookZYS" property="*"/>
程序的运行结果如图6-3所示。
图6-3 用HTML表单设置JavaBean的属性值
<jsp:setProperty name="csaiBookZYS" property="*"/>语句也可以改为如下的5句,效果相同:
<jsp:setProperty name="csaiBookZYS" property="bookId" param="bookId"/>
<jsp:setProperty name="csaiBookZYS" property="bookName" param="bookName"/>
<jsp:setProperty name="csaiBookZYS" property="bookAuthor" param="bookAuthor"/>
<jsp:setProperty name="csaiBookZYS"
property="bookPublisher" param="bookPublisher"/>
<jsp:setProperty name="csaiBookZYS" property="bookPrice" param="bookPrice"/>
JavaBean是一个可重复使用的软件组件,可以用来执行复杂的计算任务,或负责与数据库的交互以及数据提取等。Java中有两种JavaBean,即可视化和非可视化JavaBean,在JSP中一般使用后者。
编写JavaBean其实质上就是编写一个Java类,编写属性和方法。编译JavaBean用javac命令。如果类比较多,可分类用包存放,交可以打包成jar文件,打包和解包jar方便使用jar命令。
使用JavaBean也就是使用一个Java类,可以用<%@page import%>语句导入这个类后,即可在Java代码块中使用,也能用<jsp:useBean>标签来使用,再通过<jsp:setProperty>标签和<jsp:getProperty>标签设置和得到JavaBean的属性值。
第7章 Web方式上传与下载文件
【本章专家知识导学】
Web应用程序经常要进行文件的上传、下载操作。可以使用Java I/O流自定义完成文件操作的类,也可以使用专业的第三方上传、下载组件。使用Java I/O流自定义完成文件操作的类,程序代码多,设计复杂;而使用专业的上传、下载组件,程序代码短小,编制简单。常用的文件操作组件有jspSmartUpload、FileUpload。
通过本章的学习,要掌握使用jspSmartUpload组件进行文件上传、下载操作方法和使用FileUpload组件进行文件上传操作的方法。
7.1 文件操作组件介绍
jspSmartUpload是一款免费、开源的文件上传下载组件,可以从如下的网站下载得到:
http:// www.jspsmart.com
jspSmartUpload适合于在JSP文件中嵌入式实现文件的上传和下载功能。该组件有以下几个特点:
1.使用简单。在JSP文件中仅仅书写三五行Java代码就可以完成文件的上传或下载,程序员使用方便、快捷。
2.能全程控制上传。利用jspSmartUpload组件提供的对象及其操作方法,可以获得全部上传文件的信息(包括文件名、大小、类型、扩展名、文件数据等),方便存取。
3.能对上传的文件尺寸大小、类型等方面做出限制,可以过滤掉不符合要求的文件。
4.下载灵功能活。只需要写少量的代码,就能把Web服务器当成是文件服务器来使用,不论文件是在Web应用的目录下还是在Web服务器的其它任何目录下,都可以利用jspSmartUpload进行下载。
FileUpload也是一款免费开源的可实现文件上传功能的组件,可以从如下的网站下载得到:
http://jakarta.apache.org
Commons是Apache开放源代码组织中的一个Java子项目,该项目主要涉及一些开发中常用的模块,例如文件上传、命令行处理、数据库连接池、XML配置文件处理等。这些项目集合了来自世界各地软件工程师的心血,其性能、稳定性等方面都能经实际应用的考验,有效地利用这些项目将会给开发带来显而易见的效果。FileUpload就是其中用来处理HTTP文件上传的子项目。
下载得到jspSmartUpload组件后,压缩包的名字是jspSmartUpload.zip,将文件名更改为jspSmartUpload.jar(如果下载得到的是jspSmartUpload.jar则无需更改)。Tomcat对文件名大小写敏感,它要求Web应用程序相关的jar组件包所在目录为“WEB-INF/lib”。
将jspSmartUpload.jar文件拷贝到所需要使用jspSmartUpload组件的Web应用的“WEB-INF/lib”,即可以在这个Web应用的JSP文件中使用jspSmartUpload组件了。
【注意】按上述方法安装后,只有当前Web应用下的程序可以使用jspSmartUpload组件,如果想让Web服务器的所有Web应用程序都能用它,则应当将jspSmartUpload.jar文件拷贝到Tomcat安装目录的“shared/lib”目录下。
1.File类
这个类包装了一个上传文件的所有信息。通过它,可以得到上传文件的文件名、文件大小、扩展名、文件数据等信息。
File类主要提供以下方法:
(1)saveAs()
方法原型:
public void saveAs(java.lang.String destFilePathName) 或
public void saveAs(java.lang.String destFilePathName, int optionSaveAs)
其中destFilePathName是另存的文件名,optionSaveAs是另存的选项,该选项有三个值,分别是SAVEAS_PHYSICAL,SAVEAS_VIRTUAL,SAVEAS_AUTO。SAVEAS_PHYSICAL表明以操作系统的根目录为文件根目录另存文件,SAVEAS_VIRTUAL表明以Web应用程序的根目录为文件根目录另存文件,SAVEAS_AUTO则表示让组件决定,当Web应用程序的根目录存在另存文件的目录时,它会选择SAVEAS_VIRTUAL,否则会选择SAVEAS_PHYSICAL。
例如,saveAs("/upload/sample.zip",SAVEAS_PHYSICAL)执行后若Web服务器安装在C盘,则另存的文件名实际是c:\upload\sample.zip。saveAs("/upload/sample.zip",_
SAVEAS_VIRTUAL)执行后若Web应用程序的根目录是“webapps/jspsmartupload”则另存的文件名实际是“webapps/jspsmartupload/upload/sample.zip”。saveAs("/upload/sample.zip",SAVEAS_AUTO)执行时若Web应用程序根目录下存在upload目录,则其效果同saveAs("/upload/sample.zip",SAVEAS_VIRTUAL),否则同saveAs("/upload/sample.zip",SAVEAS_PHYSICAL)。
对于Web程序的开发来说,最好使用SAVEAS_VIRTUAL,以便于程序的移植。
(2)isMissing()
isMissing()方法用于判断用户是否选择了文件,也即对应的表单项是否有值。选择了文件时,它返回false。未选文件时,它返回true。方法原型:
public boolean isMissing()
(3)getFieldName()
getFieldName()方法用于获取HTML表单中对应于此上传文件的表单项的名字。 方法原型:
public String getFieldName()
(4)getFileName()
getFileName()方法用于获取文件名(不含目录信息)。方法原型:
public String getFileName()
(5)getFilePathName()
getFilePathName()方法用于获取文件全称(带路径)。方法原型:
public String getFilePathName
(6)getFileExt()
getFileExt()方法用于获取文件扩展名(后缀)。方法原型:
public String getFileExt()
(7)getSize()
getSize()方法用于获取文件长度,长度以字节计。方法原型:
public int getSize()
(8)getBinaryData()
getBinaryData()方法用于获取文件数据中指定位移处的一个字节,用于检测文件等。方法原型:
public byte getBinaryData(int index)
其中,index表示位移,其值在0到getSize()-1之间。
2. Files类
这个类表示所有上传文件的集合,通过它可以得到上传文件的数目、大小等信息。主要有以下方法:
(1)getCount()
getCount()方法可用于取得上传文件的数目。方法原型:
public int getCount()
(2)getFile()
getFile()方法可用于取得指定位移处的文件对象File(这是com.jspsmart.upload.File,不是java.io.File,注意区分)。方法原型:
public File getFile(int index)。
其中index为指定位移,其值在0到getCount()-1之间。
(3)getSize()
getSize()方法可获得上传文件的总长度,可用于限制一次性上传的数据量大小。方法原型:
public long getSize()
(4)getCollection()
getCollection()方法用于将所有上传文件对象以Collection的形式返回,以便其它应用程序引用,浏览上传文件信息。方法原型:
public Collection getCollection()
(5)getEnumeration()
getEnumeration()方法用于将所有上传文件对象以Enumeration(枚举)的形式返回,以便其它应用程序浏览上传文件的信息。方法原型:
public Enumeration getEnumeration()
3. Request类
Request类的功能等同于JSP内置的对象request。只所以提供这个类,是因为对于文件上传表单,通过request对象无法获得表单项的值,必须通过jspSmartUpload组件提供的Request对象来获取。该类提供如下方法:
(1)getParameter()
getParameter()方法用于获取指定参数之值,如果指定的参数不存在,返回值为null。 方法原型:
public String getParameter(String name)
其中name为参数的名字。
(2)getParameterValues()
当一个参数可以有多个值时,用getParameterValues()方法来取其值,getParameterValues()方法返回的是一个字符串数组如果指定的参数不存在,返回值为null。 方法原型如下:
public String[] getParameterValues(String name)。其中,name为参数的名字。
(3)getParameterNames()
getParameterNames()方法用于取得Request对象中所有参数的名字,通过返回的枚举类型数据可以遍历所有参数。方法原型:
public Enumeration getParameterNames()
4.SmartUpload类
SmartUpload类用于完成上传下载工作,它的主要方法如下。
(1)initialize()
initialize()方法是上传与下载时共用的方法,用于执行上传下载的初始化工作,必须第一个被执行。方法原型(有多个,主要使用下面这个):
public final void initialize(javax.servlet.jsp.PageContext pageContext)
其中pageContext为JSP页面内置对象(页面上下文)。
(2)upload()
upload()方法用于上传文件数据。一般地,上传操作第一步执行initialize方法,第二步就是执行upload()方法。方法原型:
public void upload()
(2)save()
save()方法用于将全部上传文件保存到指定目录下,并返回保存的文件个数。 方法原型:
public int save(String destPathName) 或
public int save(String destPathName,int option)
其中destPathName为文件保存目录,option为保存选项,它有三个值,分别是SAVE_PHYSICAL,SAVE_VIRTUAL和SAVE_AUTO。(同File类的saveAs方法的选项之值类似)SAVE_PHYSICAL指示组件将文件保存到以操作系统根目录为文件根目录的目录下,SAVE_VIRTUAL指示组件将文件保存到以Web应用程序根目录为文件根目录的目录下,而SAVE_AUTO则表示由组件自动选择。
【专家提示】save(destPathName)作用等同于save(destPathName,SAVE_AUTO)。
(3)getSize()
getSize()方法用于获取上传文件数据的总长度。方法原型:
public int getSize()
(4)getFiles()
getFiles()方法用于获取全部上传文件,以Files对象形式返回,可以利用Files类的操作方法来获得上传文件的数目等信息。方法原型:
public Files getFiles()
(5)getRequest()
getRequest()方法用于取得Request对象,以便由此对象获得上传表单参数之值。 方法原型:
public Request getRequest()
(6)setAllowedFilesList()
setAllowedFilesList()方法用于设定允许上传带有指定扩展名的文件,当上传过程中有文件名不允许时,组件将抛出异常。方法原型:
public void setAllowedFilesList(String allowedFilesList)
其中allowedFilesList为允许上传的文件扩展名列表,各个扩展名之间以逗号分隔。如果想允许上传那些没有扩展名的文件,可以用两个逗号表示。例如:setAllowedFilesList("doc,txt,,")将允许上传带doc和txt扩展名的文件以及没有扩展名的文件。
(7)setDeniedFilesList()
setDeniedFilesList()方法用于限制上传那些带有指定扩展名的文件。若有文件扩展名被限制,则上传时组件将抛出异常。方法原型:
public void setDeniedFilesList(String deniedFilesList)
其中deniedFilesList为禁止上传的文件扩展名列表,各个扩展名之间以逗号分隔。如果想禁止上传那些没有扩展名的文件,可以用两个逗号来表示。例如:setDeniedFilesList("exe,bat,,")将禁止上传带exe和bat扩展名的文件以及没有扩展名的文件。
(8)setMaxFileSize()
setMaxFileSize()方法用于设定每个文件允许上传的最大长度。方法原型:
public void setMaxFileSize(long maxFileSize)
其中maxFileSize为为每个文件允许上传的最大长度,当文件超出此长度时,将不被上传。
(9)setTotalMaxFileSize()
setTotalMaxFileSize()方法用于设定允许上传的文件的总长度,以限制一次性上传的数据量大小。方法原型:
public void setTotalMaxFileSize(long totalMaxFileSize)
其中totalMaxFileSize为允许上传的文件的总长度。
(10)setContentDisposition()
setContentDisposition()方法用于将数据追加到MIME文件头的CONTENT-DISPOSITION域。jspSmartUpload组件会在返回下载的信息时自动填写MIME文件头的CONTENT-DISPOSITION域,如果用户需要添加额外信息,请用此方法。方法原型:
public void setContentDisposition(String contentDisposition)
其中contentDisposition为要添加的数据。如果contentDisposition为null,则组件将自动添加“attachment;”,以表明将下载的文件作为附件,结果是IE浏览器将会提示另存文件,而不是自动打开这个文件(IE浏览器一般根据下载的文件扩展名决定执行什么操作,扩展名为doc的将用word程序打开,扩展名为pdf的将用acrobat程序打开,等等)。
(11)downloadFile()
downloadFile()方法用于下载文件,共有以下三个原型可用,第一个最常用,后两个用于特殊情况下的文件下载(如更改内容类型,更改另存的文件名)。
①public void downloadFile(String sourceFilePathName)
其中sourceFilePathName为要下载的文件名(带目录的文件全名)
②public void downloadFile(String sourceFilePathName,String contentType)
其中sourceFilePathName为要下载的文件名(带目录的文件全名),contentType为内容类型(MIME格式的文件类型信息,可被浏览器识别)。
③public void downloadFile(String sourceFilePathName,String contentType,String destFileName)
其中sourceFilePathName为要下载的文件名(带目录的文件全名),contentType为内容类型(MIME格式的文件类型信息,可被浏览器识别),destFileName为下载后默认的另存文件名。
下面通过三个实例来演示jspSmartUpload组件如何进行文件上传处理操作。
【例7-1】jspSmartUpload简单文件上传
下面的web应用中,将实现一次可以上传三个文件到服务器上的功能。在JSP页面上的表单中放置三个文件上传域,【浏览…】按钮用来选择要上传的文件,当选择好文件后按【上传】按钮,就可以将文件上传至服务器upload目录下。当完成上传操作以后,服务器提示成功上传多少个文件。
首先编写提供文件选择对话框的HTML页面7-1.html中的代码。
7-1.html
<HTML>
<HEAD>
<TITLE>例 7-1 jspSmartUpload 基本文件上传</TITLE>
</HEAD>
<BODY BGCOLOR="white">
<HR>
<FORM METHOD="POST" ACTION="/jspsmartupload/7-1.jsp" ENCTYPE="multipart/form-data">
<INPUT TYPE="FILE" NAME="FILE1" SIZE="50"><BR>
<INPUT TYPE="FILE" NAME="FILE2" SIZE="50"><BR>
<INPUT TYPE="FILE" NAME="FILE3" SIZE="50"><BR>
<BR>
<INPUT TYPE="SUBMIT" VALUE="上传">
</FORM>
</BODY>
</HTML>
【专家提示】凡是要上传文件的表单都必须设置enctype属性,且属性值必须是“multipart/form-data”,同时method属性必须设为POST。
7-1.html的执行结果如图7-1所示
图7-1 7-1.html运行界面
接下来定义7-1.jsp来处理程序,7-1.jsp调用jspSmartUpload组件完成文件的上传。
7-1.jsp
<%@ page contentType="text/html;charset=GB2312" %>
<%@ page language="java" import="com.jspsmart.upload.*"%>
<jsp:useBean id="mySmartUpload" scope="page" class="com.jspsmart.upload.SmartUpload" />
<HTML>
<HEAD>
<TITLE>例 7-1 jspSmartUpload 基本文件上传</TITLE>
</HEAD>
<BODY BGCOLOR="white">
<HR>
<%
// 声明变量count,用来存储上传文件个数
int count=0;
// 执行初始化操作
mySmartUpload.initialize(pageContext);
//设定上传文件最大字节数
mySmartUpload.setTotalMaxFileSize(100000);
// 上传文件到服务器
mySmartUpload.upload();
try {
/*调用SmartUpload方法的save方法保存上传文件。存储时以文件原有名称存储。
寻找存储路径首先看当前web应用程序下是否存在upload目录,如果有,则直接存储在该目录;否则,寻找web服务器所在驱动器物理路径下是否存在upload目录,如果有,则存储,如果没有,则会抛出异常。*/
count = mySmartUpload.save("/upload");
// 显示上传文件数量
out.println(count + "个文件已经上传.");
} catch (Exception e){
out.println(e.toString());
}
%>
</BODY>
</HTML>
在执行后,选择文件,单击【上传】按钮,当上传成功显示确认页面,如图7-2所示。
图7-2 上传确认页面
假设Tomcat5.5安装在D盘根目录下,
7-1.html和7-1.jsp都部署在D:\Tomcat5.5\webpass\jspsmartupload下,
jspSmartUpload.jar部署在D:\Tomcat5.5\webpass\jspsmartupload\WEB-INF\lib下。
如果 (如:D:\Tomcat5.5\webpass\jspsmartupload)以及Web服务器所在的物理路径(如D:\)都不存在upload目录,则抛出异常,如图7-3所示。
图7-3 存储路径不存在导致的异常
有时不但要上传文件,而且还要显示上传文件信息。显示上件文件信息,可以通过File类的一些方法来获得。例如getFileName( )可以得到不带目录的文件名,getFilePathName()可以得到带目录的文件名,getFileExt()可以得到文件的扩展名,getsize()可以得到文件长度信息。接下来我们看一个既能上传文件,又能得到文件信息的Web程序。
【例7-2】jspSmartUpload上传文件,并显示上传文件信息
首先定义文件选择页面7-2.html
7-2 .html
<HTML>
<TITLE>
jspSmartUpload 上传文件,并显示上传文件信息
</TITLE>
<BODY BGCOLOR="white">
<HR>
<FORM METHOD="POST" ACTION="/jspsmartupload/7-2.jsp"
ENCTYPE="multipart/form-data">
<INPUT TYPE="FILE" NAME="FILE1" SIZE="50"><BR>
<INPUT TYPE="FILE" NAME="FILE2" SIZE="50"><BR>
<INPUT TYPE="FILE" NAME="FILE3" SIZE="50"><BR>
<BR>
<INPUT TYPE="SUBMIT" VALUE="上传">
</FORM>
</BODY>
</HTML>
7-2.html的执行结果如图7-4所示
图7-4 7-2.html 的执行结果
接下来定义文件上传处理程序7-2.jsp。
7-2.jsp
<%@ page contentType="text/html;charset=GB2312" %>
<%@ page language="java" import="com.jspsmart.upload.*"%>
<jsp:useBean id="mySmartUpload" scope="page" class="com.jspsmart.upload.SmartUpload" />
<HTML>
<TITLE>
例7-2 jspSmartUpload 上传文件,并显示上传文件信息
</TITLE>
<BODY BGCOLOR="white">
<TABLE BORDER=1 bordercolor="Black">
<TR><TD WIDTH="100">文件名</TD><TD WIDTH="50">扩展名</TD>
<TD WIDTH="100"> 长度</TD> <TD WIDTH="300"> 带路径的文件名</TD> </TR>
<%
// 声明变量count,用来存储上传文件个数
int count=0;
// 执行初始化操作
mySmartUpload.initialize(pageContext);
// 上传文件到服务器
mySmartUpload.upload();
// 对上传到服务器的文件进行逐个处理
for (int i=0;i<mySmartUpload.getFiles().getCount();i++)
{
// 取出一个文件
com.jspsmart.upload.File myFile = mySmartUpload.getFiles().getFile(i);
// 只有myFile代表的文件存在时才执行存储操作
if (!myFile.isMissing()) {
// 保存该文件到web应用程序下的upload目录
myFile.saveAs("/upload/" + myFile.getFileName());
%>
<TR><TD WIDTH="100"><%=myFile.getFileName()%> </TD>
<TD WIDTH="50"><%=myFile.getFileExt()%></TD>
<TD WIDTH="100"><%=myFile.getSize()%> </TD>
<TD WIDTH="300"><%=myFile.getFilePathName()%></TD> </TR>
<%
//成功上传文件计数
count++;
}
}
%>
</TABLE>
<%
// 显示能被上传的文件数目
out.println("<BR>"+mySmartUpload.getFiles().getCount()+"个文件 能被上传.<BR>");
// 显示成功上传的文件数目
out.println(count + " 个文件 已成功上传.");
%>
</BODY>
</HTML>
得到的上传信息如图7-5所示
图7-5 上传文件,并显示文件信息
在网站中文件上传已经是常用的功能了,考虑到网络的传输速度等原因,多数情况上传的文件不应该很大,这就需要对用户上传文件的大小和类型进行限制。jspSmartUpload组件SmartUpload类提供了有效的方法来解决这些问题。例如:设置允许上传文件的大小限制用setMaxFileSize()方法,允许上传指定的带有指定扩展名的文件用setAllowedFilesList(String allowedFilesList)方法,不允许上传带有扩展名的文件用setDeniedFilesList(String allowedFilesList)方法。接下来看一个利用jspSmartUpload上传文件,并对上传文件进行限制的Web程序。
【例7-3】jspSmartUpload上传文件,并对上传文件进行限制
7-3.html
<HTML>
<TITLE>
例7-3 jspSmartUpload 上传文件,并对上传的文件进行限制
</TITLE>
<BODY BGCOLOR="white">
<HR>
<FORM METHOD="POST" ACTION="/jspsmartupload/7-3.jsp"
ENCTYPE="multipart/form-data">
<INPUT TYPE="FILE" NAME="FILE1" SIZE="50"><BR>
<INPUT TYPE="FILE" NAME="FILE2" SIZE="50"><BR>
<INPUT TYPE="FILE" NAME="FILE3" SIZE="50"><BR>
<BR>
<INPUT TYPE="SUBMIT" VALUE="上传">
</FORM>
</BODY>
</HTML>
7-3.html执行结果如图7-6所示
图7-6 7-3.html执行结果
接下来定义7-3.jsp处理程序
<%@ page contentType="text/html;charset=GB2312" %>
<%@ page language="java" import="com.jspsmart.upload.*"%>
<jsp:useBean id="mySmartUpload"
scope="page" class="com.jspsmart.upload.SmartUpload" />
<HTML>
<TITLE>
例7-3 jspSmartUpload 上传文件,并对上传的文件进行限制
</TITLE>
<BODY BGCOLOR="white">
<HR>
<%
int count=0;
mySmartUpload.initialize(pageContext);
// 只允许上传htm/html/txt/bmp/gif/mp3类型文件
mySmartUpload.setAllowedFilesList("htm,html,txt,bmp,gif,mp3,,");
//设置不允许上传的文件类型exe/bat/zip
// mySmartUpload.setDeniedFilesList("exe,bat,zip");
//设置允许上传文件的大小限制
// mySmartUpload.setMaxFileSize(50000);
mySmartUpload.upload();
//在服务器的上传目录中使用原始文件名保存文件
try {
count = mySmartUpload.save("/upload", mySmartUpload.SAVE_VIRTUAL);
} catch (Exception e){
out.println("<b>错误信息 : </b>" + e.toString());
}
//显示成功上传的文件数目
out.println(count + " 个文件 己经成功上传。");
%>
</BODY>
</HTML>
当按指定类型(htm/html/txt/bmp/gif/mp3类型)上传文件时,得到的上传处理信息,如图7-7所示。
图7-7 上传文件成功信息
当不按指定类型(禁止exe/bat/zip类型)上传文件时,就会抛出异常信息,如图7-7所示。
图7-7 抛出异常信息
FileUpload组件将页面提交的所有元素(普通form表单域,如text和文件域file)都看作一样的FileItem,这样上传页面提交的request请求也就是一个FileItem的有序组合,FileUpload组件可以解析该request,并返回一个的FileItem。而对每一个FileItem,FileUpload组件可以判断出它是普通form表单域还是文件file域,从而根据不同的类型,采取不同的操作:如果是普通的表单域,就读出其值,如果是文件域,就保存文件到服务器硬盘上或者内存中。
本小节将通过两个实例,演示FileUpload组件的用法。
【例7-4】利用apachefileupload组件进行文件上传
7-4.html
<HTML>
<TITLE>
例7-4 apachefileupload 上传文件
</TITLE>
<BODY BGCOLOR="white">
<HR>
<FORM METHOD="POST" ACTION="/apachefileupload/7-4.jsp"
ENCTYPE="multipart/form-data">
<INPUT TYPE="FILE" NAME="FILE1" SIZE="50"><BR>
<INPUT TYPE="FILE" NAME="FILE2" SIZE="50"><BR>
<INPUT TYPE="FILE" NAME="FILE3" SIZE="50"><BR>
<BR>
<INPUT TYPE="SUBMIT" VALUE="上传">
</FORM>
</BODY>
</HTML>
【注意】必须保证表单的ENCTYPE属性值为multipart/form-data,这样浏览器才能正确执行上传文件的操作。
7-4.html执行结果如图7-8所示
图7-8 7-4.html执行结果
接下来编写7-4.jsp处理程序。首先创建一个DiskFileUpload对象,通过它来解析请求。执行解析后,所有的表单都保存在一个List中。然后通过循环依次获得List里的FieldItem对象。
7-4.jsp
<%@ page contentType="text/html;charset=GB2312" %>
<%@ page import="org.apache.commons.fileupload.*"%>
<%@ page import="java.util.*"%>
<%@ page import="java.io.*"%>
<HTML>
<TITLE>例7-4 apachefileupload 上传文件</TITLE>
<BODY BGCOLOR="white">
<%
try {
//创建一个DiskFileUpload对象,通过它来解析请求
DiskFileUpload fu = new DiskFileUpload();
// 得到所有的文件:
List fileItems = fu.parseRequest(request);
%>
<TABLE BORDER=1 bordercolor="Black">
<TR><TD WIDTH="500">文件名</TD><TD WIDTH="100">文件长度</TD> </TR>
<%
Iterator i = fileItems.iterator();
// 依次处理每一个文件:
while(i.hasNext()){
FileItem fi = (FileItem)i.next();
// 获得文件名,这个文件名包括路径:
String fileName = fi.getName();
if(fileName!=null && fileName.length()!=0) {
File fullFile = new File(fi.getName());
File savedFile =
new File(application.getRealPath("/")+"/uploadfiles",_
fullFile.getName());
fi.write(savedFile);
%>
<TR><TD WIDTH="500"><%=fi.getName()%> </TD>
<TD WIDTH="100"><%=fi.getSize()%></TD> </TR>
<%
}
}
%>
</TABLE>
<%
out.println("己经成功上传" );
}
catch(Exception e) {
e.printStackTrace();
out.println(e.getMessage());
}
%>
</BODY>
</HTML>
7-4.jsp执行后页面所如图7-9所示。
图7-9 7-4.jsp执行后页面
【例7-5】利用apachefileupload组件进行文件上传,并对上传文件进限制。
7-5.html
<HTML>
<TITLE>
例7-5 apachefileupload 上传文件,并对上传文件进行限制
</TITLE>
<BODY BGCOLOR="white">
<HR>
<FORM METHOD="POST" ACTION="/apachefileupload/7-5.jsp"
ENCTYPE="multipart/form-data">
<INPUT TYPE="FILE" NAME="FILE1" SIZE="50"><BR>
<INPUT TYPE="SUBMIT" VALUE="上传">
</FORM>
</BODY>
</HTML>
7-5.hmtl执行结果如所图7-10所示
图7-10 7-5.hmtl执行结果
接下来编写7-5.jsp处理程序:处理程序首先检查服务器目录,是否有upload目录,如没有就创建;然后检查unload目录是否有tmp目录,如没有就创建;接下来就是进行上传操作。
7-5.jsp
<HTML>
<TITLE>
例7-5 apachefileupload 上传文件,并对上传文件进行限制
</TITLE>
</HTML>
<%@ page contentType="text/html;charset=GB2312" %>
<%@ page import="org.apache.commons.fileupload.*"%>
<%@ page import="java.util.*"%>
<%@ page import="java.io.*"%>
<%
File uploadPath=new File("c:\\upload");//上传文件目录
if(!uploadPath.exists()){
uploadPath.mkdirs();
}
String tempPath="c:\\upload\\tmp\\"; // 临时文件目录
File tempPathFile=new File("c:\\upload\\tmp");
if(!tempPathFile.exists()){
tempPathFile.mkdirs();
}
try {
DiskFileUpload fu = new DiskFileUpload();
// 设置最大文件尺寸 1MB
fu.setSizeMax(10485760);
// 设置缓冲区大小,这里是4KB
fu.setSizeThreshold(4096);
// 设置临时目录:
fu.setRepositoryPath(tempPath);
// 得到所有的文件:
List fileItems = fu.parseRequest(request);
Iterator i = fileItems.iterator();
// 依次处理每一个文件:
while(i.hasNext()) {
FileItem fi = (FileItem)i.next();
// 获得文件名,这个文件名包括路径:
String fileName = fi.getName();
if(fileName!=null) {
File fullFile = new File(fi.getName());
File savedFile = new File(uploadPath ,fullFile.getName());
fi.write(savedFile);
}
}
out.println("文件已经成功上传");
}
catch(Exception e) {
out.println(e.getMessage());
}
%>
7-5.jsp处理程序上传文件成功提示页面如下图7-11所示。
图7-11 7-5.jsp 文件上传成功提示页面
当文件违反限制进行上传时,处理程序会出现提示页面如图7-12所示。
图7-12 7-5.jsp 文件违反限制上传时出现的提示页面
在这个文件中需要注意的是FileUpload对象的一些参数值的意义,如下面代码所示的三个参数sizeMax、sizeThreshold、repositoryPath:
// 设置允许用户上传文件大小,单位:字节
fu.setSizeMax(104857600);
// 设置最多只允许在内存中存储的数据,单位:字节
fu.setSizeThreshold(4096);
// 设置一旦文件大小超过getSizeThreshold()的值时数据存放在硬盘的目录
fu.setRepositoryPath(tempPath);
这3个参数的意义分别为:
sizeMax:用来设置上传文件大小的最大值,一旦用户上传的文件大小超过该值时将会抛出一个FileUploadException异常,提示文件太大;
sizeThreshold:设置内存中缓冲区的大小,一旦文件的大小超过该值的时候,程序会自动将其它数据存放在repositoryPath指定的目录下作为缓冲。合理设置该参数的值可以保证服务器稳定高效的运行;
repositoryPath:指定缓冲区目录。
【专家提示】从实际应用的情况来看要想上传文件的程序能够稳定高效的工作,其中参数SizeThreshold的值至关重要,设置太大会占用过多的内存,设置太小会频繁使用硬盘作为缓冲以致牺牲性能。因此,设置该值时要根据用户上传文件大小分布情况来设定。例如大部分文件大小集中在100KB左右,则可以使用100KB作为该参数的值,当然了再大就不太合适了。
jspSmartUpload组件提供上传和下载功能,而FileUpload组件只提供上传功能。下面这个例子演示了利用jspSmartUpload组件进行文件下载操作。
【例7-7】利用 jspSmartUpload下载文件
首先编写下载页面7-7.html中的代码,该文件链接到下载处理程序7-7.jsp。
7-7.html
<HTML>
<TITLE>
例7-7 jspSmartUpload 下载文件
</TITLE>
<BODY BGCOLOR="white">
<HR>
<A HREF="/jspsmartupload/7-7.jsp"> readme.txt 下载</A>
</BODY>
</HTML>
7-7.html执行结果如图7-13所示
图7-13 7-7.html执行结果
接下来编写7-7.jsp处理程序
7-7.jsp
<jsp:useBean id="mySmartUpload"
scope="page" class="com.jspsmart.upload.SmartUpload" /><%
mySmartUpload.initialize(pageContext);
// 设定contentDisposition为null以禁止浏览器自动打开文件,
//保证点击链接后是下载文件。若不设定,则下载的文件扩展名为txt时,直接在浏览器打开。
//文件类型为doc时,浏览器将自动用word打开它。扩展名为pdf时浏览器将用acrobat打开。
mySmartUpload.setContentDisposition(null);
// 下载文件
mySmartUpload.downloadFile("/upload/readme.txt");%>
第8章 JSP数据库开发
【本章专家知识导学】
在应用系统中将数据存入数据库中最大的好处是能利用开发技术(如:JSP、ASP、PHP和Perl)对数据作出分析,并按照不同的需求而呈现出不同的内容。例如:许多网站所提供的个性化服务,搜索引擎,在线订票应用等皆以数据库作为后端架构衍生出来的应用。
本章主要探讨JDBC的架构与运作方式,让读者对JDBC有完整的基础概念后,能够快速上手JSP与数据库的运用,并介绍了一些数据库操作时常用的技术,如使用存储过程、使用事务处理等。
8.1 JDBC概述
JDBC(Java Database Connectivity)是Sun提供的一套数据库编程标准API接口,由Java语言编写的类、接口组成,其体系结构如图8-1所示。
图8-1 JDBC体系结构图
用JDBC写的程序能够自动地将SQL语句传送给相应的数据库管理系统。不但如此,使用Java编写的应用程序可以在任何支持Java的平台上运行,不必在不同的平台上编写不同的应用。Java和JDBC的结合可以让开发人员在开发数据库应用程序时真正实现“一次编写,到处运行!”
连接数据库有四种连接方式。第一种就是ODBC(Open Database Connection 开放式数据库连接)桥连接:ODBC桥连接是通过操作系统里面的数据源连接到各种不同的数据库。绝大多数数据库都支持操作系统里面的数据源。也提供了相应的驱动程序。包括:SQL Server 2000,Oracle9i等等。在JAVA刚开始的时候,SUN公司提供了用于ODBC桥连接的驱动程序JDBC-ODBC桥驱动程序,它把JDBC的调用转换为ODBC操作。这个桥使得所有支持ODBC的DBMS都可以和Java应用程序交互,但仅适用于Windows平台,适用于JDBC初学者。
图8-2 JDBC-ODBC体系结构图
第二种驱动程序也称为部分Java驱动程序(native-API partly-Java Driver),因为它们直接将JDBC API翻译成具体数据库的API。也就是本地库Java驱动程序,将JDBC调用转换为对数据库的客户端API的调用。
图8-3 JDBC本地驱动体系结构图
第三种驱动程序是网络驱动程序(net-protocol all-java driver(JDBC Proxy)),它将JDBC API转换成独立于数据库的协议。JDBC驱动程序并没有直接和数据库进行通讯;它和一个中间件服务器通讯,然后这个中间件服务器和数据库进行通讯。这种额外的中间层次提供了灵活性:可以用相同的代码访问不同的数据库,因为中间件服务器隐藏了Java应用程序的细节。
图8-4 JDBC网络驱动程序体系结构图
第四种驱动程序是纯Java驱动程序(native-protocal all-Java driver),它直接与数据库进行通讯。很多程序员认为这是最好的驱动程序,因为它通常提供了最佳的性能,并允许开发者利用特定数据库的功能。当然,这种紧密偶合会影响灵活性,特别是如果您需要改变应用程序中的底层数据库时。这种驱动程序通常用于applet和其它高度分布的应用程序。适用于企业应用,如图8-5所示。
图8-5 JDBC纯Java驱动程序体系结构图
编写正确且遵守规范的Java程序,可以无需重新编译就在任何启用Java技术的平台上运行。Java编程语言彻底地进行了规定。根据定义,启用Java技术的平台必须支持已知的核心库。就java.sql包或javax.sql包或者JDBC就是这样一个库,它们可以视为ODBC的可移植版本,且其本身就是重大的标准。Java编程语言和JDBC一起使用,可以给编写数据库应用程序提供正确的可移植性解决方案。JDBC驱动程序就是JAVA类,它实现JDBC驱动程序接口,并可以为特别的数据库转换程序(一般是SQL)请求。无疑,驱动程序在这里起了重要作用。大多数的数据库供应商现在都提供驱动程序,以实现特定系统的JDBC API,这些驱动程序通常都是免费提供的。JDBC编程的核心包为java.sql包,其结构如图8-6所示。
图8-6 java.sql包结构图
第一步:加载驱动程序。
为了与特定的数据库相连,JDBC必须加载相应的驱动程序。如:
try {
//加载JDBC-ODBC驱动
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
以下是加载各种不同的数据库驱动程序的方法:
//SQL Server
Class.forName("com.microsoft.jdbc.sqlserver.SQLServerDriver");
//MySQL
Class.forName("org.gjt.mm.mysql.Driver");
//Oracle
Class.forName("Oracle.jdbc.driver.OracleDriver");
//Informix
Class.forName("com.informix.jdbc.IfxDriver");
//Sybase
Class.forName("com.sybase.jdbc2.jdbc.SybDriver");
//AS400
Class.forName("com.ibm.as400.access.AS400JDBCConnection");
第二步:将“驱动程序”传递到DriverManager,然后获得“连接”。
DriverManager类的getConnection(String url,String user, String password)方法用于建立与某个数据库的连接。每个JDBC驱动程序使用一个专门的JDBC URL作为自我标识的一种方法。
JDBC URL的格式为:jdbc : <子协议名> : <子名称>
子协议(sub-protocol)与JDBC驱动程序有关,可以是odbc,oracle,db2,mysql,microsoft等等,根据实际的JDBC驱动程序厂商而不同。子名称(数据库定位器)是与驱动程序有关的指示器,用于唯一指定应用程序要和哪个数据库进行交互。根据驱动程序的类型,该定位器可能包括主机名,端口和数据库系统名。以下是连接语句示例:
try{
String url="jdbc:odbc:myodbc";
Connection con=DriverManager.getConnection(url);
// 或者
/*Connection con=DriverManager.getConnection(url,user,password);
}catch(SQLException e){
e.printStackTrace();
以下是连接各种不同的数据库的URL编写方法:
//SQL Server
DriverManager.("jdbc:microsoft:sqlserver://主机:端口号; DatabaseName=数据库名","用户名","密码")
//MySQL
DriverManager.getConnection("jdbc:mysql://主机:端口号:数据库名","用户名","密码")
//Oracle
DriverManager.getConnection("jdbc:Orcale:thin:@主机:端口号:数据库名","用户名","密码")
//Informix
DriverManager.getConnection("jdbc:informix-sqli://主机:端口号/数据库名:
INFORMIXSERVER=informix服务名","用户名","密码")
//Sybase
DriverManager.getConnection("jdbc:sybase:Tds:主机:端口号/数据库名","用户名","密码")
//AS400
DriverManager.getConnection("jdbc:as400://主机”,"用户名","密码")}
第三步:创建语句,Statement ,PreparedStatement,或CallableStatement,并将它们用于更新数据库或执行查询。
Statement 对象用于将SQL语句发送到数据库中。实际上有三种Statement对象,它们都可以作为在给定连接上执行SQL语句的对象:Statement、PreparedStatement( 继承Statement )和 CallableStatement(继承PreparedStatement)。它们都专用于发送特定类型的 SQL 语句:Statement 对象用于执行不带参数的简单 SQL语句;PreparedStatement 对象用于执行带或不带 IN 参数的预编译 SQL 语句;CallableStatement对象用于执行对数据库已存储过程的调用。
第四步:查询并返回包含有已请求数据的ResultSet,该ResultSet是按类型检索的。
ResultSet包含符合SQL语句中条件的所有行,并且它通过一套get方法(这些get方法可以访问当前行中的不同列)提供了对这些行中数据的访问。DatabaseMetaData和ResultSetMetaData接口可以用来提供有关数据库或ResultSet的信息。
第五步:显示数据或根据得到的查询结果完成业务逻辑处理。
第六步:最后关闭ResultSet(结果集)、Statement(语句对象)、Conection(数据库连接)。
【例8-1】通过ODBC建立与数据库的连接
配置Windows下ODBC详细步骤如下(以配置连接SQL Server 2000的pubs数据库为例):
(1)启动SQL Server 2000数据库。
从控制面板里面找到数据源,如图8-7所示。
图8-7 准备创建ODBC数据源
打开数据源,建立连接SQLServer的数据源,创建界面如图8-8所示。
图8-8 创建ODBC数据源
选择系统DSN,点击 按钮。出现图8-9的界面,选择“SQL Server”点击完成,出现图8-10的界面。
图8-9 创建ODBC数据源
图8-10 创建ODBC数据源
输入数据源的名字,如:myodbc。在服务器里面加个点,表示用本地机器上的SQL Server数据库服务器。点击下一步,显示图8-11所示的对话框。
图8-11 创建ODBC数据源
选择 ,输入登陆数据库使用的用户名和密码。这里的用户名和密码是SQLServer里面可用的用户名和密码。点击下一步,显示图8-12所示的对话框。
图8-12 创建ODBC数据源
选择要访问的数据库,这点选择pubs,点击下一步,显示图8-13所示的对话框。
图8-13 创建ODBC数据源
点击完成,显示图 8-14所示的对话框。
图8-14 创建ODBC数据源
测试数据源如果出现图 8-15,说明数据源已经创建成功。
图8-15 创建ODBC数据源
创建Web应用并编写odbc_connection.jsp页面,代码如下。
odbc_connection.jsp
<%@ page language="java" contentType="text/html; charset=GBK"%>
<%@ page import="java.sql.*" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>通过ODBC建立连接</title>
</head>
<body>
<%
Connection con = null;
try {
// 加载ODBC驱动
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
// 通过驱动管理器(DriverManager)获得连接
con = DriverManager.getConnection("jdbc:odbc:myodbc",
"sa","");
// 如果连接不成功,就会出现异常,不会执行下面这个语句
out.println("<H1>");
out.println("通过ODBC数据源连接数据库成功!");
out.println("</H1>");
} catch (Exception e) {// 如果出现异常,会打印堆栈里异常的信息
e.printStackTrace();
} finally {// 用完后,关闭连接,释放资源
try {
if (con != null) // 防止出现内存泄露
con.close();
} catch (Exception e) {
e.printStackTrace();
}
}
%>
</body>
</html>
运行结显示如图8-16所示。
图8-16 odbc_connection.jsp运行结果
【例8-2】通过SQLServer提供的驱动程序获得连接
SQL Server的驱动程序需要用到三个jar文件,它们分别为:msbase.jar、mssqlserver.jar、msutil.jar,然后设置classpath环境变量指向这三个路径。如果只是当前Web应用需要使用,可以将这三个jar文件拷贝到当前Web应用的“WEB-INF/lib”目录下。
sqlserver_connection.jsp
<%@ page language="java" contentType="text/html; charset=GBK"%>
<%@ page import="java.sql.*" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>通过Sqlserver純驱动程序建立连接</title>
</head>
<body>
<%
Connection con = null;
try {
// 加载SQLSERVER的驱动程序
Class.forName("com.microsoft.jdbc.sqlserver.SQLServerDriver");
// 通过驱动来获得一个连接
con = DriverManager.getConnection(
"jdbc:microsoft:sqlserver://localhost:1433;"
+ "databasename=pubs", "sa", "");
// 如果连接不成功,就会出现异常,不会执行下面这个语句
out.println("<H1>");
out.println("通过SQLServer纯驱动程序连接数据库成功!<br> con="+con);
out.println("</H1>");
} catch (Exception e) {// 如果出现异常,会打印堆栈里异常的信息
e.printStackTrace();
} finally {// 用完后,关闭连接,释放资源
try {
if (con != null) // 防止出现内存泄露
con.close();
} catch (Exception e) {
e.printStackTrace();
}
}
%>
</body>
</html>
运行结果显示如图 8-18所示。
图8-18 SQLServer纯Java驱动连接数据库运行结果
【例8-3】通过Oracle提供的驱动程序获得连接
下面介绍如何通过Oracle提供的驱动程序获得连接。需要使用的只有一个jar文件:classes12.jar。设置环境变量classpath 指向该jar文件。如果只是当前Web应用需要使用,可以将这个jar文件拷贝到当前Web应用的“WEB-INF/lib”目录下。
oracle_connection.jsp
<%@ page language="java" contentType="text/html; charset=GBK"%>
<%@ page import="java.sql.*" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>通过oracle純驱动程序建立连接</title>
</head>
<body>
<%
Connection con = null;
try {
// 加载ORACLE9i的驱动程序
Class.forName("oracle.jdbc.driver.OracleDriver");
// 获得连接 oracle数据库的端口号:1521 数据服务器的名字叫ora921
// 登陆的用户名为system,密码为:itjob (默认密码为manager)
con = DriverManager.getConnection(
"jdbc:oracle:thin:@127.0.0.1:1521:ora921", "system","itjob");
// 如果连接不成功,就会出现异常,不会执行下面这个语句
out.println("<H1>");
out.println("通过ora921纯驱动程序连接数据库成功!<br> con="+con);
out.println("</H1>");
} catch (Exception e) {// 如果出现异常,会打印堆栈里异常的信息
e.printStackTrace();
} finally {// 用完后,关闭连接,释放资源
try {
if (con != null) // 防止出现内存泄露
con.close();
} catch (Exception e) {
e.printStackTrace();
}
}
%>
</body>
</html>
运行结果显示如图 8-19所示。
图8-19 Oracle纯Java驱动连接数据库运行结果
【专家提示】Oracle数据库纯Java驱动包并随安装Oracle数据库或客户端一同安装,读者可以从〔Oracle安装目录〕\ jdbc\lib下找到相应的驱动包。
【专家提示】细心的读者会发现我们在演示这个例子时浏览器的URL显示为:
http://localhost:8088/jdbcdemo/oracle_connection.jsp
显然我们将Tomcat的默认HTTP端口8080改为8088了,这是因为我们在本机启动了Oracle数据库服务占用了8080端口。读者可以通过运行netstat –a –b –o命令来查看机器端口占用情况,如图 8-20所示。
图8-20 本机端口被占用情况
可以看到8080端口被Oracle数据库的监听服务进程TNSLSNR.exe进程所占用。
【例8-4】通过连接池获得连接
首先请读者根据在上面创建数据库连接的经验,来思考下列问题:
为什么需要连接池?
什么是连接池?
如何初始化连接池?
如何使用连接池?
当使用DriverManager方法来获取数据库连接时,每个对新数据库连接的请求都会导致很大的开销。如果频繁地获取新的连接,将会影响性能问题。这在Web服务器端编程的时候尤为明显。请求一个新的Connection对象会带来大量的开销和很多潜在的错误。为了最小化开销,为什么在我们使用完数据库连接后不是重新使用它们,而是删除它们呢?JDBC设计者在创建接口ConnectionPoolDataSource时使用这种流行的设计模式,这允许您创建数据库连接池,其中的连接在关闭后可以重用,而不是删除。
PooledConnection是一个特殊类型的数据库连接,在关闭时不会被删除,不象常规的Connection对象(当常规的连接不再被使用时,垃圾收集器能删除它们)。相反,PooledConnection被缓存以备将来再次使用,从而可能带来大幅度的性能提升。
配置Tomcat5数据库连接池
目前几乎所有的应用服务器都支持数据库连接池的创建和管理,接下来我们将具体演示Tomcat中SQLServer数据库连接池的创建和管理步骤。
首先在你要使用连接池的Web应用的META-INF目录下创建一个context.xml文件,如图8-21所示。
图8-21 创建一个context.xml文件
启动Tomcat5后进入Tomcat欢迎页面,如图8-21所示。
图8-22 Tomcat欢迎页面
点击“Tomcat Administration”进入Tomcat的管理员登录页面,如图8-22所示。
图8-23 Tomcat管理员登录页面
输入用户名 admin 密码 (安装时指定如果忘记了可以打开{Tomcat安装目录}\conf\ tomcat-users.xml文件查看)后点击Login按钮进入管理配置页面如图8-23所示。
图8-24 Tomcat管理配置页面
选择jdbcdemo web应用程序下的Data Sources选择Create New Data Source创建一个新的数据源,如图 8-24所示。
图8-25 Tomcat数据连接池管理配置页面
填写如下参数:
JNDI Name:jdbc/sqlserver(将来以此来查找数据库连接池)。
Data Source URL(数据源URL):
jdbc:microsoft:sqlserver://localhost:1433;databasename=pubs
User Name:sa
Vaildation Query:select 1
然后点击Save按钮保存数据库连接池的设置,如图 8-25所示。
图8-26 Tomcat数据连接池管理配置页面
点击“Commit Changes”按钮向服务器提交设置。
图8-27 Tomcat数据连接池管理配置页面
点击确定按钮完成配置
pool_connection.jsp
<%@ page language="java" contentType="text/html; charset=GBK"%>
<%@ page import="java.sql.*,javax.naming.*,javax.sql.*" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>通过数据库连接池建立连接</title>
</head>
<body>
<%
Connection con = null;
try {
Context initContext = new InitialContext();
Context envContext = (Context)initContext.lookup("java:/comp/env");
DataSource ds = (DataSource)envContext.lookup("jdbc/sqlserver");
con = ds.getConnection();
out.println("<H1>");
out.println("第一次通过数据库连接池连接数据库成功!<br> con="+con);
out.println("</H1>");
con.close();
con = ds.getConnection();
out.println("<H1>");
out.println("第二次通过数据库连接池连接数据库成功!<br> con="+con);
out.println("</H1>");
con.close();
} catch (Exception e) {// 如果出现异常,会打印堆栈里异常的信息
e.printStackTrace();
}
%>
%>
</body>
</html>
运行结果,显示如图 8-28所示。
图8-28 pool_connection.jsp页面运行显示结果
从运行显示结果来看我们两次取得的数据库连接对象为同一个对象。
【专家提示】请读者注意由于连接池对象是在构建Web应用时由容器创建并管理的,此时Web应用并没有开始被加载。如果采用Java纯驱动来连接数据库的驱动程序包应由原来放在“WEB-INF/lib”目录移到“{Tomcat安装目}/shared/lib”目录下,否则会抛出找不到相应驱动程序包而产生异常。
第9章 XML操作
【本章专家知识导学】
XML(eXtensible Markup Language,可扩展的标记语言)是一种在Internet上被广泛应用的标记语言,它将SGML的丰富功能与HTML的易用性结合到Web的应用中,常用来作为各种网络应用系统的数据交换报文、描述系统配置参数文件等。
操作XML文件的开源软件接口包括DOM 、SAX、JDOM等,程序员需要熟悉并掌握这些常用的API接口的使用,能够通过这些接口来编写Java语句来实现对XML文件的处理。
本章将在介绍XML、DTD等基础知识后,详细解说DOM 、SAX、JDOM对XML文件的处理方法。
9.1 XML概述
XML即可扩展的标记语言,可以定义语义标记,是元标记语言。XML不像HTML(Hyper Text Markup Language,超文本标记语言),HTML只能使用规定的标记,不能扩展;而对于XML用户则可以定义自己所需要的标记。XML和HTML师出同门,都是从SGML(Standard Generalized Markup Language)延伸发展而来的标记语言。
XML是一个精简的SGML,它将SGML的丰富功能与HTML的易用性结合起来。XML保留了SGML的可扩展功能,这使XML从根本上有别于HTML。XML要比HTML强大得多,它不再是固定的标记,而是允许定义数量不限的标志来描述文件中的资料,允许嵌套的信息结构。HTML只是Web页面用来显示数据的通用方法,而XML提供了一个直接处理Web数据的通用方法。HTML着重描述Web页面的显示格式,而XML着重描述的是Web页面的内容的结构。
XML正被越来越广泛地应用,它可以用来表示数据和数据结构;可以用来定义数据规范;在企业间交换数据时作为信息的载体——XML报文等。XML已成为Internet中不可缺少的技术,JSP作为Web编程的语言就有可能需要处理XML文件。
程序员可以自行编写Java程序操作XML文件,操作时可以使用DOM、SAX、JDOM等流行的开源软件接口操作XML文件。然而,在JSP页面中插入处理XML文件的Java代码将使得JSP页面代码冗长、逻辑复杂。
由于超文本标记语言HTML的简单易学、句法简明紧凑等优点,使得它在Web主页上大显身手。HTML只是提供了在网络上数据显示方式的通用方法,它不描述数据间的逻辑结构。可扩展标记语言XML(Extensible Markup Language)从不同角度解决了HTML存在的问题。XML同HTML一样是一种标记语言,它们都来自于标准通用标记语言SGML。但是XML提供了一个描述数据逻辑结构和交换数据的有效手段,显示方式提交给样式表来处理,这是它与HTML的最大区别。
总而言之,XML 是一种元标记语言,该语言提供一种描述结构数据的格式。这有助于更精确地声明内容,方便跨越多种平台的更有意义的搜索结果。此外,XML 将起用新一代的基于 Web 的数据查询和处理应用程序。
XML文件的编辑可以使用一些基本的文本编辑器进行,如记事本、写字板等。当然,如果使用所见即所得的编辑器就更方便了,如XML.Spy。在编写好代码后将文件另存为后缀名为xml的文件。在任何支持XML的浏览器中打开XML文件(或直接双击文件)即可看到显示结果。一个完整的XML文件一般有三个部分构成:XML版本说明、文件类型定义和文件主体,下面看一个简单的XML文件结构的例子。
【例9-1】一个简单的XML文件示例
links.xml
<?xml version="1.0" encoding="gb2312"?>
<links>
<link>
<text>JSP Insider</text>
<url newWindow="no">http://www.jspinsider.com</url>
<author>周雄伟</author>
<date>
<day>16</day>
<month>6</month>
<year>2007</year>
</date>
<description>A JSP information site.</description>
</link>
<link>
<text>The makers of Java</text>
<url newWindow="no">http://java.sun.com</url>
<author>Sun Microsystems</author>
<date>
<day>17</day>
<month>6</month>
<year>2007</year>
</date>
<description>Sun Microsystem's website.</description>
</link>
<link>
<text>The standard JSP container</text>
<url newWindow="no">http://jakarta.apache.org</url>
<author>Apache Group</author>
<date>
<day>18</day>
<month>6</month>
<year>2007</year>
</date>
<description>Some great software.</description>
</link>
</links>
该文件用来保存用户数据,双击links.xml文件,该文件在IE浏览器中的显示效果如图9-1所示。
图9-1 一个简单的XML文件示例运行效果图
文件的第一行如下:
<?xml version="1.0" encoding="gb2312"?>
这是XML 处理指令声明的第一句,即XML版本说明,它以“<?”开始,以“?>”结束。每个XML文件都是从一个XML版本说明开始,其作用是告诉浏览器或者其它处理程序:这个文件是XML文件。XML版本信息说明格式如下:
<?xml version="版本号" standalone="yes/no" encoding="UTF-8"?>
其中:
version:表示文件遵守的XML规范的版本,在本例中的version 是1.0;
standalone:表示文件内部包含文件类型定义DTD。如果无,参数为no,默认值为no;
encoding:表示文件所用的字符编码,一般是UTF-8或UTF-16,默认UTF-8。如果希望XML文件中可以使用中文标志和处理中文字符,必须说明为gb2312。
【专家提示】<? xml version=…?> 必须是文件的第一行,并且前面不能有空格。
例子中接下来的语句是XML文件主体。XML文件类型定义了文件的逻辑结构,与XML文件类型对应的数据叫XML文件的主体,也简称为XML文件。XML文件主体部分是由XML元素属性值组成的。一个XML 元素由一个起始标记、一个结束标记,以及夹在这两个标记之间的数据内容所组成,它用于标识XML文件元素的属性值。其基本形式如下:
<标记名> 数据内容 </标记名>
其中文件根元素则是一个可以包含多个嵌套子元素的顶层元素。在例9-1中<links>和 </links>就是根元素,它包含了另外的四个元素。
在XML文件中,标记大小写是有区别的,并且必须配对使用。<P>和<p>是不同的标志。在定义元素时,前后标志大小写要保持一样。例如<author>张驰</author>,写成<Author>张驰</author>是错误的。
【专家提示】XML是为了便于阅读和理解而在XML文件中附加的信息,它们将不会被扫描程序解释或浏览器显示。注释的语法格式与HTML中的一样。
【专家提示】一个XML 元素的下一级节点可以用“标记名=数据内容”这种“name=value”键值对的方式表示。如例子中的第一个date节点可以表示为:
……..
<link day="16" month="6" year="2007">
</link>
…….
XML文件的类型定义简称DTD(Document Type Describtion,文档类型描述),它指明了文件主体必须符合的规范。例子中没有DTD,这部分的知识在将下一节中讲述。
XML文件的类型定义简称DTD,它指明了文件主体必须符合的规范。在一个DTD中,具体规定了引用该DTD的XML文件可使用哪些标记、父元素中能够包括哪些子元素、各个元素出现的先后顺序、元素可包含的属性、元素和属性值的数据类型,以及可使用的实体及符号规则等。
DTD可以在XML文件内部直接定义,也可以是一个完全独立的文件。DTD由许多约定和声明语句构成,这些语句可以包含在XML文件内部,被称为内部DTD;也可以独立保存为一个文件,而称为外部DTD,此时,在XML文件使用到它时只要直接引用它就可以了。
DTD主要具有下列几方面的作用:
(1)可以验证XML 文件数据的有效性。
(2)可以为某类XML文件提供统一的格式和相同的结构。
(3)可以保证在一定范围内,XML 文件数据的交流和共享。
(4)应用程序设计人员根据DTD 就能够知道对应XML 文件的逻辑结构,从而编写出相应的处理应用程序。
在一份DTD 中,包含了对XML 文件所使用的元素、元素间的关系、元素可用的属性、可使用的实体等的定义规则。一份DTD 实际上是若干条有关元素、属性、实体等定义和声明语句的集合。
例9-1中links.xml文件中的XML文件的类型定义DTD可以是如下的内容:
<? DOCTYPE links[
<!ELEMENT link(text,URL,athuor,date)>
<!ELEMENT text(#PCDATA)>
<!ELEMENT URL(#PCDATA)>
<!ELEMENT athuor(#PCDATA)>
<!ELEMENT date(#PCDATA)>
<!ELEMENTdater(day,month,year)
<!ELEMENT day(#PCDATA)>
<!ELEMENT month(#PCDATA)>
<!ELEMENT year(#PCDATA)>
]>
在DTD中可以包含下列各种声明语句:
(1)DTD 声明开始语句。
(2)元素类型声明语句。
(3)属性列表声明语句。
(4)实体声明语句。
(5)注释语句。
下面对这些内容进行简单的说明。
DTD的声明根据使用情况的不同,一般可以分内部DTD、外部DTD和混合DTD的引用。
(1)引用内部DTD
DTD的声明语法格式如下:
<!DOCTYPE 根元素名称 [DTD声明语句序列]>
其中:
“<!DOCTYPE”表示DTD声明的开始,关键字DOCTYPE必须大写。
根元素名称是指定XML文件的根元素名称,这个根元素名称必须精确地与文件中实际的根元素名称一致。
DTD声明语句序列是指包含在一对方括号([ ])之内的若干条语句,用来对XML文件中所使用的元素、属性和实体等进行具体声明。
【专家提示】在XML文件中引用内部DTD时,应该在文件开头的XML声明语句中添加standalone="yes" 的说明。
(2)引用外部DTD
引用外部DTD首先要创建外部DTD。外部DTD 是一个独立于XML 文件的文件,使用.dtd为其文件扩展名。此种文件实际上也是一个文本文件,可用任何文本编辑器创建。在外部DTD 中,除了没有内部DTD 中的“<!DOCTYPE 根元素名称” 语句之外,其他声明语句都是一样的。
根据外部DTD性质的不同,又可将其分为私有DTD文件和公共DTD文件。私有DTD文件是指并未公开的DTD文件,通常属于某个组织内部或个人所有;公共DTD文件则是为某一应用领域或行业所制定,被国际上的标准组织或行业组织广泛认可的、公开的、标准的DTD文件。
引用私有DTD文件的语法格式为:
<!DOCTYPE根元素名称SYSTEM DTD的URL>
引用公共DTD文件的语法格式为:
<!DOCTYPE根元素名称PUBLIC DTD名 DTD的URL>
【专家提示】在XML文件中引用外部DTD时,应该在开头的XML声明语句中添加standalone="no" 说明。
(3)混合引用DTD
混合引用DTD是指在一个带有内部DTD的XML文件中,再引用一个或多个外部DTD来共同规范文件中的内容。
DTD属性声明的语法:
<! ATTLIST Element_Name
Attribute_Name Type [added_declare]
Attribute_Name Type [added_declare]
……
>
其中:
<!ATTLIST:表示属性定义语句的开始,ATTLIST是关键字,必须大写。
Element_Name:元素名,用来指定对该元素的属性进行声明。
Attribute_Name:该元素具有的某个属性名,属性的命名规则与元素的命名规则是一致的。
Type:属性的数据类型。
added_declare:属性的附加声明,是一个可选项。
对于DTD属性声明的内容比较多,使用也不是很多,对这一部分感兴趣的读者可以参考有关XML的专门书籍。
在XML规格中,实体(Entity)具有广泛的含义,通常是指有效的XML文件本身、外部的DTD子集、定义成DTD中外部实体的外部文件或在DTD中定义的用引号括起来的字符串等与XM 文件相关的储存单元。XML的实体都有一个名称,用这个名称来代替这些数据。
XML有两种类型的实体。一种是在XML文件中使用的实体;另一种是参数实体,只在DTD文件中使用。
在DTD文件中使用的实体的定义格式为:
<!ENTITY [%] Entity_Name Entity_Value >或
<!ENTITY [%] Entity_Name SYSTEM Entity_URL>
其中:
<!ENTITY :表示开始声明一个实体,关键字ENTITY必须大写。
[%]:[]表示可选项,%表示声明的是一个参数实体。
Entity_Name :表示内部参数实体的名称。
Entity_Value :表示实体的内容。
SYSTEM :是定义为外部实体的关键字。
Entity_URL :外部实体文件的URL。
已定义的实体在文件中的引用格式为:&实体名;
例如定义版权信息的实体:
<!DOCTYPE copyright [<!ENTITY copyright "版权所有 所有人:周雄伟">]>
如果版权信息内容和他人共享一个XML文件,也可以外部引用它,外部引用格式实体为©right。
【例9-2】带实体元素的XML文件示例
entity.xml
<?xml version="1.0" encoding="GB2312"?>
<!DOCTYPE copyright [<!ENTITY copyright "版权所有 所有人:周雄伟">]>
<myfile>
<title>lucene开发设计</title>
<author>周雄伟等</author>
<email> daweycs@163.com</email>
<date>20020715</date>
©right;
</myfile>
带实体元素的XML文件示例entity.xml文件在IE中显示如图9-2所示。
图9-2 带实体元素的XML文件示例运行效果图
XML中标志都是用户创建的,在不同的DTD文件中,有可能出现含义不同但名称相同的标志。当在一个XML文件中使用多个DTD文件时,就有可能出现标志同名的矛盾,这会引起数据混乱。
例如下面的文件表示桌子:
<table>wood table</table>
下面这一个文件却表示表格:
<table>namelist</table>
如果需要同时处理这两个文件,就会发生名字冲突。
Namespaces实际上就是名字空间。引进了Namespaces这个概念就能解决这个问题。Namespaces通过给标志名称加一个Namespaces名(URL)定位以区别这些名称相同的标志。
Namespaces名同样需要在XML文件的开头部分说明,说明的格式如下:
<document xmlns:yourname='URL'>
其中yourname是定义的Namespaces的名称,URL就是名字空间的网址。
假设上面的"桌子<table>"文件来自http://www.zhuozi.com,可以说明为
<document xmlns:itsname='http://www.zhuozi.com'>
然后在后面的标志中使用定义好的名字空间:
<itsname :table>wood table</table>
这样就将这两个<table>区分开来。
【专家提示】设置URL并不是说这个标志真的要到那个网址去读取,仅仅作为一种区别的标志而已。
在Java中对XML的解析接口常用的有3大类:基于DOM(Document Object Model)的解析接口、基于SAX(Simple API for XML)的解析接口和基于JDOM(Java Document Object Model)技术的解析接口。解析器实际上就是一段代码,它读入一个XML文件并分析其结构。目前主流的解析器有:JAXP(Java API for XML Processing)、Xerces(Apache)、XML4J(IBM)和xalan等,主流的解析器都支持SAX和DOM。支持JDOM的解析器目前只有SUN公司发布的jdom包。
DOM即文件对象模型。在应用程序中,基于DOM的XML分析器将一个XML文件转换成了一个对象模型的集合(这个集合通常被称为DOM树),应用程序可以通过对该对象模型的操作,实现对XML文件中数据的操作。
(1)Document类
Document类描述了整个XML的文件语法结构,它包含了一系列Node类形成的树形结构。程序员可以先扫描XML源文件,得到相应的Document对象,遍历这颗树来得到XML文件的所有内容,这是对XML文件操作的起点。然后再继续其他对XML文件的操作。
Document类包含了创建相关类对象的方法,主要有:
createAttribute(String):用给定的属性名创建一个Attribute对象.,然后可使用setAttributeNode方法将其放置在一个Element对象上面。
createElement(String):用给定的参数创建一个Element对象,代表XML文件中的一个标志,然后可以在这个Element对象上添加属性或进行其它操作。
createTextNode(String):用给定的字符串创建一个Text对象,Text对象代表了标志或者属性中所包含的纯文本字符串。如果在一个标志内没有其它的标志,那么标志内的文本所代表的Text对象是这个Element对象的唯一子对象。
getElementsByTagName(String):返回特定标志元素的NodeList对象。
getDocumentElement():返回一个代表DOM根节点的Element对象,也就是代表XML文件根元素的那个对象。
(2)Node对象
Node类是DOM结构中最为基本的类,它描述了文件树中的一个抽象的节点。它包含类型为Element、Attr、Text和其他类的特征。Node对象引用的其成员变量来操作XML文件。Node包含的主要方法有:
appendChild(org.w3c.dom.Node):为这个节点添加一个子节点,并放在所有子节点的最后,如果这个子节点已经存在,则先把它删掉再添加进去。
getFirstChild():如果节点存在子节点,则返回第一个子节点,对等的,还有getLastChild()方法返回最后一个子节点。
getNextSibling():返回DOM树中这个节点的下一个兄弟节点,对等的,还有getPreviousSibling()方法返回其前一个兄弟节点。
getNodeName():根据节点的类型返回节点的名称。
getNodeType():返回节点的类型。
getNodeValue():返回节点的值。
hasChildNodes():判断是不是存在有子节点。
hasAttributes():判断这个节点是否存在有属性。
getOwnerDocument():返回节点所处的Document对象
insertBefore(org.w3c.dom.Node new,org.w3c.dom.Node ref):在给定的一个子对象前再插入一个子对象。
removeChild(org.w3c.dom.Node):删除给定的子节点对象。
replaceChild(org.w3c.dom.Node new,org.w3c.dom.Node old):用一个新的Node对象代替给定的子节点对象。
(3) NodeList对象
NodeList对象,顾名思义,就是代表了一个包含了一个或者多个Node的列表。可以通过下列方法获得列表中的元素:
getLength():返回列表的长度。
item(int):返回指定位置的Node对象。
(4) Element对象
Element类描述XML文件中的标志元素,继承于Node,亦是Node的最主要的子对象。在标志中可以包含有属性,因而Element对象中有存取其属性的方法,而任何Node中定义的方法,Element都继承下来。Element对象所包含的主要方法有:
getElementsByTagName(String):返回一个NodeList对象,它包含了在这个标志中其下的子孙节点中具有给定标志名字的标志。
getTagName():返回一个代表这个标志名字的字符串。
getAttribute(String):返回标志中给定属性名称的属性的值。在这儿需要注意的是,因为XML文件中允许有实体属性出现,而这个方法对这些实体属性并不适用。这时候需要用getAttributeNodes()方法来得到一个Attribute对象来进行进一步的操作。
getAttributeNode(String):返回一个代表给定属性名称的Attribute对象。
(5) Attribute对象
Attribute对象代表了某个标志中的属性。Attribute继承Node,但是因为Attr实际上是包含在Element中的,它不能被看作是Element的子对象。在DOM中Attributer并不是DOM树的一个节点,所以Node中的getparentNode(),getpreviousSibling()和getnextSibling()返回的都将是null。也就是说,Attribute是Element类的一部分,它并不作为DOM树中单独的一个节点出现。这一点在使用的时候要同其它的Node子对象相区别。
DOM类在DOM中都是用接口描述语言IDL定义的,因而,DOM可以映射到任何面向对象的语言,只要它实现了DOM所定义的接口就可以了。许多公司和厂家提供了符合DOM规范的DOM接口和程序包。下面以微软的DOM接口为例,讲述DOM的使用。
DOM读取XML文件通常要用到以下五个基本的步骤:
(1)建立一个解析器工厂。
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
(2)以利用这个工厂来获得一个具体的解析器对象。
DocumentBuilder builder= factory.newDocumentBuilder();
(3)利用DocumentBuilder的parse()方法接受一个XML文件名作为输入参数,返回一个Document对象。Document对象代表了一个XML文件的树模型。
Document doc=builder.parse("candidate.xml");
(4)使用Document对象的getElementsByTagName()方法,我们可以得到一个NodeList对象,它是XML文件中的标签元素列表,可以使用NodeList对象的item()方法来得到列表中的每一个Node对象。
NodeList nl =doc.getElementsByTagName("PERSON");
Element node=(Element) nl.item(i);
(5)通过Node对象的getNodeValue()方法提取某个标签内的内容。
node.getElementsByTagName("NAME").item(0).getFirstChild().getNodeValue()
下面我们来看一个具体的DOM读取XML文件的示例。
【例9-3】DOM读取XML文件示例
本例将DOM读取XML文件示例程序是对例9-1的links.xml文件的用户数据读取出来并用表格的方式在IE浏览器上显示。源程序如下:
display.jsp
<%@ page contentType="text/html;charset=gb2312"%>
<%@page import="javax.xml.parsers.*,java.io.*,java.util.*, "%>
<%@page import=org.w3c.dom.*,org.apache.xml.serialize.* "%>
<html>
<head>
<title> DOM读取XML文件示例</title>
</head>
<body>
<table border=1>
<!--输出表头-->
<tr >
<td>content</td>
<td>url</td>
<td>author</td>
<td>date</td>
<td>description</td>
</tr>
<%
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();//(1)
DocumentBuilder builder=factory.newDocumentBuilder();//(2)
Document doc=builder.parse("link.xml");//(3)
doc.normalize();
NodeList links =doc.getElementsByTagName("link");//(4)
<!--输出数据-->
for (int i=0;i<links.getLength();i++){
Element link=(Element) links.item(i);//(4)
out.print("<tr>");
out.println("<td>"+link.getElementsByTagName("text").item(0).getFirstChild().getNodeValue()+"</td>");//(5)
out.println("<td>"+link.getElementsByTagName("url").item(0).getFirstChild().getNodeValue()+"</td>");//(5)
out.println("<td>"+link.getElementsByTagName("author").item(0).getFirstChild().getNodeValue()+"</td>");//(5)
Element linkdate=(Element) link.getElementsByTagName("date").item(0); //(4)
String day=linkdate.getElementsByTagName("day").item(0).getFirstChild().getNodeValue();//(5)
String month=linkdate.getElementsByTagName("month").item(0).getFirstChild().getNodeValue();
String year=linkdate.getElementsByTagName("year").item(0).getFirstChild().getNodeValue();
out.println("<td>"+day+"-"+month+"-"+year+"</td>");
out.println("<td>"+link.getElementsByTagName("description").item(0).getFirstChild().getNodeValue()+"</td>");//(5)
out.println();
}
%>
</table>
</body>
</html>
该程序基本上完全遵循上面讲到的DOM读取XML文件五个基本的步骤(程序中有具体的注释),只是第(4)、(5)部在反复多次使用,程序的基本原理非常简单。其运行的结果如图9-3所示。
图9-3 DOM读取XML文件示例运行结果图
修改XML文件就是在修改了DOM树后重新写入到XML文件中去的问题了。在修改XML文件通常会遇到两个方面的问题:
(1)是在XML文件中增加记录
在XML文件中增加记录,首先要在DOM树中增加一个节点元素,然后在这个节点上增加子节点元素,并给相应的叶节点赋值,最后把DOM树保存到XML文件中。
(2)是在XML文件中修改节点的值。
修改XML文件中节点的值,先是读入到DOM树中,再遍历DOM树,在遍历的过程中找到相应的节点并修改其值,在把修改的DOM保存到XML文件中。
这里我们先看一个在XML文件中增加记录的示例。
【例9-4】用DOM修改XML文件示例
该示例仍然用例9-1的应用环境,功能是对links.xml增加一条数据记录。增加的数据记录的XML内容如下:
<link>
<text>Wudong's Homepage</text>
<url newWindow="no">http://www.wudong.com</url>
<author>Wudong Liu</author>
<date>
<day>6</day>
<month>10</month>
<year>2006</year>
</date>
<description>A site from Wudong Liu,give u lots of suprise!!!</description>
</link>
具体程序实现的源代码如下。
domchangxml.jsp
<%@ page contentType="text/html;charset=gb2312"%>
<%@ page import="javax.xml.parsers.*, javax.xml.transform.*,javax.xml.transform.dom.*"%>
<%@ page import="javax.xml.transform.stream.*,java.io.*,java.util.*, org.w3c.dom.*, org.apache.xml.serialize.* "%>
<html>
<head>
<title> DOM修改XML文件示例</title>
</head>
<body>
<% DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder=factory.newDocumentBuilder();
Document doc=builder.parse("links.xml");
doc.normalize();
//---取得变量----
String text="Wudong's Homepage";
String url="www.wudong.com";
String author="Wudong Liu";
String discription="A site from Wudong Liu, give u lots of suprise!!!";
//创建一个link对象
Text textseg;
Element link=doc.createElement("link");
//XML文件中添加一个link项目的具体元素
Element linktext=doc.createElement("text");
textseg=doc.createTextNode(text);
linktext.appendChild(textseg);
link.appendChild(linktext);
Element linkurl=doc.createElement("url");
textseg=doc.createTextNode(url);
linkurl.appendChild(textseg);
link.appendChild(linkurl);
Element linkauthor=doc.createElement("author");
textseg=doc.createTextNode(author);
linkauthor.appendChild(textseg);
link.appendChild(linkauthor);
String day="6";
String month="10";
String year="2006";
Element linkdate=doc.createElement("date");
Element linkdateday=doc.createElement("day");
textseg=doc.createTextNode(day);
linkdateday.appendChild(textseg);
Element linkdatemonth=doc.createElement("month");
textseg=doc.createTextNode(month);
linkdatemonth.appendChild(textseg);
Element linkdateyear=doc.createElement("year");
textseg=doc.createTextNode(year);
linkdateyear.appendChild(textseg);
linkdate.appendChild(linkdateday);
linkdate.appendChild(linkdatemonth);
linkdate.appendChild(linkdateyear);
link.appendChild(linkdate);
Element linkdiscription=doc.createElement("description");
textseg=doc.createTextNode(discription);
linkdiscription.appendChild(textseg);
link.appendChild(linkdiscription);
//创建好的节点添加到DOM树中
doc.getDocumentElement().appendChild(link);
//用XSLT把DOM树输出
TransformerFactory tFactory =TransformerFactory.newInstance();
Transformer transformer = tFactory.newTransformer();
DOMSource source = new DOMSource(doc);
StreamResult result = new StreamResult(new File("links.xml"));
Transformer transform(source, result);
%>
<table border=1>
<!--输出表头-->
<tr >
<td>content</td>
<td>url</td>
<td>author</td>
<td>date</td>
<td>description</td>
</tr>
<% //输出显示数据
NodeList links =doc.getElementsByTagName("link");
for (int i=0;i<links.getLength();i++){
link= (Element) links.item(i);
out.print("<tr>");
out.println("<td>"+link.getElementsByTagName("text").item(0).getFirstChild().getNodeValue()+"</td>");
out.println("<td>"+link.getElementsByTagName("url").item(0).getFirstChild().getNodeValue()+"</td>");
out.println("<td>"+link.getElementsByTagName("author").item(0).getFirstChild().getNodeValue()+"</td>");
linkdate=(Element) link.getElementsByTagName("date").item(0);
day=linkdate.getElementsByTagName("day").item(0).getFirstChild().getNodeValue();
month=linkdate.getElementsByTagName("month").item(0).getFirstChild().getNodeValue();
year=linkdate.getElementsByTagName("year").item(0).getFirstChild().getNodeValue();
out.println("<td>"+day+"-"+month+"-"+year+"</td>");
out.println("<td>"+link.getElementsByTagName("description").item(0).getFirstChild().getNodeValue()+"</td>");
out.println();
}
%>
</table>
</body>
</html>
该程序首先在DOM 树中增加一条记录,将DOM树保存到links.xml文件中,然后再将DOM树中的内容显示出来,程序的流程非常清晰明了(与上面讲到步骤的一致),但要注意各种方法,特别是把DOM树输出使用XSLT显得特别简单。程序的运行结果如图9-4所示,与例9-3比较多了一条记录数据。
图9-4 DOM修改XML文件示例运行结果图
程序的运行后,links.xml文件在IE中显示如图9-5所示。
图9-5 程序的运行后links.xml文件在IE中显示结果
SAX即XML简单应用程序接口。SAX提供了一种对XML文件进行顺序访问的模式,这是一种快速读写XML数据的方式。当使用SAX分析器对XML文件进行分析时,会触发一系列事件,并激活相应的事件处理函数,从而完成对XML文件的访问,所以SAX接口也被称作事件驱动接口。
SAX是一种轻量型的方法。在处理DOM的时候,需要读入整个的XML文件,然后在内存中创建DOM树,生成DOM树上的每个Node对象。
当文件比较小的时候,这不会造成什么问题,但是一旦文件变大,处理DOM就会变得相当费时费力。特别是其对于内存的需求,将是成倍的增长,以至于在某些应用中使用DOM是一件很不经济的事,一个较好的替代解决方法就是SAX。
XML不仅规定了如何表示和显示数据,还提供了标准的API供处理XML数据,这也就是我们称之为智能数据或数据标准的原因。
SAX(The Simple API for XML)是基于事件的XML分析API,功能比较简单。 这一API是事件驱动的,又称"顺序访问"协议。每当它看到一个新的XML标记(或遇到一个错误,或想告诉你什么事时)就用一个SAX解析器注册你的句柄,激活你的回调方法。
DOM 定义了分析程序应当显露的标准命令集,使您能够在程序中访问 HTML 和 XML 文件内容。支持 DOM 的 XML 分析程序取出 XML 文件中的数据,并通过一组可以对它编程的对象来显露它。DOM将一个XML文件转换成你程序中的一个对象集合。然后你可以任意处理对象模型。这一机制也称为"随机访问"协议,因为你可以在任何时间访问数据的任何一部分,然后修改、删除或插入新数据。 DOM的特点是功能强大,但分析时间长,占用资源多。
SAX 提供了处理 XML 文件的快速、低内存的另一种方法。在使用 DOM 分析 XML 文件时,它在内存中建立了完整的文件树。相比而言,SAX 将遍历文件,并将新元素的开始或结束等通知分析事件的调用应用程序。使用 SAX 的一个最佳功能是分析长文件。例如,用 SAX 分析器,应用程序可以监视发生的事件,只将文件中必要的部分读入内存。
在Tomcat5.5中已经安装了SAX解析器,提供了SAX方式的API接口。
SAX方式解析XML文件最重要的类就是ContentHandler,ContentHandler类的方法无须调用,它们就象一个事件监听器,在解析XML文件的过程中会自动触发相应的事件来调用方法。此类的常用方法如下:
(1)startDocument()
当遇到文档的开头时调用这个方法,可以在其中做一些预处理的工作。调用方法如下:
void startDocument()
(2)endDocument()
当文档结束时会调用这个方法,可以在其中做一些善后的工作。调用方法如下:
void endDocument()
(3)startElement()
当读到一个标签开始时,会触发事件并调用这个方法。调用方法如下:
void startElement(String namespaceURI,String localName,String qName,Attributes atts)
其中,参数namespaceURI指的是名域,在本书的实例中无须用到,参数localName为使用名域时的标签名,如果不使用名域,此参数值均为null。参数qName为标签名,参数atts是标签的属性集。
(4)endElement()
在遇到结束标签时,会调用这个方法。调用方法如下:
void endElement(String namespaceURI,String localName,String qName)
其中,参数含义同startElement()。
(5)characters()
这个方法用来处理在XML文件中读到的字符串,以及读到的这个字符串在这个数组中的起始位置和长度,它的参数是一个字符数组,可以很容易用String类的一个构造方法来获得这个字符串的String类:String charEncontered=new String(ch,start,length)。
SAX解析器工作的过程至少包含3步。
(1)和DOM一样,需要建立一个解析器工厂。
SAXParserFactory spf = SAXParserFactory.newInstance();
(2)创建一个解析器对象。
SAXParser saxParser = spf.newSAXParser();
(3)将解析器和XML文件联系起来,开始解析。
saxParser.parse(new File(filename),new sax());
在进行具体的应用中(包括用SAX读取XML文件)主要使用ContentHandler类的方法自动触发相应的事件来调用这些方法进行的工作,下面是一个用SAX读取XML文件的示例程序。
【例9-5】用SAX读取XML文件示例
saxParseUserXML1.jsp
<%@ page contentType="text/html;charset=GB2312" %>
<%@ page import="javax.xml.parsers.*,org.xml.sax.*,org.xml.sax.helpers.*,
org.xml.sax.helpers.DefaultHandler,java.io.*"%>
<html>
<head><title>用SAX解析并遍历user.xml</title></head>
<body>
<%!
static class SAXParseUser extends DefaultHandler{
StringBuffer tempString=new StringBuffer();
//文件解析开始
public void startDocument() throws SAXException {
tempString.append("开始解析xml文件......<br>");
}
//标签解析开始
public void startElement(String namespaceURI, String localName,String qName, Attributes atts) throws SAXException{
tempString.append("开始解析结点,结点名称:"+qName+"<br>");
//解析得到标签所有的属性
for(int i=0;i<atts.getLength();i++){
tempString.append(" 属性"+atts.getQName(i)+",值为:"+atts.getValue(i)+"<br>");
}
}
//标签解析结束
public void endElement(String namespaceURI,String localName,String qName) throws SAXException{
tempString.append("解析结点结束,结点名称:"+qName+"<br>");
}
//字符串解析
public void characters(char[] ch,int start,int length){
tempString.append(" 解析出字符串,值为:'"+(new String(ch,start,length))+"'<br>");
}
//文件解析结束
public void endDocument() throws SAXException {
tempString.append("解析xml文件结束!<br>");
}
//得到解析结果
public StringBuffer getPrintXML(){
return tempString;
}
}
%>
<%//生成SAX解析器工厂长
SAXParserFactory spf = SAXParserFactory.newInstance();
XMLReader xmlReader = null;
SAXParser saxParser=null;
SAXParseUser saxParseUser=new SAXParseUser();
String filename=pageContext.getServletContext().getRealPath("/link.xml");
try {
// 创建一个解析器SAXParser对象
saxParser = spf.newSAXParser();
// 得到SAXParser中封装的SAX XMLReader
xmlReader = saxParser.getXMLReader();
//设置解析时处理事件的对象
xmlReader.setContentHandler(saxParseUser);
//开始解析XML文件
xmlReader.parse(filename);
}catch (Exception ex) {
System.out.println(ex);
}
%>
<!--输出解析结果-->
<table border=1>
<!--输出表头-->
<tr >
<td>content</td>
</tr>
<%
out.println("<td>"+saxParseUser.getPrintXML()+"</td>");
%>
</table>
</body>
</html>
程序的注释很清晰,这个例子需要注意的是ContentHandler类本身的特点:ContentHandler类的方法无须调用,它们就象一个事件监听器,在解析XML文件的过程中会自动触发相应的事件来调用方法。在实际开发过程中可以把ContentHandler类的继承类声明为JavaBean。程序的运行结果如图9-6所示。
图9-6 用SAX读取XML文件示例运行结果
JDOM的处理方式是与DOM类似的操作。是SUN公司发布的一种简单方便的XML处理接口。JDOM设计者的目标是:“Java + XML = JDOM”。2002年的JavaOne会议上JDOM的主要创始人Jason Hunter有一篇精彩的演讲介绍了JDOM技术,题目就是“JDOM Makes XML Easy”在那篇文件里,JDOM被拿来与DOM比较,JDOM是为了在Java中提供比DOM和SAX更为方便的XML处理接口而开发的。在http://jdom.org可以下载JDOM的最新版本。JDOM的jar组件包文件是jdom.jar。
JDOM的处理方式有些类似于DOM,但它主要是用SAX实现的,不必担心处理速度和内存的问题。另外,JDOM中几乎没有接口,全部是类,主要的类有:
Attribute(属性)、CDATA(内容)、Comment(注释)、Document(文件)、Element(元素)Namespace(命名空间)、ProcessingInstruction(处理指令)和Text(文本)。
JDOM中提供了如下7个包,各自的功能如下:
org.jdom——提供描述XML文件的基本类。
org.jdom.input——提供DOMBuilder和SAXBuilder类, 用来读取及解析已有的XML文件。
org.jdom.output——提供建立XML文件的类,有DOMOutput、SAXOutput、XMLOutput等类。
org.jdom.adapters——该包是用来与DOM进行沟通的。
org.jdom.filter——包含了xml文档的过滤器类。
org.jdom.transform——包含了将jdom xml文档接口转换为其他xml文档接口。
org.jdom.xpath——包含了对xml文档xpath操作的类.
在org.jdom中提供的类主要有:
Document——文件类,提供设置或获取根元素、元素内容、注释、处理命令等的方法。
Element——元素类,提供设置或获取元素的子元素、内容、属性等的方法。
Attribute——属性类,提供设置或获取元素属性名、属性值的方法。
Entity——实体类,提供设置或获取实体名、内容的方法。
DocType——DTD声明类,提供设置或获取DTD声明及内容的方法。
ProcessingInstruction——处理命令类,提供设置或获取处理命令及内容的方法。
org.jdom中还提供了Comment(注释类)、CDATA、Namespace等一些类。
下面来看一下具体的类的常用方法,以及这些常用方法的使用。
1、Document类
(1)Document的操作方法
Element root = new Element("GREETING");
Document doc = new Document(root);
root.setText("Hello JDOM!");
或者简单的使用
Document doc = new Document(new Element("GREETING").setText("Hello JDOM!t"));
这点和DOM不同。Dom则需要更为复杂的代码,如下:
DocumentBuilderFactory factory =DocumentBuilderFactory.newInstance();
DocumentBuilder builder =factory.newDocumentBuilder();
Document doc = builder.newDocument();
Element root =doc.createElement("root");
Text text = doc.createText("This is the root");
root.appendChild(text);
doc.appendChild(root);
【专家提示】JDOM不允许同一个节点同时被2个或多个文档相关联,要在第2个文档中使用原来老文档中的节点的话。首先需要使用detach()把这个节点分开来。
(2)从文件、流、系统ID、URL得到Document对象
DOMBuilder builder = new DOMBuilder();
Document doc = builder.build(new File("jdom_test.xml"));
SAXBuilder builder = new SAXBuilder();
Document doc = builder.build(url);
在新版本中DOMBuilder 已经很少使用DOMBuilder.builder(url),用SAX效率会比较快。
(3)DOM的Document和JDOM的Document之间的相互转换使用方法
DOMBuilder builder = new DOMBuilder();
org.jdom.Document jdomDocument = builder.build(domDocument);
DOMOutputter converter = new DOMOutputter();// work with the JDOM document…
org.w3c.dom.Document domDocument = converter.output(jdomDocument);
2、XMLOutPutter类
JDOM的输出非常灵活,支持很多种io格式以及风格的输出。
Document doc = new Document(...);
XMLOutputter outp = new XMLOutputter();
outp.output(doc, fileOutputStream); // 用XMLOutputter对象输出JDOM文档对象
outp.setTextTrim(true); // Compressed output
outp.output(doc, socket.getOutputStream());
outp.setIndent(" ");// 设置紧缩编排使用的字符串
outp.setNewlines(true);
outp.output(doc, System.out);
3、Element类
(1)浏览Element树
Element root = doc.getRootElement();//获得根元素element
List allChildren = root.getChildren();// 获得所有子元素的一个list
List namedChildren = root.getChildren("name");// 获得指定名称子元素的list
Element child = root.getChild("name");//获得指定名称的第一个子元素
JDOM给了我们很多很灵活的使用方法来管理子元素(这里的List是java.util.List)
List allChildren = root.getChildren();
allChildren.remove(3); // 删除第四个子元素
allChildren.removeAll(root.getChildren("jack"));// 删除叫“jack”的子元素
root.removeChildren("jack"); // 便捷写法
allChildren.add(new Element("jane"));// 加入
root.addContent(new Element("jane")); // 增加子节点元素
allChildren.add(0, new Element("first"));
(2)移动Element
在JDOM里移动Element很简单:
Element movable = new Element("movable");
parent1.addContent(movable); // 向Document中添加子元素
parent1.removeContent(movable); //向Document中删除子元素
parent2.addContent(movable); // add
在Dom里移动Element:
Element movable = doc1.createElement("movable");
parent1.appendChild(movable); //向Document中添加子元素
parent1.removeChild(movable); //向Document中删除子元素
parent2.appendChild(movable); // 出错!
(3)Element的text内容读取
String desc = element.getText();//得到元素文本值
String desc = element.getTextTrim();//得到元素文本值并去除左右空格
(4)Elment内容修改
element.setText("A new description");//设置元素的文本
4、Attribute类
<table width="100%" border="0"> </table>
String width = table.getAttributeValue("width");//获得attribute
int border = table.getAttribute("width").getIntValue();
table.setAttribute("vspace", "0");//设置attribute
table.removeAttribute("vspace");// 删除一个或全部attribute
table.getAttributes().clear();
使用JDOM通过程序建立一个新的XML文件,或者对一个已有XML文件进行转换(存取以及修改、添加等操作)。
使用JDOM创建一个新XML文件的一般步骤是:
(1) 建立元素及其内容
建立一个元素(Element对象)作为根结点;
建立根结点的子结点(如果有):建一新元素,加入到根结点中作为子结点;
建立子结点的下面一层子结点:
……
直至建完叶子。
建立DOM结构树时可以横向一层一层地依次建立,也可以纵向向下建完叶子再回头建立另一结点,建立顺序不限。
(2)以根结点为根元素建立文件(Document对象)
(3)建立XML文件
(4)使用org.output包中的方法建立输出流对象
(5)使用输出流对象的output方法将Document对象输出到文件中,完成XML文件的建立。
【例9-6】用JDOM读取XML文件示例
本例将利用JDOM编写一个JSP程序(CreateXML.java),该程序实现建立一个新的XML文件“books.xml”,并用表格输出在浏览器XML文件books.xml的数据。
源程序如下:
jdomreadxml.jsp
<%@ page contentType="text/html;charset=gb2312"%>
<%@ page import="org.jdom.*,org.jdom.input.*,org.jdom.output.XMLOutputter,
java.io.*,org.jdom.output.Format,java.util.*"%>
<html>
<head>
<title> JDOM读取XML文件示例</title>
</head>
<body>
<%
Element root,ele,name,name1,name2,name3;
//Element root=new Element("教材表")
root=new Element("教材表"); //建立根元素<教材表>
//建立根元素的第一个子元素<教材>
ele=new Element("教材"); //建新元素<教材>
root.addContent(ele); //将<教材>加入根元素, 成为根元素的子元素
name=new Element("书名"); //建新元素<书名>
ele.addContent(name); //将<书名>加入<教材>中, 成为<教材>的子元素
name.setText("Java网络编程"); //设置<书名>的内容
name1=new Element("作者"); //建新元素<作者>
ele.addContent(name1); //将<作者>加入<教材>中, 成为<教材>的第2个子元素
name1.setText("陈春颖等");
name2=new Element("出版社"); //建新元素<出版社>
ele.addContent(name2); //将<出版社>加入<教材>中, 成为<教材>的第3个子元素
name2.setText("高等教育出版社");
name3=new Element("价格"); //建新元素<价格>
ele.addContent(name3); //将<价格>加入<教材>,成为<教材>的第4个子元素
name3.setText("46.0"); //设置<价格>的内容
//建立根元素的第二个子元素<教材>
ele=new Element("教材");
name=new Element("书名");
name1=new Element("出版社");
name2=new Element("作者");
name3=new Element("价格");
name.setText("专家导学JSP应用开发");
name1.setText("电子工业出版社");
name2.setText("周雄伟等");
name3.setText("51.0");
ele.addContent(name);
ele.addContent(name1);
ele.addContent(name2);
ele.addContent(name3);
root.addContent(ele); //将<教材>加入根元素, 成为根元素的第2个子元素
//建立根元素的第二个子元素<教材>
ele=new Element("教材");
name=new Element("书名");
name1=new Element("出版社");
name2=new Element("作者");
name3=new Element("价格");
name.setText("Lucene开发");
name1.setText("电子工业出版社");
name2.setText("周雄伟等");
name3.setText("55.0");
ele.addContent(name);
ele.addContent(name1);
ele.addContent(name2);
ele.addContent(name3);
root.addContent(ele); //将<教材>加入根元素, 成为根元素的第3个子元素
Document doc=new Document(root); //以根元素建立文件
FileOutputStream f=new FileOutputStream("books.xml");
XMLOutputter out1=new XMLOutputter(); //建立输出流
Format format=Format.getPrettyFormat(); //格式化文件
format.setEncoding("gb2312"); //格式设为gbk2312中文将显示
out1.setFormat(format);
out1.output(doc,f); //将文件输出到XML文件中
%>
<table border=1>
<!--输出表头-->
<tr >
<td>书名</td>
<td>价格</td>
<td>出版社</td>
<td>作者</td>
</tr>
<%//---得到数据---
SAXBuilder builder = new SAXBuilder();//创建对象
//建立Document对象
Document readDocument = builder.build(pageContext.getServletContext().getRealPath("books.xml"));
//得到根元素
Element rootElement = readDocument.getRootElement();
//得到根无素的子元素列表,实际上就是user元素列表
List list = rootElement.getChildren();
//-----输出数据----
for(Iterator i = list.iterator();i.hasNext();){
Element current = (Element)i.next();
out.println("<tr>");
//----输出书名--
out.println("<td>"+current.getChildText("书名")+"</td>");
//----输出价格--
out.println("<td>"+current.getChildText("价格")+"</td>");
//----输出出版社--
out.println("<td>"+current.getChildText("出版社")+"</td>");
//----输出作者--
out.println("<td>"+current.getChildText("作者")+"</td>");
out.println("</tr>");
}
%>
</table>
</body>
</html>
结合使用JDOM创建一个新XML文件的一般步骤和源程序的注释,这个程序不难理解,程序的运行结果如图9-7所示。
图9-7 DOM读取XML文件示例运行结果
该程序运行后,在该程序的目录下即生成XML文件“books.xml”,双击“books.xml”文件,即可看到如图9-8所示效果。
图9-8 books.xml的运行结果
使用JDOM修改XML文件与DOM修改XML文件一样,也包含两个方面的。是在XML文件中在XML文件中增加记录和是在XML文件中修改节点的值两个方面,它们的进行的基本原理类似。用JDOM由现有XML文件生成DOM模型时,对一个XML文件进行转换过程如图9-9所示,可以分为如下几步:
(1)建立一个解析器
(2)解析XML文件并传回Document对象
(3)JDOM提供的方法存取这个Document对象
图9-9 XML转换DOM模型
前面DOM讲了在XML文件中增加记录的示例,这里我们来讲一个在XML文件中修改节点值的例子。
【例9-7】JDOM修改XML文件示例
本例将编写一个JSP程序,它利用JDOM读出例9-6所生成的XML文件“books.xml”的内容,并对其进行修改后再写回XML文件“books.xml”中,源程序如下:
jdomchangxml.jsp
<%@ page contentType="text/html;charset=gb2312"%>
<%@ page import="javax.xml.parsers.*,org.jdom.*,org.jdom.output.XMLOutputter,java.io.*,java.util.*"%>
<%@ page import="org.jdom.*,org.jdom.input.*,org.jdom.output.XMLOutputter,java.io.*,
org.jdom.output.Format,java.util.*"%>
<html>
<head>
<title> JDOM修改XML文件示例</title>
</head>
<body>
<%
SAXBuilder sb = new SAXBuilder(); //建立一个解析器
//构造一个Document,读入books.xml文件的内容
Document doc = sb.build(new FileInputStream("books.xml"));
Element root = doc.getRootElement(); //得到根元素
java.util.List books = root.getChildren(); //得到根元素所有子元素的集合
Element book = (Element)books.get(0); //得到第1个子元素<教材>
//为第1个元素<教材>添加一条属性
Attribute a = new Attribute("有课件","true");
//book.addAttribute(a);
book.setAttribute(a);
//为第1个元素<教材>添加一个元素<作者>
Element author = new Element("作者"); //建新元素<作者>
author.setText("yinzhaolin"); //设置<作者>的内容
book.addContent(author); //将<作者>加入<教材>中
book.removeChild("价格"); //删除子元素<价格>
book = (Element)books.get(1); //得到第2个元素<教材>
//修改价格
Element price = book.getChild("价格"); //得到指定的子元素<价格>
price.setText("56"); //将价格改为20元
XMLOutputter out1 = new XMLOutputter();//建立输出流
out1.output(doc, new FileOutputStream("books.xml")); //将文件输回到XML文件中
%>
<table border=1>
<!--输出表头-->
<tr >
<td>书名</td>
<td>价格</td>
<td>出版社</td>
<td>价格</td>
</tr>
<%//---得到数据---
Element newroot = doc.getRootElement();
//得到根无素的子元素列表,实际上就是user元素列表
List list = newroot.getChildren();
//-----输出数据----
for(Iterator i = list.iterator();i.hasNext();){
Element current = (Element)i.next();
out.println("<tr>");
//----输出书名--
out.println("<td>"+current.getChildText("书名")+"</td>");
//----输出作者--
out.println("<td>"+current.getChildText("出版社")+"</td>");
//----输出出版社--
out.println("<td>"+current.getChildText("作者")+"</td>");
//----输出价格--
out.println("<td>"+current.getChildText("价格")+"</td>");
out.println("</tr>");
}
%>
</table>
</body>
</html>
程序的源码进行了详细的注释,对比以前的代码,JDOM比DOM和SAX方式操作XML文件明显要方便,而且效率比DOM方式要高。程序的运行结果如图9-10所示
图9-10 JDOM修改XML文件示例运行结果
上面的程序修改了例9-6所建立的XML文件books.xml,修改后的books.xml显示的结果如图9-11。
图9-11 修改后的XML文件显示结果
XML文件与数据库的操作应用非常广泛,既可以用XML文件对数据库进行连接与配置,也可以把数据库的表记录用XML文件进行备份。其中数据库表记录集转换到XML文件大体可以分两步:
(1)建立数据库和表,利用ODBC中设置数据源,并指向该数据库。
(2)把数据库的记录集转换成XML文件。其步骤如下:
利用select语句建立需要的记录集(ResultSet);
获得记录集的结构;
把记录集转换成XML文件。
下面看一个JDOM把数据库的表转化成XML文件示例。
【例9-8】JDOM把数据库的表转化成XML文件示例
本例将编写使用JDOM技术的JSP应用程序,它读出Access数据库student.mdb中学生信息表(information)表的记录集,并将它们转换成名为DB.xml的XML文件。student.mdb的information表结构如下。
表9-1 学生信息表结构
源程序如下:
jdomchangDBtoxml.jsp
<%@ page contentType="text/html;charset=gb2312"%>
<%@ page import="javax.xml.parsers.*,org.jdom.*,org.jdom.output.XMLOutputter,java.io.*,java.util.*, java.sql.*,org.jdom.output.Format"%>
<html>
<head>
<title> JDOM把数据库的表转化成XML文件示例</title>
</head>
<body>
JDOM把数据库的表转化成XML文件<br>
<%
//建立数据库连接并获取学生表的记录集和结构
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"); //加载桥驱动程序
Connection con=DriverManager.getConnection("jdbc:odbc:Demo"); //建立数据库连接
Statement stmt=con.createStatement();
ResultSet rs=stmt.executeQuery("select * from information"); //获取结果集
rs.next();
out.println(rs.getString("字段1"));
//获得结果集的结构
ResultSetMetaData rsmd=rs.getMetaData();
//获得结果集的列数,也就是数据项的数目
int numcols=rsmd.getColumnCount();
//记录集到XML文件的转换
Element root,ele,name; //定义Element类的对象根元素(root)
root=new Element("student"); //建立根元素
//循环, 将结果集中的每一条记录转换成一个元素(标记名为“第…个记录”)
while(rs.next())
{ele=new Element("第"+rs.getRow()+"条纪录"); //设置元素,名为“第…个记录”
out.println(ele);
for(int i=1;i<=numcols;i++) //循环,将一条记录中的每个字段转换成一个元素
{// getColumnLabel(i) 获取第i个列的字段名(列数从0开始)。
//out.println(rs.getString(i));
name=new Element(rsmd.getColumnLabel(i)); //以获得的字段名为标记名设置元素
name.setText(rs.getString(i)); //获取字段内容作为字段名元素的内容
ele.addContent(name); //将字段名元素置为记录的子元素
}
root.addContent(ele); //将记录元素置为根元素的子元素
}
Document doc=new Document(root); //以根元素建立文件
FileOutputStream f=new FileOutputStream("DB.xml");
XMLOutputter out1=new XMLOutputter(); //将文件输出形成XML文件
Format format=Format.getPrettyFormat(); //格式化文件
format.setEncoding("gb2312"); //编码设为gb2312中文将显示正常
out1.setFormat(format);
out1.output(doc,f);
rs.close(); con.close(); //关闭各个对象
out.println("数据库表Information已成功地转化成XML文件DB.xml!");
%>
</table>
</body>
</html>
程序的运行结果如图9-12所示。
图9-12 JDOM把数据库的表转化成XML文件
程序运行后在工程的目录下生成XML文件“DB.xml”,双击“DB.xml”文件,即可看到如图9-13所示结果。
图9-13 由数据库记录集转换得到的XML文件显示结果
XML在Web开发中应用广泛,已经进入Java程序员必备的知识范畴。本章从XML的基本概念讲起,由浅入深,介绍了XML的文件结构以及DTD知识,主要讲解XML文件的三种处理方式:DOM\SAX和JDOM。
DOM和SAX是常用的两种操作XML文件的接口。DOM方式先将XML文件读入内存,建立起一棵文档树,通过对这棵文档树的操作来完成对XML文件的操作。SAX API是事件驱动的,又称"顺序访问"协议。每当它看到一个新的XML标记(或遇到一个错误,或想告诉你什么事时)就用一个SAX解析器注册你的句柄,激活你的回调方法。
DOM 定义了分析程序应当显露的标准命令集,使您能够在程序中访问 HTML 和 XML 文件内容。DOM的特点是功能强大,但分析时间长,占用资源多。
SAX是一种轻量型的方法,它是基于事件的XML分析API,功能比较简单。SAX 提供了处理 XML 文件的快速、低内存的另一种方法。建议较小的XML文件或需要修改XML时使用DOM接口,速度要求较高时采用SAX接口。
JDOM组合了DOM和SAX的优点,功能特别强大,还提供了Document,Element,Comment,DocType,Attribute,Text等Java类,可以利用这些类创建、遍历并修改JDOM文档以及把数据库的表转换成XML文件。
对于Java平台而言,JavaMail算是比较晚加入的API,目前在Java网站(http://java.sun.com/products/javamail/)上最新版本是JavaMail1.4API,本章中将使用它来作为开发工具。
1.mail.jar
读者可以到以下的网址下载最新的JavaMail API:
http://java.sun.com/products/javamail/downloads/index.html
下载后得到的文件名为javamail-1_4.zip。首先将javamail-1_4.zip解压缩后,mail.jar是最重要的文件,它里面就是JavaMail API的类,除mail.jar之外,它还内附dsn.jar、imap.jar、javamailapi.jar、pop3.jar、smtp.jar。
【专家提示】读取可以用WinRar工具查看dsn.jar、imap.jar、javamailapi.jar、pop3.jar、smtp.jar几个文件的内容,会发现它们仅仅是mail.jar文件的拆分而已,所以通常在开发过程中只需要选用mail.jar文件即可。
2.activation.jar
除了mail.jar之外,读者还可以到如下的网址下载最新的JavaBeans Activation Framework(JAF):
http://java.sun.com/products/javabeans/jaf/
JAF目前最新版本为1.1。
接下来将activation.jar和mail.jar这两个jar文件拷贝至当前Web应用的“WEB-INF/lib”目录下,如图10-1所示:
图10-1 构建JavaMail开发环境
这些前期工作都准备好后,接下来就是学习JavaMail的开发步骤了。
JavaMail应用程序接口(API)用于发送和接收邮件。它不仅独立于平台,可以在Windows、Macintosh和Linux上使用,还独立于底层传输协议,可以使用邮局协议(Post Office Protocol,POP3)和Internet邮件访问协议(IMAP)接收消息。JavaMail中的消息是使用简单邮件传输协议(Simple Mail Transfer Protocol,SMTP)服务器来发送的。
JavaMail API的工作原理如图10-2所示。
图10-2 JavaMail API的工作原理
在图10-2中,发件人使用Transport对象发送消息。Transport对象将消息提交到网络,然后发送给收件人。发送给收人的消息存储在消息服务器中。存储由许多个文件夹和邮件组成,收件人可以访问该文件夹并读取消息。
在JavaMail API中定义四个主要组件:Message、Folder、Sotre和Session.
Message
Message类是一个抽象类,定义邮件信息之内容类型等属性,包含标题和内容:标题包含所有的寻址信息,而内容包含数据。Message类的部分常用方法如下:
setFrom:指定消息的发件人。
setRecipients:指定收件人的地址。
setSubject:指定消息的主题。
setText:指定消息的文本。
setSentDate:指定消息的发送日期。
setReplyTo:指定消息回复导向的地址。
getFlags:返回当前消息的标记。
RECENT:指明收到的新消息。
SEEN:指明已查看的消息。
Folder
Folder类是一个抽象类,Folder包含消息和子文件夹。Folder对象用于消息的通信和管理。默认情况下,Folder类处于关闭状态,在这种状态下不能查看消息和子文件夹。要查看消息和子文件夹,需要使用Folder类的open()方法打开文件夹。Folder类的部分常用的方法如下:
open:打开文件,可以用两种模式打开文件夹。
READ_ONLY模式:以只读模式打开文件夹。
READ_WRITE模式:以读写模式打开文件夹。在这种模式下,用户可以编写、更新和删除消息。
create:新建一个文件夹。
exists:检查文件夹是否存在。
getFolder:获得文件夹。
getMessage:获得指定的消息对象。
Store
Store类是一个抽象类,消息存储在文件夹(如Inbox和Drafts等)中,而这些文件夹存储在消息存储器中(Store)。Store类提供对文件夹的方法并并验证连接,Store类的方法还用于查看消息和文件夹。在连接Store之前,用户需要创建一个会话。创建Store的语法为:
Store store=obj_session.getStore(protocol);
store.connect(hostname,username,password);
其中,getStore(protocol)获得实现指定协议的Store对象,服务器使用该协议发送和接收消息;connect(hostname,username,password)连接到指定地址,并接受主机名、用户名和密码作为参数。
Session
Session类是JavaMail API的*别的类。它定义了用来与邮件系统进行通信的邮件会话,使用户可以使用相应的getXXX()方法来访问Store和Transport对象。还用于存储信息与服务器建立会话连接。Session类能够控制和加载类,可以创建共享和非共享会话,而共享会话可以被多个应用程序共享。创建非共享会话的语法为:
Session session=Session.getDefaultInstance(p,null) ;
其中,Session.getDefaultInstance(p,null)获得默认的Session对象。如果未设置默认对象,就新建一个Session对象并作为默认对象安装。p是属性对象,null表示没有验证程序,任何人都可以获得默认会话。
【思考1】JavaMail组件中为什么Message、Folder和Store都被定义为抽象类?我们如果取得它们相应的实例对象?
这节将学习编写一个最简单的发信程序,编写邮件程序时,使用JavaMail要导入一些包,这些包分别如下:
java..util.*:定义集合框架、日期和时间工具,以及事件模型的集合。
java.io.*:通过数据流,序列化和文件系统来提供系统输入和输出。
javax.mail.*:定义对所有邮件系统都通用的类。
javax.mail.internet.*:定义特定于Internet邮件系统的类。
javax.activation.*:由JavaMail API使用,以管理MIME数据。
构造JavaMail发邮件程序涉及的步骤如图10-3所示。
图10-3 构建JavaMail发邮件程序的步骤
Session对象管理用以与消息传送系统进行交互的配置选项和用户验证信息,创建会话的语法为:
Properties props = new Properties();
props.put("mail.smtp.host", host);
props.put("mail.smtp.auth", "true");
Session mailSession = Session.getDefaultInstance(props);
mailSession.setDebug(true);
其中,props.put("mail.smtp.host", host)指定发送邮件时所使用的SMTP服务器,props.put("mail.smtp.auth", "true")指定发送邮件时所使用的SMTP服务器是否需要进行身份验证。
其中,mailSession.setDebug(boolean )指定是否在系统控制台显示发送邮件的DEBUG信息。如:
图10-4 发送邮件的提示信息
【专家提示】setDebug(true)时,会将JavaMail所做的每一步工作的调试信息在系统控制台上输出。当发送邮件的数据量较大和数量较多时会引起系统I/O阻塞导致系统运行缓慢,所以当程序员开发调试完成正式发布运行时要将setDebug(false)。
创建会话和设置属性之后,构造要发送的消息由于Message类是一个抽象类,我们使用Message类的子类MimeMessage来创建构造消息的实例。创建消息实例的语法为:
//通过会话实例创建新的消息实例
Message message=new MimeMessage(mailSession);
//设置发件人信息
message.setFrom(new InternetAddress(from));
//设置收件人信息
message.addRecipient(Message.RecipientType.TO,new InternetAddress(to));
//设置抄送人信息
message.addRecipient(Message.RecipientType.CC,new InternetAddress("orion@csai.cn"));
//设置邮件主题
message.setSubject(subject);
//设置邮件文本内容
message.setText(content);
//设置发信日期
message.setSentDate(new java.util.Date());
//保存邮件设置信息
message.saveChanges();
【专家提示】调用完Message类的setFrom()、setRecipients()、setSubject()和setText()方法后,一定要调用saveChanges()方法才保存相应的设置信息。
【专家提示】RecipientType.表示邮件以何种方式传送给收人相应属性有TO直送、CC(Carbon Copy,抄送)和BCC(Bind Carbon Copy,暗送)等。
简单邮件传输协议(Simple Mail Transfer Protocol,SMTP)是用于发送邮件的协议。创建会话和构造消息之后,获得Transport对象,然后使用这个类的Send()方法发送消息。
Transport transport = mailSession.getTransport("smtp");
transport.connect(host, user, password);
transport.sendMessage(message, message.getAllRecipients());
transport.close();
其中:getTransport(“SMTP”)返回实现了SMTP协议的传输对象。
【例10-1】发送简单的邮件
先做一个JavaMailSend.html的用户界面让用户输入数据,然后再传关到JavaMailSend.jsp做处理。
JavaMailSend.html
<html>
<head>
<title>CH10 - JavaMailSend.html</title>
<meta http-equiv="Content-Type" content="text/html; charset=GB2312">
</head>
<body>
<h2>利用JavaMail来发送电子邮件</h2>
<form name="Form" method="post" action="JavaMailSend.jsp">
<p>邮件主机:<input type="text" name="Host" size="30" maxlength="30"></p>
<p>帐号:<input type="text" name="User" size="30" maxlength="30"></p>
<p>密码:<input type="password" name="Password" size="30" maxlength="30"></p>
<p>寄信人:<input type="text" name="From" size="30" maxlength="30"></p>
<p>收信人:<input type="text" name="To" size="30" maxlength="30"></p>
<p>主题:<input type="text" name="Subject" size="30" maxlength="30"></p>
<p>内容:</p><p><textarea name="Message" cols=40 rows=5></textarea></p>
<input type="submit" value="发送">
<input type="reset" value="清除">
</form>
</body>
</html>
JavaMailSend.html页面的执行结果如图10-5所示,其中包括邮件主机、在邮件主机上注册的帐号、密码、寄信人、收信人、主题和内容。当用户填好数据后,按下【发送】,就会将窗体数据传送到JavaMailSend.jsp做发送电子邮件的工作。
图10-5 JavaMailSend.html的执行结果
JavaMailSend.jsp
<%@ page import="javax.mail.*" %>
<%@ page import="javax.mail.internet.*" %>
<%@ page import="javax.activation.*" %>
<%@ page import="java.util.*,java.io.*" %>
<%@ page contentType="text/html;charset=GBK" %>
<html>
<head>
<title>CH10 - JavaMailSend.jsp</title>
</head>
<body>
<h2>利用JavaMail来传送电子邮件 </h2>
<% //设置中文参数的编码
request.setCharacterEncoding("GBK");
//接收相应的参数
String host = request.getParameter("Host");
String user = request.getParameter("User");
String password = request.getParameter("Password");
String From = request.getParameter("From");
String to = request.getParameter("To");
String Subject = request.getParameter("Subject");
String messageText = request.getParameter("Message");
Properties props = new Properties();
//指定SMTP服务器
props.put("mail.smtp.host", host);
//指定是否需要SMTP验证
props.put("mail.smtp.auth", "true");
try {
//创建JavaMail会话
Session mailSession = Session.getDefaultInstance(props);
//在控制台显示debug信息
mailSession.setDebug(true);
//构造新的消息对象
Message message = new MimeMessage(mailSession);
////发件人
message.setFrom(new InternetAddress(From));
//收件人
message.addRecipient(Message.RecipientType.TO,
new InternetAddress(to));
//抄送人
message.addRecipient(Message.RecipientType.CC,
new InternetAddress("jspadmin@csai.cn"));
//邮件主题
message.setSubject(Subject);
//邮件内容
message.setText(messageText);
//发信日期
message.setSentDate(new java.util.Date());
message.saveChanges();
//构建传送对象
Transport transport = mailSession.getTransport("smtp");
transport.connect(host, user, password);
transport.sendMessage(message, message.getAllRecipients());
transport.close();
out.println("<h5>邮件已顺利传送至:"+to+"</h5>");
}
catch (MessagingException mex) {
mex.printStackTrace();
}
%>
</body>
</html>
图10-6 JavaMailSend.jsp执行结果
JavaMailSend.jsp范例中,笔者以csai.cn的邮件服务器来做测试,读者也可以使用其他免费电子邮信箱的邮件服务器,只要它有提供SMTP的服务。
【专家提示】如果读取在运行上述示例时系统控制台出现如下错误提示
图10-7 错误的异常信息
有可能是您机器防火墙软件阻止了邮件传送程序,请您更改防火墙设置。
登录orion@csai.cn邮箱查看结果如图 10-8所示
图10-8 邮箱查看信件
文件夹名INBOX是一个保留名字,代表所有消息第一次到达系统的位置。收到的消息存储在消息存储器中的文件夹中。列出INBOX中所有消息的语法为:
Folder inbox=store.getDefaultFolder().getFolder("INBOX");
inbox.open(Folder.READ_WRITE);
Message[] message=inbox.getMessages();
其中,getFolder("INBOX") 将获得INBOX文件夹并将其存储在对象中。
inbox.open(Folder.READ_WRITE) 将以读写模式打开文件夹,采用这种模式在收取服务器端的邮件可以删除服务器端的副本并释放服务器空间。
getMessage() 返回文件夹中所有消息对象。
在JavaMail中,POP3是用于接收消息的协议。要读取消息,可连接到Store并打开指定文件夹,检索数组中的消息,读取消息的语法为:
for (int i = 0; i < message.length; i++) {
message[i].setFlag(Flags.Flag.DELETED,true);
out.println("邮件主题:"+message[i].getSubject()+"<br>");
out.println("邮件发送者:"+message[i].getFrom()[0]+"<br>");
out.println("发送时间:"+message[i].getSentDate()+"<br>");
out.println("内容:"+message[i].getContent()+"<br>");
}
inbox.close(true);
store.close();
其中,setFlag(Flags.Flag.DELETED,true) 将消息标记为删除。
Inbox.close(true) 关闭INBOX文件夹,参数为true表示删除其中标记为删除的消息。
【专家提示】在读取消息后请使用Store对象的close()方法关闭存储器,如果再次连接到一个已经连接的存储器将发生错误。
【例10-2】接收邮件
先来做一个JavaMailReceiver.html的用户界面让用户输入数据,然后再传送到JavaMailReceiver.jsp做处理。
JavaMailReceiver.html
<html>
<head>
<title>CH10 - JavaMailReceiver.html</title>
<meta http-equiv="Content-Type" content="text/html; charset=GB2312">
</head>
<body>
<h2>利用JavaMail来接收电子邮件</h2>
<form name="Form" method="post" action="JavaMailReceiver.jsp">
<p>邮件主机:<input type="text" name="Host" size="30" maxlength="30"></p>
<p>帐号:<input type="text" name="User" size="30" maxlength="30"></p>
<p>密码:<input type="password" name="Password" size="30" maxlength="30"></p>
<input type="submit" value="接收">
<input type="reset" value="清除">
</form>
</body>
</html>
JavaMailReceiver.html的执行结果如图10-10所示,其中包括邮件主机、在邮件主机上注册的帐号、密码。当用户填好数据后,按下【接收】,就会将窗体数据传送到JavaMailReceiver.jsp做接收电子邮件的工作。
图10-10 JavaMailReceiver.html执行结果
JavaMailReceiver.jsp
<%@ page import="javax.mail.*" %>
<%@ page import="javax.mail.internet.*" %>
<%@ page import="javax.activation.*" %>
<%@ page import="java.util.*,java.io.*" %>
<%@ page contentType="text/html;charset=GBK" %>
<html>
<head>
<title>CH10 - JavaMailSend.jsp</title>
</head>
<body>
<h2>利用JavaMail来接收电子邮件 </h2>
<%
//设置中文参数的编码
request.setCharacterEncoding("GBK");
//接收相应的参数
String host = request.getParameter("Host");
String user = request.getParameter("User");
String password = request.getParameter("Password");
try {
Properties props=new Properties();
//指定的POP3邮件服务器
props.put("mail.pop3.host",host);
//创建邮件会话
Session mailsession=Session.getDefaultInstance(props);
//创建Store对象
Store store=mailsession.getStore("pop3");
//登录到Store
store.connect(host,user,password);
//获得INBOX文件夹
Folder inbox=store.getDefaultFolder().getFolder("INBOX");
//以读写模式打开INBOX文件夹
inbox.open(Folder.READ_WRITE);
//从INBOX文件夹中取得所有的消息对象
Message[] message=inbox.getMessages();
//循环读取消息
for (int i = 0; i < message.length; i++) {
message[i].setFlag(Flags.Flag.DELETED,true);
out.println("邮件主题:"+message[i].getSubject()+"<br>");
out.println("邮件发送者:"+message[i].getFrom()[0]+"<br>");
out.println("发送时间:"+message[i].getSentDate()+"<br>");
out.println("内容:"+message[i].getContent()+"<br>");
}
//关闭文件夹,并删除标记为删除的消息
inbox.close(true);
//关闭登录的Store
store.close();
}
catch (MessagingException ex) {
ex.printStackTrace();
}
%>
</body>
</html>
图10-11 JavaMailReceiver.jsp执行结果
前面我讲述的邮件消息都是只包含文本的消息,在实际应用中一个邮件消息通常除了文本内容之外还包含图片、HTML网页和附件文件等。在JavaMail中一条邮件消息可由多个部分组成,每一部分是一个BodyPart(报文部分),或更特殊一点,在操作MIME消息时则是MimeBodyPart。不同的报文部分组合到一个称为Multipart的容器中,或者又更特殊一点,是一个MimeMultipart容器(如图 10-12 所示)。要发送一个复杂的邮件消息您要创建每一个用于消息文本的部分,并将这多个部分组合成一个multipart(多个部分)。然后您可以把这个multipart添加到一个合适的注明地址的消息中并发送它。创建Multipart消息的步骤如图10-13所示。
【例10-2】发送带附件的邮件
接下来将演示发送一个内容为HTML格式并且带有附件的邮件消息指导读者如何构造Multipart消息。示例的做法是:先将处理好的邮件内容存入到MimeBodyPart对象中;然后将文件存入到另一个MimeBodyPart对象中;最后把两个MimeBodyPart对象(一份是邮件内容本身,另一份是文本或HTML部分)全部存入到Multipart对象,合而为一加入Message对象中,发送到收信人的邮件服务器。附件的功能需要使用到上传文件的机制,因此使用了欧莱礼的文件上传组件。
JavaMailAttachment.html页面用于接受用户邮件消息数据录入并将请求传送给JavaMailAttachment.jsp处理,JavaMailAttachment.html页面的源代码如下。
JavaMailAttachment.html
<html>
<head>
<title>CH10 - JavaMailAttachment.html</title>
<meta http-equiv="Content-Type" content="text/html; charset=GB2312">
</head>
<body>
<h2>利用JavaMail来传送电子邮件 - 附件</h2>
<form name="SendMessage" Method="post" action="JavaMailAttachment.jsp" enctype="multipart/form-data">
<p>邮件主机:<input type="text" name="Host" size="30" maxlength="30"></p>
<p>帐号:<input type="text" name="User" size="30" maxlength="30"></p>
<p>密码:<input type="password" name="Password" size="30" maxlength="30"></p>
<p>寄信人:<input type="text" name="From" size="30" maxlength="30"></p>
<p>收信人:<input type="text" name="To" size="30" maxlength="30"></p>
<p>主题:<input type="text" name="Subject" size="30" maxlength="30"></p>
<p>格式:<select name="Type" size="1">
<option value="text/plain">Text</option>
<option value="text/html">HTML</option>
</select></p>
<p>附件:<input type="file" name="FileName" size="20" maxlength="20"></p>
<p>内容:</p><p><textarea name="Message" cols=40 rows=5></textarea></p>
<input type="submit" value="传送">
<input type="reset" value="清除">
</form>
</body>
</html>
JavaMailAttachment.html页面的运行结果如图 10-14所示。
图10-14 avaMailAttachment.html执行结果
JavaMailAttachment.jsp
<%@ page import="javax.mail.*" %>
<%@ page import="javax.mail.internet.*" %>
<%@ page import="javax.activation.*" %>
<%@ page import="java.util.*,java.io.*" %>
<%@ page import="com.oreilly.servlet.MultipartRequest" %>
<%@ page contentType="text/html;charset=GB2312" %>
<html>
<head>
<title>CH17 - JavaMail2.jsp</title>
</head>
<body>
<h2>利用JavaMail来传送电子邮件 - 附件</h2>
<%
InternetAddress[] address = null;
request.setCharacterEncoding("GBK");
MultipartRequest multi = new MultipartRequest(request , "." , 5*1024*1024 , "GBK");
String host = multi.getParameter("Host");
String user = multi.getParameter("User");
String password = multi.getParameter("Password");
String From = multi.getParameter("From");
String to = multi.getParameter("To");
String Subject = multi.getParameter("Subject");
String type = multi.getParameter("Type");
String messageText = multi.getParameter("Message");
String FileName = multi.getFilesystemName("FileName");
try {
// 设定所要用的Mail 服务器和所使用的传输协议
Properties props = new Properties();
props.put("mail.smtp.host", host); //指定SMTP服务器
props.put("mail.smtp.auth", "true"); //指定是否需要SMTP验证
// 产生新的Session 服务
Session mailSession = Session.getDefaultInstance(props);
Message msg = new MimeMessage(mailSession);
// 设定传送邮件的发信人
msg.setFrom(new InternetAddress(From));
// 设定传送邮件至收信人的信箱
address = InternetAddress.parse(to,false);
msg.setRecipients(Message.RecipientType.TO, address);
// 设定信中的主题
msg.setSubject(Subject);
// 设定送信的时间
msg.setSentDate(new Date());
if (FileName != null)
{
File file = new File(FileName);
// 如果有附件,先将邮件内容部分存起来
MimeBodyPart mbp1 = new MimeBodyPart();
// 设定邮件内容的类型为 text/plain 或 text/html
mbp1.setContent(messageText, type + ";charset=GB2312");
// 再来对附件作处理
MimeBodyPart mbp2 = new MimeBodyPart();
FileDataSource fds = new FileDataSource(FileName);
mbp2.setDataHandler(new DataHandler(fds));
mbp2.setFileName(MimeUtility.encodeText(fds.getName(), "GB2312", "B"));
// 最后再将两者整合起来,当作一份邮件送出
Multipart mp = new MimeMultipart();
mp.addBodyPart(mbp1);
mp.addBodyPart(mbp2);
msg.setContent(mp);
}else{
// 若没有附件时,就直接存邮件内容
msg.setContent(messageText,type + ";charset=GB2312");
}
Transport transport = mailSession.getTransport("smtp");
transport.connect(host, user, password);
transport.sendMessage(msg, msg.getAllRecipients());
transport.close();
out.println("邮件已顺利发送至:"+to);
}catch (MessagingException mex){
mex.printStackTrace();
}
%>
</body>
</html>
执行结果如图 10-15所示:
图10-15 JavaMailAttachment.jsp的执行结果
邮件服务器上的显示结果如图 10-16所示。
图10-16 邮件服务器上显示结果
【专家提示】JavaMail API只支持ASCII单字节字符串处理,为了解决附件中文文件名的问题,我们必须加上一段程序来做转码工作。
mbp2.setFileName(MimeUtility.encodeText(fds.getName(), "GB2312", "B"));
其功能是将多字节Unicode字符串转化为单字节US-ASCII字符串。
【专家提示】发送附件邮件存在支持附件大小的问题,邮件消息的大小要受到您的SMTP服务器的限制,而不是由JavaMail API限制的。
JavaMail含有一组用于发送和接收邮件消息的API,一般地发送邮件使用SMTP协议,接收邮件使用POP3协议。
Message类是一个抽象类,可带有消息的寻址信息和内容;Folder类是一个抽象类,包含子文件夹和消息等内容;Store类是一个抽象类,定义用于访问文件夹和检索消息的协议, 要查看文件夹和消息,需要使用它的connect()方法;编写发送邮件的程序常用Transport类的send()方法发送消息;Multipart消息是Message类的对象,Multipart消息可以有多个BodyPart。
【思考1】JavaMail组件中为什么Message、Folder和Store都被定义为抽象类?我们如果取得它们相应的实例对象?
答:JavaMail API定义了一个用来管理邮件的通用接口,并且JavaMail允许程序员通过API里的接口来撰写自己的应用程序,然后在执行时期再请求使用某种类型的处理。这样程序员采用JavaMail编写邮件消息收发程序具有适应于任何邮件服务器的通用性和跨平台的优点。Message类我们一般使用其相应的子类MimeMessage创建实例,Store类实例用Session的getStore()方法取得,Folder类实例用Store的getFolder()方法取得。
第11章 表达式与标签
【本章专家知识导学】
JSP标签是在JSP页面中对JavaBean的封装功能进一步补充,从而实现Java代码与HTML分离的一种服务器端程序。这样做的目的是让程序的实现与显示分离,分离的好处是让美工专心做界面,开发人员专心实现业务。
本章旨在引导读者学习表达式语言(EL)和JSTL标签以及如何自定义标签,将详细介绍EL的语法和各种运算符的使用,JSTL标签中的核心标签、格式标签、SQL标签、XML标签等标签的用法,最后介绍自定义标签的实现。通过学习本章内容后读者应能够利用EL和标签来开发Web应用,取代传统直接在页面上嵌入Java程序的做法,以提高程序的可阅读性和可维护性。
11.1 表达式语言简介
表达式语言(EL)是JSP技术的主要特点之一。Java社区组织(JCP,Java Community Process)的JSP标准标签库专家组和JSP2.0专家组共同开发了表达式语言。取代传统直接在页面上嵌入Java脚本程序以完成复杂功能的做法,以提高程序的阅读性、维护性和方便性。EL表达式可用于:
静态文本:包含EL表达式,其值在运行时计算出来。求值后,EL表达式被计算值替换。
标准标签和自定义标签的属性,以帮助显示网页上的动态内容。
读取JavaBean属性,使用EL表达式可以简洁地读出JavaBean的属性值。
下面演示一个JSP使用EL示例程序。
【例11-1】在JSP中应用EL的示例
el_example.jsp
<%@ page language="java" contentType="text/html; charset=GBK"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>EL表达式示例</title>
</head>
<body>
${"请选购商品"} <br>
<form action="el_example1.jsp" method="post">
<select name="trade">
<option value="专家导学JSP应用开发">专家导学JSP应用开发</option>
<option value="专家导学Java面向对象编程">专家导学Java面向对象编程</option>
</select>
单价:<input type="text" name="price"/>
数量:<input type="text" name="count"/>
<input type="submit" value="提交">
</form>
</body>
</html>
el_example.jsp运行结果如图11-1所示。
图11-1 el_example.jsp运行结果
选择相应的商品填写单价和购买数量后点击“提交”按钮将参数提交至el_example1.jsp页面处理。el_example1.jsp将利用JavaBean接收参数并用EL显示购买结果。
TradeBean.java
package eldemo;
/**
* 定义接收商品定单信息的JavaBean
* @author Administrator *
*/
public class TradeBean {
private String trade;// 商品名称
private int count;// 购买数量
private float price;//单价
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
public float getPrice() {
return price;
}
public void setPrice(float price) {
this.price = price;
}
public String getTrade() {
return trade;
}
public void setTrade(String trade) {
this.trade = trade;
}
}
el_example1.jsp
<%@ page language="java" contentType="text/html; charset=GBK"%>
<jsp:directive.page import="eldemo.TradeBean"/>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>EL表达式示例</title>
</head>
<%request.setCharacterEncoding("GBK"); %>
<jsp:useBean id="myorder" class="eldemo.TradeBean">
<jsp:setProperty name="myorder" property="*"/>
</jsp:useBean>
<body>
您购买的商品是: ${myorder.trade}<br>
商品单价是:${myorder.price}<br>
您购买的数量是:${myorder.count}<br>
您应支付的总金额是:${myorder.price*myorder.count }
</body>
</html>
el_example1.jsp的运行结果如图11-2所示。
图11-2 el_example1.jsp的运行结果
【专家提示】假若您所用的JSP容器只支持Servlet 2.3/JSP 1.2,如:Tomcat 4.1.29,就不能在JSP 网页中直接使用EL,需要配合JSTL标签一起使用,直接使用EL必须安装支持Servlet 2.4/JSP2.0或以上版本的JSP容器如:Tomcat5.0以上的版本。
EL 提供“.”和“[ ]”两种运算符来存取数据。下列两者所代表的意思是一样的:
${myorder.trade}
等同于
${ myorder["trade"] }
. 和 [ ] 也可以同时混合使用,如下:
${pageScope[“myorder”].trade}
回传结果为你选购中商品的名称。
不过,以下情况,两者会有差异:
当要存取的属性名称中包含一些特殊字符,如 . 或 – 等并非字母或数字的符号,就一定要使用“[ ]”,例如:
${ myorder.trade-Name }
上述是不正确的方式,应当改为:
${ myorder ["trade-Name"] }
EL变量可以用于存储和访问JSP程序中的值。默认情况下,JSP表达式${myorder}的计算结果与调用pageContext.findAttribute("myorder")一样。EL表达式中的变量可以引用存储在标准范围(page、request、session和application范围)中的属性。变量在标准范围中的默认搜索顺序是page、request、session和application。如果需要,可以使用toString()方法将变量的返回值转换成为字符串;如果找不到变量,则返回null值。
在 EL 表达式中,数字、字符串、布尔值和 null 都可以被指定为文字值。字符串可以用单引号或双引号定界。布尔值被指定为 true 和 false 。
6.5E+3(或6.5E3) 表示双精度值6500.0;
7D 表示双精度值7.0;
.01f 表示单精度值0.01;
'Welcome you'和"Welcom you"都是合法的字符串。
【专家提示】在EL中,"应表示为\",'应表示为 \', \应表示为\\,\(斜杠)称为转义符。
EL隐式对象总共有11 个,详见表11-2所示。
表11-2 EL隐式对象
表 11-2 中列出了 11 个 EL 隐式对象的标识符。不要将这些对象与 JSP 隐式对象混淆,其中只有一个对象是它们所共有的。尽管 JSP 和 EL 隐式对象中只有一个公共对象( pageContext ),但通过 EL 也可以访问其它 JSP 隐式对象。原因是 pageContext 拥有访问所有其它八个 JSP 隐式对象的特性。
其余所有 EL 隐式对象都是映射,可以用来查找对应于名称的对象。可以用它们来查找特定作用域中的标识符,而不用依赖于 EL 在缺省情况下使用的顺序查找过程。
接下来我们通过一个示例演示EL中隐式对象的使用方法。
【例11-2】使用EL隐式对象
UserBean.java
package eldemo;
public class UserBean {
private String name;
private String age;
public UserBean() {
}
public void setName(String name) {
this.name = name;
}
public void setAge(String age) {
this.age = age;
}
public String getName() {
return name;
}
public String getAge() {
return age;
}
}
setimplicit.jsp
<%@ page contentType="text/html; charset=GBK" %>
<%@ page import="eldemo.*" %>
<html>
<head>
<title>
setimplicit
</title>
</head>
<body bgcolor="#ffffff">
<%
UserBean user=new UserBean();
user.setName("张三");
user.setAge("18");
request.setAttribute("user",user);
UserBean user1=new UserBean();
user1.setName("李四");
user1.setAge("18");
session.setAttribute("user",user1);
pageContext.forward("getimplicit.jsp?bar=foo");
%>
</body>
</html>
getimplicit.jsp
<%@ page contentType="text/html; charset=GBK" %>
<html>
<head>
<title>
getimplicit
</title>
</head>
<body bgcolor="#ffffff">
我是${user.name}<br>
我还是${sessionScope['user'].name}<br>
参数是:${param.bar}<br>
主机是:${header['host']}<br>
你的浏览器是:${header['User-Agent']}
</body>
</html>
setimplicit.jsp往JSP容器的不同作用域中存入变量,然后调用getimplicit.jsp将其取。运行结果如图11-3所示。
图11-3 setimplicit.jsp运行结果
EL 还包括了几个用来操作和比较 EL 表达式所访问数据的运算符,表11-3 中汇总了这些运算符。
表11-3 EL运算符
算术运算符支持数值的加法、减法、乘法和除法。还提供了一个求余运算符。对几个 EL 表达式应用算术运算符的结果是将该算术运算符应用于这些表达式返回的数值所得的结果。
【例11-3】使用EL的算术运算符
Arithmetic.jsp
<%@ page contentType="text/html; charset=GBK" %>
<html>
<head>
<title>
基本的算述运算
</title>
</head>
<body bgcolor="#ffffff">
<h1>
基本的算述运算表达式
</h1>
<table border="1">
<thead>
<td><b>EL Expression</b></td>
<td><b>Result</b></td>
</thead>
<tr>
<td>\${1}</td>
<td>${1}</td>
</tr>
<tr>
<td>\${1 + 2}</td>
<td>${1 + 2}</td>
</tr>
<tr>
<td>\${1.2 + 2.3}</td>
<td>${1.2 + 2.3}</td>
</tr>
<tr>
<td>\${1.2E4 + 1.4}</td>
<td>${1.2E4 + 1.4}</td>
</tr>
<tr>
<td>\${-4 - 2}</td>
<td>${-4 - 2}</td>
</tr>
<tr>
<td>\${21 * 2}</td>
<td>${21 * 2}</td>
</tr>
<tr>
<td>\${3/4}</td>
<td>${3/4}</td>
</tr>
<tr>
<td>\${3 div 4}</td>
<td>${3 div 4}</td>
</tr>
<tr>
<td>\${3/0}</td>
<td>${3/0}</td>
</tr>
<tr>
<td>\${10%4}</td>
<td>${10%4}</td>
</tr>
<tr>
<td>\${10 mod 4}</td>
<td>${10 mod 4}</td>
</tr>
<tr>
<td>\${(1==2) ? 3 : 4}</td>
<td>${(1==2) ? 3 : 4}</td>
</tr>
</table>
</body>
</html>
页面运行结果如图11-4所示。
图11-4 Arithmetic.jsp运行结果
关系运算符允许比较数字或文本数据,比较的结果作为布尔值返回。逻辑运算符允许合并布尔值,返回新的布尔值,还可以将 EL 逻辑运算符应用于嵌套的关系或逻辑运算符的结果。
最后一种 EL 运算符是 empty ,它对于验证数据特别有用。 empty 运算符采用单个表达式作为其变量(也即, ${empty input} ),并返回一个布尔值,该布尔值表示对表达式求值的结果是不是“空”值。求值结果为 null 的表达式被认为是空,即无元素的集合或数组。如果参数是对长度为零的 String 求值所得的结果,则 empty 运算符也将返回 true 。
【例11-4】使用EL的关系运算符
Comparisons.jsp
<%@ page contentType="text/html; charset=GBK" %>
<html>
<head>
<title>
基本的关系运算
</title>
</head>
<body bgcolor="#ffffff">
<h1>
基本的关系运算示例
</h1>
<blockquote>
<u><b>数字处理</b></u>
<code>
<table border="1">
<thead>
<td><b>EL Expression</b></td>
<td><b>Result</b></td>
</thead>
<tr>
<td>\${1 < 2}</td>
<td>${1 < 2}</td>
</tr>
<tr>
<td>\${1 lt 2}</td>
<td>${1 lt 2}</td>
</tr>
<tr>
<td>\${1 > (4/2)}</td>
<td>${1 > (4/2)}</td>
</tr>
<tr>
<td>\${1 > (4/2)}</td>
<td>${1 > (4/2)}</td>
</tr>
<tr>
<td>\${4.0 >= 3}</td>
<td>${4.0 >= 3}</td>
</tr>
<tr>
<td>\${4.0 ge 3}</td>
<td>${4.0 ge 3}</td>
</tr>
<tr>
<td>\${4 <= 3}</td>
<td>${4 <= 3}</td>
</tr>
<tr>
<td>\${4 le 3}</td>
<td>${4 le 3}</td>
</tr>
<tr>
<td>\${100.0 == 100}</td>
<td>${100.0 == 100}</td>
</tr>
<tr>
<td>\${100.0 eq 100}</td>
<td>${100.0 eq 100}</td>
</tr>
<tr>
<td>\${(10*10) != 100}</td>
<td>${(10*10) != 100}</td>
</tr>
<tr>
<td>\${(10*10) ne 100}</td>
<td>${(10*10) ne 100}</td>
</tr>
</table>
</code>
<br>
<u><b>字符的处理</b></u>
<code>
<table border="1">
<thead>
<td><b>EL Expression</b></td>
<td><b>Result</b></td>
</thead>
<tr>
<td>\${'a' < 'b'}</td>
<td>${'a' < 'b'}</td>
</tr>
<tr>
<td>\${'hip' > 'hit'}</td>
<td>${'hip' > 'hit'}</td>
</tr>
<tr>
<td>\${5 > '4'}</td>
<td>${5 > '4'}</td>
</tr>
</table>
</code>
</blockquote>
</body>
</html>
页面的运行结果如图11-5所示。
图11-5 Comparisons.jsp运行结果
【例11-5】使用EL的逻辑运算符
logic.jsp
<%@ page contentType="text/html; charset=GBK"%>
<html>
<body>
<h1>
逻辑 EL
</h1>
<table border="1">
<tr>
<td>
<b>运算</b>
</td>
<td>
<b>EL 表达式</b>
</td>
<td>
<b>结果</b>
</td>
</tr>
<tr>
<td>
与
</td>
<td>
${'${'}true and true}
</td>
<td>
${true and true}
</td>
</tr>
<tr>
<td>
与
</td>
<td>
${'${'}true && false}
</td>
<td>
${true && false}
</td>
</tr>
<tr>
<td>
或
</td>
<td>
${'${'}true or true}
</td>
<td>
${true or true}
</td>
</tr>
<tr>
<td>
或
</td>
<td>
${'${'}true || false}
</td>
<td>
${true || false}
</td>
</tr>
<tr>
<td>
非
</td>
<td>
${'${'}not true}
</td>
<td>
${not true}
</td>
</tr>
<tr>
<td>
非
</td>
<td>
${'${'}!false}
</td>
<td>
${!false}
</td>
</tr>
</table>
${empty user.name}
</body>
</html>
页面的运行结果如图11-6所示。
图11-6 logic.jsp运行结果
JSP EL表达式包含在符号“${”与“}”之间。在某些特殊应用中我需要Java服务器不自动执行EL表达式计算工作。因此,有时需要禁用EL表达式,在JSP2.0中默认启用JSP EL表达式。
在JSP页面中禁用EL表达式计算语法如下:
<%@page isELIgnored="true|false"%>
其中,page表示页面指令,isELIgnored确定是否应忽略对EL表达式进行计算。
如果isELIgnored设置为true,当EL表达式在静态文本或标签属性中出现时将被忽略;如果isELIgnored设置为false,EL表达式则由JSP容器进行计算。
下面的例子将演示如何通过使用page指令来禁用或启用EL表达式计算。
【例11-6】禁用EL表达式
eldisable.jsp
<%@ page contentType="text/html; charset=GBK" isELIgnored="true"%>
<html>
<head>
<title>演示表达式的禁用</title>
</head>
<body bgcolor="#ffffff">
<h1>
EL表达式禁用
</h1>
<%
for (int i = 0; i < 10; i++) {
%>
${header["host"]}
<br>
<%
}
%>
</body>
</html>
页面的运行结果如图11-7所示。
图11-7 eldisable.jsp运行结果
可以看到页面把EL表达式完整输出了,并没有进行运算,这是因为禁用了EL表达式后,Web容器并能识别EL表达式。
JSTL1.1 必须在支持Servlet 2.4 且JSP 2.0 以上版本的容器中才可使用。JSTL 主要由Apache组织的Jakarta Project 所实现。读者可以到如下的网址下载得到JSTL的安装包:
http://jakarta.apache.org/builds/jakarta-taglibs/releases/standard/
下载后得到的文件为jakarta-taglibs-standard-current.zip。下载完后解压缩,将lib目录 中的jstl.jar、standard.jar 复制到当前Web应用的“WEB-INF\lib”中,此后就可以在当前Web应用的JSP 网页中使用JSTL了。除了复制 .jar 文件外,最好也把tld 文件的目录也复制到WEB-INF 中,以便日后使用。接下来将介绍几类常用的JSTL标签的使用方法。
JSTL标签核心标签库包含通用标签、条件标签、迭代标签、URL标签。通用标签用于操作JSP页面创建存储在隐式对象中的变量;条件标签用于对JSP页面中的代码进行条件判断和处理;迭代标签用于循环遍历一个对象集合;URL标签用于控制页面的跳转动作。
图11-9 JSTL的核心标签库
要在JSP页面中使用核心标签库,首先需要导入核心标签库的URI。
<%taglib uri="http://java.sun.com/jstl/core" prefix="c" %>
【专家提示】上述的功用在于声明将使用JSTL 的核心标签库。假若没有上述声明指令,将无法使用JSTL的核心功能,这是读者在使用JSTL 时要特别注意的地方。
在通用标签中,下面将重点介绍<c:set>、<c:out>、<c:remove>三个标签的使用方法。
<c:set>标签主要用来将变量储存至JSP 隐式对象中或是JavaBean 的属性中,其语法为:
语法1:将 value 的值储存至范围为scope 的 varName 变量之中
<c:set value="value" var="varName" [scope="{ page|request|session|application }"]/>
语法2:将本体内容的数据储存至范围为scope 的 varName 变量之中
<c:set var="varName" [scope="{ page|request|session|application }"]>
本体内容
</c:set>
value:要被储存的值
var:欲存入的变量名称
scope:指定变量的JSP 范围
<c:out>标签主要用来显示数据的内容,就像是 <%= scripting-language %> 一样,其语法为:
语法1:没有本体(body)内容
<c:out value="value" [escapeXml="{true|false}"] [default="defaultValue"] />
语法2:有本体内容
<c:out value="value" [escapeXml="{true|false}"]>
default value
</c:out>
value:需要显示出来的值。
Default:如果value 的值为null,则显示default 的值。
escapeXml:是否转换特殊字符,如:<转换成<
<c:remove>标签主要用来移除变量,其语法为:
<c:remove var="varName" [scope="{ page|request|session|application }"] />
var:欲移除的变量名称。
scope:var变量的JSP 范围
接下来看一个使用通用标签的演示示例。
【例11-7】使用通用标签
coredemo1.jsp
<%@ page contentType="text/html; charset=GBK"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<html>
<head>
<title>JSTL标签库中通用标签</title>
</head>
<body bgcolor="#ffffff">
<P>
该 JSP 页面在一个 session 作用域内的变量中存储 sessionvariable, 此 Web 应用程序中的其他 JSP
页面可以访问此变量.
</p>
<c:set var="sessionvariable" scope="session">
${80+8}
</c:set>
在删除 sessionvariable 之前先显示它的值:
<c:out value="${sessionScope.sessionvariable}" />
<c:remove var="sessionvariable" scope="session" />
<br>
显示并删除后的 sessionvariable 的值.
<br>
<c:out value="${sessionvariable}"> sessionvariable为NULL </c:out>
</body>
</html>
页面的运行结果如图11-10所示。
图11-10 coredemo1.jsp运行结果
条件标签中将重点介绍<c:if>、<c:choose>、<c:when>和<c:otherwise>四个标签的使用方法。
<c:if>标签用于有条件地执行代码。如果test属性的值为true,则会执行<c:if>标签的标签体。该标签是一个容器标签,其语法为:
语法1:没有标签体内容(body)
<c:if test="testCondition" var="varName"
[scope="{page|request|session|application}"]/>
语法2:有标签体内容
<c:if test="testCondition" [var="varName"]
[scope="{page|request|session|application}"]>
本体内容
</c:if>
test:如果表达式的结果为true,则执行标签体内容,false则相反
var:用来储存test 运算后的结果,即true 或false
scope:变量的JSP 范围
<c:choose>标签类似于Java语言的switch语句,它用于执行条件语句块。
<c:choose>
本体内容( <when> 和 <otherwise> )
</c:choose>
<c:when>标签的用途就和Java程序中用的when 一样。
<c:otherwise>标签在同一个 <c:choose>标签中,当所有 <c:when>标签的条件都没有成立时,则执行<c:otherwise>标签的标体内容。
【例11-8】使用条件标签
coredemo2.jsp
<%@ page contentType="text/html; charset=GBK" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<html>
<head>
<title>
JSTL标签库中条件标签
</title>
</head>
<body bgcolor="#ffffff">
<%
int i=(int)(Math.random()*10);
pageContext.setAttribute("signalStrength",new Integer(i));
%>
<c:if test="${pageScope.signalStrength < 5}">
<c:set var="signalFailure" value="true"
scope="page" />
</c:if>
<h1> 使用 If 和 Choose</h1>
<c:choose>
<c:when test="${pageScope.signalFailure == true}">
信号断开
</c:when>
<c:otherwise>
信号打开
</c:otherwise>
</c:choose>
</body>
</html>
页面的运行结果如图11-11所示。
图11-11 coredemo2.jsp运行结果
在跌代标签中我们将介绍<c:forEach>标签和<c:forTokens>标签这两个标签的使用方法。
<c:forEach>标签为循环控制,它可以将集合(Collection)中的成员循序浏览一遍。运作方式为当条件符合时,就会持续重复执行<c:forEach>标签的标签体内容。其语法为:
语法1:迭代一集合对象之所有成员
<c:forEach [var="varName"] items="collection" [varStatus="varStatusName"]
[begin="begin"][end="end"] [step="step"]>
本体内容
< /c:forEach>
语法2:迭代指定的次数
<c:forEach [var="varName"] [varStatus="varStatusName"] begin="begin" end="end"
[step="step"]>
本体内容
</c:forEach>
var:用来存放当前指到的成员。
items:被迭代的集合对象(集合对象类型为Arrays、Collection、Iterator、Enumeration、Map)。
varStatus:用来存放当前指到的相关成员信息。
begin:开始的位置,假若有begin 属性时,begin 必须大于等于 0。
end:结束的位置默认值最后一个成员,假若有end 属性时,end必须大于begin。
step:每次迭代的间隔数,假若有step 属性时,step 必须大于等于0。
<c:forTokens>标签用来浏览一字符串中所有的成员,其成员是由定义符号(delimiters)所分隔的。使用语法如下:
<c:forTokens items="stringOfTokens" delims="delimiters" [var="varName"]
[varStatus="varStatusName"] [begin="begin"] [end="end"] [step="step"]>
本体内容
</c:forTokens>
var:用来存放现在指到的成员。
items:被迭代的字符串。
delims:定义用来分割字符串的字符。
varStatus:用来存放现在指到的相关成员信息。
begin:开始的位置,假若有begin 属性时,begin 必须大于等于 0。
end:结束的位置,假若有end 属性时,end必须大于begin。。
step:每次迭代的间隔数,假若有step 属性时,step 必须大于等于0。
接下来我们来看一个跌代标签的示例程序。
【例11-9】使用迭代标签
coredemo3.jsp
<%@ page contentType="text/html; charset=GBK"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ page import="java.util.*"%>
<html>
<head>
<title>JSTL标签库中跌代标签</title>
</head>
<body bgcolor="#ffffff">
<%
String[] numbers = { "1", "2", "3", "4" };
pageContext.setAttribute("numbers", numbers);
Vector vnumbers = new Vector();
vnumbers.add("1");
vnumbers.add("2");
vnumbers.add("3");
vnumbers.add("4");
vnumbers.add("5");
pageContext.setAttribute("vnumbers", vnumbers);
%>
<c:set var="member" value="Joe:Petter;Ryan|John" scope="page" />
<h1>
数组跌代
</h1>
<c:forEach var="numbering" items="${numbers}">
<c:out value="${numbering}" />
<br>
</c:forEach>
<h1>
集合类跌代
</h1>
<c:forEach var="vnumbering" items="${vnumbers}">
<c:out value="${vnumbering}" />
<br>
</c:forEach>
<h1>
自变量循环
</h1>
<c:forEach var="i" begin="1" end="10" step="1">
<c:out value="${i}" />
<br />
</c:forEach>
<h1>
字符串分割
</h1>
<c:forTokens items="${pageScope.member}" delims=":;|" var="membername">
<c:out value="${membername}" />
<br />
</c:forTokens>
</body>
</html>
页面的运行结果如图11-12所示。
图11-12 coredemo3.jsp运行结果
下面将介绍URL标签中的<c:import>、<c:redirect>和<c:url>三个标签的使用方法。
<c:import>标签可以把其他静态或动态文件包含至本身JSP 网页。它和JSP动作指令的<jsp:include>最大的差别在于:<jsp:include>只能包含和自己同一个Web应用下的文件;而<c:import>除了能包含和自己同一个web application 的文件外,亦可以包含不同Web应用或者是其他网站的文件。
语法1:
<c:import url="url" [context="context"] [var="varName"]
[scope="{page|request|session|application}"] [charEncoding="charEncoding"]>
本体内容
</c:import>
语法2:
<c:import url="url" [context="context"]
varReader="varReaderName" [charEncoding="charEncoding"]>
本体内容
</c:import>
url:被包含文件的地址 。
context:在相同Web容器下,其他web应用必须以“/”开头。
var:储存被包含的文件的内容(以String类型存入) 。
scope:变量的JSP 作用域 。
charEncoding:被包含文件之内容的编码格式 。
varReader:储存被包含的文件的内容(以Reader类型存入)。
例如:
<c:import url="http://www.csai.cn" />的功能是:用<c:import>把http://www.csai.cn 的内容加到网页中。
<c:url>标签主要用来产生一个URL。
语法1:没有标签体内容
<c:url value="value" [context="context"] [var="varName"]
[scope="{page|request|session|application}"] />
语法2:有标签体内容代表查询字符串(Query String)参数
<c:url value="value" [context="context"] [var="varName"]
[scope="{page|request|session|application}"] >
<c:param> 标签
</c:url>
value:执行的URL。
context:在相同Web容器下,其他web应用必须以“/”开头。
var:储存被包含文件的内容(以String 类型存入)。
scope:变量的JSP作用域。
例如:
<c:url value="http://www.csai.cn">
<c:param name="figure " value="希赛顾问"/>
</c:url>
会产生一个“http://www.csai.cn?figure=希赛顾问”的超链接。
<c:redirect>标签可以将客户端的请求从一个JSP 网页导向到其他文件。
语法1:没有标签体内容
<c:redirect url="url" [context="context"] />
语法2:有标签体内容代表查询字符串(Query String)参数
<c:redirect url="url" [context="context"] > <c:param> </c:redirect >
url:导向的目标地址 。
context:相同Container 下,其他web 站台必须以“/”开头。
例如:
<c:redirect url="http://www.csai.cn" />,那么网页将会自动导向到http://www.csai.cn。
国际化(I18N)与格式化标签库可用于创建国际化的Web应用程序,它们对数字和日期、时间的输出进行了标准化。国际化的应用程序支持多语言。在JSP页面中导入国际化与格式化标签库的语法是:
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
本章中我们将重点介绍常用的<fmt:setLocale>、<fmt:bundle>、<fmt:setBundle>、<fmt:message>几个常用标签的使用方法。
<fmt:setLocale>标签用于重写客户端指定的区域设置。它将区域设置存储在javax.servlet.jsp.jstl.fmt配置变量中。setLocale是一个空标签,其使用语法为:
<fmt:setLocale value="setting" variant="variant"
scope="page/request/session/application"/>
value:包含一个含有两个小写字母的语言代码和一个含有两个大写字母的国家或地区代码。语言和国家或地区代码应该用连字符或下划线分隔如:zh_CN。
variant:指定特定于浏览器的变量,它是可选的。
scope:指定配置变量的范围。
接下来看一个使用<fmt:setLocale>示例
【例11-10】使用<fmt:setLocale>标签
fmt_setlocal.jsp
<%@ page contentType="text/html; charset=GBK"%>
<%@ page import="java.util.*"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%>
<html>
<head>
<title>设置local方式来显示不同数值,日期,时间格式</title>
</head>
<body bgcolor="#ffffff">
<%
Date now = new Date();
pageContext.setAttribute("now", now);
out.println("本地:" + request.getLocale().toString() + "<br>");
%>
<H2>
数字格式示例
</H2>
将
<B>123.41234"</B> 格式化为:
<fmt:formatNumber value="123.41234" type="number"
maxFractionDigits="2" />
<BR>
<HR>
<H2>
货币格式示例
</H2>
<c:set var="salary" value="125000" />
工资:
<c:out value="${salary}" />
<BR>
<fmt:setLocale value="en_US" />
用本机的
<B>en_US</B> 将工资格式化为:
<fmt:formatNumber type="currency" value="${salary}" />
<BR>
日期:
<fmt:formatDate value="${now}" />
<br>
<fmt:setLocale value="zh_CN" />
用本机的
<B>zh_CN</B> 将工资格式化为:
<fmt:formatNumber type="currency" value="${salary}" />
<BR>
日期:
<fmt:formatDate value="${now}" />
<br>
<HR>
</body>
</html>
页面的运行结果如图11-13所示。
图11-13 fmt_setlocal.jsp运行结果
<fmt:bundle>标签用于创建一个I18N本地化上下文,并将它的资源包加载到其中。资源包的名称由<fmt:bundle>标签的basename属性指定。此标签的语法为:
<fmt:bundle basename="basename">
本体内容
</fmt:bundle>
<fmt:message>标签用于给出资源包的输出值。<fmt:message>标签的语法为:
<fmt:message key="messagekey ">
其中,key属性指定消息的关键字。
接下来通过一个示例演示<fmt:bundle>和<fmt:message>的用法。
定义好的资源包文件存放在web应用目录下的“WEB-INF/classes”目录下,如:
input.properties
title=this is fmtdemo!
hello=hello world
中文内容版为input_zh_CN1.properties
title=国际演示!
hello=朋友你好
country=你的国家
由于Java对于资源文件的处理只支持单字节,所以对中文资源需要使用:
native2ascii -encoding gb2312 input_zh_CN1.properties input_zh_CN.properties
对中文资源文件进行编码生成单字节的input_zh_CN.properties资源文件。
title=\u56fd\u9645\u6f14\u793a!
hello=\u670b\u53cb\u4f60\u597d
country=\u4f60\u7684\u56fd\u5bb6
【例11-11】用标签使用资源文件
Fmt_resouce1.jsp
<%@ page contentType="text/html; charset=GBK"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<html>
<head>
<fmt:bundle basename="input">
<title><fmt:message key="title" /></title>
</head>
<body bgcolor="#ffffff">
<h1>
<fmt:message key="hello" />
</h1>
</fmt:bundle>
</body>
</html>
运行结果,如图11-14所示。
图11-14 Fmt_resouce1.jsp运行结果
接下来按图 11-15所示的方式修改浏览器的语言设置。
图11-15 修改浏览器的语言设置
修改后Fmt_resouce1.jsp的运行结果,如图11-16所示。
图11-16 Fmt_resouce1.jsp运行结果
<fmt:setBundle>:创建一个I18N本地化上下文,并将它存储在范围变量中。该标签是一个空标签。其语法为:
<fmt:setBundle basename="basename" var="varName"
scope="page/request/session/application"/>
basename:指定资源包的名称。
var:指定导出的范围变量的名称,它存储I18N本地化上下文。
scope:指定var的范围。
接下来通过一个示例程序看看如何使用<fmt:setBundle>。
【例11-12】使用<fmt:setBundle>标签
Fmt_resouce.jsp
<%@ page contentType="text/html; charset=GBK"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<html>
<head>
<fmt:setLocale value="zh_CN" scope="page" />
<fmt:setBundle basename="input" scope="page" />
<title><fmt:message key="title" /></title>
</head>
<body bgcolor="#ffffff">
<h1>
<fmt:message key="hello" />
</h1>
</body>
</html>
Fmt_resouce.jsp的运行结果如图11-17所示。
图11-17 Fmt_resouce.jsp运行结果
采用这种方式载入资源包会发现就算采用图11-15的方式修改浏览器的语言设置其运行结果始终如图11-17所示。
JSTL的SQL标签用于访问各种关系数据库,是为了基于Web的小型应用程序而设计的。它提供的各种标签可用于在JSP页面内直接访问数据库,在JSP页面中导入SQL标签库的语法是:
<%@ taglib uri="http://java.sun.com/jsp/jstl/sql" prefix="sql"%>
这样每个SQL标签库中的标签都冠以一个<sql>前缀,本章我们将重点介绍<sql:setDataSource>、<sql:query>、<sql:update>、<sql:transaction>等几个常用标签的使用方法。
<sql:setDataSource>标签用于为数据库设置数据源。它是一个空标签,允许用户为数据库设置数据源信息,其语法为:
<sql:setDataSource DataSource="datasource" url="jdbcurl"
driver="driverclassdriver" user="username" password="usepwd"
var="varname" scope="page|request|session|application"/>
DataSource:可以是Java命名和目录接口(JNDI,Java Naming and Directory Interface)资源的路径或JDBC参数字符串。
url:是与数据库关联的URL。
driver:是一个JDBC参数,其值为驱动程序的类名。
user:是数据库的用户名。
password:是用户的密码。
var:是指定数据源的导出范围变量的名称。
scope:指定范围。
【专家提示】在<sql:setDataSource>中,如果使用了DataSource属性,则无法使用url属性。
<sql:query>标签用于搜索数据库并返回包含数据行的结果集。其语法为:
<sql:query var="varname" dataSource="datasource"
scope="page|request|session|application" maxRows="maxrows"
startRow="startRow">
要执行的SQL语句
<sql:param/>
</sql:query>
var:为查询结果指定导出的范围变量的名称。
scope:指定变量的范围。
dataSource:指定与要查询的数据库关联的数据源。
maxRows:指定结果中所包含的数据的最大行数。
startRow:指定从指定索引开始的数据行。
<sql:update>标签用于执行INSERT、UPDATE和DELETE语句。如果所有数据行都没有受到插入、更新或删除操作的最响,则会返回0。其语法为:
<sql:update datasource="datasource" var="varName"
scope="page|request|session|application ">
SQL语句
<sql:param/>
</sql:update>
其中:
SQL语句:指定UPDATE、INSERT或DELETE语句。
dataSource:是与要更新的数据库关联的数据源。
var:为数据库更新的结果指定导出的范围变量的名称。
scope:指定变量的范围。
<sql:transaction>标签用于为<sql:query>标签和<sql:update>标签建立事务处理上下文。
<sql:transaction dataSource="datasource" isolation="isolationLevel">
使用<sql:query>或<sql:update>语句
</sql:transaction>
dataSource:设置SQL的数据源,它可以是字符串或一个DataSource对象。
isolation:设置事务处理的隔离级别。隔离级别可以是read_committed、read_uncommitted、repeatable_read或serializable。
接下来演示一个使用SQL标签的示例程序。
【例11-13】使用SQL标签
sqldemo.jsp
<%@ page contentType="text/html; charset=GBK"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/sql" prefix="sql"%>
<html>
<head>
<title>sqldemo</title>
</head>
<body bgcolor="#ffffff">
<h1>
使用 SQL 标签库
</h1>
<c:set var="valprice" value="1000" />
<sql:setDataSource
driver="com.microsoft.jdbc.sqlserver.SQLServerDriver"
url="jdbc:microsoft:sqlserver://localhost:1433;DataBaseName=pubs"
user="sa" password="" var="conn" />
<sql:transaction dataSource="${conn}">
<sql:update var="newTable">
CREATE TABLE ProductDetails
(
ProductId int IDENTITY (1000, 1) NOT NULL primary key,
ProductName varchar (20) NOT NULL ,
ProductType varchar (15) NOT NULL ,
Price varchar (5) NOT NULL ,
Brand varchar (25) NOT NULL ,
Description varchar (50) NOT NULL
)
</sql:update>
</sql:transaction>
<sql:update var="newrow" dataSource="${conn}">
INSERT INTO ProductDetails(ProductName, ProductType,
Price, Brand, Description)
VALUES('JSP专家导学 ', '编程书籍', '1000', 'Lee', '适合大专院校的教材或参考书籍')
</sql:update>
<sql:query var="products" dataSource="${conn}">
select * from ProductDetails
</sql:query>
<table border="1">
<c:forEach items="${products.rows}" var="row">
<tr>
<td>
${row.ProductName}
</td>
<td>
${row.ProductType}
</td>
<td>
${row.Price}
</td>
<td>
${row.Description}
</td>
</tr>
</c:forEach>
</table>
</body>
</html>
页面的运行结果如图11-18所示。
图11-18 sqldemo.jsp运行结果
JSTL提供了一些有关XML的标签,让开发人员可以不用深入了解SAX和DOM等API,就可以轻易地处理XML文件。在JSP页面中导入XML标签库的语法是:
<%@ taglib prefix="x" uri="http://java.sun.com/jsp/jstl/xml" %>
这里将介绍<x:parse>、<x:out>和<x:set>几个标签的使用方法。
<x:parse>标签用来解析XML文件。使用<x:parse>标签的语法为:
<x:parse {var="var" [scope="{page|request|session|application}"] |varDom="var"
[scopeDom="{page|request|session|application}"]}
[systemId="systemId"] [filter="filter"]>
需要解析的XML文档
</x:parse>
var:储存解析后的XML文件。
scope:变量的JSP范围。
varDom:储存解析后的XML文件(类型为org.w3c.dom.Document)。
scopeDom:的范围。
systemId :XML文件的URI。
filter:XMLFilter过滤器。
<x:out> 标签主要用来取出XML中的节点值。
<x:out select=”XPathExpression” [escapeXml=”{true|false}”]/>
其中:
select XPath语句。
escapeXml 是否转换特殊字符,例如:<转换成&It;
【专家提示】有关XPath的内容请读者参考本系列丛书中《专家导学Java与XML应用开发》相关内容。
<x:set> 将从XML文件中取得内容存储至JSP范围中。
语法为:
<x:set select="XPathExpression" var="var"
[scope="{page|request|session|application}"]/>
其中:
select:XPath语句。
var:将从XML文件中取得的内容储存至varName中。
scope:变量的JSP范围。
【例11-14】使用XML标签
Books.xml
<?xml version="1.0" encoding="GBK"?>
<books>
<book lang="java">
<title>《Java 编程思想》</title>
<author>Bruce Eckel</author>
</book>
<book lang="java">
<title>《JSP 专家导学》</title>
<author>希赛顾问团</author>
</book>
<book lang=".net">
<title>《专家导学.NET开发框架》</title>
<author>希赛顾问团</author>
</book>
</books>
xmldemo.jsp
<%@ page language="java" contentType="text/html; charset=GBK"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ taglib prefix="x" uri="http://java.sun.com/jsp/jstl/xml" %>
<html>
<head>
<title>XML标签示例程序</title>
</head>
<body>
<x:parse var="sampleXml">
<c:import charEncoding="GBK" url="Books.xml"/>
</x:parse>
<h3>装载XML文件成功</h3>
<h3>显第一本书的标题:</h3>
<x:out select="$sampleXml//title"/><br/>
<h3>查找.net书:</h3>
<x:set select="$sampleXml//book[@lang='.net']" var="DoNetBook" />
<x:out select="$DoNetBook"/>
</body>
</html>
页面的运行结果如图11-19所示。
图11-19 xmldemo.jsp运行结果
【专家提示】使用XML标签时,Web应用环境中除在“WEB-INF/lib”目录下加入jstl.jar和standard.jar外还应加入xalan.jar和xercesImpl.jar两个文件。
在JSP1.1时,新增了JSP标签函数,它最大的功用在于让用户能够自行制订一个标签,例如前面所讲的JSTL标签都是属于此类。以往开发标签时,不是继承TagSupport,就是继承BodyTagSupport类,然后实现doStartTag()、doEndTag()或doAfterBody()方法,而且还要搞清楚这些方法的回传值,如:EVAL_BODY_INCLUDE,EVAL_PAGE、SKIP_BODY、SKIP_PAGE、EVAL_BODY_AGAIN等等。
JSP2.0为了简化开发标签的复杂性,因此增加了一个制作Simple Tag的SimpleTagSupport类。SimpleTagSupport类是实现SimpleTag接口的,它只须要实现一个doTag()的方法即可,而不再需要一堆回传值。使用标签处理程序实现自定义标签三个步骤:
编写标签处理程序(Java 类)。
编写标签库描述符(提供有关标签和库文件的元信息的 XML 文件)
JSP 实现(包含自定义标签的 JSP 文件)
【例11-15】开发简单的自定义标签
标签处理程序的源代码如下。
SimpleTagDemo.java
package customtag;
import javax.servlet.jsp.tagext.*;
import java.io.*;
import java.util.*;
import javax.servlet.jsp.*;
/**
* @author Administrator
*
*/
public class SimpleTagDemo extends SimpleTagSupport {
private int num;
public void setNum(int num) {
this.num = num;
}
public void doTag() throws JspException, IOException {
JspWriter out = this.getJspContext().getOut();
out.println("<h1>演示" + num + "次输出标签体的内容</h1>");
for (int i = 0; i < num; i++) {
getJspContext().setAttribute("count", String.valueOf(i + 1));
getJspBody().invoke(null);
}
}
}
标签描述tld文件的内容如下。
simpledemo.tld
<?xml version="1.0" encoding="UTF-8" ?>
<taglib 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-jsptaglibrary_2_0.xsd"
version="2.0">
<description>
A tag library exercising SimpleTag handlers.
</description>
<tlib-version>1.0</tlib-version>
<short-name>SimpleTagLibrary</short-name>
<uri>/SimpleTagLibrary</uri>
<tag>
<description>Outputs Hello, World</description>
<name>simpletagdemo</name>
<tag-class>customtag.SimpleTagDemo</tag-class>
<body-content>scriptless</body-content>
<attribute>
<name>num</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
</taglib>
tlib-version:是必须元素,指定标记库函数的版本号,例如:
<tlib-version>1.0.123.123.234</tlib-version>。
short-name:为标记库指定一个缩略名称,用它可以作为标记库的缺省名字空间,如:
< short-name >demotaglib</ short-name >
info:用来描述标记的文档信息,缺省值为空
uri:用来指定连接到标记的附加源文件,即TLD的公共可用副本。
tag:是标记描述文件中最重要的一个元素,与其它元素不同的是,它可以有子元素。tag元素有六个子元素:name、tagclass、teiclass、bodycontent、info、attribute。name子元素用户指定的标记名称;tag-class子元素指定标记处理类,此处应该是全限定名称,即带包名的类;bod-ycontent子元素说明标记体的类型,分为四种:empty、jsp、scriptless、tagdependent,empty表示没有标记体,jsp表示标记体可以为jsp代码,scriptless表示标记中不能用jsp脚本,tagdependent表示标签中的内容交给标记处理程序去进行处理;info记录标记的描述信息;attribute用来指定零个或者多个标记属性,这个元素又有三个子元素:name, required, rtextprvalue,name子元素是必须设定的元素,用来表示大小写敏感属性名称,required子元素是必须设定的元素,标识属性是否一定要存在,默认为false,如果设置为flase,则在标记处理程序中不会自动去调用属性的setAttribute方法,rtextprvalue子元素用来说明属性是否可以是动态生成的,但设定为true时,属性既可以是一个常量值,也可以由表达式动态生成。
simpletagdemo.jsp
<%@ page contentType="text/html; charset=GBK"%>
<%@ taglib uri="/WEB-INF/simpledemo.tld" prefix="mytag"%>
<html>
<head>
<title>simpletagdemo演示示例</title>
</head>
<body bgcolor="#ffffff">
<h1>
结果
</h1>
<mytag:simpletagdemo num="7">
重复输出数据:${count} of 7<br>
</mytag:simpletagdemo>
</body>
</html>
页面的运行结果如图11-20所示。
图11-20 simpletagdemo.jsp运行结果
【思考1】JSP标记与JavaBean有何区别?
标签文件是JSP2.0新增的主要功能之一。根据JSP2.0规范中的定义:所谓的Tag File就是让JSP网页开发人员可以直接使用JSP语法来制作标签,而不须了解Java语言。
所有Tag File文件的扩展名都为.tag或.tagx。假若Tag File包含其他完整或片段的Tag File,JSP2.0规范建议扩展名为.tagf。
JSP2.0标签文件的新特性有:
Tag File的七个隐式对象
request、response、out、jspContext、session、application、config。
Tag File的指令
新增的几个动作
<jsp:attribute>标签用来指定自定义标签属性块,必要时可以作为代码段输出。
<jsp:doBody>标签告诉容器去处理body,并且将处理结果添加到response中。你可以有选择性的使用“var” 属性,捕获处理的结果,并在下一步进行处理。
<jsp:invoke>标签对作为属性传递的特定参数进行操作
【例11-16】开发与使用标签文件
datetag.tag
<%@tag pageEncoding="GBK"%>
<%@tag import="java.util.*,java.text.*"%>
<%@variable name-given="fulldate" scope="AT_BEGIN"%>
<%
DateFormat fullformate = DateFormat.getDateInstance(DateFormat.FULL);
Date now = new Date();
jspContext.setAttribute("fulldate", fullformate.format(now));
%>
<jsp:doBody/>
simpletagdemo2.jsp
<%@ page contentType="text/html; charset=GBK"%>
<%@ taglib prefix="mytag" tagdir="/WEB-INF/tags/"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<html>
<head>
<title>datetag</title>
</head>
<body bgcolor="#ffffff">
<h1>
日期tag标签文件演示
</h1>
<mytag:datetag>
标签体的输出:${fulldate}<br>
</mytag:datetag>
页面的输出:${fulldate}
</body>
</html>
页面的运行结果如图11-21所示。
图11-21 simpletagdemo2.jsp运行结果
sumtag.tag
<%@tag pageEncoding="GBK"%>
<%@attribute name="items" required="true" rtexprvalue="true"%>
<%@attribute name="great" fragment="true"%>
<%@attribute name="less" fragment="true"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@variable name-given="sum" variable-class="java.lang.Object"%>
<c:if test="${not empty items}">
<c:forEach items="${items}" var="item">
<c:set var="sum" value="${item+sum}" />
</c:forEach>
<c:if test="${sum>=1000}">
<jsp:invoke fragment="great" />
</c:if>
<c:if test="${sum<1000}">
<jsp:invoke fragment="less" />
</c:if>
</c:if>
sumtag.jsp
<%@ page contentType="text/html; charset=GBK"%>
<%@ taglib prefix="mytag" tagdir="/WEB-INF/tags/"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<html>
<head>
<title>sumtag</title>
</head>
<body bgcolor="#ffffff">
<h1>
tag标签文件演示
</h1>
<mytag:sumtag items="111,222,333">
<jsp:attribute name="great">
<font color="red"> SUM: ${sum}</font>
</jsp:attribute>
<jsp:attribute name="less">
<font color="blue"> SUM: ${sum}</font>
</jsp:attribute>
</mytag:sumtag>
</body>
</html>
页面的运行结果如图11-22所示。
图11-22 sumtag.jsp 运行结果
【思考1】JSP标记与JavaBean有何区别?
答:JavaBean存在先天的不足,它只是可复用的组件,独立于运行环境而包装了一些数据和业务逻辑功能,无法获得运行环境信息,无法与jsp页面进行更加深层次的交互。
自定义标记机制,解决了JavaBean的这种缺陷,标记库的处理类,不但具有javabean的功能,而且可以jsp的环境信息传送到类中间,不但能得到jsp传递过来的属性,而且还能得与jsp共用页面环境,jsp 页面中可以通过session、page与标记处理程序进行数据通信,这一点是javaBean所不能实现的。
第12章 Web图表开发
【本章专家知识导学】
Web统计图形和报表的开发往往是令程序员感到头痛、棘手的问题,但在各种管理信息系统却是很常见的,好在JSP开发现在已经有了许多开源的组件,有了这些组件,程序员就不必再编写大量的代码,也不必关注Java底层的图形图象知识,只需编写少量的代码即可快速开发出漂亮的统计图形和报表,还能实现打印、导出成Excel、Word文件等强大的功能。
本章将给读者介绍两款快速进行Web统计图形和报表的开发工具——JFreeChart和JavaReport。JFreeChart用于图形的开发;JavaReport既可用于图形的开发也可用于报表的开发。掌握这两个组件来开发出适用的图形和报表并不难,下面就一起来学习。
12.1 JFreeChart组件介绍
程序员开发Java Web系统经常碰到需要开发Web图形与报表的情形,如果是C/S模式的开发,因为有集成开发工具的支持,如Microsft Visual Studio,开发图形与报表变得快捷,然而Web开发就显得比较麻烦了。
有程序员可能尝试过调用Office的ocx组件的方式来显示图形,这要求客户端必须要安装有office软件,但有时客户端使用的可能是WPS Office或其它的办公软件,这就使得Web系统失去了通用性。那么有没有别的方法实现呢?JDK中带有图形开发的软件包AWT,然而,从事应用软件系统开发的程序员并非图形图像方面的专家,短时间内无法掌握JDK中AWT的开发,况且这还要求程序员了解较多的底层API。因此,可以考虑使用比较成熟的第三方的组件包来实现。
JFreeChart是一个开源的组件包,它提供了Java中常用统计图形的快速开发API,程序员只要掌握少量的接口就能绘制出漂亮的图形,而且这些图形不仅可用于带有客户端界面的Java应用程序中,也可以是在JSP, Servlet, Java Applet中。本章中将主要介绍在JSP中如何使用JFreeChart组件包开发常用的图形,如果要在Servlet、应用程序或Java Applet中实现类似的功能,可自行参考JSP编程方法实现的程序来编写代码。
JFreeChart可用于创建饼图、线图、柱状图、散点图、甘特图等。可以从如下的网址得到许多有关于JFreeChart的详细信息:
http://www.jfree.org/jfreechart/
本书使用的JFreeChart版本是至本书成稿之日最新的1.0.5,可从如下的网址下载得到:
http://downloads.sourceforge.net/jfreechart/jfreechart-1.0.5.zip?modtime=1174667923&big_mirror=0
从以下的地址可以得到JFreeChart1.0.5的HTML格式的详细API说明文档:
http://downloads.sourceforge.net/jfreechart/jfreechart-1.0.5-javadocs.zip?modtime=1174668033&big_mirror=0
要在JSP中用JFreeChart开发Web图表,就需要安装JFreeChart组件包。将JFreeChart的zip压缩包jfreechart-1.0.5.zip解压缩,解压缩软件可以使用WinRar、WinZip等。在解压后得到的目录的lib子目录中的所有jar包拷贝到Web应用的WEB-INF/lib目录下,即可在JSP中使用JFreeChart了。
【专家提示】上段所述的这种安装方法仅对当前Web应用有效,如果要让当前网站中的所有的Web应用都可以使用JFreeChart1.0.5,则应将jar包拷贝到Tomcat5的安装目录的common/lib子目录中。
JFreeChart1.0.5需要JDK1.3版本或更高版本的JDK环境作为支持,使用了JDK中的Java 2D API来作图。JFreeChart1.0.5还使用到了JCommon组件,可以从如下的地址得到有关JCommon的更多信息:
http://www.jfree.org/jcommon/index.html
JFreeChart1.0.5的lib子目录中已带用JCommon组件包jcommon-1.0.9.jar,不必再行安装JCommon组件。
用JFreeChart创建Web图形需要经过三个步骤:
第一步:创建一个包含要在Web图形中显示的数据的数据集对象。
第二步:创建一个用于作图的JFreeChart对象。
第三步:向目标作图。Web图表的作图目标为response.getOutputStream(),即response对象的输出流。
【例12-1】 一个简单的饼图
下面以创建一个简单的饼形图(用户年龄阶段分布统计图)为例。
第一步:创建一个包含要在Web图形中显示的数据的数据集对象。代码如下:
String dataName[]=new String[]{"0-30岁","30-50岁","50-70岁","70岁以上"};//显示数据系列
int dataValueCount[]={4,5,4,6};//数据系列对应的值
DefaultPieDataset pieDataset = new DefaultPieDataset();
for(int i=0;i<dataName.length;i++)
pieDataset.setValue(dataName[i],dataValueCount[i]);
【专家提示】饼图的数据集对象可以是实现了JFreeChart组件中的PieDataset接口的类的实例,读者可以自行编写类实现,也可以使用JFreeChart组件中已有的已实现了PieDataset接口的DefaultPieDataset类,更加方便和快捷。
第二步:创建一个用于作图的JFreeChart对象。代码如下:
String titleString="用户年龄阶段分布统计图";//图的标题
JFreeChart chart = ChartFactory.createPieChart(titleString,//图形标题
pieDataset,//数据集
true, //是否显示图例
true, //是否有工具提示
false//是否有URL
);
ChartFactory是一个工厂类,可用于创建JFreeChart对象。这里使用的方法是createPieChart()方法,参数作了简单的设置。创建JFreeChart对象的方法还有许多种形式,这里仅给出简单的一种,有兴趣可以参考本章后续的JFreeChart常用类的说明。
第三步:向目标作图。代码如下:
ChartUtilities.writeChartAsJPEG(response.getOutputStream(),chart,500,300);
下面结出本实例的完整代码。
firstPie.jsp
<%@ page contentType="text/html;charset=GB2312" %>
<%@ page import="org.jfree.chart.JFreeChart,org.jfree.chart.ChartUtilities,
org.jfree.chart.ChartFactory,org.jfree.data.general.DefaultPieDataset"%>
<html>
<body>
<%
//第一步:创建一个包含要在Web图形中显示的数据的数据集对象
String dataName[]=new String[]{"0-30岁","30-50岁","50-70岁","70岁以上"};//显示数据系列
int dataValueCount[]={4,5,4,6};//数据系列对应的值
DefaultPieDataset pieDataset = new DefaultPieDataset();
for(int i=0;i<dataName.length;i++)
pieDataset.setValue(dataName[i],dataValueCount[i]);
//第二步:创建一个用于作图的JFreeChart对象
String titleString="用户年龄阶段分布统计图";//图的标题
JFreeChart chart = ChartFactory.createPieChart(titleString,pieDataset,true,true, false);
//第三步:向目标作图。
ChartUtilities.writeChartAsJPEG(response.getOutputStream(),chart,500,300);
%>
</body>
</html>
程序的运行效果如图12-1所示。
图12-1 第一个简单的饼图
【专家提示】如果数据集中的数据来源于数据库中,则可以在第一步中编写程序从数据库中取出数据,再设置数据集中的数据,第二步与第三步仍照旧,即可以图形方式显示数据库中的数据了。
用JFreeChart创建条形图主要需要用实现了CategoryDataset接口或IntervalXYDataset接口的类的实例来作为数据集对象,常用JFreeChart中自带的已实现了CategoryDataset接口的DefaultCategoryDataset类。
【例12-2】 创建条形图
创建条形图同样需要经历与创建饼形图类似的三步。本例将生成一个表示用户年龄阶段分布的条形图。
第一步:创建一个包含要在Web图形中显示的数据的数据集对象。代码如下:
String dataName[]=new String[]{"0-30岁","30-50岁","50-70岁","70岁以上"};//显示数据系列
int dataValueCount[]={4,5,4,6};//数据系列对应的值
//------创建数据集,并设置值------
DefaultCategoryDataset categoryDataset = new DefaultCategoryDataset();
for(int i=0;i<dataName.length;i++)
categoryDataset.addValue(dataValueCount[i],dataName[i],dataName[i]);
第二步:创建一个用于作图的JFreeChart对象。代码如下:
String titleString="用户年龄阶段分布统计图";//图的标题
JFreeChart chart = ChartFactory.createBarChart(titleString,//图形标题
"用户年龄阶段",//分类轴标签(默认为横向)
"数量",//数值轴标签(默认为纵向)
categoryDataset,//数据集
PlotOrientation.VERTICAL,//图形显示方向,竖向
true,//是否显示图例
true,//是否有工具提示
false//是否有URL
);
第三步:向目标作图。
ChartUtilities.writeChartAsJPEG(response.getOutputStream(),chart,500,300);
下面结出本实例的完整代码。
barChartd.jsp
<%@ page contentType="text/html;charset=GB2312" %>
<%@ page import="org.jfree.chart.JFreeChart,org.jfree.chart.ChartUtilities,
org.jfree.chart.ChartFactory,
org.jfree.chart.plot.PlotOrientation,
org.jfree.data.category.DefaultCategoryDataset"%>
<html>
<body>
<%
//第一步:创建一个包含要在Web图形中显示的数据的数据集对象
String dataName[]=new String[]{"0-30岁","30-50岁","50-70岁","70岁以上"};//显示数据系列
int dataValueCount[]={4,5,4,6};//数据系列对应的值
//------创建数据集,并设置值------
DefaultCategoryDataset categoryDataset = new DefaultCategoryDataset();
for(int i=0;i<dataName.length;i++)
categoryDataset.addValue(dataValueCount[i],dataName[i],dataName[i]);
//第二步:创建一个用于作图的JFreeChart对象
String titleString="用户年龄阶段分布统计图";//图的标题
JFreeChart chart = ChartFactory.createBarChart(titleString,"用户年龄阶段","数量",
categoryDataset,PlotOrientation.VERTICAL,true,true,false);
//第三步:向目标作图。
ChartUtilities.writeChartAsJPEG(response.getOutputStream(),chart,500,300);
%>
</body>
</html>
程序的运行效果如图12-3所示。
图12-3 创建条形图
条形图的显示也可以作一些定制性的修改,常用的修改有:
(1)修改图形的背景颜色
修改条形图的背景颜色使用如下的方法:
chart.setBackgroundPaint(new Color(0xBBBBDD));
chart是JFreeChart实例的名称。
(2)修改图形的柱状条颜色
修改图形的柱状条颜色需要使用setSeriesPaint()方法,但这个方法并非JFreeChart类的方法,而需要先用JFreeChart类的实例的getCategoryPlot()得到一个CategoryPlot对象,再用CategoryPlot对象的getRenderer()方法得到一个BarRenderer对象,用BarRenderer对象的setSeriesPaint()方法来设置柱状条的颜色,如:
CategoryPlot plot = chart.getCategoryPlot();
BarRenderer renderer = (BarRenderer) plot.getRenderer();
renderer.setSeriesPaint(0, Color.red);
renderer.setSeriesPaint(1, Color.green);
renderer.setSeriesPaint(2, Color.blue);
(3)柱状条的间距
JFreeChart允许修改类型轴的一些设置,如:
第一条柱状条与数值轴之间的空白间距。
柱状条之间的空白间距。
最后一条柱状条后的空白距离。
此三项的设置方法示例如下:
CategoryPlot plot = chart.getCategoryPlot();
CategoryAxis axis = plot.getDomainAxis();
axis.setLowerMargin(0.02); //百分之二
axis.setCategoryMargin(0.10); //百分之十
axis.setUpperMargin(0.02); //百分之二
JFreeChart也可以创建线形图,这时使用的数据集可以是CategoryDataset接口或XYDataset的实现类的实例。
【例12-3】 创建线形图
创建线形图同样需要经历与创建饼形图类似的三步。本例将实现输出一个学生物理、化学、数学三门课程6次考试成绩的线图。
第一步:创建一个包含要在Web图形中显示的数据的数据集对象。代码如下:
//------数据线------
String dataLine1="物理";
String dataLine2="化学";
String dataLine3="数学";
//------数据列------
String dataCol1="1";
String dataCol2="2";
String dataCol3="3";
String dataCol4="4";
String dataCol5="5";
String dataCol6="6";
//------创建数据集,并设置值------
DefaultCategoryDataset categoryDataset = new DefaultCategoryDataset();
categoryDataset.addValue(60,dataLine1,dataCol1);
categoryDataset.addValue(70,dataLine1,dataCol2);
categoryDataset.addValue(83,dataLine1,dataCol3);
categoryDataset.addValue(90,dataLine1,dataCol4);
categoryDataset.addValue(92,dataLine1,dataCol5);
categoryDataset.addValue(85,dataLine1,dataCol6);
categoryDataset.addValue(63,dataLine2,dataCol1);
categoryDataset.addValue(45,dataLine2,dataCol2);
categoryDataset.addValue(67,dataLine2,dataCol3);
categoryDataset.addValue(76,dataLine2,dataCol4);
categoryDataset.addValue(90,dataLine2,dataCol5);
categoryDataset.addValue(87,dataLine2,dataCol6);
categoryDataset.addValue(50,dataLine3,dataCol1);
categoryDataset.addValue(76,dataLine3,dataCol2);
categoryDataset.addValue(84,dataLine3,dataCol3);
categoryDataset.addValue(96,dataLine3,dataCol4);
categoryDataset.addValue(88,dataLine3,dataCol5);
categoryDataset.addValue(80,dataLine3,dataCol6);
第二步:创建一个用于作图的JFreeChart对象。代码如下:
//------创建线图------
String titleString="XXXXXX学生成绩线图";//图的标题
JFreeChart chart = ChartFactory.createLineChart(titleString,//图形的标题
"第几次考试",//区域轴标签
"成绩",//数值轴标签
categoryDataset,//数据集
PlotOrientation.VERTICAL,//图形显示方向,竖向
true,//是否显示图例
true,//是否有工具提示
false//是否有URL
);
第三步:向目标作图。代码如下
ChartUtilities.writeChartAsJPEG(response.getOutputStream(),chart,500,300);
下面结出本实例的完整代码。
lineChart.jsp
<%@ page contentType="text/html;charset=GB2312" %>
<%@ page import="org.jfree.chart.JFreeChart,org.jfree.chart.ChartUtilities,
org.jfree.chart.ChartFactory,
org.jfree.chart.plot.PlotOrientation,
org.jfree.data.category.DefaultCategoryDataset"%>
<html>
<body>
<%
//------数据线------
String dataLine1="物理";
String dataLine2="化学";
String dataLine3="数学";
//------数据列------
String dataCol1="1";
String dataCol2="2";
String dataCol3="3";
String dataCol4="4";
String dataCol5="5";
String dataCol6="6";
//------创建数据集,并设置值------
DefaultCategoryDataset categoryDataset = new DefaultCategoryDataset();
categoryDataset.addValue(60,dataLine1,dataCol1);
categoryDataset.addValue(70,dataLine1,dataCol2);
categoryDataset.addValue(83,dataLine1,dataCol3);
categoryDataset.addValue(90,dataLine1,dataCol4);
categoryDataset.addValue(92,dataLine1,dataCol5);
categoryDataset.addValue(85,dataLine1,dataCol6);
categoryDataset.addValue(63,dataLine2,dataCol1);
categoryDataset.addValue(45,dataLine2,dataCol2);
categoryDataset.addValue(67,dataLine2,dataCol3);
categoryDataset.addValue(76,dataLine2,dataCol4);
categoryDataset.addValue(90,dataLine2,dataCol5);
categoryDataset.addValue(87,dataLine2,dataCol6);
categoryDataset.addValue(50,dataLine3,dataCol1);
categoryDataset.addValue(76,dataLine3,dataCol2);
categoryDataset.addValue(84,dataLine3,dataCol3);
categoryDataset.addValue(96,dataLine3,dataCol4);
categoryDataset.addValue(88,dataLine3,dataCol5);
categoryDataset.addValue(80,dataLine3,dataCol6);
//------创建线图------
String titleString="XXXXXX学生成绩线图";//图的标题
JFreeChart chart = ChartFactory.createLineChart(titleString,"第几次考试","成绩",
categoryDataset,PlotOrientation.VERTICAL,true,true,false);
ChartUtilities.writeChartAsJPEG(response.getOutputStream(),chart,500,300);
%>
</body>
</html>
程序的运行效果如图12-4所示。
图12-4 生成线形图
JFreeChart的组件包中的类相当之多,需要对图形多越多的个性化设置或要开发越复杂的图形,就需要了解这些组件包中更多的类及其API。JFreeChart中常用有如下一些包:
(1)org.jfree.chart:这个包包含了JFreeChart最为重要也是最为常用的几个类和接口。
(2)org.jfree.chart.data:数据集相关的接口和类。
(3)org.jfree.chart.plot:绘图相关的类和接口。
(4)org.jfree.chart.data.general:常用的数据集相关的接口和类。
还有许多其它的包,就不再一一介绍,有兴趣的读者可进一步参考JFreeChart的开发参考手册。
下面介绍开发中最为常用的几个类(或接口)。
(1)JFreeChart类
即org.jfree.chart.JFreeChart,这个类用于协调整个画图的处理过程。生成这个类通常使用ChartFactory类。JFreeChart主要的方法有:
public void draw(Graphics2D g2, Rectangle2D area)
此方法用于在图形设备上的特定区域画图。参数g2代码一个图形,参数area表包容这个图形的矩形容器。Java中提供了几个图形设备:屏幕、打印机、图形缓冲区。JFreeChart绘图是由不同的Java.awt.Graphics2D包中的抽象类实现的,只有利用它们,JFreeChart才能向不同的图形设备输出图形。
(2)ChartFactory类
这个类是一个工厂类,即org.jfree.chart.ChartFactory,它提供了一些比较方便的创建图形对象的方法。此类常用的方法有:
public static JFreeChart createPieChart(String title,PieDataset dataset, boolean legend, boolean tooltips, boolean urls)
此方法用于创建一个标准的饼图。参数title为要创建的饼图的标题;参数dataset为创建饼图所使用的特定的数据集(可以为空);参数legend表是否是一个图例;参数tooltips用于配置图形是否生成提示信息,参数urls配置图形是否生成URL。
public static JFreeChart createBarChart(String title,String categoryAxisLabel, String valueAxisLabel, CategoryDataset dataset,PlotOrientation orientation, boolean legend, boolean tooltips, boolean urls)
此方法用于根据dataset数据集生成条形图;参数categoryAxisLabel指定横向轴(分类轴)的提示字符串;参数valueAxisLabel指出竖向轴(值轴)的提示字符串,其它参数的含义同createPieChart()方法;参数orientation指出条形图是横向的还是竖向的,如果为竖向的其值可设为PlotOrientation.VERTICAL。
public static JFreeChart createLineChart(String title,String categoryAxisLabel, String valueAxisLabel, CategoryDataset dataset,PlotOrientation orientation, boolean legend, boolean tooltips, boolean urls)
此方法根据dataset数据集生成线图,其它参数的含义同createBarChart()方法。
(3)ChartUtilities类
即org.jfree.chart.ChartUtilities,通过ChartUtilities类可从chart对象生成PNG格式或JPEG格式的图片,也可生成带有点击范围的HTML图片,这些生成图片的方法都是静态的。ChartUtilities类有如下常用的方法:
public static void writeChartAsPNG(OutputStream out, JFreeChart chart, int width, int height) throws IOException
JFreeChart通过一个开源的PNG格式图片的解码器来生成PNG图片。ChartUtilities类提供了一些方便使用的方法来直接通过输出流(OutputStream)生成图片。参数width为图形的宽度,参数heigth为图形的高度。
public static void writeChartAsJPEG(OutputStream out, JFreeChart chart, int width, int height) throws IOException
public static void writeChartAsJPEG(OutputStream out, JFreeChart chart, int width, int height, ChartRenderingInfo info)
这两个方法用于生成JPEG格式的图片。参数的含义同writeChartAsPNG()方法。
(4)PlotOrientation类
即org.jfree.chart.plot.PlotOrientation,此类用于表示图形的显示方向,这个类主要有两个属性,分别用于表示图形显示的两个方向:横向和纵向。
PlotOrientation.HORIZONTAL:图形横向显示。
PlotOrientation.VERTICAL :图形纵向显示。
(5)DefaultCategoryDataset接口
即org.jfree.data.category.DefaultCategoryDataset,这是一个默认情况下使用的分类数据集接口。它的构造函数如下:
public DefaultCategoryDataset()
这个函数将生成一个空的数据集。也可以使用DatasetUtilities类的静态方法以数组作为参数来生成一个新的数据集,并用这个数组作为数据集中的数据。
public addValue(Number value, Comparable rowKey, Comparable columnKey)
此方法用于向数据集中加入一个值。向数据要中增加值时,如果已经存在则会覆盖。
public setValue(Number value, Comparable rowKey, Comparable columnKey)
此方法的功能同addValue()方法。
JSP程序员总觉得在开发Web系统时报表是个老大难的问题,要么需要程序员编写一大堆的程序,要么需要使用到第三方的组件以快速开发。在Web系统中一般程序员不能专注于开发报表,诸于调整报表格式之类的工作,而应当把精力集中放在实现系统的业务逻辑、数据逻辑,因此笔者提倡使用成熟的第三方报表组件。
现在市场上林林总总不下20 家专业报表工具,如果再加上各家公司自己开发、用于项目的报表控件、程序等等,用数以百计来形容报表工具的种类并不过分。这里并不想列罗出各种报表工具,而只是想介绍一款实用的,支持快速开发,可满足大多数应用场合下的报表开发工具——JavaReport。
JavaReport由伟才工作室开发,这个组件简单易用,只须编写少量的代码即可开发出复杂的报表和图形,中文支持较好,较为实用。JavaReport可从网上免费下载得到,网址如下:
http://www.javareport.com.cn/download.htm
JavaReport是纯Java的实时的Web报表开发平台。是B/S结构应用系统的报表解决方案。系统采用HTML的方式显示展示,同时可直接导出Word,Excel,PDF,CSV等各种格式文件,并且保持样式一致。JavaReport主要有如下优点:
(1)支持实时的、动态的报表Web统计报表。
(2)具有丰富统计图形接口,有良好的图表混合报表扩展能力。
(3)支持纯HTML的Web报表展现方式,且无需安装任何插件。
(4)自动实现数据分页功能、上下翻页等功能。
(5)报表可以动态导出成Word,Excel,PDF,CSV各种格式的文档。
(6)中文支持不错,不存在多种编码乱码问题,为程序员省心不少。
有许多的问题在JavaReport组件内已经解决,如报表的导出功能、打印功能和数据的分页处理等。在很多B/S结构体系的系统开发时,分页是开发过程中要重点考虑的问题。但在JavaReport中,就不需要考虑上下翻页,跨页分页,因为这些问题报表引擎已经实现了。在设计报表的过程中,把Report类当作容器类,统计图类,表格类,标签类,图片类等,把它们当作组件类。至于跨页分页,上下翻页由报表引擎自动完成。JavaReport系统中有自动跨页分页的功能,当表格超过当前页的大小时,系统会自动把接着部分放到下一页。报表设计在开发过程中是感觉不到要跨页的存在,只有一个全部数据完整的表格对象。
如果要把引入图片文件加入到报表中,可使用报表系统的Report类,通过这个类的addImage()方法把Image对象加进来,如果需要自定义图像,可通过第三方画图程序(例如是JFreeChart程序)生产需要的统计图/图片,然后再把图像加到报表中去。
本书中使用的JavaReport版本是V3.0,JavaReport根据客户的应用需要,按照功能和性能的等级,分成三个版本:专业版,服务器版,企业版。本书使用企业版。JavaReport三个版本产品都是免费使用的。也就是说这三个版本都不需要购买License许可,可以*使用JavaReport的所有功能。
专业版客户(Client)的IP最大连接数限制为10个,也就是JavaReport同时并行处理线程的最大数受限制。限制是为了避免系统资源占用过大,使系统反应迟钝。该版本适合中小型的应用系统,保障应用系统正常运行。
服务器版在最大连接数没有受限,它能充分发挥服务器的个部分硬件设备的作用;相对要求服务器的设备配置高,保障最优性能效率。该版本适合大中型的应用系统使用。
企业版提供垃圾回收机制管理,自动处理在内存中无用对象的引用;支持多服务器处理模式,提供在多个服务器的集群功能和服务器之间负载均衡功能。提高服务器群的负载能力和和快速响应能力。该版本适合负载繁重的应用系统使用。
JavaReport生成的报表在显示时的情况如图12-5所示。
图12-5 JavaReport生成的报表显示时的情况
最上面的一栏中有许多的按钮,称为报表导航按钮,其中就有将报表导出成文件的按钮,鼠标在按钮上悬停不久后即会有文字提示按钮的功能。报表导航按钮的后面预留了一些空余的地方,用于程序员自定义一些按钮。JavaReport把一张报表分为表头、报表中的内容、表尾三个部分,一张报表中可以有多个表格。
JavaReport的组件包中有许多的类,体系庞大而复杂,在这里仅列出一些常用的类来作出说明,有兴趣的可参考详细的API手册。
(1)WebReportEngine类
即com.javareport.http.WebReportEngine,这个类是整个JavaReport中的Web引擎的开发接口。所有的JSP或Servlet从这个类继承下来,程序员只需要覆盖createReport()函数就能实现报表开发工作中大部分的需求,诸如怎样在Web上显示、怎样形成Word、PDF文件等之类的功能由JavaReport报表引擎来完成,程序员就不必再考虑这些实现的细节了。
如果是在JSP页面中,在首部需加入如下的语句:
<%@ page extends="com.javareport.http.WebReportEngine"%>
表示JSP页面类继承自com.javareport.http.WebReportEngine。
【专家提示】有的读者可能觉得不理解,什么叫JSP类?JSP实质上就是一个Java类,我表面上看来它是一个动态的网页,在Web引擎中会最终将它编译成一个Java类,再执行这个Java类。
如果是在Servlet中,相应的Servlet类声明时语句如下:
public class Servlet类名称 extends WebReportEngine{
……
}
WebReportEngine类常用的方法有:
public Report createReport(javax.servlet.http.HttpServletRequest request)
throws java.lang.Exception
public java.lang.String validate(javax.servlet.http.HttpServletRequest request)
public java.lang.String getStartScript(javax.servlet.http.HttpServletRequest request)
public java.lang.String getEndScript(javax.servlet.http.HttpServletRequest request)
public java.lang.String getToolbarScript(javax.servlet.http.HttpServletRequest request)
public boolean isShowToolbar()
public int getAllEchoButton()
createReport()方法用于建立报表,并返回报表的实例。这个报表实例可以在Web上显示,同时也可以导出Word, Excel, PDF, CSV, HTML等格式的文档以供使用。方法中的参数request可用于获取所有的动态请求的数据。
validate()方法用于对上一个页面Form提交的参数进行检查,由于实时报表需要动态的参数,在这里进行数据校验。方法中的参数request可用于获取所有的动态请求的数据;方法的返回值为null时代表通过,其他内容则为参数错误的提示信息。
getStartScript()方法用于构造报表内容在Web页面上显示之前执行的JavaScript或VBScript脚本,如果要定制则要重载这个方法。方法中的参数request可用于获取所有的动态请求的数据;方法返回值为null时代表没有脚本内容。
getEndScript()方法用于构造报表内容在Web页面上显示之后执行的JavaScript或VBScript脚本,如果要定制则要重载这个方法。方法中的参数request可用于获取所有的动态请求的数据;方法返回值为null时代表没有脚本内容。
getToolbarScript()方法用于定制Web报表在页面首部显示的工具栏为标准的样式(上下翻页,导出文件),可以在此扩展工具栏的内容,如:添加公司主页的链接,返回上一层链接的“返回”按钮,如果要定制则要重载这个方法。方法中的参数request可用于获取所有的动态请求的数据;方法返回值为null时代表不添加内容。
isShowToolbar()方法用于设定是否在页面上显示报表工具栏。如果不想在页面上显示报表工具栏,可重载这个方法,并设定返回值为false。需要注意的是,如果是多页报表,上下翻页按钮就无法使用。
getAllEchoButton()方法用于自定义显示在Web页面中的报表导出文件的按钮,比如应用中只导出PDF文件,其他的不需要,就可以这里设定。按钮值从Word按钮开始是(1,2,4,8,...),需要显示的按钮则将它们的值相加就可以了。要作自定义,需要重载这个方法,并将返回值设为要显示的按钮对应的和值。默认情况下工具栏上的按钮如图12-6所示:
图12-6 默认情况下工具栏上的按钮
(2)Report类
即com.javareport.beans.Report,报表类。这个类的对象用于代表一张报表,是所有报表元素的容器,这是整个系统的核心,也是在浏览器上显示和生成电子文档(Word,Excel,PDF,Html等)的基础。
Report类常用的方法有:
//--在报表的页眉添加一条横直线,参数num表示横直线的粗细程度,数字越大线越粗
public java.lang.String addHeaderSeparator(int num)
//--在页眉中添加若干个空格,参数num是要添加的空格的个数
public java.lang.String addHeaderSpace(int num)
//--用于在页眉中添加若干个【Tab】键,参数num是要添加的【Tab】键的个数
public java.lang.String addHeaderTab(int num)
//--在页眉中添加一个换行符号,紧跟后面的内容则从下行第一个字符的位置开始
public java.lang.String addHeaderBreak()
//--在页眉中添加文本信息内容,紧跟后面的内容则从下行第一个字符的位置开始
public java.lang.String addHeaderText(java.lang.String text)
//--在报表中添加图表信息内容,参数chart是要添加的图表
public java.lang.String addChart(ChartImpl chart)
//--设置报表当前的字体,参数是要设置的字体对象,为Java中java.awt.*包中的Font对象
public void setCurrentFont(java.awt.Font font)
//--设置报表当前的背景颜色
public void setCurrentBackground(java.awt.Color color)
//--设置报表当前的前景颜色
public void setCurrentForeground(java.awt.Color color)
//--在报表中添加图片信息内容
public java.lang.String addImage(java.awt.Image image)
//--添加项目符号的表示符号。第一种形式添加默认的项目符号(圆点)的表示符号,
//--第二种形式用自定义的图片内容代替默认的圆点内容
public java.lang.String addBullet()或
public java.lang.String addBullet(java.awt.Image image)
//--在报表中添加一个换行符号,紧跟后面的内容则从下行第一个字符的位置开始。
//--参数num代表换行的数量,即多次换行
public java.lang.String addNewline(int num)
//--在报表中添加一条横直线,参数num表示横直线的粗细程度
public java.lang.String addSeparator(int num)
//--添加若干个空格,参数num代表空格的个数
public java.lang.String addSpace(int num)
//--在报表中添加若干个【Tab】键,参数num是要添加的【Tab】键的个数
public java.lang.String addTab(int num)
//--在报表中添加文本信息内容,紧跟后面的内容则从下行第一个字符的位置开始
//--参数text为要添加的文本信息内容
public java.lang.String addText(java.lang.String text)
//--添加报表中的表格信息内容,参数table是要添加的表格
public java.lang.String addTable(Table table)
//--在页尾添加一条横直线,参数num为横直线的粗细程度
public java.lang.String addFooterSeparator(int num)
//--在页尾添加若干个空格,参数num代表要添加的空格的个数
public java.lang.String addFooterSpace(int num)
//--在页尾添加若干个【Tab】键,参数num为要添加的【Tab】键的个数
public java.lang.String addFooterTab(int num)
//--在页尾添加一个换行符号,紧跟后面的内容则从下行第一个字符的位置开始
public java.lang.String addFooterBreak()
//--在页尾添加文本信息内容,紧跟后面的内容则从下行第一个字符的位置开始
public java.lang.String addFooterText(java.lang.String text)
addHeaderText ()方法和addFooterText()方法中的参数text是要添加的文本信息内容。其中,{P}代表当前页,{N}代表总页数,如:“第{P}页,共{N}页”。
(3)Table类
即com.javareport.beans.Table,表格类。这个类的对象属于报表对象Report中的元素。表格在报表中是不可缺少的,整齐排列着数据内容。表格单元里面的内容可以是文本内容,也可以是图形和其他元素,同时这个对象也是表套表的基础。JavaReport会自动处理表格的跨页、分页问题和新页中的表头显示问题,开发过程中把它想象成连续的就可以了。
Table类的常用方法有:
//--构造函数,参数data为填充表格内容的二维数组
public Table(java.lang.Object[][] data)
(4)Chart类
即com.javareport.beans.Chart,图表类。这个类的对象属于报表对象Report中的元素。统计图在报表中不可缺少的,使用户浏览更加直观,可以用这个类生成十几种报表统计图。Chart类常用的方法有:
//--设置图表中指定的单元或所有单元的数据
public void setData(int i,int j,java.lang.Number data)或
public void setData(java.lang.Number[][] data)
//--设置统计图的类型,统计图可以是:曲线图,百分比图等。
public void setStyle(int type)
setData()方法的第一种形式参数说明如下:
参数i指定的二维数据单元的坐标x的值;参数j指定的二维数据单元的坐标y的值;参数data用于给指定的单元赋值,数据可以是Byte, Double, Float, Integer, Long, Short,这些数据类型都是Number类的子类。
setData()方法的第二种形式参数data是一个二维的数据,数据可以是Byte、Double、Float、Integer、Long、Short。
setStyle()方法中的参数type是指定的统计图类型,总共有十多种,常用常量来表示,常用的有以下几种。
Chart.CHART_PIE3D:立体饼图
Chart.CHART_STACKBAR3D:立体条形图
Chart.CHART_CURVE:曲线图
Chart.CHART_LINE:线图
Chart.CHART_POINT:点图
Chart.CHART_INVERTED_CURVE:反向曲线图
Chart.CHART_INVERTED_LINE:反向线图
Chart.CHART_INVERTED_STACKBAR:横向的条形图
(5)RsTable类
即com.javareport.beans.RsTable,记录集表格类。这个类的对象属于报表对象Report中的元素。RsTable类具有Table类的全部功能,是针对统计报表中显示记录集是一个非常频繁使用的动作而设计的,开发者使用它时能够用几行代码就可把一个JDBC记录集里的数据以表格形式列举显示出来。RsTable类常用的方法有:
//--构造函数,参数rs是填充表格内容的记录集;参数as是记录集中列的名称映射表,
//--比如记录值中“ID”映射为“编号”,则报表的表头名称列显示为“编号”
public RsTable(java.sql.ResultSet rs)或
public RsTable(java.lang.String[] as,java.sql.ResultSet rs)
//--设置表头与列名的映射关系,参数as是映射关系中的对照表,是一个二维数组
public void setMapping(java.lang.String[][] as)
在JSP页面中与在Servlet中开发Web报表的用法有所不同。
(1)在JSP页面中开发Web报表
开发JSP时,自定义的JSP需要从WebReportEngine类继承下来:
<%@ page contentType="text/html; charset=GBK" %>
<%@ page import="javax.servlet.*"%>
<%@ page import="com.javareport.beans.*"%>
<%@ page extends="com.javareport.http.WebReportEngine"%>
<%!
public Report createReport(HttpServletRequest request) throws Exception {
……
}
……
%>
根据以上的代码,大多数情况下重载了createReport()方法就能满足需要了,报表的样式和内容等设置就在这函数里来实现;如果需要也可以重载其他函数。更详细的一个编程参考如下:
JSP报表开发模板
<%@ page contentType="text/html; charset=GBK" %>
<%@ page import="javax.servlet.*"%>
<%@ page import="com.javareport.beans.*"%>
<%@ page extends="com.javareport.http.WebReportEngine"%>
<%!
/******************************************************************************
* 这是报表系统在应用中给开发人员的JSP模板文件,可以根据需要调整接口内容。部分函
* 数可以适当删除。在开发中一般是实现createReport()函数就可以,形成实时动态报表
* 就是在这个函数里实现的。剩下的工作(怎样在Web上显示,怎样形成Work,PDF文件等)
* 交给报表引擎自动实现。
******************************************************************************/
/**
* 建立报表,返回报表的实例。这个报表实例可以在Web上显示,同时也可以导出Word,Excel,
* PDF,CSV,HTML等格式的文档供使用。
*/
public Report createReport(HttpServletRequest request) throws Exception{
Report report = new Report();
report.addText("This is a template !");
return report;
}
/**
* 这是对上一个页面Form提交的参数进行检查,由于实时报表需要动态的参数,在这里进
* 行数据校验。
* 返回值为null时代表通过,其他内容则为参数错误的提示信息。
*/
public String validate(HttpServletRequest request){
return null;
}
/**
* 这是报表在Web上显示时,内容显示出来前执行的脚本,脚本内容一般为JavaScript脚
* 本或VBScript脚本。
* 返回值为null时代表通过没有脚本内容。
*/
public String getStartScript(HttpServletRequest request){
return null;
}
/**
* 这是报表在Web上显示时,内容显示出来后执行的脚本,脚本内容一般为JavaScript脚
* 本或VBScript脚本。
* 返回值为null时代表通过没有脚本内容。
*/
public String getEndScript(HttpServletRequest request){
return null;
}
/**
* 这是报表在Web上显示时,上面的工具栏为标准的样式(上下翻页,导出文件)。可以在
* 此扩展工具栏的内容,一般可以添加公司主页的链接,返回上一层链接的“返回”按钮就是
* 在这里添加脚本的。
* 返回值为null时代表不添加内容。
*/
public String getToolbarScript(HttpServletRequest request){
return null;
}
/**
* 这是报表在Web上显示时,如果不想让工具栏显示出来,就让函数的返回值就false就可以。
* 注意:如果是多页报表,上下翻页按钮就无法使用。
*/
public boolean isShowToolbar(){
return true;
}
/**
* 这是报表在Web上显示时,导出文件的按钮可以自定义,比如应用中只要导出PDF文件,
* 其他的不需要,就可以这里设定。按钮值从Work按钮开始是(1,2,4,8,...),需要
* 显示的按钮则将它们的值相加就可以了。
*/
public int getAllEchoButton(){
return 0xFFFF;
}
%>
(2)在Servlet中开发Web报表
在Servlet中,自定义的servlet需要从WebReportEngine类继承下来,如下所示:
import javax.servlet.http.*;
import com.javareport.beans.*;
public class ReportExam extends WebReportEngine {
public Report createReport(HttpServletRequest request) throws Exception {
……
}
……
}
下面给出一个Servlet报表开发的详细模板,以供参考。
Servlet报表开发模板
<%@ page contentType="text/html; charset=GBK" %>
import javax.servlet.http.*;
import com.javareport.beans.*;
public class Template extends WebReportEngine {
/******************************************************************************
* 这是报表系统在应用中给开发人员的JSP模板文件,可以根据需要调整接口内容。部分函
* 数可以适当删除。在开发中一般是实现createReport()函数就可以,形成实时动态报表
* 就是在这个函数里实现的。剩下的工作(怎样在Web上显示,怎样形成Work,PDF文件等)
* 交给报表引擎自动实现。
******************************************************************************/
/**
* 建立报表,返回报表的实例。这个报表实例可以在Web上显示,同时也可以导出Word,Excel,
* PDF,CSV,HTML等格式的文档供使用。
*/
public Report createReport(HttpServletRequest request) throws Exception{
Report report = new Report();
report.addText("This is a template !");
return report;
}
/**
* 这是对上一个页面Form提交的参数进行检查,由于实时报表需要动态的参数,在这里进
* 行数据校验。
* 返回值为null时代表通过,其他内容则为参数错误的提示信息。
*/
public String validate(HttpServletRequest request){
return null;
}
/**
* 这是报表在Web上显示时,内容显示出来前执行的脚本,脚本内容一般为JavaScript脚
* 本或VBScript脚本。
* 返回值为null时代表通过没有脚本内容。
*/
public String getStartScript(HttpServletRequest request){
return null;
}
/**
* 这是报表在Web上显示时,内容显示出来后执行的脚本,脚本内容一般为JavaScript脚
* 本或VBScript脚本。
* 返回值为null时代表通过没有脚本内容。
*/
public String getEndScript(HttpServletRequest request){
return null;
}
/**
* 这是报表在Web上显示时,上面的工具栏为标准的样式(上下翻页,导出文件)。可以在
* 此扩展工具栏的内容,一般可以添加公司主页的链接,返回上一层链接的“返回”按钮就是
* 在这里添加脚本的。
* 返回值为null时代表不添加内容。
*/
public String getToolbarScript(HttpServletRequest request){
return null;
}
/**
* 这是报表在Web上显示时,如果不想让工具栏显示出来,就让函数的返回值就false就可以。
* 注意:如果是多页报表,上下翻页按钮就无法使用。
*/
public boolean isShowToolbar(){
return true;
}
/**
* 这是报表在Web上显示时,导出文件的按钮可以自定义,比如应用中只要导出PDF文件,
* 其他的不需要,就可以这里设定。按钮值从Work按钮开始是(1,2,4,8,...),需要
* 显示的按钮则将它们的值相加就可以了。
*/
public int getAllEchoButton(){
return 0xFFFF;
}
%>
【例12-4】 用JavaReport在Web中输出统计图
JavaReport支持多种统计图,本例将实现用JavaReport来在JSP页面中显示一维数据的图形,共实现了8种统计图。
chartReport.jsp
<%@ page contentType="text/html; charset=GBK" %>
<%@ page import="com.javareport.beans.*"%>
<%@ page extends="com.javareport.http.WebReportEngine"%>
<%!
public Report createReport(HttpServletRequest request) throws Exception{
//图片类型数组
int[] chartType = new int[]{
Chart.CHART_PIE3D,Chart.CHART_STACKBAR3D,
Chart.CHART_CURVE,Chart.CHART_LINE,
Chart.CHART_POINT,Chart.CHART_INVERTED_CURVE,
Chart.CHART_INVERTED_LINE,Chart.CHART_INVERTED_STACKBAR};
//单元数据的显示标签字符串数组
String[] labels = new String[] {"湖南省","湖北省","广西壮族自治区","广东省"};
//实例化报表对象
Report report = new Report();
//在页眉中添加文本信息内容
report.addHeaderText("希赛软考学院各省报名人数情况");
//在报表的页眉添加一条横直线
report.addHeaderSeparator(1);
//在页尾添加一条横直线
report.addFooterSeparator(1);
//在页尾添加文本信息内容
report.addFooterText("第{P}页, 共{N}页");
//循环输出各种类型的图片
for (int i = 0; i < chartType.length; i++) {
try {
//实例化一个图表对象
Chart chart = new Chart((Number[][])getData(request));
//设置图表中的单元数据的显示的标签
chart.setLabels(labels);
//设置统计图的类型
chart.setStyle(chartType[i]);
//设置统计图中显示的时候把具体的数值也显示出来
chart.setShowValue(true);
//在报表中添加文本信息内容
report.addText("报表中常见的报表统计图表("+i+"): ");
//在报表中添加图表信息内容
report.addChart(chart);
//在报表中添加换行符号
report.addBreak();
report.addBreak();
report.addBreak();
}
catch (Exception ex) {
ex.printStackTrace();
}
}
return report;
}
//读者可根据需要设置数组的值,或从数据库中取出值放入数组中以动态显示数据
public Double[][] getData(HttpServletRequest request){
Double[][] data = new Double[1][4];
data[0][0] = new Double(50);
data[0][1] = new Double(50);
data[0][2] = new Double(35);
data[0][3] = new Double(55);
return data;
}
//定制Web报表在页面首部显示的工具栏为标准的样式,增加一个"返回"按钮,返回到首页
public String getToolbarScript(HttpServletRequest request){
return "<a href=\"../index.htm\"><img src=\""+request.getRequestURI()+
"?op=Resource&name=/resource/back.gif\" border=\"0\" alt=\"返回\"></a>";
}
%>
程序的运行结果如图12-7所示。
图12-7 用JavaReport输出Web统计图
程序中首先是重载了createReport()方法,在这个方法的方法体中,先用“new Report()”生成了一个报表Report对象report,再设置了报表的页眉页脚,然后再用一个for循环输出了8种Web统计图。
重载getData()方法的目的是为了设置显示的Web统计图所要表示的数据,这个方法的返回值是一个Double类型的二维数组,由于第一维只有一行,即相当于一维数据。方法的返回值就是Web统计图要表示的数据。
重载getToolbarScript()方法的目的是为了定制Web报表在页面首部显示的工具栏为标准的样式,增加了一个“返回”按钮,当点击时返回到网站的首页。
从上可见,JavaReport只使用了少量的代码就输出了美观而又实用的Web图形;实际工程中一维数据的数据来源常常来自于对数据库中数据查询的结果,这需要读者自行编写代码实现,有兴趣的读者不妨试试。
例12-4中的代码只是实现了一维数据的展现,有时也需要展现二维的数据。二维数据图形与一维数据图形的区别就是在数据展现上加大了数据的展现量,在同一个单元数据标签处可显示属于同一个单元数据的多个数据。修改一下例12-4中getData()方法的内容,修改后的代码如下:
public Double[][] getData(HttpServletRequest request){
Double[][] data = new Double[4][4];
data[0][0] = new Double(200); data[0][1] = new Double(250);
data[0][2] = new Double(220); data[0][3] = new Double(280);
data[1][0] = new Double(500); data[1][1] = new Double(700);
data[1][2] = new Double(520); data[1][3] = new Double(900);
data[2][0] = new Double(350); data[2][1] = new Double(400);
data[2][2] = new Double(380); data[2][3] = new Double(320);
data[3][0] = new Double(550); data[3][1] = new Double(590);
data[3][2] = new Double(337); data[3][3] = new Double(340);
return data;
}
则程序运行的结果如图12-8所示。
图12-8 用JavaReport输出二维数据图形
【例12-5】 用JavaReport输出报表
table.jsp
<%@ page contentType="text/html; charset=GBK" %>
<%@ page import="java.awt.*"%>
<%@ page import="com.javareport.beans.*"%>
<%@ page extends="com.javareport.http.WebReportEngine"%>
<%!
public Report createReport(HttpServletRequest request) throws Exception{
//实例化报表对象
Report report = new Report();
//在页眉中添加文本信息内容
report.addHeaderText("报表输出示例");
//在报表的页眉添加一条横直线
report.addHeaderSeparator(1);
//在页尾添加一条横直线
report.addFooterSeparator(1);
//在页尾添加文本信息内容
report.addFooterText("第{P}页, 共{N}页");
//在报表中添加文本信息内容
report.addText("销售情况一览表(合并表格):");
//在报表中添加换行符号
report.addBreak();
//在报表中添加表格
report.addTable(getTable());
//在报表中添加换行符号
report.addBreak();
return report;
}
//------得到销售情况一览表(合并表格)对象------
public Table getTable(){
String[][] data = getTotalData();
Table table = new Table(data);
table.setAlignment(Table.H_CENTER + Table.V_CENTER);
table.setColAutoSize(true);
table.setRowBackground(0,Color.LIGHT_GRAY);
table.setRowBackground(1,Color.LIGHT_GRAY);
table.setColBackground(0,Color.LIGHT_GRAY);
table.setRowBackground(7,new Color(255,255,128));
table.setHeaderRowCount(2);
table.setHeaderColCount(1);
table.setRowBorder(table.LINE_THIN);
table.setColBorder(table.LINE_THIN);
table.setCellSpan(0,0,new Dimension(1,2));
table.setCellSpan(0,1,new Dimension(2,1));
table.setCellSpan(0,3,new Dimension(2,1));
table.setCellSpan(0,3,new Dimension(2,1));
return table;
}
//生成销售情况数据,实际工程中一般从数据库中获取
public String[][] getData(){
String[][] data = new String[6][4];
data[0][0] = "区域"; data[0][1] = "第一季度"; data[0][2] = "第二季度"; data[0][3] = "第三季度";
data[1][0] = "华南地区"; data[1][1] = "¥2,000,000";
data[1][2] = "¥2,500,000"; data[1][3] = "¥2,200,000";
data[2][0] = "华东地区"; data[2][1] = "¥6,000,000";
data[2][2] = "¥4,500,000"; data[2][3] = "¥4,800,000";
data[3][0] = "华中地区"; data[3][1] = "¥500,000";
data[3][2] = "¥400,000"; data[3][3] = "¥700,000";
data[4][0] = "华北地区"; data[4][1] = "¥3,000,000";
data[4][2] = "¥3,200,000"; data[4][3] = "¥2,500,000";
data[5][0] = "东北地区"; data[5][1] = "¥4,000,000";
data[5][2] = "¥5,000,000"; data[5][3] = "¥4,400,000";
return data;
}
//得到销售汇总统计数据,实际工程中一般从数据库中获取
public String[][] getTotalData(){
String[][] data = new String[8][5];
data[0][0] = "区域"; data[0][1] = "上半年"; data[0][3] = "下半年";
data[1][1] = "第一季度"; data[1][2] = "第二季度";
data[1][3] = "第三季度";data[1][4] = "第四季度";
data[2][0] = "华南地区"; data[2][1] = "¥2,000,000"; data[2][2] = "¥2,500,000";
data[2][3] = "¥2,200,000";data[2][4] = "¥0";
data[3][0] = "华东地区"; data[3][1] = "¥6,000,000"; data[3][2] = "¥4,500,000";
data[3][3] = "¥4,800,000";data[3][4] = "¥0";
data[4][0] = "华中地区"; data[4][1] = "¥500,000"; data[4][2] = "¥400,000";
data[4][3] = "¥700,000";data[4][4] = "¥0";
data[5][0] = "华北地区"; data[5][1] = "¥3,000,000"; data[5][2] = "¥3,200,000";
data[5][3] = "¥2,500,000";data[5][4] = "¥0";
data[6][0] = "东北地区"; data[6][1] = "¥4,000,000"; data[6][2] = "¥5,000,000";
data[6][3] = "¥4,400,000";data[6][4] = "¥0";
data[7][0] = "总计"; data[7][1] = "¥15,500,000"; data[7][2] = "¥15,600,000";
data[7][3] = "¥14,600,000";data[7][4] = "¥0";
return data;
}
//定制Web报表在页面首部显示的工具栏为标准的样式,增加一个"返回"按钮,返回到首页
public String getToolbarScript(HttpServletRequest request){
return "<a href=\"../index.htm\"><img src=\""+request.getRequestURI()+
"?op=Resource&name=/resource/back.gif\" border=\"0\" alt=\"返回\"></a>";
}
%>
程序运行的结果如图12-9所示。
图12-9 用JavaReport输出报表
读者对照图12-9再分析一下源代码应当不难。程序中用到了table类的许多方法,用于设置表格中的各种格式,如合并单元格,背景颜色,前景颜色等。
【专家提示】实际工程中,报表的数据往往来自于数据库中,因此需要在getTotalData()方法中编写代码从数据库中得到统计数据,统计的方法是可以使用特定的SQL语句或取出数据后在Java语句中统计。
输出报表还有一种更直接的方法,就是用SQL语句查询出数据库中的数据后会得到一个ResultSet对象,如rs,再用如下的语句:
RsTable rsTable = new RsTable(rs)
rsTable对象取为RsTable类的一个实例。重载如下的方法:
public RsTable getRsTable(HttpServletRequest request) throws Exception
将返回值设为rsTable即可将数据库查询的结果作为二维表格中的数据直接输出了,而不必再转换成二维数组中的数据再输出到报表中,这样可以大大减少程序员需要编写的代码量。
JFreeChart是一个开源的组件包,它提供了Java中常用统计图形来快速开发API,程序员只须掌握少量的接口就能绘制出漂亮的图形,而且这些图形不仅可用于带有客户端界面的Java应用程序中,也可以是在JSP、Servlet、Java Applet中。JFreeChart可用于创建饼图、线图、柱状图、散点图和甘特图等,本章重点介绍了条形图和线形图的开发。如果读者想要开发出更复杂和丰富多彩的图形,可对JFreeChart的API作进一步的研究。
JavaReport是一款国产的第三方组件包,可用于开发Web报表,报表中可以有数据,也可以使用其简单的统计图形编程接口,以少量的代码开发出所需的报表,并可导出成PDF、EXCEL等格式文件,且中文支持较好,较为实用。
第13章 Struts开发
【本章专家知识导学】
Struts是近年来相当流行的一种web开发框架,是在Model 2的基础上实现的一个MVC框架,其目标就是希望分离Web程序的表示层、控制层和后台模型层,使程序员能将更多的精力投入到后台的业务逻辑设计与开发中,而不是底层的Web基础架构。
本章首先对Stuts作了简要的概述,包括MVC模式、JSP模式的发展及Struts的工作原理;接着介绍了Struts的安装及其配置文件的功能及使用;然后以一个Struts应用程序实例讲解了Struts开发Web程序的过程,最后介绍了标签库的使用。
这一章旨在让大家对Struts框架有一个整体认识,读者能够灵活地运用Struts自行开发各种Web应用程序。
13.1 Struts概述
Struts是一种web开发框架,是在Model 2的基础上实现的一个MVC框架,可以在Java Servlet和JSP中用来构建Java Web应用程序,是Apache软件基金下Jakarta项目的一部分。
为什么叫Struts呢?Struts这个名字来源于在建筑和旧式飞机中使用的支持金属架。当建立一个物理建筑时,建筑工程师会使用支柱为建筑的每一层提供支持。同样,软件工程师可以使用Struts为业务应用的每一层提供支持。它的目的是就是帮助你减少运用MVC设计模型开发Web应用时的时间。
为了更好地理解Struts,读者需要对MVC模式和Model 2有一个认识,下面就先来讲解MVC模式。
MVC是目前广泛流行的一种软件设计模式,早在20世纪70年代,IBM就推出了Sanfronscisico项目计划,这其实就是MVC设计模式的研究。
MVC是Model-View-Controller的简称,即把一个应用程序的输入、处理、输出流程按照View 、Model、Controller的方式进行分离,这样一个应用被分成三个层——视图层、模型层和控制层。图13-1显示了MVC模式各个模块的功能及相互关系。
图13-1 MVC模式
1、视图(View)
视图是模型的表示,是用户看到并与之交互的界面,对于Web应用来说,可以概括为HTML界面,但也有可能为XHTML、XML或Applet。随着应用的复杂性和规模性,界面的处理也变得具有挑战性。一个应用可能有很多不同的视图,MVC设计模式对于视图的处理仅限于视图上数据的采集和处理,以及用户的请求,而不包括在视图上的业务流程的处理。业务流程的处理交予模型(Model)处理。
2、模型(Model)
模型是应用程序的主体,负责业务流程、业务状态的处理以及业务规则的制定。业务流程的处理过程对其他层来说是黑箱操作,模型接受视图请求的数据,并返回最终的处理结果。业务模型的设计可以说是MVC最主要的核心。目前流行的EJB模型就是一个典型的应用例子,它从应用技术实现的角度对模型做了进一步的划分,以便充分利用现有的组件,但它不能作为应用设计模型的框架。它仅仅告诉你按这种模型设计就可以利用某些技术组件,从而减少了技术上的困难。对一个开发者来说,就可以专注于业务模型的设计。MVC设计模式告诉我们,把应用的模型按一定的规则抽取出来,抽取的层次很重要,这也是判断开发人员是否优秀的设计依据。抽象与具体不能隔得太远,也不能太近。MVC并没有提供模型的设计方法,而只告诉你应该组织管理这些模型,以便于模型的重构和提高重用性。我们可以用对象编程来做比喻,MVC定义了一个*类,告诉它的子类你只能做这些,但没法限制你能做这些。这点对编程的开发人员非常重要。
3、控制器(Controller)
控制器主要负责View和Model之间的流程控制,也就是完成两个方向的动作:一个是将View的操作映射到具体的Model,以完成具体的业务逻辑;另一个是将通过Model处理完的业务数据及时反映到View上。控制层的作用就像是一个分发器,它清楚地告诉你应该选择什么样的模型,选择什么样的视图,可以完成什么样的用户请求,而它并不做任何的数据处理。当用户通过某个视图的控制器改变了模型的数据时,所有其它依赖于这些数据的视图都会反映这些变化。因此,无论何时发生了何种数据变化,控制器都会将变化通知所有的视图,导致显示的更新。应次,模型、视图与控制器的分离,使得一个模型可能对应多个视图,一个视图可能对应多个模型。
MVC设计模式基于以下设计理念:在一个应用系统中,用户界面发生变动的可能性最大,控制部分次之,而业务逻辑是最稳定的。因此为业务逻辑编写的代码不应该和反应用户界面的代码混杂在一起,而是彼此应该尽可能地独立,由控制器来担当两者交互的中介。下面我们以一个基于Internet的电信交话费这一过程为例介绍一下基于MVC模式的实现,处理过程如图13-2所示。
图13-2 电信交话费过程基于MVC模式的实现
JSP开发模式的发展主要经历了以下三个阶段:
1、阶段1
早期的Java Web开发应用中,所有工作都交给JSP来处理,如图13-3所示。
图13-3 JSP Model
在这种模式下,JSP文件不仅要负责生成网页、控制网页流程,还要负责处理业务逻辑,给WEB开发带来了一系列的问题
①强耦合。HTML和Java强耦合在一起,导致页面设计与逻辑处理无法分离。
②调试困难。
③可读性差,不利于维护。在需要更改业务处理逻辑或数据时,可能要牵扯到多个网页。
2、阶段2
随着技术的进一步发展,JSP页面功能开始逐步划分,引入了JavaBean与JSP页面共同协作完成任务,这就是JSP Model 1,如图13-4所示。在Model 1 架构中,JSP 直接处理Web 浏览器送来的请求,并辅以JavaBean 处理应用相关逻辑。Model 1 架构单纯编写比较容易,但在Model 1 中JSP 可能同时肩负View 与Controller 角色,两类程序代码有可能混杂而不易维护。
图13-4 Model 1
3、阶段3
功能进一步划分,在Model 1的基础上又引入了Servlet,形成Model 2,如图13-5所示。Model 2 中将Servlet 纳入架构中扮演前端Controller 角色,将Web 浏览器送出的请求集中送至Servlet,Servlet再视需求转向给对应的JSP 处理。在这一模式中,JSP负责生成动态的网页,Servlet负责流程控制,JavaBean负责业务逻辑。Model 2 中采用了较佳的MVC 模式,但Model2 容易使系统出现多个Controller,并且对页面导航的处理也比较复杂,有些人觉得model 2仍不够好,于是Craig R. McClanahan 于2000年5月提交了一个WEB framework给Java Community,这就是后来的Struts。2001年7月,Struts1.0正式发布。
图13-5 Model 2
作为一个MVC的框架,Struts对Model、View和Controller都提供了对应的实现组件,图13-6显示了Struts框架响应客户请求时各个组成部分的工作原理。
图13-6 Struts的组件结构
1、视图
Struts应用程序中的View主要采用JSP技术和通用标签库技术实现。利用Struts提供的自定义标记库(tag libraries)定义的标记创建的JSP表单,可以实现和Model部分中的ActionForm的映射,完成对用户数据的封装,同时这些自定义标记还提供了像模板定制等多种显示功能。
2、模型
Struts为Model部分提供了Action和ActionForm对象:所有的Action处理器对象都是开发者从Struts的Action类派生的子类。Action处理器对象封装了具体的处理逻辑,调用业务逻辑模块,并且把响应提交到合适的View组件以产生响应。Struts提供的ActionForm组件对象,它可以通过定义属性描述客户端表单数据。开发者可以从它派生子类对象,利用它和Struts提供的自定义标记库结合可以实现对客户端的表单数据的良好封装和支持,Action处理器对象可以直接对它进行读写,而不再需要和request、response对象进行数据交互。通过ActionForm组件对象实现了对View和Model之间交互的支持。
3、控制器
在Struts中,Controller功能由图中ActionServlet和ActionMapping对象实现。ActionServlet接受客户端的请求,而ActionServlet包括一组基于配置的ActionMapping对象,每个ActionMapping对象实现了一个请求到一个具体的Model部分中Action处理器对象之间的映射。所以,Controller的作用是从客户端接受请求,并且选择执行相应的业务逻辑,然后把响应结果送回到客户端。
Struts的工作流程概括如下:
1、用户提出请求,Controller ActionServlet接受此请求并在struts-config.xml文件中检索与用户请求相匹配的ActionMapping实例,如果找不到则返回用户请求路经无效的信息。
2、如果找到,则利用配置的ActionMapping对象把请求映射到Action处理器对象进行处理。Action处理对象访问ActionForm中的数据,处理和响应客户请求。Action处理器对象根据处理结果通知Controller,Controller进行下一步的处理。
3、Controller最后将Model处理的结果通过调用View呈现给用户。
Struts可到如下的网址下载得到:
http://struts.apache.org/downloads.html
目前Struts的版本比较多,在这儿我们以struts-1.3.8版本为例介绍它的安装方法。解压后的目录结构如图13-7所示。
图13-7 Struts1.3.8目录结构
要使用Struts开发应用程序,就必须把Struts的解压包lib文件夹下的库文件拷到应用程序的“WEB-INF\lib”文件夹下;把所有扩展名为tld的标签库文件拷到WEB-INF文件夹下,以便使用标签库中提供的标记来创建JSP视图。在Struts1.3.8中,读者可以到“src\taglib\src\main\resources\META-INF\tld”文件夹下找到标签库文件。此外,开发应用程序时所创建的struts-config.xml和web.xml配置文件也要放到WEB-INF\文件夹下。下面就来给介绍Struts的这两个配置文件。
1、web.xml文件
web.xml文件是整个web工程的配置文件,它负责对Web工程的ActionServlet类、Struts标签库等进行配置。
web.xml文件
<!--web.xml文件以一个xml头开始,声明可以使用的xml版本为1.0, -->
<!--并给出文件的字符编码为UTF-8-->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"
"http://java.sun.com/j2ee/dtds/web-app_2_2.dtd">
<!--顶层根元素为web-app -->
<!--注意:xml元素大小写敏感,如WEB-APP、WEB-app都是非法的,只能写为web-app-->
<!--xml还对元素次序敏感,如xml头和web-app的次序不能改变-->
<!--下面的servlet元素也必须出现在所有servlet-mapping元素之前-->
<!--元素次序不对时,服务器可以拒绝执行web应用-->
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.4"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
>
<servlet>
<!--<servlet-name>元素定义用于Web应用的servlet-->
<!--<servlet-name>和<servlet-class>指定由ActionServlet接受请求,并确定其如何响应-->
<servlet-name>action</servlet-name>
<servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
<!--<init-param>元素定义servlet初始化参数-->
<init-param>
<param-name>config</param-name>
<param-value>/WEB-INF/struts-config.xml</param-value>
</init-param>
<!--下面的debug为整数值,指定将处理的详细信息写到控制台的程度-->
<!--默认值为0,表示记录相对最少的日志信息-->
<init-param>
<param-name>debug</param-name>
<param-value>3</param-value>
</init-param>
<!--detail参数指示将“映射”详细信息写到控制台的程度-->
<init-param>
<param-name>detail</param-name>
<param-value>3</param-value>
</init-param>
<!--使用<load-on-startup>实现在启动应用程序时装入servlet的功能-->
<load-on-startup>0</load-on-startup>
</servlet>
<!--<servlet-mapping>元素指定URL结尾的命名模式-->
<servlet-mapping>
<servlet-name>action</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
<!--<welcome-file-list>元素指定欢迎页面-->
<welcome-file-list>
<welcome-file>hello.jsp</welcome-file>
</welcome-file-list>
<!--声明web应用所使用的Struts标签库,也可声明用户自定义标签库-->
<taglib>
<taglib-uri>/WEB-INF/struts-bean.tld</taglib-uri>
<taglib-location>/WEB-INF/struts-bean.tld</taglib-location>
</taglib>
<taglib>
<taglib-uri>/WEB-INF/struts-html.tld</taglib-uri>
<taglib-location>/WEB-INF/struts-html.tld</taglib-location>
</taglib>
<taglib>
<taglib-uri>/WEB-INF/struts-logic.tld</taglib-uri>
<taglib-location>/WEB-INF/struts-logic.tld</taglib-location>
</taglib>
</web-app>
2、struts-config.xml文件
struts-config.xml是创建Struts框架的配置文件,在此文件中对Struts用到的视图、模型和控制器进行配置。Struts框架允许把应用划分成多个组件以提高开发速度,而Struts框架的配置文件struts-config.xml可以把这些组件组装起来,决定如何使用它们。
struts-config.xml
<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE struts-config PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 1.1//EN"
"http://jakarta.apache.org/struts/dtds/struts-config_1_1.dtd">
<struts-config>
<!-- <form-beans>元素配置了一个ActionForm Bean,名叫HelloForm -->
<!-- 其对应的类为hello.HelloForm -->
<form-beans>
<form-bean name="HelloForm" type="hello.HelloForm"/>
</form-beans>
<!-- <action-mappings>元素配置了一个Action组件-->
<!--parh属性指定请求访问Action的路径为HelloWorld.do -->
<!--type属性指定Action的完整路径为hello.HelloWorld -->
<!--scope属性指定ActionForm Bean的存放范围为request -->
<!-- validate属性指定是否执行表单验证-->
<!--input属性指定当表单验证失败时的转发路经为hello.jsp -->
<action-mappings>
<action path = "/HelloWorld"
type = "hello.HelloAction"
name= "HelloForm"
scope = "request"
validate = "true"
input = "/hello.jsp"
>
<!--<action>元素还包含<forward>子元素,来定义请求转发路经-->
<forward name="SayHello" path="/hello.jsp" />
</action>
</action-mappings>
<!--<message-resources>元素定义了Resources Bundle使用的资源文件-->
<!-- parameter属性定义了该资源文件的文件名为hello. ApplicationResources -->
<!--全名为hello. ApplicationResources .properties-->
<!--存放路径为WEB-INF/classes/hello/ ApplicationResources.properties-->
<message-resources parameter="hello.ApplicationResources "/>
<!--< plug-in >元素配置Struts插件,可以包含零个或多个<set-property >子元素-->
<!--本例配置了验证规则Validator插件-->
<plug-in className="org.apache.struts.validator.ValidatorPlugIn">
<set-property value="/WEB-INF/validator-rules.xml,
/WEB-INF/asset-validation.xml" property="pathnames" />
</plug-in>
</struts-config>
本例中的<action>元素配置了HelloAction组件,对应的类为hello.HelloAction,请求访问路径为HelloWorld.do,当Action类被调用时,Struts框架应该把已经包含表单数据的HelloForm Bean传给它,HelloForm Bean存放在request范围内,并且在调用Action类之前,应该进行表单验证(表单验证规则存在于validator-rules.xml 和asset-validation.xml文件中)。如果表单验证失败,请求将被转发到接收用户输入的网页hello.jsp,让用户纠正错误。
3、ApplicationResources.properties文件
ApplicationResources.properties是Struts中的消息资源文件,用来支持国际化和本地化。除了默认的消息资源文件(使用本地语言编写)以外,一个web应用可以包含很多个用不同语言编写的消息资源文件。下面我们来看一下用Strtus实现国际化和本地化的步骤:
首先要定义资源文件的名称,一般命名为ApplicationResources.properties。这个文件包含了用默认语言编写的在程序中会出现的所有消息,这些消息以“关键字-值”的形式存储。ApplicationResources.properties文件的内容如下:
login.title=Struts Demo
login.heading=<h1>Welcome to the StrutsDemo !</h1>
lgoin.message=Welcome to the StrutsDemo !
login.inputPassword=Please input Password :
login.inputName=Please input Username :
loginOK.title= User Login Successfull
loginOK.message=<h1>Login Successfull !</h1>
error.username.required=<h3><font color="red">UserName is Required !</font></h3>
error.password.required=<h3><font color="red">Password is Required !</font></h3>
error.username.wrong=<h3><font color="red">UserName is Wrong !</font></h3>
error.password.wrong=<h3><font color="red">Password is Wrong !</font></h3>
errors.footer=</ul><hr>
errors.header=<h3><font color="red">Validation Error</font></h3>You must correct the following error(s) before proceeding:<ul>
error.email.exist=<h3><font color="red">emaail 已经存在 !</font></h3>
error.emailpwd.error=<h3><font color="red">emaail 两次输入密码不一致!</font></h3>
error.dlzh.exist=<h3><font color="red">登录帐号 已经存在 !</font></h3>
error.dlzhpwd.error=<h3><font color="red">登录帐号 两次输入密码不一致!</font></h3>
这个文件需要存储在类的路径下,而且它的路径要作为初始化参数传送给ActionServlet,作为参数进行传递时,路径的格式要符合完整Java类的标准命名规范。在struts-config.xml中已给大家讲过定义方法,在此不再累述。
其次,为了实现国际化,所有的资源文件必须都存储在基本资源文件所在的目录中。基本资源文件就是用默认地区语言-本地语言编写的消息。如果基本资源文件的名称是ApplicationResources.properties,那么用其他特定语言编写的资源文件的名称就应该是ApplicationResources_xx.properties(xx为ISO编码,如英语是en)。这些文件应包含相同的关键字,但关键字的值是用特定语言编写的。
最后,ActionServlet的区域初始化参数必须与一个true值一起传送,这样ActionServlet就会在用户会话中的Action.LOCALE_KEY关键字下存储一个特定用户计算机的区域对象。
Struts可以集成到很多开发环境中去来开发Web应用程序,在这里选用MyEclipse+Tomcat +Struts的环境配置。
【例13-1】 第一个Struts程序
下面以一个最简单的例子来给大家讲解一下Struts的应用。该例子将实现一个简单的用户登陆功能。处理逻辑如图13-8所示。
图13-8 用户登陆处理流程图
首先用户进入登陆界面login.jsp,输入用户名和密码(设都为struts)。此登录页将调用LoginAction 来执行用户的登录操作。如果出现验证错误或问题,LoginaAction 将引导用户返回 login.jsp 页面。然而,如果登录成功,应用将转向 loginSuccess.jsp 页面,用户可重新输入。实现步骤如下:
1、先创建一个 Web Project,通过菜单 File > New > Project > MyEclipse>J2EE Projects> Web Project ,出现如图13-9所示界面。
图13-9 配置Web工程
输入工程名ch13,其它项默认,然后单击Finish按钮,这样我们就创建了一个Web工程。
2、为ch13工程添加Struts组件。 选中ch13工程,点击右键,MyEclipse > Add Struts Capabilities… 。出现如图13-10所示界面。
图13-10 配置Struts Capabilities
这儿我们将该项目所创建的类存放到test包下,其它选项默认,点击Finish按钮,这样我们就把Struts所需的组件添加到项目ch13中,添加后的项目的目录结构如图13-11所示。
图13-11 ch13的目录结构
4、创建Struts应用。我们先来创建login.jsp及其ActionForm 和 Action。
通过菜单选择 File > New > Other... > Web-Struts > Struts 1.1> Struts 1.1 Form,Action & JSP, 如图13-12所示。
图13-12 New Form对话框
在Use case中输入登陆用例的名字login(注意:Use case的名字必须输入),则Name与Form type两项则会自动填充。然后选中Form Properties标签项,点击Add按钮,出现如图13-13所示界面,为login.jsp表单添加两个属性name和password。其中,需要把password属性的JSP input type设为password,这样可以在输入密码时不显示输入的内容。
图13-13 添加表单属性
然后选中Methods标签,使复选框处于非选中状态;选择JSP标签,选中复选框,并为创建的JSP文件指定存放路经。如图13-14所示。
图13-14 创建JSP页面
设置完毕后,单击 Next 按钮,出现 New Action 对话框,如图13-15所示。在此对话框为一些选项提供了默认值,在此无需修改。
图13-15 New Action对话框
在此对话框可单击 Forwards 标签来定义重定向的属性,定义后的属性如图13-16所示。
图13-16 定义ActionForwards
至此就完成了login用例的JSP、Action和ActionForm的创建。MyEclipse 会自动更新 struts-config.xml 文件。创建成功后的各文件代码如下:
login.jsp
<%@ page language="java"%>
<%@ taglib uri="http://jakarta.apache.org/struts/tags-bean" prefix="bean"%>
<%@ taglib uri="http://jakarta.apache.org/struts/tags-html" prefix="html"%>
<html>
<head>
<title>JSP for loginForm form</title>
</head>
<body>
<html:form action="/login">
name : <html:text property="name"/><html:errors property="name"/><br/>
password : <html:password property="password"/>
<html:errors property="password"/><br/>
<html:submit/><html:cancel/>
</html:form>
</body>
</html>
LoginForm..java
package test.form;
import org.apache.struts.action.ActionForm;
public class LoginForm extends ActionForm {
private String password;
private String name;
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
LoginAction.java
package test.action;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import test.form.LoginForm;
public class LoginAction extends Action {
/**
* Method execute
* @param mapping
* @param form
* @param request
* @param response
* @return ActionForward
*/
public ActionForward execute(
ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response) {
LoginForm loginForm = (LoginForm) form;
if(loginForm.getName().equals("struts") &&
loginForm.getPassword().equals("struts"))
{
request.setAttribute("Name", loginForm.getName()); //蒋获取的输入的用户名赋给Name,以便在登陆成功页面显示登陆成功的用户名
return mapping.findForward("success");
}
return mapping.findForward("failure");
}
}
Struts-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts-config PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 1.1//EN" "http://jakarta.apache.org/struts/dtds/struts-config_1_1.dtd">
<struts-config>
<data-sources />
<form-beans >
<form-bean name="loginForm" type="test.form.LoginForm" />
</form-beans>
<global-exceptions />
<global-forwards />
<action-mappings >
<action
input="/login.jsp"
name="loginForm"
path="/login"
scope="request"
type="test.action.LoginAction">
<forward name="failure" path="/login.jsp" />
<forward name="sucess" path="/loginSuccess.jsp" />
</action>
</action-mappings>
<message-resources parameter="test.ApplicationResources" />
</struts-config>
5、下面还需要创建登陆成功的页面loginSuccess.jsp。这只是单纯的一个JSP页面,我们只需通过New>JSP,出现如图13-17所示对话框。
图13-17 new JSP page对话框
点击Finish后,编写代码如下:
loginSuccess.jsp
<%@ page language="java" pageEncoding="UTF-8" %>
<%@ taglib uri="http://jakarta.apache.org/struts/tags-bean" prefix="bean" %>
<%@ taglib uri="http://jakarta.apache.org/struts/tags-html" prefix="html" %>
<%@ taglib uri="http://jakarta.apache.org/struts/tags-logic" prefix="logic" %>
<%@ taglib uri="http://jakarta.apache.org/struts/tags-tiles" prefix="tiles" %>
<%@ taglib uri="http://jakarta.apache.org/struts/tags-template" prefix="template" %>
<%@ taglib uri="http://jakarta.apache.org/struts/tags-nested" prefix="nested" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html:html locale="true">
<head>
<title>loginSuccess.jsp</title>
</head>
<body>
<h2>Hello,<bean:write name="Name" scope="request" />! you successfully logged in!</h2>
</body>
</html:html>
5、把创建的应用程序部署到 Tomcat5服务器 。单击工具条上的 Deploy J2EE Project to Server…按钮,按钮如图13-18所示,出现图13-19所示的对话框,在此对话框中,从Project下拉列表框选择ch13,然后点击Add按钮,在弹出的对话框中选择Tomcat 5,确定之后显示Successfully deployed说明部署成功。
图13-18 Deploy Project按钮
图13-19 Project Deployments对话框
6、运行应用程序。在图13-18所示的工具条中点击Open MyEclipse Web Browser按钮,然后在地址栏内输入http://localhost:8080/ch13/login.jsp,则运行效果如图13-20所示。
图13-20 运行login.jsp
如果在两文本框中分别输入’”struts”,则显示图13-21所示页面,说明登陆成功。
图13-21 登陆成功界面
在文件struts-html.tld中定义了HTML 标签库的标记库描述器。Struts HTML标记可以大致地分为以下几个部分:
1、基本元素
(1)<html:html>标记
<html:html>标记在文件的起始位置产生<html>元素。该标记在1.2以前版本中有一个local属性,但到了1.2版本以后被lang属性所取代。如下的语句是使用<html:html>标记的一个示例:
<html:html lang="true">
当使用lang="true"属性时,页面将根据客户端浏览器提供的Accept-Language头部,来设置其locale值。
(2)<html:base>标记
<html:base>标记在文件的<head>处产生一个<base>的HTML元素,用于生成当前网页的绝对URL路经。这个标签只有内嵌在head标签中才有效。
(3)<html:img>标记
<html:img>标记用来指定所显示的图像,属性page用来指定图像文件的路径,还可通过heignt、width属性来指定图像显示时的大小。如下的语句是使用<html:img>标记的一个示例:
<html:img page="/logo.gif" height="50" width="200">
这个语句的功能是显示logo图像,高度为50像素,宽度为200像素。
(4)<html:link>标记和<html:rewrite>标记
<html:link>标记用来产生HTML中的<a>标记,而<html:rewrite>标记只能用来创建超链接中的URI部分,并不生成HTML的<a>元素。如下的语句是使用<html:link>标记的一个示例:
<html:link page="/index.html">Click Here</html:link>
2、基本表单元素
(1)<html:form>标记
<html:form>标记用来显示HTML表单,可以在此标记中指定AcitonForm bean的名称和它的类名。如果没有设置这些属性,就需要有配置文件来指定ActionMapping以表明当前输入的是哪个JSP页,以及从映射中检索的bean名和类。如果在ActionMapping指定的作用域中没有找到指定的名称,就会创建并存储一个新的bean,否则将使用找到的bean。
我们下面所介绍的各种HTML输入字段要内嵌在<html:form>标记中。<html:form>标记属性如表13-1所示。
表13-1 <html:form>标记属性
如下的语句是使用<html:form>标记的一个示例:
<html:form action="validateEmploee.do" method="post"/>
与表单相关的操作路径是validateEmployee,而表单数据是通过POST传递的。对于这个表单来说,ActionForm bean的其他信息,如bean名称类型、作用域,都是从表单指定操作的ActionMapping中检索得到的。
(2)<html:text>和<html:textarea>标记
<html:text>和<html:textarea>标记分别表示HTML文本框和文本区,属性如表13-2、13-3和13-4所示。
表13-2 <html:text>和<html:textarea>标记共有属性
表13-3 <html:text>标记特有属性
表13-4 <html:textarea>标记特有属性
通常情况下,<html:text>或<html:textarea>标记的Name属性与ActionForm Bean中的一个属性相对应,这样在提交后,Struts就会自动从网页中获得该属性的值赋给Bean。
(3)<html:hidden>标记
<html:hidden>标记能够产生HTML的隐藏输入元素,这与HTML页面的<input type="hidden">功能是一样的。属性如表13-5所示。
表13-5 <html:hidden>标记属性
(4)<html: password >标记
<html:password>标记能够显示HTML密码控件,属性如表13-6所示。
表13-6 <html: password>标记属性
(5)<html:reset>、<html:submit>、<html:canel>标记
<html:reset>、<html:submit>和<html:canel>标记分别能够显示HTML复位按钮、提交按钮和取消按钮。
3、表单选择元素
(1)<html:checkbox>和<html:multibox>标记
<html:checkbox>标记能够显示检查框控件。<html:multibox>标记能够显示复选框控件,请求对象在传递检查框名称时使用的getParameterValues()调用将返回一个字符串数组。属性如表13-7所示。
表13-7 <html:checkbox>和<html:multibox>标记属性
【专家提示】这两种标记的区别是与底层表单Bean的交互不同。通常情况下,如果希望加入多个Checkbox且希望在ActionForm Bean中用单个数组来表示它们,就可以使用<html:checkbox>。<html:multibox>比<html:checkbox>具有更高的灵活性,它可以动态决定被选中的复选框的数目,只要这些复选框的property属性一样,它们就可以作为一个数组存在于ActionForm Bean中,而只能和中的布尔型变量相对应。
(2) <html:radio>标记
<html:radio>标记用来显示HTML单选钮控件,属性如表13-8所示。
表13-8 <html:radio>标记属性
当多个<html:radio>标记的property值相同时,说明它们同属于一个控件组,在这个组内只能有一个被选中。
(3)<html:select>标记
<html:select>标记能够显示HTML选择控件,属性如表13-9所示。
表13-9 <html:select>标记属性
(4)<html:option>标记
<html:option>标记用来显示HTML的一个选项元素,与<html:select>标记联合使用,放在<html:select>中,属性如表13-10所示。
表13-10 <html:option>标记属性
如下的语句表示显示下拉菜单,其中的选项分别为red、green、blue,选项的值为指定固定值。
<html:select property="color" size="3">
<html:option value="r">red</html:option>
<html:option value="g"> </html:option>
<html:option value= "b"> </html:option>
</html:select>
(5)<html:options>标记
<html:options>标记用来生成多个HTML的选项元素,属性同<html:option>标记。
如下的语句表示显示下拉菜单,其中的选项分别为red、green、blue,选项的值来源于列表。
<html:select property=color">
<html:options collection="colorlist" property="dm" labelProperty="mc"/>
</html:select>
4、显示错误信息的标记
<html:errors>标记能够与ActionErrors结合在一起来显示错误信息。这个标记首先要从当前区域的资源文件中读取消息关键字errors.header,然后显示消息的文本。接下去它会在ActionErrors对象(通常作为请求参数而存储在Action.ERROR_KEY关键字下)中循环,读取单个ActionError对象的消息关键字,从当前区域的资源文件中读取并格式化相应的消息,并且显示它们。然后它读取与errors.footer关键字相对应的消息并且显示出来。
通过定义property属性能够过滤要显示的消息,这个属性的值应该与ActionErrors对象中存储ActionError对象的关键字对应。属性如表13-11所示。
表13-11 <html:errors>标记属性
如下的语句是应用<html:errors标签的例子:
<html:errors/> //显示集合中所有的错误。
<html:errors property=”missing.name”/> //显示存储在missing.name关键字的错误。
此标签库和Java Bean有很强的关联性,设计的本意是要在JSP 和Java 之间提供一个接口。这个标签库中包含用于定义、访问bean及其属性的标签。这些标签被封装在一个普通的标记库中,在文件struts-bean.tld中定义了它的标记库描述器。Struts Bean标记可以大致地分为以下几个部分:
1、创建和复制Bean的标记
创建和复制Bean的标记<bean:define>主要用于以下3种用途:
定义新字符串常数
将现有的bean复制到新定义的bean对象
复制现有bean的属性来创建新的bean
<bean:define>标记属性见表13-12。
表13-12 <bean:define>标记属性
如下的语句定义新字符串常数。
<bean:define id="foo" value="This is a new String"/>//定义了名为foo的字符串类型的变量
<bean:define id="bar" value='<%= "Hello, " + user.getName() %>'/>
<bean:define id="last" scope="session" value='<%= request.getRequestURI() %>'/>
如下的语句复制一个现有的bean给新的bean。
<bean:define id="foo" name="bar"/>
<bean:define id="baz" name="bop" type="com.mycompany.MyClass"/> //定义脚本变量的类型,默认为Object
如下的语句复制一个现有的bean的属性给新的bean。
<bean:define id="bop" name="user" property="role[3].name"/>
<bean:define id=”targetBean” name=”sourceBean” scope=”page” toScope=”request”/>
//源bean在页作用域中被拷贝复制到请求作用域中的另一个bean中
<bean:define id="foo" name="bar" property="baz" scope="page" toScope="request"/>
//把名为bar的bean的baz属性赋值给foo
2、定义脚本变量的标记
定义脚本变量的标记主要包括<bean:header>、<head:parameter>、<head:cookie>等,它们从多种资源中定义和生成脚本变量,这些资源包括cookie、请求参数、HTTP标头等等。属性如13-13所示。
表13-13 脚本变量定义标记属性
<bean:header>和<bean:parameter>标签定义的是一个字符串,<bean:cookie>标签定义的是一个Cookie对象。你可以使用value属性做为默认值。如果找不到指定的值,且默认值没有设定的话,会抛出一个request time异常。如果你期望返回多个值的话,可把multiple属性设为true。
如下的语句是应用<bean:cookie>标签的例子:
<bean:cookie id="sessionID" name="JSESSIONID" value="JSESSIONID-ISUNDEFINED"/>
这段代码定义了一个名为sessionID的脚本变量,如果找不到一个名为JSESSIONID的cookie,那sessionID的值就被设置为JSESSIONID-ISUNDEFINED。
如下的语句在request中输出所有header:
<%
java.util.Enumeration names =((HttpServletRequest) request).getHeaderNames();
%>
<%
while (names.hasMoreElements()) {
String name = (String) names.nextElement();
%>
<bean:header id="head" name="<%= name %>"/>
<%= name %>
<%= head %>
<%
}
%>
如下的语句是应用<bean:parameter>标签的例子:
<bean:parameter id="param1" name="param1"/> //脚本变量名称是myPatameter,它保存的请求参数的名称也是myParameter。
<bean:parameter id="param2" name="param2" multiple="true"/> // 此处定义了一个param2[]。
<bean:parameter id="param3" name="param3" value="UNKNOWN VALUE"/>
3、访问Web资源标记
(1)<bean:include>标记
将对一个资源的响应进行检索,并引入一个脚本变量和字符串类型的页作用域属性。这个资源可以是一个页,一个ActionForward或一个外部URL。与<jsp:include>的不同是资源的响应被存储到一个页作用域的bean中,而不是写入到输出流。属性如表13-14所示。
表13-14 <bean:include>标记属性
如下的语句是应用<bean:include >标签的例子:
<bean:include id=”myInclude” page=”MyJsp?x=1”/>
脚本变量的名称是myInclude,要检索的响应来自资源MyJsp?x=1。
(2)<bean:resource>标记
将检索web应用中的资源,这个资源可以是一个String或从java.io.InputStream中读入。使用ServletContext.getResource()或ServletContext.getResourceAsStream()方法检索web应用中的资源,如果在检索资源时发生问题,就会产生一个ruquest time异常。属性如表13-15所示。
表13-15 <bean:resource>标记属性
如下的语句是应用<bean:resource>标签的例子:
<bean:resource id="webxml" name="/WEB-INF/web.xml"/>
脚本变量的名称是myResource,要检索的资源的名称是web.xml。
4、显示Bean属性
标记库中定义了<bean:write>标记,用来以字符串形式输出bean的属性值。这个标记与<jsp:getProperty>类似,属性如表13-16所示。
表13-16 <bean:write>标记属性
如下的语句是应用<bean:write>标签的例子:
<bean:write name=”myBean” property=”myProperty” scope=”request” filter=”true”/>
myBean的属性myProperty将会被显示,作用域为请求,如果发现任何HTML特殊字符都将被转化为相应的实体引用。
5、消息国际化标记
我们前面讲过,Struts框架对国际化的支持是定义不同的消息资源文件,要想在页面显示定义的这些消息,我们可使用<bean:message>标记以及java.util数据包中定义的Locale和ResourceBundle类来实现。Java.text.MessageFormat类定义的技术可以支持消息的格式。利用此功能,开发人员不需了解这些类的细节就可进行国际化和设置消息的格式。如表13-17所示。
表13-17 <bean:message>标记属性
如下的语句使用特定的字符串来替换部分消息
在资源文件中的定义:info.myKey = The numbers entered are {0},{1},{2},{3}
标记的使用:<bean:message key="info.myKey" arg0="5" arg1="6" arg2="7" arg3="8"/>
JSP页面的显示:The numbers entered are 5,6,7,8 // 最多支持4个参数
Struts Logic标签库包含的标记能够有条件地产生输出文本,在对象集合中循环从而重复地产生输出文本,以及应用程序流程控制。除此之外,它还提供了一组在JSP页中处理流程控制的标记。这些标记封装在文件名为struts-logic.tld的标记包中。
1、条件逻辑
struts有三类条件逻辑。
第一类可以比较实体(如cookie、请求参数、bean或bean的参数、请求标头等)与一个常数的大小。表13-18列出了这一类标记:
表13-18 第一类条件标记
这一类的所有标记有相同的属性,如表13-19所示。
表13-19 第一类条件标记的属性
【专家提示】<logic:empty> 标签一般用于以下情况的判断:
Java对象为null;
String对象为"";
java.util.Collection对象中的isEmpty()返回true;
java.util.Map对象中的isEmpty()返回true。
如下的语句当某学生的成绩大于等于90时,输出“优秀”
<logic:greaterEqual name="student" property="score" value="90">优秀
</logic:greaterEqual>
如下的语句将判断在页的作用域中是否存在名为“bean”的bean,其prop属性的值是否大于7。如果这个属性能够转化为数值,就按数值比较,否则就按字符串进行比较。
<logic:greaterThan name="bean" property="prop" scope="page" value="7">
The value of bean.Prop is greater than 7
</logic:greaterThan>
第二类条件标记定义了两个标记:
<logic:present>
<logic:notPresent>
它们的功能是在计算标记体之前判断特定的项目是否存在。标记的属性和属性值决定了要进行检查的项目。如表13-20所示。
表13-20 第二类条件标记的属性
如下的语句中,user对象和其name属性在request作用域中都存在时,输出“user对象和该对象的name属性都存在”字符串
<logic:present name="user" property="name">
user对象和该对象的name属性都存在
</logic:present>
第三类条件标记比较复杂,这些标记根据模板匹配的结果检查标记体的内容。换句话说,这些标记判断一个指定项目的值是否是一个特定常数的子字符串:
<logic:match>
<logic:notMatch>
这些标记允许JSP引擎在发现了匹配或是没有发现时计算标记主体。属性如表13-21所示。
表13-21 第三类标记的属性
如下的语句检查在request范围内的名为“name”的bean是否包含“amigo”串
<logic:match name="name" scope="request" value="amigo">
<bean:write name="name"/>中有一个“amigo”串。
</logic:match>
如下的语句检查名为“name”的请求参数是否是“xyz”的子字符串,但是子字符串必须从“xyz”的索引位置1开始(也就是说子字符串必须是“y”或“yz”)。
<logic:match parameter="name" value="xyz" location="1">
The parameter name is a sub-string of the string xyz from index 1
</logic:match>
2、重复标记
重复标记<logic:iterate>,它能够根据特定集合中元素的数目对标记体的内容进行重复的检查,与循环结构功能相同。集合的类型可以是java.util.Iterator、java.util.Collection、java.util.Map或是一个数组。
有三种方法可以定义集合:
使用运行时间表达式来返回一个属性集合的集合
将集合定义为bean,并且使用name属性指定存储属性的名称。
使用name属性定义一个bean,并且使用property属性定义一个返回集合的bean属性。
当前元素的集合会被定义为一个页作用域的bean。属性如表13-22下,所有这些属性都能使用运行时表达式。
表13-22 <logic:iterate>标记属性
【专家提示】<logic:iterate>与<bean:write>配合使用,显示集合中的元素。
如下的语句逐一输出用户列表(userlList)中用户的姓名:
<logic:iterate id="user" name="userList">
<bean:write name="user" property="name"/><br>
</logic:iterate>
如下的语句从用户列表中输出从1开始的两个用户的姓名:
<logic:iterate id="user" name="userList" indexId="index" offset="1" length="2">
<bean:write name="index"/>
<bean:write name="user" property="name"/><br>
</logic:iterate>
如下的语句<logic:iterate>的嵌套使用:
<logic:iterate id="user" indexId="index" name="userList">
<bean:write name="index"/>
<bean:write name="user" property="name"/><br>
<logic:iterate id="address" name="user" property="addressList" length="3" offset="1">
<bean:write name="address"/><br>
</logic:iterate>
</logic:iterate>
3、转发和重定向标记
(1)转发标记
<logic:forward>标记能够将响应转发或重定向到特定的全局ActionForward上。ActionForward的类型决定了是使用PageContext转发响应,还是使用sendRedirect将响应进行重定向。此标记只有一个”name”属性,用来指定全局ActionForward的名称。
如下的语句是使用<logic:forward>标记的示例:
<logic:forward name=”myGlobalForward”/>
(2)重定向标记
<logic:redirect>标记是一个能够执行HTTP重定向的强大工具。根据指定的不同属性,它能够通过不同的方式实现重定向。它还允许开发人员指定重定向URL的查询参数。属性如表13-23所示。
表13-23 <logic:redirect>标记属性
使用这个标记时至少要指定forward、href或page中的一个属性,以便标明将重定向到哪个资源。如下的语句是使用<logic:redirect>标记的示例:
<logic:redirect href="http://www.chinaitlab.com"/>
【例13-2】 Struts综合应用举例
下面将以学生信息的添加为例给大家介绍一下Struts与MySQL数据库的交互(如果要采用其它的数据库,修改连接字符串即可)。其开发步骤如下:
1、创建数据库及表
在MySQL的命令行输入如下语句,创建名为student数据库及名为studinfo的表:
create database student;
create table studinfo ( xh char(8),xm char(8), xb char(2), nl int(3), zzmm char(4), bz char(50) );
2、创建JSP文件
本例中创建一个名为studinfo.jsp的文件,用来接收学生信息的录入,代码如下:
studInfo.jsp
<%@ page contentType="text/html; charset=GBK" %>
<%@ taglib uri="/WEB-INF/struts-logic.tld" prefix="logic" %>
<%@ taglib uri="/WEB-INF/struts-template.tld" prefix="template" %>
<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>
<html:html>
<head>
<title>学生信息录入</title>
</head>
<body>
<h2 align="center">学生信息录入</h2>
<html:form action="/studInfo" method="post">
<table border="1" align="center" cellPadding="2" cellSpacing="0"
id="AutoNumber1" style="BORDER-COLLAPSE: collapse" width="100%">
<tr>
<td width="15%" nowrap bgcolor="#eeeeee">
<div align="right">学号</div></td>
<td width="35%"><html:text property="xh"/>
</td>
<td width="15%" nowrap bgcolor="#eeeeee">
<div align="right">姓名</div></td>
<td width="35%"><html:text property="xm"/></td>
</tr>
<tr>
<td width="15%" nowrap bgcolor="#eeeeee">
<div align="right">性别</div></td>
<td width="35%">
<html:select property="xb">
<html:option value="男">男</html:option>
<html:option value="女">女</html:option>
</html:select></td>
<td width="15%" nowrap bgcolor="#eeeeee">
<div align="right">年龄</div></td>
<td width="35%"><html:text property="nl"/></td>
</tr>
<tr>
<td width="15%" nowrap bgcolor="#eeeeee">
<div align="right">政治面貌</div></td>
<td width="35%">
<html:select property="zzmm">
<html:option value="党员">党员</html:option>
<html:option value="团员">团员</html:option>
<html:option value="群众">群众</html:option>
</html:select></td>
<td width="15%" nowrap bgcolor="#eeeeee">
<div align="right">备注</div></td>
<td width="35%"><html:textarea property="bz"/></td>
</tr>
<tr>
<td colspan="4" align="center"><html:submit property="submit" value="保存"/>
<html:reset value ="重置"/></td>
</tr>
</table>
</html:form>
</body>
</html:html>
3、创建后台处理模型
本例的处理模型名为StudentBean.java,它包含与表单字段及数据库中表的字段相对应的属性及get方法,还包含一个insert方法,用来将数据持久化数据库,代码如下:
StudentBean.java
package student;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class StudentBean {
//属性
private String xh;
private String xm;
private String xb;
private int nl;
private String zzmm;
private String bz;
//构造方法
public StudentBean(String xh,String xm,String xb,String zzmm,String bz,int nl){
this.xh=xh;
this.xm=xm;
this.xb=xb;
this.zzmm=zzmm;
this.nl=nl;
this.bz=bz;
}
//成员方法
public String getBz() {
return bz;
}
public int getNl() {
return nl;
}
public String getXb() {
return xb;
}
public String getXh() {
return xh;
}
public String getXm() {
return xm;
}
public String getZzmm() {
return zzmm;
}
//insert方法将StudentBean的属性值持久化到数据库
public void insert() throws Exception{
//JDBC驱动名
String drivername = "org.gjt.mm.mysql.Driver";
//连接字符串
String connName="jdbc:mysql://localhost:3306/student?user=root&password=root";
Class.forName(drivername).newInstance();
//生成一个连接
Connection conn=DriverManager.getConnection(connName);
PreparedStatement state=null;
try{//执行SQL语句
state=conn.prepareStatement("INSERT INTO studinfo(xh,xm,xb,zzmm,bz,nl)"+"
values(?,?,?,?,?,?)");
conn.setAutoCommit(false);
state.setString(1,xh);
state.setString(2,xm);
state.setString(3,xb);
state.setString(4,zzmm);
state.setString(5,bz);
state.setInt(6,nl);
state.executeUpdate();
conn.commit();
System.out.println("数据添加成功");
}catch(Exception e){
try{
conn.rollback();
}catch(SQLException se){
se.printStackTrace();
}
}finally{//关闭数据库连接
state.close();
conn.close();
}
}
}
4、创建ActionForm和Action
ActionForm用来接受用户通过表单输入的信息。代码如下:
StudInfoForm.java
package test.form;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionMapping;
import javax.servlet.http.HttpServletRequest;
public class StudInfoForm extends ActionForm {
private static final long serialVersionUID = 1L;
//属性定义
private int nl;
private String xh;
private String zzmm;
private String xm;
private String xb;
private String bz;
//方法定义
public int getNl() {
return nl;
}
public void setNl(int nl) {
this.nl = nl;
}
public String getXh() {
return xh;
}
public void setXh(String xh) {
this.xh = xh;
}
public String getZzmm() {
return zzmm;
}
public void setZzmm(String zzmm) {
this.zzmm = zzmm;
}
public String getXm() {
return xm;
}
public void setXm(String xm) {
this.xm = xm;
}
public String getXb() {
return xb;
}
public void setXb(String xb) {
this.xb = xb;
}
public String getBz() {
return bz;
}
public void setBz(String bz) {
this.bz = bz;
}
//reset方法定义
public void reset(ActionMapping mapping,HttpServletRequest request){
this.bz="";
this.xb="";
this.xh="";
this.xm="";
this.zzmm="";
this.nl=0;
}
}
Action是Struts的控制器,本例中的StudInfoAction主要用来得到用户输入的数据并调用后台处理模型将输入的数据添加到后台数据库中,代码如下:
StudInfoAction.java
package test.action;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import test.form.StudInfoForm;
import student.StudentBean;
public class StudInfoAction extends Action {
/**
* Method execute
* @param mapping
* @param form
* @param request
* @param response
* @return ActionForward
*/
public ActionForward execute(
ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response) {
StudInfoForm studForm = (StudInfoForm) form;
String xh=studForm.getXh();
String xm=studForm.getXm();
String xb=studForm.getXb();
String zzmm=studForm.getZzmm();
String bz=studForm.getBz();
int nl=studForm.getNl();
try{
StudentBean stud = new StudentBean(xh,xm,xb,zzmm,bz,nl);
stud.insert();
}catch(Exception e){
e.printStackTrace();
}
form.reset(mapping,request);
return (mapping.findForward("studinfo"));
}
}
5、配置文件
最后一步就是将以上创建的各个组件信息在struts-config.xml中注册,使之联成一整体上下文。代码如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts-config PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 1.1//EN" "http://jakarta.apache.org/struts/dtds/struts-config_1_1.dtd">
<struts-config>
<data-sources />
<form-beans >
<form-bean name="loginForm" type="test.form.LoginForm" />
<form-bean name="studInfoForm" type="test.form.StudInfoForm" />
</form-beans>
<action-mappings >
<action
attribute="studInfoForm"
input="/student/studInfo.jsp"
name="studInfoForm"
path="/studInfo"
scope="request"
type="test.action.StudInfoAction">
<forward name="studinfo" path="/student/studInfo.jsp" />
</action>
</action-mappings>
<message-resources parameter="test.ApplicationResources" />
</struts-config>
6、运行程序
至此,我们已完成了整个程序的创建,可以在浏览器中运行了,运行结果如图13-22所示。
图13-22 学生信息录入界面
第14章 基于JSP实现的办公自动化系统
【本章专家知识导学】
为方便读者理解需求,综合本书前面各章所学知识,本章准备了一个完整的项目案例:希赛网络办公自动化系统Version1.0(CSAI-OAV1.0)。系统简单地使用了JSP技术,并没有使用JSTL、Struts等技术。Java代码全部在JSP页面中,与HTML代码夹杂在一起,为增强可读性,程序代码中有许多的注释,以方便读者阅读。
本章将办公自动化系统从系统需求分析,到系统的总体架构设计、数据库设计、系统目录设计,再到说明系统的关键技术,最后详细讲解了系统是如何实现的,以引领读者进入真实的项目演练环境。
14.1 系统功能
为提高企业办公处理的效率,逐步用无纸化的网络办公自动化系统取代纸质文档流转的现象,企业可以使用Web方式的办公自动化系统。办公自动化系统的需求不同于其它专业业务性的软件,各家企业的功能需求有许多的共同之处,如审批管理,公告消息发布等。为此,本章的系统将实现几个常用的功能,有兴趣的读者可以遵循着设计思路再自行加入和开发一些新的功能。
企业的组织机构一般还会分成若干个部门或分支机构,因此将办公自动化系统的用户归入某一个部门。系统的主要用户将有两类:一是系统管理员;二是普通用户。系统管理员是个特殊的角色,拥有系统所有功能的使用权限。
现已开发的CSAI-OAV1.0系统功能比较简单,主要有:
(1)信息中心模块。在这里可以查看系统发布的各种信息,如会议通知、工作简讯等。发布信息的功能由系统管理员来完成。
(2)行政审批模块。由用户发起审批事务,如报销差旅费、请假申请等。申请成功后,由系统管理员分配此事务需要哪些人员来进行审批。再由审批此事务的人员审批。审批的流程如图14-1所示。
图14-1 行政审批的流程
(3)用户管理模块。系统管理员可以增加、修改、删除用户,普通用户登录之后可以修改自己的密码。
(4)登录与退出系统。登录时作要作检查,核对用户名种密码是否无误,如果正确则记住用户名。退出系统将返回到用户登录界面。
根据数据库设计的ER图,可设计出数据库的物理模型,设计完成的在SQL Server中体现的关系图如图14-4所示。
图14-4 数据库物理设计关系图
比较ER图与物理设计关系图,可以发现“公众信息表info”没有外键,这取决于程序员的设计和系统的需求,看是否需要记住某条公众信息是哪个用户发布的,此处不需要,故info表成为单独的一张表。此外,由于审批事务与用户是多对多的关系,需要专门用一张表thingArra来体现这种关系。thingArra表,即事务安排表,其中记录了哪项审批事务需要哪些用户审批,以及审批结果。
下面对关系图中的每张表的设计作详细的分析:
(1)用户表
表14-1 oaUser表
范式分析:
oaUserId( oaUserName, oaUerPassword, oaUserTrueName, departmentId)
(user_name,user_password)(oaUserId, oaUserTrueName, departmentId)
为1NF(第一范式)。
建表语句:
------表定义------
CREATE TABLE [dbo].[oaUser] (
[oaUserId] [bigint] IDENTITY (1, 1) NOT NULL ,
[oaUserName] [varchar] (40) COLLATE Chinese_PRC_CI_AS NULL ,
[oaUserPassword] [varchar] (40) COLLATE Chinese_PRC_CI_AS NULL ,
[oaUserTrueName] [varchar] (40) COLLATE Chinese_PRC_CI_AS NULL ,
[departmentId] [int] NULL
) ON [PRIMARY]
GO
------主键约束------
ALTER TABLE [dbo].[oaUser] ADD
CONSTRAINT [PK_oaUser] PRIMARY KEY CLUSTERED
(
[oaUserId]
) ON [PRIMARY]
GO
------外键约束------
ALTER TABLE [dbo].[oaUser] ADD
CONSTRAINT [FK_oaUser_department] FOREIGN KEY
(
[departmentId]
) REFERENCES [dbo].[department] (
[departmentId]
) ON DELETE CASCADE ON UPDATE CASCADE
GO
(2)部门表
表14-2 department表
系统内置了一个部门ID号为1的部门:系统管理员。
范式分析:
departmntIddepartmentName
为3NF(第三范式)。
建表语句:
------表定义------
CREATE TABLE [dbo].[department] (
[departmentId] [int] IDENTITY (1, 1) NOT NULL ,
[departmentName] [varchar] (40) COLLATE Chinese_PRC_CI_AS NULL
) ON [PRIMARY]
GO
------主键约束------
ALTER TABLE [dbo].[department] ADD
CONSTRAINT [PK_department] PRIMARY KEY CLUSTERED
(
[departmentId]
) ON [PRIMARY]
GO
(3)审批事务表
表14-3 thing表
范式分析:
thingId(thingTitle, thingContent, thingAddTime)
为3NF(第三范式)。
建表语句:
------表定义------
CREATE TABLE [dbo].[thing] (
[thingId] [bigint] IDENTITY (1, 1) NOT NULL ,
[thingTitle] [varchar] (80) COLLATE Chinese_PRC_CI_AS NULL ,
[thingContent] [varchar] (4000) COLLATE Chinese_PRC_CI_AS NULL ,
[thingAddTime] [datetime] NULL
) ON [PRIMARY]
GO
------主键约束------
ALTER TABLE [dbo].[thing] ADD
CONSTRAINT [DF_thing_thingAddTime] DEFAULT (getdate()) FOR [thingAddTime],
CONSTRAINT [PK_thing] PRIMARY KEY CLUSTERED
(
[thingId]
) ON [PRIMARY]
GO
(4)事务审批安排表
表14-4 thingArra表
thingArra表体现了审批事务表与用户表的多对多的关系。
范式分析:
thingArraId(thingId, oaUserId, thingArraEnd)
(thingId, oaUserId)( thingArraId, thingArraEnd)
为1NF(第一范式)。
建表语句:
------表定义------
CREATE TABLE [dbo].[thingArra] (
[thingArraId] [bigint] IDENTITY (1, 1) NOT NULL ,
[thingId] [bigint] NULL ,
[oaUserId] [bigint] NULL ,
[thingArraEnd] [varchar] (400) COLLATE Chinese_PRC_CI_AS NULL
) ON [PRIMARY]
GO
------主键约束------
ALTER TABLE [dbo].[thingArra] ADD
CONSTRAINT [PK_thingArra] PRIMARY KEY CLUSTERED
(
[thingArraId]
) ON [PRIMARY]
GO
------外键约束------
ALTER TABLE [dbo].[thingArra] ADD
CONSTRAINT [FK_thingArra_oaUser] FOREIGN KEY
(
[oaUserId]
) REFERENCES [dbo].[oaUser] (
[oaUserId]
) ON DELETE CASCADE ON UPDATE CASCADE ,
CONSTRAINT [FK_thingArra_thing] FOREIGN KEY
(
[thingId]
) REFERENCES [dbo].[thing] (
[thingId]
) ON DELETE CASCADE ON UPDATE CASCADE
GO
(5)公众信息表
表14-5 info表
范式分析:
info(thingTitle, thingContent, thingAddTime)
为3NF(第三范式)。
建表语句:
------表定义------
CREATE TABLE [dbo].[info] (
[infoId] [bigint] IDENTITY (1, 1) NOT NULL ,
[infoTitle] [varchar] (80) COLLATE Chinese_PRC_CI_AS NULL ,
[infoContent] [varchar] (4000) COLLATE Chinese_PRC_CI_AS NULL ,
[infoAddTime] [datetime] NULL
) ON [PRIMARY]
GO
------主键约束------
ALTER TABLE [dbo].[info] ADD
CONSTRAINT [DF_info_infoAddTime] DEFAULT (getdate()) FOR [infoAddTime],
CONSTRAINT [PK_info] PRIMARY KEY CLUSTERED
(
[infoId]
) ON [PRIMARY]
GO
本章中的办公自动化系统使用了连接池技术,在系统的配置文件中配置了一个数据源。配置文件csaioa.xml文件中的内容如下。
csaioa.xml
<Context path="/csaioa" docBase="D:/eclipse/workspace/csaioa/webRoot" debug="0" reloadable="true" crossContext="true">
<Resource
name="jdbc/sqlserver"
auth="Container"
type="javax.sql.DataSource"
driverClassName="com.microsoft.jdbc.sqlserver.SQLServerDriver"
url="jdbc:microsoft:sqlserver://localhost:1433;DatabaseName=csaioa"
username="sa"
password="123"
maxActive="20"
maxIdle="10"
/>
</Context>
从以上的配置可以看出,连接的数据库的名称为csaioa,连接数据库使用的用户名为sa,密码为123,最大连接数为20,连接池的最小连接数为10。
在JSP页面中可以通过如下的Java代码段得到一个数据库连接对象:
Context initCtx = new InitialContext();
Context ctx = (Context) initCtx.lookup("java:comp/env");
//获取连接池对象
Object obj = (Object) ctx.lookup("jdbc/sqlserver");
//类型转换
DataSource ds = (javax.sql.DataSource)obj;
//得到数据库连接
Connection conn = ds.getConnection();
以上语句需要导入的类如下:
import javax.naming.Context;
import javax.naming.InitialContext;
import java.sql.Connection;
import javax.sql.DataSource;
JSP页面中的数据分页的方法有很多种,基本的原理都是先查询出数据记录集,再显示当前页需要显示的数据。
第一种方法是用select语句查询出所有的数据,再通过移动当前记录指针到当前页面需要显示的数据的记录位置,再作显示。这样做的优点是程序比较简单,但每次返回的记录集数据都相对较大,特别是当数据库表的数据量很大时,SQL查询的速度较慢,由于查询出来的结果较大,花费的网络开销也较大。
第二种方法是使用存储过程。存储过程在数据库中会作预编译处理,所以执行速度较快。用存储过程可以得到指定的起始位置和结束位置之间的记录,再在JSP页面中显示。但这种方法优点是效率最高,网络开销小,问题就是程序员需要编写较多的程序,针对每个查询要编写不同的存储过程,而且编写的存储过程程序较为复杂,需要使用到游标技术等功能,一些小型的数据库功能毕竟有限,如MySQL、Access。
第三种方法是先使用一个“select count(*)”语句得到满足查询条件的记录总条数,以得到记录总数,并根据每页显示的记录条数运算得到总页数;再根据当前页的页码构造相对比较复杂的SQL语句,在此语句的where子句中用“not in”子句排除当前页之前的代码,且使用“select top pageSize”语句限定只取指定的每页记录条数大小的记录,这样就可以取到当前页的记录了,网络开销也小,查询出来的结果也只有当前页的记录。
本系统使用了第三种方法查询数据。如在显示用户数据时,由于用户较多,需要作分页处理。页面首先用如下的语句初始化一些参数:
<%
int pageSize=10;//每页显示的记录条数
int allRecordCount=0;//记录总条数
int pageRecordCount=0;//当前页记录条数
int pageCount=0;//总页数
int diPage=1;//当前页码
//------接收请求参数------
try{
diPage=Integer.parseInt(request.getParameter("diPage"));
}catch(Exception e){
diPage=1;
}
%>
构造的SQL语句如下:
<%//------构造查询的SQL语句------
//sqlStr1为查询到当前页数据的SQL语句
//sqlStr2为查询到总记录条数的SQL语句
String sqlStr1="select top "+diPage*pageSize+" * from oaUser where "+
"oaUserId not in (select top "+(diPage-1)*pageSize+" oaUserId from"+
" oaUser order by oaUserId asc) order by oaUserId asc";
String sqlStr2="select count(*) as allCount from oaUser";
%>
构造了两个SQL语句,一个为sqlStr1,用于查询当前页数据;另一个为sqlStr2,用于查询到总记录条数。
查询到总记录条数后,即可设置相关的变量的值,如下语句所示:
if(allRecordCount%pageSize==0)
pageCount=allRecordCount/pageSize;
else
pageCount=(int)(allRecordCount/pageSize)+1;
if(diPage==pageCount||pageCount==0)//最后一页或没有数据
pageRecordCount=allRecordCount-(diPage-1)*pageSize;
else
pageRecordCount=pageSize;
作翻页处理时,显示的超链接代码如下:
用户信息(共<%=allRecordCount%>条,当前页<%=pageRecordCount%>条)
<%if(diPage!=1){//不是第一页
out.print("<a href='modiUser1.jsp?diPage=1'>首页</a> ");
out.print("<a href='modiUser1.jsp?diPage="+(diPage-1)+"'>上一页</a> ");
}
if(diPage!=pageCount&&pageCount!=0){//不是最后一页
out.print("<a href='modiUser1.jsp?diPage="+(diPage+1)+"'>下一页</a> ");
out.print("<a href='modiUser1.jsp?diPage="+pageCount+"'>尾页</a> ");
}
%>
如果当前页不是第1页,则应显示“首页 上一页”超链接,“首页”超链接传入的参数page值为1,“上一页”超链接传入的page参数值为“page-1”。如果当前页不是最后一页,就应当显示“下一页 尾页” 超链接,“下一页”超链接传入的参数page值为“page+1”,“尾页”超链接传入的page参数值为“pageCount”,pageCount即为页数。
【专家提示】有时链接需要带更多的参数,如作综合查询时可以将查询条件作为参数在超链接后带过去,超链接后的第一个参数用“?”,第二个之后的参数用“&”,如:
<a href="query.jsp?page=1&username=dzy">首页</a>
其它具体显示数据的代码详见本章的后续内容。
用户登录的界面如图14-6所示。
图14-6 系统登录界面
这个界面是用户访问的第一个界面。但是,Tomcat的Web应用默认情况下被访问的第一个页面是“index.jsp”,为什么是login.jsp呢?解决的办法有两个:一是可以有Tomcat的当前Web应用的web.xml文件中作出配置,如下所示:
<welcome-file-list>
<welcome-file>login.jsp</welcome-file>
</welcome-file-list>
第二种方法是,在成功登录后就用session变量sysuser_id记住登录的用户的ID号,再在index.jsp页中的首部用程序作会话检查,如果session变量sysuser_id的值为null,则表明用户还没有登录,立即将页面重定向到login.jsp页面。
login.jsp页面的代码如下。
login.jsp
<%@ page contentType="text/html;charset=GB2312" %>
<%
session.setAttribute("oaUserName",null);
session.setAttribute("oaUserTrueName",null);
session.setAttribute("oaUserId",null);
session.setAttribute("departmentId",null);
%>
<!------系统交互javascript----->
<script language="JavaScript">
<!--
function checkData() {
if(form1.oaUserName.value.length==0){
alert("用户名不能为空");
return false;
}
if(form1.oaUserPassword.value.length==0){
alert("密码不能为空");
return false;
} else return true;
}
-->
</script>
<html>
<body bgcolor="#DCDADA" topmargin="80">
<table border="0" CELLSPACING=0 CELLPADDING=0 align="center">
<tr><td align="center" colspan="2">
<IMG src="../img/csai.gif">
</td></tr>
<form name="form1" action="checkLogin.jsp" method="post" onsubmit="return
checkData()">
<tr><td align="center" colspan="2">
登录希赛OA系统
</td></tr>
<tr><td align="right">用户名:</td>
<td align="left">
<input type="text" size="19" name="oaUserName">(*)
</td></tr>
<tr><td align="right">密码:</td>
<td align="left">
<input type="password" size="20" name="oaUserPassword">(*)
</td></tr>
<tr><td align="center" colspan="2">
<input type="submit" value="登录">
</td></tr>
</form>
</table>
</body>
</html>
页面首先用几个setAttribute()语句将几个会话级变量的值设置为null,因为既然到了这个页面即表示要重新登录,可以将记录的会话数据清空了。
页面的表单中有两个输入框,用户名和密码,输入完后按“登录”按钮会将表单数据提交到checkLogin.jsp。这个页面还用JavaScript作了基本的数据校验,要求输入的用户名和密码均不能为空。checkLogin.jsp页面的源代码如下。
checkLogin.jsp
<%@ page contentType="text/html;charset=GB2312" %>
<%@ page import="java.sql.*,javax.naming.*,javax.sql.DataSource" %>
<%
//------接收请求参数------
String oaUserName=request.getParameter("oaUserName");
String oaUserPassword=request.getParameter("oaUserPassword");
//------如果接收数据有误------
if(oaUserName==null||oaUserPassword==null||
oaUserName.length()==0||oaUserPassword.length()==0)
response.sendRedirect("login.jsp");
//------构造查询数据库的SQL语句-----
String sqlStr=new String("select * from oaUser where "+
"oaUserName=? and oaUserPassword=?");
//------得到数据库连接------
Context initCtx = new InitialContext();
Context ctx = (Context) initCtx.lookup("java:comp/env");
Object obj = (Object) ctx.lookup("jdbc/sqlserver");
DataSource ds = (javax.sql.DataSource)obj;
Connection conn = ds.getConnection();
//------查询数据------
PreparedStatement preSQLSelect=conn.prepareStatement(sqlStr,ResultSet._
TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY);
oaUserName=new String(oaUserName.getBytes("ISO-8859-1"));
preSQLSelect.setString(1,oaUserName);
oaUserPassword=new String(oaUserPassword.getBytes("ISO-8859-1"));
preSQLSelect.setString(2,oaUserPassword);
ResultSet rs=preSQLSelect.executeQuery();
if(rs==null){
response.sendRedirect("login.jsp");
}else{
rs.next();
int i=rs.getRow();
if(i>=1){
rs.first();
session.setAttribute("oaUserName",rs.getString("oaUserName"));
session.setAttribute("oaUserTrueName",rs.getString("oaUserTrueName"));
session.setAttribute("oaUserId",rs.getString("oaUserId"));
session.setAttribute("departmentId",rs.getString("departmentId"));
}
response.sendRedirect("../index.jsp");
}
if(conn!=null)
conn.close();
%>
这个页面先是接收前一个页面传来的数据,并构造了带“?”参数的SQL语句,在SQL查询时需要使用PreparedStatement对象,再用setString()方法设置参数的值。如果用户名或密码有误则将页面重定向到login.jsp;如果通过了用户名和密码校验,则用session变量分别记下用户名、用户真实姓名、用户ID号、用户所属部门ID号,再将页面重定向到index.jsp页面。
使用带“?”参数的SQL语句的好处是可以防范SQL注入攻击,如果在login.jsp页面中的用户名和密码输入框中输入“1' or '1'='1”是不能登录成功的。
系统登录成功后进入首页index.jsp,如图14-7所示。
图14-7 系统首页
首页index.jsp使用了框架技术,将页面分成三个部分:首部的框架中放置使用OA系统的企业的logo,系统的名称等;左边的框架中菜单是系统的功能菜单;右边的框架中是操作的具体内容,进入系统时显示的就是firstPage.jsp,此后作功能操作时显示的是具体功能操作的内容。
首页index.jsp的源代码如下。
index.jsp
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page contentType="text/html;charset=GB2312" %>
<%//------session检查------
if(session.getAttribute("oaUserId")==null){
response.sendRedirect("user/login.jsp");
}%>
<html>
<head>
<title>希赛网络办公自动化系统</title>
</head>
<frameset framespacing="1" rows="69,*" frameborder="0" name="all">
<frame name="banner" scrolling="no" noresize target="contents" src="banner.htm">
<frameset cols="178,*">
<frame name="menu" target="main" src="menu.jsp" marginwidth="0" marginheight="0"
scrolling="auto" noresize>
<frame src="firstPage.jsp" name="main" scrolling="auto">
</frameset>
<noframes>
<body>
</body>
</noframes>
</frameset>
</html>
代码中首先做了session检查,看session变量oaUserId的值是否为null,如果为null表示尚未登录,将跳转到login.jsp页面。框架menu的target属性值设置为main,则表示目标框架为main,当点击框架menu中的超链接时,改变了框架main中包含的网页。
框架banner中的页面为banner.htm,源代码如下。
banner.htm
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
<title>学生成绩管理系统</title>
</head>
<body bgcolor="#DCDADA">
<table border="0" width="80%">
<tr align="center">
<td align="right"><IMG src="img/csai.gif"></td>
<td width="80%">
<p align="center"><font size="5"><b>
希赛公司OA<font size="4">(Version 1.0)</font></b></font>
<br>――――供读者学习使用,版权所有,违者必究!
</td>
</tr>
</table>
</body>
</html>
框架menu中的menu.jsp页面的源代码如下:
menu.jsp
<%@ page contentType="text/html;charset=GB2312" %>
<html>
<head>
<SCRIPT LANGUAGE="JavaScript">
<!--
var bV=parseInt(navigator.appVersion);
var NS4=(document.layers) ? true : false;
var IE4=((document.all)&&(bV>=4))?true:false;
var ver4 = (NS4 || IE4) ? true : false;
function expandIt(){return}
function expandAll(){return}
function nomsg(){self.status="";}
if(ver4){
document.write("<SCRIPT LANGUAGE=\"JavaScript\" SRC=\"rsmenu.js\"></SCRIPT>");
}
//-->
</SCRIPT>
</head>
<body leftMargin="0" topMargin="0" marginheight="0" marginwidth="0" bgcolor="#DCDADA">
<span class="label"></span><br>
<!---------- begin OUTLINE ----------->
<!--上方全部展开/关闭-->
<DIV style="margin-left: 20px">
<FONT STYLE="font-size: 12pt">
您好,<%=session.getAttribute("oaUserTrueName")%><br>
操作功能菜单
</FONT></A>
</div>
<DIV ID="elOneParent" class="parent" style="margin-left: 20px">
<TABLE BORDER=1 CELLSPACING=0 CELLPADDING=0 WIDTH=101><tr><td class="label">
<A HREF="#" text-decoration:none;" onClick="expandIt('elOne'); return false">
信息中心
</a></td></tr></table>
</DIV>
</table>
<!--相应的子菜单-->
<DIV ID="elOneChild" class="child" style="margin-left: 24px">
<TABLE BORDER=1 CELLSPACING=0 CELLPADDING=0 WIDTH=126>
<tr><td>
<A HREF="info/viewInfo1.jsp" target="main">查看信息</A><BR>
<%if(Integer.parseInt(session.getAttribute("departmentId").toString())==1){%>
<A HREF="info/addInfo1.jsp" target="main">发布信息</A><BR>
<%}%>
</td></tr>
</table>
</DIV>
<DIV ID="elTwoParent" class="parent" style="margin-left: 20px">
<TABLE BORDER=1 CELLSPACING=0 CELLPADDING=0 WIDTH=101><tr><td class="label">
<A HREF="#" text-decoration:none;" onClick="expandIt('elTwo'); return false">
行政审批
</a></td></tr></table>
</DIV>
</table>
<!--相应的子菜单-->
<DIV ID="elTwoChild" class="child" style="margin-left: 24px">
<TABLE BORDER=1 CELLSPACING=0 CELLPADDING=0 WIDTH=126>
<tr><td>
<A HREF="man/addThing1.jsp" target="main">发起审批事务</A><BR>
<%if(Integer.parseInt(session.getAttribute("departmentId").toString())==1){%>
<A HREF="man/thingArra1.jsp" target="main">安排审批事务</A><BR>
<%}%>
<A HREF="man/thingToMe1.jsp" target="main">待审批事务</A><BR>
</td></tr>
</table>
</DIV>
<!--父菜单-->
<DIV ID="elFiveParent" class="parent" style="margin-left: 20px">
<TABLE BORDER=1 CELLSPACING=0 CELLPADDING=0 WIDTH=101><tr><td class="label">
<A HREF="#" text-decoration:none;" onClick="expandIt('elFive'); return false">
用户管理
</a></td></tr></table>
</DIV>
</table>
<!--相应的子菜单-->
<DIV ID="elFiveChild" class="child" style="margin-left: 24px">
<TABLE BORDER=1 CELLSPACING=0 CELLPADDING=0 WIDTH=126>
<tr><td>
<%if(Integer.parseInt(session.getAttribute("departmentId").toString())==1){%>
<A HREF="user/addUser1.jsp" target="main">增加新用户</A><BR>
<A HREF="user/modiUser1.jsp" target="main">修改用户信息</A><BR>
<%}%>
<A HREF="user/modiPass1.jsp" target="main">修改您的密码</A><BR>
<%if(Integer.parseInt(session.getAttribute("departmentId").toString())==1){%>
<A HREF="user/department.jsp" target="main">公司部门管理</A><BR>
<%}%>
</td></tr>
</table>
</DIV>
<!--父菜单-->
<DIV ID="elTwoParent" class="parent" style="margin-left: 20px">
<TABLE BORDER=1 CELLSPACING=0 CELLPADDING=0 WIDTH=101><tr><td class="label">
<A HREF="user/login.jsp" target="_parent">
退出系统
</a></td></tr></table>
</table>
</DIV>
<!---------- end OUTLINE ----------->
<br>
<SCRIPT LANGUAGE="JavaScript1.2">
<!--
if(NS4){
firstEl = "elOneParent";
firstInd = getIndex(firstEl);
showAll();
arrange();
}
//-->
</SCRIPT>
</body>
</html>
menu.jsp中显示的是系统的功能菜单。系统主要基于菜单来作权限控制。如果部门ID号不是1表示是普通用户,是1表示是系统管理员。普通用户不能使用发布消息、安排审批事务、增加新用户、修改用户信息、公司部门管理等功能。实际上就是根据如下的判断来实现的:
<%if(Integer.parseInt(session.getAttribute("departmentId").toString())==1){%>
… …
<%}%>
menu.jsp中的菜单为实现动态效果使用了JavaScript代码,并导入了rsmenu.js文件,这个文件中全部是JavaScript代码,有兴趣的读者可参看光盘中的这个文件中的具体代码。
main框架中第一个页面firstPage.jsp的源代码如下。
firstPage.jsp
<%@ page contentType="text/html;charset=GB2312" %>
<%@ page import="java.util.Date" %>
<html>
在系统的功能菜单的下面有一个功能菜单叫“退出系统”,点击后就会将页面重定向到登录界面login.jsp。退出系统功能要做的事主要有两个:一是将记录的会话变量值为null,这样就表示退出当前会话了;二是退出当前的框架网页而重定向到没有框架的login.jsp页面。
退出系统功能的超链接代码如下:
<a HREF="user/login.jsp" target="_parent">
退出系统
</a>
将超链接的target属性置为“_parent”,则会跳出当前的框架而到上一级。将记录的会话变量值设为null的功能将在login.jsp页面的首部完成。login.jsp页面中有如下语句将记录的会话变量值设为null:
<%
session.setAttribute("oaUserName",null);
session.setAttribute("oaUserTrueName",null);
session.setAttribute("oaUserId",null);
session.setAttribute("departmentId",null);
%>
查看信息的界面如图14-8所示。
图14-8 查看信息
查看信息功能页面viewInfo1.jsp作了分页处理。viewInfo1.jsp页面详细的源代码如下。
viewInfo1.jsp
<%@ page contentType="text/html;charset=GB2312" %>
<%@ page import="java.sql.*,javax.naming.*,javax.sql.DataSource" %>
<html>
<body bgcolor="#DCDADA">
当前位置:信息中心-->查看信息<br>
<%int pageSize=10;//每页显示的记录条数
int allRecordCount=0;//记录总条数
int pageRecordCount=0;//当前页记录条数
int pageCount=0;//总页数
int diPage=1;//当前页码
//------接收请求参数------
try{
diPage=Integer.parseInt(request.getParameter("diPage"));
}catch(Exception e){
diPage=1;
}
%>
<%//------构造查询的SQL语句------
//sqlStr1为查询到当前页数据的SQL语句
//sqlStr2为查询到总记录条数的SQL语句
String sqlStr1="select top "+diPage*pageSize+" * from info where "+
"infoId not in (select top "+(diPage-1)*pageSize+" infoId from"+
" info order by infoId desc) order by infoId desc";
String sqlStr2="select count(*) as allCount from info";
%>
<%//------得到数据库连接------
Context initCtx = new InitialContext();
Context ctx = (Context) initCtx.lookup("java:comp/env");
Object obj = (Object) ctx.lookup("jdbc/sqlserver");
DataSource ds = (javax.sql.DataSource)obj;
Connection conn = ds.getConnection();
%>
<%//------得到总页数、总记录条数与当前页记录条数------
java.sql.Statement sql1=conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY);
java.sql.ResultSet rs1=null;
rs1=sql1.executeQuery(sqlStr2);
if(rs1!=null){
//---得到总页数、总记录条数与当前页记录条数---
rs1.next();
allRecordCount=rs1.getInt("allCount");
if(allRecordCount%pageSize==0)
pageCount=allRecordCount/pageSize;
else
pageCount=(int)(allRecordCount/pageSize)+1;
if(diPage==pageCount||pageCount==0)//最后一页或没有数据
pageRecordCount=allRecordCount-(diPage-1)*pageSize;
else
pageRecordCount=pageSize;
}
%>
<table border="1" CELLSPACING=0 CELLPADDING=0>
<tr><td colspan="3" align="center">
查看信息(共<%=allRecordCount%>条,当前页<%=pageRecordCount%>条)
<%if(diPage!=1){//不是第一页
out.print("<a href='viewInfo1.jsp?diPage=1'>首页</a> ");
out.print("<a href='viewInfo1.jsp?diPage="+(diPage-1)+"'>上一页</a> ");
}
if(diPage!=pageCount&&pageCount!=0){//不是最后一页
out.print("<a href='viewInfo1.jsp?diPage="+(diPage+1)+"'>
下一页</a> ");
out.print("<a href='viewInfo1.jsp?diPage="+pageCount+"'>尾页</a> ");
}
%>
</td>
</tr>
<tr><td>信息标题</td><td>查看?</td>
<%if(session.getAttribute("departmentId")!=null){
if(Integer.parseInt(session.getAttribute("departmentId").toString())==1){%>
<td>删除?</td>
<%}
}
%>
</tr>
<%//------得到当前面数据并显示------
java.sql.ResultSet rs2=null;
java.sql.Statement sql2=conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY);
rs2=sql2.executeQuery(sqlStr1);
if(rs2!=null){
while(rs2.next()){%>
<tr><td>[<%=rs2.getString("infoAddTime").substring(0,10)%>]
<%=rs2.getString("infoTitle")%></td>
<td><a href="#"
onclick="window.open('viewInfo2.jsp?infoId=<%=rs2.getLong("infoId")%>')">
查看</a></td>
<%if(session.getAttribute("departmentId")!=null){
if(Integer.parseInt(session.getAttribute("departmentId").toString())==1){%>
<td><a href="delInfo.jsp?infoId=<%=rs2.getLong("infoId")%>">删除</a></td>
<%}
}
%>
</tr>
<%
}
}%>
<%
if(conn!=null)
conn.close();
%>
</table>
</body>
</html>
代码中,首先将每页显示的记录条数变量pageSize设为10,如果需要每页显示其它数目的记录,如8条,则可将pageSize变量的值设为8;总记录条数allRecordCount、当前页记录条数pageRecordCount、总页数pageCount变量的值均设为0,在后续程序中再作变更设置;当前页码默认设为1,即第1页。
在接收当前页diPage参数时,要对它作数据类型转换,因为传来的参数都是字符串,需要转换为整型。当第一次打开viewInfo1.jsp时,diPage参数的值会为null,在使用Integer.parseInt(request.getParameter("diPage"))方法时会抛出异常,因此需要及时捕获异常并将diPage的值设为默认的1。
点击viewInfo1.jsp中的超链接“查看”,会弹出一个页面viewInfo2.jsp用于显示消息的详细内容。viewInfo1.jsp向viewInfo2.jsp页面传入了infoId参数,即消息ID号,用于唯一地标识一条消息。viewInfo2.jsp页面的源代码如下。
viewInfo2.jsp
<%@ page contentType="text/html;charset=GB2312" %>
<%@ page import="java.sql.*,javax.naming.*,javax.sql.DataSource" %>
<html>
<body bgcolor="#DCDADA">
当前位置:信息中心-->查看信息<br>
<%//------接收请求参数------
String infoId=request.getParameter("infoId");
%>
<%//------如果接收数据有误------
if(infoId==null||infoId.length()==0)
response.sendRedirect("viewInfo1.jsp");
%>
<%//------构造查询的SQL语句------
String sqlStr="select * from info where infoId="+infoId;
%>
<%//------得到数据库连接------
Context initCtx = new InitialContext();
Context ctx = (Context) initCtx.lookup("java:comp/env");
Object obj = (Object) ctx.lookup("jdbc/sqlserver");
DataSource ds = (javax.sql.DataSource)obj;
Connection conn = ds.getConnection();
%>
<%//------查询出数据------
java.sql.Statement sql=conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,
ResultSet.CONCUR_READ_ONLY);
java.sql.ResultSet rs=null;
rs=sql.executeQuery(sqlStr);
if(rs!=null){
rs.next();
%>
<table border="1" CELLSPACING=0 CELLPADDING=0>
<tr><td align="right" width="80">信息标题:</td>
<td align="left">
<%=rs.getString("infoTitle")%>
</td></tr>
<tr><td align="right">信息内容:</td>
<td align="left">
<%=rs.getString("infoContent")%>
</td></tr>
</table>
<%}%>
<%
if(conn!=null)
conn.close();
%>
</body>
</html>
在viewInfo1.jsp页面中还有一个超链接“删除”,点击即会删除当前的这条消息,但这个功能只有管理员才能使用。viewInfo1.jsp页面中有如下的控制代码:
<%if(session.getAttribute("departmentId")!=null){
if(Integer.parseInt(session.getAttribute("departmentId").toString())==1){%>
<td>删除?</td>
<% }
}
%>
如果会话变量departmentId的内容不为null,且值为1则会显示超链接“删除”,链接到delInfo.jsp,并传入infoId参数。delInfo.jsp页面的源代码如下。
delInfo.jsp
<%@ page contentType="text/html;charset=GB2312" %>
<%@ page import="java.sql.*,javax.naming.*,javax.sql.DataSource" %>
<html>
<body bgcolor="#DCDADA">
当前位置:用户管理-->删除信息<br>
<%//------接收请求参数------
String infoId=request.getParameter("infoId");
%>
<%//------如果接收数据有误------
if(infoId==null||infoId.length()==0)
response.sendRedirect("viewInfo1.jsp");
%>
<%//------构造将数据插入到数据库的SQL语句-----
String sqlStr=new String("delete from info where infoId="+infoId);
%>
<%//------得到数据库连接------
Context initCtx = new InitialContext();
Context ctx = (Context) initCtx.lookup("java:comp/env");
Object obj = (Object) ctx.lookup("jdbc/sqlserver");
DataSource ds = (javax.sql.DataSource)obj;
Connection conn = ds.getConnection();
%>
<%//------将数据插从数据库中删除------
java.sql.Statement sql=conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE);
int i=sql.executeUpdate(sqlStr);
if(i==0){//删除失败
response.sendRedirect("viewInfo1.jsp");
}else{
out.print("删除信息成功!");
}
if(conn!=null)
conn.close();
%>
</body>
</html>
发布信息页面addInfo1.jsp的界面如图14-9所示。
图14-9 发布信息
信息标题和信息内容都是必输的内容,输入完成后,按“提交”按钮即可提交表单。发布信息页面addInfo1.jsp的源代码如下。
addInfo1.jsp
<%@ page contentType="text/html;charset=GB2312" %>
<!------系统交互javascript----->
<script language="JavaScript">
<!--
function checkData() {
if(form1.title.value.length==0){
alert("请输入信息标题");
return false;
}if(form1.content.value.length==0){
alert("请输入信息内容");
return false;
} else return true;
}
-->
</script>
<html>
<body bgcolor="#DCDADA">
当前位置:信息中心-->发布信息<br>
<table border="1" CELLSPACING=0 CELLPADDING=0>
<form name="form1" action="addInfo2.jsp" method="post" onsubmit="return
checkData()">
<tr><td align="center" colspan="2">
发布一条新的信息
</td></tr>
<tr><td align="right">信息标题:</td>
<td align="left">
<input type="text" size="40" name="title">
</td></tr>
<tr><td align="right">信息内容:</td>
<td align="left">
<textarea name="content" rows="10" cols="40"></textarea>
</td></tr>
<tr><td align="center" colspan="2">
<input type="submit" value="提 交">
</td></tr>
</form>
</table>
</body>
</html>
上述代码用JavaScript语句作了基本的数据校验,即输入的信息标题和信息内容均不能为null。提交后,表单中的数据将会传递到addInfo2.jsp,相应的源代码如下。
addInfo2.jsp
<%@ page contentType="text/html;charset=GB2312" %>
<%@ page import="java.sql.*,javax.naming.*,javax.sql.DataSource" %>
<html>
<body bgcolor="#DCDADA">
当前位置:信息中心-->发布信息<br>
<%//------接收请求参数------
String title=request.getParameter("title");
String content=request.getParameter("content");
%>
<%//------如果接收数据有误------
if(title==null||content==null||
title.length()==0||content.length()==0)
response.sendRedirect("addInfo1.jsp");
%>
<%//------构造将数据插入到数据库的SQL语句-----
String sqlStr=new String("insert into info(infoTitle,infoContent"+
") values(?,?)");
%>
<%//------得到数据库连接------
Context initCtx = new InitialContext();
Context ctx = (Context) initCtx.lookup("java:comp/env");
Object obj = (Object) ctx.lookup("jdbc/sqlserver");
DataSource ds = (javax.sql.DataSource)obj;
Connection conn = ds.getConnection();
%>
<%//------将数据插入到数据库------
PreparedStatement preSQLinsert=conn.prepareStatement(sqlStr);
title=new String(title.getBytes("ISO-8859-1"));
preSQLinsert.setString(1,title);
content=new String(content.getBytes("ISO-8859-1"));
preSQLinsert.setString(2,content);
int i=preSQLinsert.executeUpdate();
if(i==0)//插入失败
response.sendRedirect("addUser1.jsp");
else
out.print("增加新的信息成功!");
if(preSQLinsert!=null)
preSQLinsert.close();
if(conn!=null)
conn.close();
%>
</body>
</html>
程序接收传来的参数后,再构造了带“?”的SQL语句,再作插入数据处理。
【专家提示】传来的中文数据必须作转码处理,否则保存在数据库中的数据将会是乱码。其中标题的编辑转换语句如下:
title=new String(title.getBytes("ISO-8859-1"));
普通用户即可发起审批事务。发起审批事务页面addThing1.jsp的界面如图14-10所示。
图14-10 发起审批事务
审批事务标题和审批事务详细说明都是必填项,在页面的代码中用JavaScript作了基本的校验。发起审批事务页面addThing1.jsp的源代码如下。
addThing1.jsp
<%@ page contentType="text/html;charset=GB2312" %>
<!------系统交互javascript----->
<script language="JavaScript">
<!--
function checkData() {
if(form1.title.value.length==0){
alert("请输入审批事务标题");
return false;
}if(form1.content.value.length==0){
alert("请输入审批事务详细说明");
return false;
} else return true;
}
-->
</script>
<html>
<body bgcolor="#DCDADA">
当前位置:行政审批-->发起审批事务<br>
<table border="1" CELLSPACING=0 CELLPADDING=0>
<form name="form1" action="addThing2.jsp" method="post" onsubmit="return checkData()">
<tr><td align="center" colspan="2">
发起一个新的审批事务
</td></tr>
<tr><td align="right">审批事务标题:</td>
<td align="left">
<input type="text" size="40" name="title">
</td></tr>
<tr><td align="right">审批事务详细说明:</td>
<td align="left">
<textarea name="content" rows="10" cols="40"></textarea>
</td></tr>
<tr><td align="center" colspan="2">
<input type="submit" value="提 交">
</td></tr>
</form>
</table>
</body>
</html>
在发起审批事务页面addThing1.jsp中输入完数据后,按“提交”按钮,就会将数据提交到addThing2.jsp页面,addThing2.jsp页面的源代码如下。
addThing2.jsp
<%@ page contentType="text/html;charset=GB2312" %>
<%@ page import="java.sql.*,javax.naming.*,javax.sql.DataSource" %>
<html>
<body bgcolor="#DCDADA">
当前位置:信息中心-->发布信息<br>
<%//------接收请求参数------
String title=request.getParameter("title");
String content=request.getParameter("content");
%>
<%//------如果接收数据有误------
if(title==null||content==null||
title.length()==0||content.length()==0)
response.sendRedirect("addThing1.jsp");
%>
<%//------构造将数据插入到数据库的SQL语句-----
String sqlStr=new String("insert into thing(thingTitle,thingContent"+
") values(?,?)");
%>
<%//------得到数据库连接------
Context initCtx = new InitialContext();
Context ctx = (Context) initCtx.lookup("java:comp/env");
Object obj = (Object) ctx.lookup("jdbc/sqlserver");
DataSource ds = (javax.sql.DataSource)obj;
Connection conn = ds.getConnection();
%>
<%//------将数据插入到数据库------
PreparedStatement preSQLinsert=conn.prepareStatement(sqlStr);
title=new String(title.getBytes("ISO-8859-1"));
preSQLinsert.setString(1,title);
content=new String(content.getBytes("ISO-8859-1"));
preSQLinsert.setString(2,content);
int i=preSQLinsert.executeUpdate();
if(i==0)//插入失败
response.sendRedirect("addThing1.jsp");
else
out.print("增加新的审批事务成功!");
if(preSQLinsert!=null)
preSQLinsert.close();
if(conn!=null)
conn.close();
%>
</body>
</html>
addThing2.jsp页面用于根据传来的数据,将审批事务插入到数据库的thing表中。
“安排审批事务”功能只有系统管理员才能使用。点击系统操作界面左边的系统功能菜单中的“行政审批”→“安排审批事务”,即可出现如图14-11所示的界面。
图14-11 安排审批事务
安排审批事务功能的thingArra1.jsp页面的源代码如下。
thingArra1.jsp
<%@ page contentType="text/html;charset=GB2312" %>
<%@ page import="java.sql.*,javax.naming.*,javax.sql.DataSource" %>
<html>
<body bgcolor="#DCDADA">
当前位置:行政审批-->安排审批事务<br>
<%int pageSize=10;//每页显示的记录条数
int allRecordCount=0;//记录总条数
int pageRecordCount=0;//当前页记录条数
int pageCount=0;//总页数
int diPage=1;//当前页码
//------接收请求参数------
try{
diPage=Integer.parseInt(request.getParameter("diPage"));
}catch(Exception e){
diPage=1;
}
%>
<%//------构造查询的SQL语句------
//sqlStr1为查询到当前页数据的SQL语句
//sqlStr2为查询到总记录条数的SQL语句
String sqlStr1="select top "+diPage*pageSize+" * from thing where "+
"thingId not in (select top "+(diPage-1)*pageSize+" thingId from"+
" thing order by thingId desc) order by thingId desc";
String sqlStr2="select count(*) as allCount from thing";
%>
<%//------得到数据库连接------
Context initCtx = new InitialContext();
Context ctx = (Context) initCtx.lookup("java:comp/env");
Object obj = (Object) ctx.lookup("jdbc/sqlserver");
DataSource ds = (javax.sql.DataSource)obj;
Connection conn = ds.getConnection();
%>
<%//------得到总页数、总记录条数与当前页记录条数------
java.sql.Statement sql1=conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,
ResultSet.CONCUR_READ_ONLY);
java.sql.ResultSet rs1=null;
rs1=sql1.executeQuery(sqlStr2);
if(rs1!=null){
//---得到总页数、总记录条数与当前页记录条数---
rs1.next();
allRecordCount=rs1.getInt("allCount");
if(allRecordCount%pageSize==0)
pageCount=allRecordCount/pageSize;
else
pageCount=(int)(allRecordCount/pageSize)+1;
if(diPage==pageCount||pageCount==0)//最后一页或没有数据
pageRecordCount=allRecordCount-(diPage-1)*pageSize;
else
pageRecordCount=pageSize;
}
%>
<table border="1" CELLSPACING=0 CELLPADDING=0>
<tr><td colspan="4" align="center">
查看审批信息(共<%=allRecordCount%>条,当前页<%=pageRecordCount%>条)
<%if(diPage!=1){//不是第一页
out.print("<a href='viewInfo1.jsp?diPage=1'>首页</a> ");
out.print("<a href='viewInfo1.jsp?diPage="+(diPage-1)+"'>上一页</a> ");
}
if(diPage!=pageCount&&pageCount!=0){//不是最后一页
out.print("<a href='viewInfo1.jsp?diPage="+(diPage+1)+"'>
下一页</a> ");
out.print("<a href='viewInfo1.jsp?diPage="+pageCount+"'>尾页</a> ");
}
%>
</td>
</tr>
<tr><td>审批事务标题</td>
<td>查看详细说明与审批记录?</td>
<%if(session.getAttribute("departmentId")!=null){
if(Integer.parseInt(session.getAttribute("departmentId").toString())==1){%>
<td>审批安排?</td>
<td>删除?</td>
<%}
}
%>
</tr>
<%//------得到当前面数据并显示------
java.sql.ResultSet rs2=null;
java.sql.Statement sql2=conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,
ResultSet.CONCUR_READ_ONLY);
rs2=sql2.executeQuery(sqlStr1);
if(rs2!=null){
while(rs2.next()){%>
<tr><td>[<%=rs2.getString("thingAddTime").substring(0,10)%>]
<%=rs2.getString("thingTitle")%></td>
<td><a href="#"
onclick="window.open('manRecord.jsp?thingId=<%=rs2.getLong("thingId")%>')">
查看</a></td>
<%if(session.getAttribute("departmentId")!=null){
if(Integer.parseInt(session.getAttribute("departmentId").toString())==1){%>
<td><a href="thingArra2.jsp?thingId=<%=rs2.getLong("thingId")%>"
target="_blank">审批安排</a></td>
<td><a href="delThing.jsp?thingId=<%=rs2.getLong("thingId")%>">删除</a></td>
<%}
}
%>
</tr>
<%
}
}%>
<%
if(conn!=null)
conn.close();
%>
</table>
</body>
</html>
在这个页面中作了数据分页处理。由于调试程序时,只有3条记录,而每页的记录条数是10条,故看不到“上一页”、“下一页”这样的超链接文字。
thingArra1.jsp页面的功能是列出当前页中待审批的事项,并在事项后给出“查看”、“审批安排”、“删除”超链接。点击“查看”超链接,将弹出一个对话框显示该事项的历史审批情况,如图14-12所示。
图14-12 查看事项的审批记录
审批情况显示页面manRecord.jsp中有两个表格,上面的表格显示审批的内容;下面的表格显示谁审批的,审批的语句是什么。manRecord.jsp页面的源代码如下。
manRecord.jsp
<%@ page contentType="text/html;charset=GB2312" %>
<%@ page import="java.sql.*,javax.naming.*,javax.sql.DataSource" %>
<%//------得到数据库连接------
Context initCtx = new InitialContext();
Context ctx = (Context) initCtx.lookup("java:comp/env");
Object obj = (Object) ctx.lookup("jdbc/sqlserver");
DataSource ds = (javax.sql.DataSource)obj;
Connection conn = ds.getConnection();
%>
<%//------接收请求参数------
String thingId=request.getParameter("thingId");
%>
<html>
<body bgcolor="#DCDADA">
当前位置:行政审批-->审批情况<br>
<table border="1" CELLSPACING=0 CELLPADDING=0 width="500">
<%//------查询出审批信息并显示------
String sqlStr1="select * from thing where thingId="+thingId;
java.sql.Statement sql1=conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,
ResultSet.CONCUR_READ_ONLY);
java.sql.ResultSet rs1=null;
rs1=sql1.executeQuery(sqlStr1);
if(rs1.next()){
%>
<tr><td align="right" width="200">审批事务标题:</td>
<td align="left"><%=rs1.getString("thingTitle")%></td>
</tr>
<tr><td align="right">审批内容详细说明:</td>
<td align="left"><%=rs1.getString("thingContent")%></td>
</tr>
<%}%>
</table><br>
<table border="1" CELLSPACING=0 CELLPADDING=0 width="500">
<%//------查询出审批记录并显示------
String sqlStr2="select thingArra.thingArraEnd as thingArraEnd,"
+"oaUser.oaUserName as oaUserName from thingArra,oaUser"+
" where oaUser.oaUserId=thingArra.oaUserId and thingArra.thingId="+thingId;
java.sql.Statement sql2=conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,
ResultSet.CONCUR_READ_ONLY);
java.sql.ResultSet rs2=null;
rs2=sql1.executeQuery(sqlStr2);
while(rs2.next()){
%>
<tr><td align="right" width="200">审批人:</td>
<td align="left"><%=rs2.getString("oaUserName")%></td>
</tr>
<tr><td align="right">审批:</td>
<td align="left">
<%if(rs2.getString("thingArraEnd")!=null)
out.print(rs2.getString("thingArraEnd"));%> </td>
</tr>
<%}%>
</table><br>
</body>
</html>
manRecord.jsp页面根据上一页面thingArra1.jsp传来的thingId参数,即审批的事务的ID号,根据这个参数可以在数据库中的thing表中找到事务标题、事务内容等数据;根据事务ID号还可以通过查找数据库中的thingArra表找到审批的安排情况及审批结果。
在thingArra1.jsp页面中,每条记录后有一个“删除”超链接,只有当登录的用户是系统管理员时才会显示“删除”超链接。点击“删除”超链接,将跳转到delThing.jsp页面,并传入thingId参数,因为thingId可以唯一地标识数据库thing表中的一条记录。delThing.jsp页面用于删除一个审批事务,页面源代码如下。
delThing.jsp
<%@ page contentType="text/html;charset=GB2312" %>
<%@ page import="java.sql.*,javax.naming.*,javax.sql.DataSource" %>
<html>
<body bgcolor="#DCDADA">
当前位置:用户管理-->删除信息<br>
<%//------接收请求参数------
String thingId=request.getParameter("thingId");
%>
<%//------如果接收数据有误------
if(thingId==null||thingId.length()==0)
response.sendRedirect("thingArra1.jsp");
%>
<%//------构造将数据插入到数据库的SQL语句-----
String sqlStr=new String("delete from thing where thingId="+thingId);
%>
<%//------得到数据库连接------
Context initCtx = new InitialContext();
Context ctx = (Context) initCtx.lookup("java:comp/env");
Object obj = (Object) ctx.lookup("jdbc/sqlserver");
DataSource ds = (javax.sql.DataSource)obj;
Connection conn = ds.getConnection();
%>
<%//------将数据插从数据库中删除------
java.sql.Statement sql=conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,
ResultSet.CONCUR_UPDATABLE);
int i=sql.executeUpdate(sqlStr);
if(i==0){//删除失败
response.sendRedirect("thingArra1.jsp");
}else{
out.print("删除审批事项成功!");
}
if(conn!=null)
conn.close();
%>
</body>
</html>
在thingArra1.jsp页面中,每条记录后有一个“审批安排”超链接,只有当登录的用户是系统管理员时才会显示“审批安排”超链接。点击“审批安排”超链接,将跳转到thingArra2.jsp页面,并传入thingId参数。thingArra2.jsp页面的显示效果如图14-13所示。
图14-13 安排审批事务
这个界面中有三个表格:上面的表格用于对当前的审批事务,增加一个审批过程,其实也就是看要由谁审批的问题,所以在选择了审批人后按“确定”按钮即提交数据;中间的表格显示当前事务已有的审批过程,如果审批人已经填写了审批意见,则会在审批结果栏中显示审批意见;下面的表格显示当前事务的相关信息。
thingArra2.jsp页面的源代码如下。
thingArra2.jsp页面
<%@ page contentType="text/html;charset=GB2312" %>
<%@ page import="java.sql.*,javax.naming.*,javax.sql.DataSource" %>
<%//------得到数据库连接------
Context initCtx = new InitialContext();
Context ctx = (Context) initCtx.lookup("java:comp/env");
Object obj = (Object) ctx.lookup("jdbc/sqlserver");
DataSource ds = (javax.sql.DataSource)obj;
Connection conn = ds.getConnection();
%>
<%//------接收请求参数------
String thingId=request.getParameter("thingId");
String man=request.getParameter("man");
%>
<%//------如果接收数据有误------
if(thingId==null||thingId.length()==0)
response.sendRedirect("thingArra1.jsp");
%>
<%//------如果需要增加新的审批安排------
if(man!=null){
String sqlStrAdd="insert into thingArra(thingId,oaUserId) values(?,?)";
PreparedStatement preSQLinsert=conn.prepareStatement(sqlStrAdd);
preSQLinsert.setLong(1,Long.parseLong(thingId));
preSQLinsert.setLong(2,Long.parseLong(man));
preSQLinsert.executeUpdate();
}
%>
<html>
<body bgcolor="#DCDADA">
当前位置:行政审批-->安排审批事务<br>
<table border="1" CELLSPACING=0 CELLPADDING=0 width="400">
<tr><td colspan="3" align="center">
增加一个审批过程:</tr>
<form action="thingArra2.jsp" method="post">
<input type="hidden" name="thingId" value="<%=thingId%>">
<tr><td align="right">选择审批人:</td>
<td align="left">
<select name="man">
<%String sqlStr1="select * from oaUser";
java.sql.Statement sql1=conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,
ResultSet.CONCUR_READ_ONLY);
java.sql.ResultSet rs1=null;
rs1=sql1.executeQuery(sqlStr1);
while(rs1.next()){%>
<option value="<%=rs1.getString("oaUserId")%>">
<%=rs1.getString("oaUserTrueName")%>
</option>
<%}%>
</select>
</td><td>
<input type="submit" value="确 定">
</tr>
</form>
</table><br>
<table border="1" CELLSPACING=0 CELLPADDING=0 width="500">
<tr><td colspan="4" align="center">
已有审批过程</tr>
<tr><td>序号</td><td>审批人</td><td>删除?</td><td>审批结果</td></tr>
<%//------查询出已有的审批过程数据------
String sqlStr2="select * from thingArra where thingId="+thingId;
java.sql.Statement sql2=conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,
ResultSet.CONCUR_READ_ONLY);
java.sql.ResultSet rs2=null;
rs2=sql2.executeQuery(sqlStr2);
int i=0;
while(rs2.next()){
i++;%>
<tr><td><%=i%></td>
<td>
<%
String sqlStr3="select * from oaUser where oaUserId="+rs2.getString("oaUserId");
java.sql.Statement sql3=conn.createStatement(ResultSet._
TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY);
java.sql.ResultSet rs3=null;
rs3=sql3.executeQuery(sqlStr3);
if(rs3.next())
out.print(rs3.getString("oaUserTrueName"));
%> </td>
<td><a href="delthingArra.jsp?thingArraId=<%=rs2.getString("thingArraId")%>
&thingId=<%=thingId%>">删除</a></td>
<td>
<%
if(rs2.getString("thingArraEnd")!=null)
out.print(rs2.getString("thingArraEnd"));
%>
</td>
</tr>
<%}%>
</table><br>
<table border="1" CELLSPACING=0 CELLPADDING=0 width="500">
<%//------查询出审批信息并显示------
String sqlStr4="select * from thing where thingId="+thingId;
java.sql.Statement sql4=conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,
ResultSet.CONCUR_READ_ONLY);
java.sql.ResultSet rs4=null;
rs4=sql4.executeQuery(sqlStr4);
if(rs4.next()){
%>
<tr><td align="right" width="200">审批事务标题:</td>
<td align="left"><%=rs4.getString("thingTitle")%></td>
</tr>
<tr><td align="right">审批内容详细说明:</td>
<td align="left"><%=rs4.getString("thingContent")%></td>
</tr>
<%}%>
</table>
</body>
</html>
<%
if(conn!=null){
conn.close();
}
%>
当选择了审批人再点击“确定”按钮后,根据表单的action属性设置值,可以得知,数据被提交到本页面。因此在本页面中接收数据交用insert操作。
点击系统操作界面左边的系统功能菜单中的“行政审批”→“待审批事务”,即可看到审批人为登录的用户的事务。如图14-14所示。
图14-14 待审批事务
这个页面为thingToMe1.jsp,点击“查看”超链接会弹出一个窗口以显示此事务的审批情况。thingToMe1.jsp页面的源代码如下。
thingToMe1.jsp
<%@ page contentType="text/html;charset=GB2312" %>
<%@ page import="java.sql.*,javax.naming.*,javax.sql.DataSource" %>
<html>
<body bgcolor="#DCDADA">
当前位置:行政审批-->安排审批事务<br>
<%//------会话检查------
if(session.getAttribute("oaUserId")==null)
response.sendRedirect("../user/login.jsp");
%>
<%int pageSize=10;//每页显示的记录条数
int allRecordCount=0;//记录总条数
int pageRecordCount=0;//当前页记录条数
int pageCount=0;//总页数
int diPage=1;//当前页码
//------接收请求参数------
try{
diPage=Integer.parseInt(request.getParameter("diPage"));
}catch(Exception e){
diPage=1;
}
%>
<%//------构造查询的SQL语句------
//sqlStr1为查询到当前页数据的SQL语句
//sqlStr2为查询到总记录条数的SQL语句
String sqlStr1="select top "+diPage*pageSize+" * from thingArra where "+
"thingId not in (select top "+(diPage-1)*pageSize+" thingId from"+
" thingArra where oaUserId="+session.getAttribute("oaUserId")+
" order by thingId desc) and oaUserId="+session.getAttribute("oaUserId")+
" order by thingId desc";
String sqlStr2="select count(*) as allCount from thingArra where oaUserId="+
session.getAttribute("oaUserId");
%>
<%//------得到数据库连接------
Context initCtx = new InitialContext();
Context ctx = (Context) initCtx.lookup("java:comp/env");
Object obj = (Object) ctx.lookup("jdbc/sqlserver");
DataSource ds = (javax.sql.DataSource)obj;
Connection conn = ds.getConnection();
%>
<%//------得到总页数、总记录条数与当前页记录条数------
java.sql.Statement sql1=conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,
ResultSet.CONCUR_READ_ONLY);
java.sql.ResultSet rs1=null;
rs1=sql1.executeQuery(sqlStr2);
if(rs1!=null){
//---得到总页数、总记录条数与当前页记录条数---
rs1.next();
allRecordCount=rs1.getInt("allCount");
if(allRecordCount%pageSize==0)
pageCount=allRecordCount/pageSize;
else
pageCount=(int)(allRecordCount/pageSize)+1;
if(diPage==pageCount||pageCount==0)//最后一页或没有数据
pageRecordCount=allRecordCount-(diPage-1)*pageSize;
else
pageRecordCount=pageSize;
}
%>
<table border="1" CELLSPACING=0 CELLPADDING=0>
<tr><td colspan="3" align="center">
查看我的审批记录(共<%=allRecordCount%>条,
当前页<%=pageRecordCount%>条)
<%if(diPage!=1){//不是第一页
out.print("<a href='viewInfo1.jsp?diPage=1'>首页</a> ");
out.print("<a href='viewInfo1.jsp?diPage="+(diPage-1)+"'>上一页</a> ");
}
if(diPage!=pageCount&&pageCount!=0){//不是最后一页
out.print("<a href='viewInfo1.jsp?diPage="+(diPage+1)+"'>
下一页</a> ");
out.print("<a href='viewInfo1.jsp?diPage="+pageCount+"'>尾页</a> ");
}
%>
</td>
</tr>
<tr><td>审批事务标题</td>
<td>查看详细说明与审批记录?</td>
<td>审批?</td>
</tr>
<%//------得到当前面数据并显示------
java.sql.ResultSet rs2=null;
java.sql.Statement sql2=conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,
ResultSet.CONCUR_READ_ONLY);
rs2=sql2.executeQuery(sqlStr1);
if(rs2!=null){
while(rs2.next()){
String sqlStr3="select * from thing where thingId="+rs2.getLong("thingId");
java.sql.Statement sql3=conn.createStatement(ResultSet._
TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY);
java.sql.ResultSet rs3=null;
rs3=sql3.executeQuery(sqlStr3);
if(rs3.next()){
%>
<tr><td>[<%=rs3.getString("thingAddTime").substring(0,10)%>]
<%=rs3.getString("thingTitle")%></td>
<td><a href="#"
onclick="window.open('manRecord.jsp?thingId=<%=rs2.getLong("thingId")%>')">
查看</a></td>
<td><a href="thingToMe2.jsp?thingId=<%=rs2.getLong("thingId")%>&
thingArraId=<%=rs2.getLong("thingArraId")%>" target="_blank">审批
</a></td>
</tr>
<%}
}
}%>
<%
if(conn!=null)
conn.close();
%>
</table>
</body>
</html>
这个页面作了数据的分页处理。点击thingToMe1.jsp页面显示的界面中的记录后的“审批”超链接,会跳转么thingToMe2.jsp页面,可以修改所作的审批意见,如图14-15所示。
图14-15 修改审批意见
在“审批意见”输入框中输入审批的意见后按“提交审批”按钮,会将数据提交到thingToMe3.jsp页面。thingToMe2.jsp页面的源代码如下。
thingToMe2.jsp
<%@ page contentType="text/html;charset=GB2312" %>
<%@ page import="java.sql.*,javax.naming.*,javax.sql.DataSource" %>
<%//------得到数据库连接------
Context initCtx = new InitialContext();
Context ctx = (Context) initCtx.lookup("java:comp/env");
Object obj = (Object) ctx.lookup("jdbc/sqlserver");
DataSource ds = (javax.sql.DataSource)obj;
Connection conn = ds.getConnection();
%>
<%//------接收请求参数------
String thingId=request.getParameter("thingId");
String thingArraId=request.getParameter("thingArraId");
%>
<%//------如果接收数据有误------
if(thingId==null||thingArraId==null||
thingId.length()==0||thingArraId.length()==0)
response.sendRedirect("thingToMe1.jsp");
%>
<html>
<body bgcolor="#DCDADA">
当前位置:行政审批-->审批事务<br>
<table border="1" CELLSPACING=0 CELLPADDING=0 width="500">
<%//------查询出审批信息并显示------
String sqlStr1="select * from thing where thingId="+thingId;
java.sql.Statement sql1=conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,
ResultSet.CONCUR_READ_ONLY);
java.sql.ResultSet rs1=null;
rs1=sql1.executeQuery(sqlStr1);
if(rs1.next()){
%>
<tr><td align="right" width="200">审批事务标题:</td>
<td align="left"><%=rs1.getString("thingTitle")%></td>
</tr>
<tr><td align="right">审批内容详细说明:</td>
<td align="left"><%=rs1.getString("thingContent")%></td>
</tr>
<%}%>
</table><br>
<table border="1" CELLSPACING=0 CELLPADDING=0 width="500">
<form action="thingToMe3.jsp" method="post">
<tr><td>审批意见:</td>
<td><textarea name="content" rows="10" cols="54">
<%
String sqlStr2="select * from thingArra where thingArraId="+thingArraId;
java.sql.Statement sql2=conn.createStatement(ResultSet._
TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY);
java.sql.ResultSet rs2=null;
rs2=sql2.executeQuery(sqlStr2);
if(rs2.next())
if(rs2.getString("thingArraEnd")!=null)
out.print(rs2.getString("thingArraEnd"));%>
</textarea></td>
</tr>
<tr><td align="center" colspan="2">
<input type="hidden" value="<%=thingArraId%>" name="thingArraId">
<input type="hidden" value="<%=thingId%>" name="thingId">
<input type="submit" value="提交审批">
</td></tr>
</form>
</table>
</body>
</html>
<%
if(conn!=null){
conn.close();
}
%>
程序中使用了两个hidden输入项,一个是审批安排项的ID号,另一个是审批事务的ID号。接收数据的thingToMe3.jsp页面的源代码如下。
thingToMe3.jsp
<%@ page contentType="text/html;charset=GB2312" %>
<%@ page import="java.sql.*,javax.naming.*,javax.sql.DataSource" %>
<html>
<body bgcolor="#DCDADA">
当前位置:行政审批-->审批事务<br>
<%//------接收请求参数------
String thingArraId=request.getParameter("thingArraId");
String thingId=request.getParameter("thingId");
String content=request.getParameter("content");
%>
<%//------如果接收数据有误------
if(thingArraId==null||content==null||
thingArraId.length()==0||content.length()==0)
response.sendRedirect("thingToMe2.jsp?thingId="+thingId+
"&thingArra?Id="+thingArraId);
%>
<%//------构造将数据插入到数据库的SQL语句-----
String sqlStr=new String("update thingArra set thingArraEnd=?"
+" where thingArraId=?");
%>
<%//------得到数据库连接------
Context initCtx = new InitialContext();
Context ctx = (Context) initCtx.lookup("java:comp/env");
Object obj = (Object) ctx.lookup("jdbc/sqlserver");
DataSource ds = (javax.sql.DataSource)obj;
Connection conn = ds.getConnection();
%>
<%//------将数据更新到数据库------
PreparedStatement preSQLupdate=conn.prepareStatement(sqlStr);
content=new String(content.getBytes("ISO-8859-1"));
preSQLupdate.setString(1,content);
preSQLupdate.setString(2,thingArraId);
int i=preSQLupdate.executeUpdate();
if(i==0)//更新失败
response.sendRedirect("thingToMe2.jsp?thingId="+thingId+
"&thingArra?Id="+thingArraId);
else
out.print("审批成功!");
if(preSQLupdate!=null)
preSQLupdate.close();
if(conn!=null)
conn.close();
%>
</body>
</html>
“增加新用户”的功能只有系统管理员才能使用。点击系统操作界面左边的系统功能菜单中的“用户管理”→“增加新用户”,即可出现如图14-16所示的界面。
图6-16 增加新用户
要求输入新建的用户的用户名,为保证正确,密码需要输入两次,且两次输入的密码相同,再输入真实的姓名和用户所属的部门。“增加新用户”功能页面addUser1.jsp的源代码如下。
addUser1.jsp
<%@ page contentType="text/html;charset=GB2312" %>
<%@ page import="java.sql.*,javax.naming.*,javax.sql.DataSource" %>
<!------系统交互javascript----->
<script language="JavaScript">
<!--
function checkData() {
if(form1.oaUserName.value.length==0){
alert("用户名不能为空");
return false;
}
if(form1.oaUserPassword.value.length==0){
alert("密码不能为空");
return false;
}
if(form1.rePassowrd.value.length==0){
alert("再次输入的密码不能为空");
return false;
}
if(form1.oaUserPassword.value!=form1.rePassowrd.value){
alert("两次输入的密码不相同");
return false;
}
if(form1.oaUserTrueName.value.length==0){
alert("用户真实姓名不能为空");
return false;
} else return true;
}
-->
</script>
<html>
<body bgcolor="#DCDADA">
当前位置:用户管理-->增加新用户<br>
<table border="1" CELLSPACING=0 CELLPADDING=0>
<form name="form1" action="addUser2.jsp" method="post"
onsubmit="return checkData()">
<tr><td align="center" colspan="2">
增加一名新的系统用户
</td></tr>
<tr><td align="right">用户名:</td>
<td align="left">
<input type="text" size="36" name="oaUserName">(*)
</td></tr>
<tr><td align="right">密码:</td>
<td align="left">
<input type="password" size="40" name="oaUserPassword">(*)
</td></tr>
<tr><td align="right">再输入一次密码:</td>
<td align="left">
<input type="password" size="40" name="rePassowrd">(*)
</td></tr>
<tr><td align="right">用户真实姓名:</td>
<td align="left">
<input type="text" size="36" name="oaUserTrueName">(*)
</td></tr>
<tr><td align="right">所属部门:</td>
<td align="left">
<select name="department">
<%//------查询出部门数据------
//得到数据库连接
Context initCtx = new InitialContext();
Context ctx = (Context) initCtx.lookup("java:comp/env");
Object obj = (Object) ctx.lookup("jdbc/sqlserver");
DataSource ds = (javax.sql.DataSource)obj;
Connection conn = ds.getConnection();
//SQL查询语句
String sqlStr="select * from department";
//通过查询得到记录集
java.sql.Statement sql=conn.createStatement();
ResultSet rs=sql.executeQuery(sqlStr);
//显示数据
while(rs.next()){
if(rs.getInt("departmentId")!=1)//如果不系统管理员
out.print("<option value=\""+rs.getInt("departmentId")+"\""+
">"+rs.getString("departmentName")+"</option>");
}
if(rs!=null)
rs.close();
if(conn!=null)
conn.close();
%>
</select>(*)
</td></tr>
<tr><td align="center" colspan="2">
<input type="submit" value="提交">
</td></tr>
</form>
</table>
</body>
</html>
程序中用JavaScript作了基本的数据校验。按“提交”按钮后数据会被提交到addUser2.jsp页面。addUser2.jsp页面的源代码如下。
addUser2.jsp
<%@ page contentType="text/html;charset=GB2312" %>
<%@ page import="java.sql.*,javax.naming.*,javax.sql.DataSource" %>
<html>
<body bgcolor="#DCDADA">
当前位置:用户管理-->增加新用户<br>
<%//------接收请求参数------
String oaUserName=request.getParameter("oaUserName");
String oaUserPassword=request.getParameter("oaUserPassword");
String oaUserTrueName=request.getParameter("oaUserTrueName");
String department=request.getParameter("department");
%>
<%//------如果接收数据有误------
if(oaUserName==null||oaUserPassword==null||
oaUserTrueName==null||department==null||
oaUserName.length()==0||oaUserPassword.length()==0||
oaUserTrueName.length()==0||department.length()==0)
response.sendRedirect("addUser1.jsp");
%>
<%//------构造将数据插入到数据库的SQL语句-----
String sqlStr=new String("insert into oaUser(oaUserName,oaUserPassword,"+
"oaUserTrueName,departmentId) values(?,?,?,?)");
%>
<%//------得到数据库连接------
Context initCtx = new InitialContext();
Context ctx = (Context) initCtx.lookup("java:comp/env");
Object obj = (Object) ctx.lookup("jdbc/sqlserver");
DataSource ds = (javax.sql.DataSource)obj;
Connection conn = ds.getConnection();
%>
<%//------将数据插入到数据库------
PreparedStatement preSQLinsert=conn.prepareStatement(sqlStr);
oaUserName=new String(oaUserName.getBytes("ISO-8859-1"));
preSQLinsert.setString(1,oaUserName);
oaUserPassword=new String(oaUserPassword.getBytes("ISO-8859-1"));
preSQLinsert.setString(2,oaUserPassword);
oaUserTrueName=new String(oaUserTrueName.getBytes("ISO-8859-1"));
preSQLinsert.setString(3,oaUserTrueName);
preSQLinsert.setInt(4,Integer.parseInt(department));
int i=preSQLinsert.executeUpdate();
if(i==0)//插入失败
response.sendRedirect("addUser1.jsp");
else
out.print("增加新用户成功!");
if(preSQLinsert!=null)
preSQLinsert.close();
if(conn!=null)
conn.close();
%>
</body>
</html>
以上程序先接收传来的数据,如果参数不为空则构造SQL语句,再作插入处理。考虑到用户名、密码和用户真实姓名三个数据中可能会有中文字符,故用getBytes("ISO-8859-1")方法作了字符编码转换,以保证数据的正确性。
<body bgcolor="#DCDADA">
<br><br><br><br><br>
<jsp:useBean id="dateTest" class="java.util.Date"/>
现在时间是:
<%=new Date()%>
<br>
欢迎访问希赛网络办公自动化系统!<br>
本系统供希赛图书的作者和读者使用,版权所有,违者必究!
</body>
</html>
“修改用户信息”只有系统管理员才能使用。点击系统操作界面左边的系统功能菜单中的“用户管理”→“修改用户信息”,即可出现如图14-17所示的界面。
图14-17 修改用户信息
这个modiUser1.jsp页面的界面显示了系统已有的当前页的用户信息。modiUser1.jsp页面的源代码如下。
modiUser1.jsp
<%@ page contentType="text/html;charset=GB2312" %>
<%@ page import="java.sql.*,javax.naming.*,javax.sql.DataSource" %>
<html>
<body bgcolor="#DCDADA">
当前位置:用户管理-->修改用户信息<br>
<%int pageSize=10;//每页显示的记录条数
int allRecordCount=0;//记录总条数
int pageRecordCount=0;//当前页记录条数
int pageCount=0;//总页数
int diPage=1;//当前页码
//------接收请求参数------
try{
diPage=Integer.parseInt(request.getParameter("diPage"));
}catch(Exception e){
diPage=1;
}
%>
<%//------构造查询的SQL语句------
//sqlStr1为查询到当前页数据的SQL语句
//sqlStr2为查询到总记录条数的SQL语句
String sqlStr1="select top "+diPage*pageSize+" * from oaUser where "+
"oaUserId not in (select top "+(diPage-1)*pageSize+" oaUserId from"+
" oaUser order by oaUserId asc) order by oaUserId asc";
String sqlStr2="select count(*) as allCount from oaUser";
%>
<%//------得到数据库连接------
Context initCtx = new InitialContext();
Context ctx = (Context) initCtx.lookup("java:comp/env");
Object obj = (Object) ctx.lookup("jdbc/sqlserver");
DataSource ds = (javax.sql.DataSource)obj;
Connection conn = ds.getConnection();
%>
<%//------得到总页数、总记录条数与当前页记录条数------
java.sql.Statement sql1=conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,
ResultSet.CONCUR_READ_ONLY);
java.sql.ResultSet rs1=null;
rs1=sql1.executeQuery(sqlStr2);
if(rs1!=null){
//---得到总页数、总记录条数与当前页记录条数---
rs1.next();
allRecordCount=rs1.getInt("allCount");
if(allRecordCount%pageSize==0)
pageCount=allRecordCount/pageSize;
else
pageCount=(int)(allRecordCount/pageSize)+1;
if(diPage==pageCount||pageCount==0)//最后一页或没有数据
pageRecordCount=allRecordCount-(diPage-1)*pageSize;
else
pageRecordCount=pageSize;
}
%>
<table border="1" CELLSPACING=0 CELLPADDING=0>
<tr><td colspan="6" align="center">
用户信息(共<%=allRecordCount%>条,当前页<%=pageRecordCount%>条)
<%if(diPage!=1){//不是第一页
out.print("<a href='modiUser1.jsp?diPage=1'>首页</a> ");
out.print("<a href='modiUser1.jsp?diPage="+(diPage-1)+"'>_
上一页</a> ");
}
if(diPage!=pageCount&&pageCount!=0){//不是最后一页
out.print("<a href='modiUser1.jsp?diPage="+(diPage+1)+"'>_
下一页</a> ");
out.print("<a href='modiUser1.jsp?diPage="+pageCount+"'>尾页</a> ");
}
%>
</td>
</tr>
<tr><td>用户ID号</td><td>用户名</td>
<td>用户真实姓名</td><td>用户所属部门</td>
<td>修改?</td><td>删除?</td>
</tr>
<%//------得到当前面数据并显示------
java.sql.ResultSet rs2=null;
java.sql.Statement sql2=conn.createStatement(ResultSet.
TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY);
rs2=sql2.executeQuery(sqlStr1);
if(rs2!=null){
while(rs2.next()){%>
<tr><td><%=rs2.getLong("oaUserId")%></td>
<td><%=rs2.getString("oaUserName")%></td>
<td><%=rs2.getString("oaUserTrueName")%></td>
<td>
<%//------查询出部门名称并显示------
String sqlStr3="select * from department where departmentId="+
rs2.getInt("departmentId");
java.sql.Statement sql3=conn.createStatement(ResultSet.
TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY);
java.sql.ResultSet rs3=sql3.executeQuery(sqlStr3);
if(rs3!=null){
rs3.next();
out.print(rs3.getString("departmentName"));
}
%>
</td>
<td><a href="modiUser2.jsp?oaUserId=<%=rs2.getLong("oaUserId")%>">
修改</a></td>
<td><a href="delUser.jsp?oaUserId=<%=rs2.getLong("oaUserId")%>">
删除</a></td>
</tr>
<%
}
}%>
<%
if(conn!=null)
conn.close();
%>
</table>
</body>
</html>
程序中作了分页处理。表格中的每一行对应着oaUser表中的一条记录。每条记录后有两个超链接。“删除”超链接用于删除用户。“修改”超链接用于修改用户的信息。点击“删除”超链接后,跳转到delUser.jsp页面,并传入oaUserId参数,即用户的ID号。delUser.jsp页面的源代码如下。
delUser.jsp
<%@ page contentType="text/html;charset=GB2312" %>
<%@ page import="java.sql.*,javax.naming.*,javax.sql.DataSource" %>
<html>
<body bgcolor="#DCDADA">
当前位置:用户管理-->删除用户<br>
<%//------接收请求参数------
String oaUserId=request.getParameter("oaUserId");
%>
<%//------如果接收数据有误------
if(oaUserId==null||oaUserId.length()==0)
response.sendRedirect("modiUser1.jsp");
%>
<%//------构造将数据插入到数据库的SQL语句-----
String sqlStr=new String("delete from oaUser where oaUserId="+oaUserId);
%>
<%//------得到数据库连接------
Context initCtx = new InitialContext();
Context ctx = (Context) initCtx.lookup("java:comp/env");
Object obj = (Object) ctx.lookup("jdbc/sqlserver");
DataSource ds = (javax.sql.DataSource)obj;
Connection conn = ds.getConnection();
%>
<%//------将数据插从数据库中删除------
java.sql.Statement sql=conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,
ResultSet.CONCUR_UPDATABLE);
int i=sql.executeUpdate(sqlStr);
if(i==0){//删除失败
response.sendRedirect("modiUser1.jsp");
}else{
out.print("删除用户成功!");
}
if(conn!=null)
conn.close();
%>
</body>
</html>
在图14-17所示的界面中点击每条记录后的“修改”超链接,将显示如图14-18所示的界面。
图14-18 修改系统用户信息
在这个页面中根据上一个页面传来的oaUserId参数查找数据库中的oaUser表将用户的信息填充在表单的相应输入项中,修改后,按“提交”按钮即可完成修改工作。图14-17的界面所表示的页面为modiUser2.jsp,源代码如下。
modiUser2.jsp
<%@ page contentType="text/html;charset=GB2312" %>
<%@ page import="java.sql.*,javax.naming.*,javax.sql.DataSource" %>
<!------系统交互javascript----->
<script language="JavaScript">
<!--
function checkData() {
if(form1.oaUserName.value.length==0){
alert("用户名不能为空");
return false;
}
if(form1.oaUserPassword.value.length==0){
alert("密码不能为空");
return false;
}
if(form1.oaUserTrueName.value.length==0){
alert("用户真实姓名不能为空");
return false;
} else return true;
}
-->
</script>
<html>
<body bgcolor="#DCDADA">
当前位置:用户管理-->修改用户信息<br>
<%//------接收请求参数------
String oaUserId=request.getParameter("oaUserId");
%>
<%//------如果接收数据有误------
if(oaUserId==null||oaUserId.length()==0)
response.sendRedirect("modiUser1.jsp");
%>
<%//------构造将数据插入到数据库的SQL语句-----
String sqlStr=new String("select * from oaUser where oaUserId="+oaUserId);
%>
<%//------得到数据库连接------
Context initCtx = new InitialContext();
Context ctx = (Context) initCtx.lookup("java:comp/env");
Object obj = (Object) ctx.lookup("jdbc/sqlserver");
DataSource ds = (javax.sql.DataSource)obj;
Connection conn = ds.getConnection();
%>
<%//------查询数据------
java.sql.Statement sql1=conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,
ResultSet.CONCUR_READ_ONLY);
ResultSet rs1=sql1.executeQuery(sqlStr);
if(rs1!=null){
rs1.next();
%>
<table border="1" CELLSPACING=0 CELLPADDING=0>
<form name="form1" action="modiUser3.jsp" method="post" onsubmit="return
checkData()">
<input type="hidden" name="oaUserId" value="<%=rs1.getString("oaUserId")%>">
<tr><td align="center" colspan="2">
修改系统用户信息
</td></tr>
<tr><td align="right">用户名:</td>
<td align="left">
<input type="text" size="36" name="oaUserName"
value="<%=rs1.getString("oaUserName")%>">(*)
</td></tr>
<tr><td align="right">密码:</td>
<td align="left">
<input type="password" size="40" name="oaUserPassword"
value="<%=rs1.getString("oaUserPassword")%>">(*)
</td></tr>
<tr><td align="right">用户真实姓名:</td>
<td align="left">
<input type="text" size="36" name="oaUserTrueName"
value="<%=rs1.getString("oaUserTrueName")%>">(*)
</td></tr>
<tr><td align="right">所属部门:</td>
<td align="left">
<select name="department">
<%//------查询出部门数据------
//SQL查询语句
sqlStr="select * from department";
//通过查询得到记录集
java.sql.Statement sql2=conn.createStatement();
ResultSet rs2=sql2.executeQuery(sqlStr);
//显示数据
while(rs2.next()){
if(rs2.getInt("departmentId")!=1)//如果不系统管理员
if(rs1.getInt("departmentId")!=rs2.getInt("departmentId"))
out.print("<option value=\""+rs2.getInt("departmentId")+
"\">"+rs2.getString("departmentName")+"</option>");
else
out.print("<option value=\""+rs2.getInt("departmentId")+
"\" selected>"+rs2.getString("departmentName")+"</option>");
}
%>
</select>(*)
</td></tr>
<tr><td align="center" colspan="2">
<input type="submit" value="提交">
</td></tr>
</form>
</table>
<%}%>
<%
if(conn!=null)
conn.close();
%>
</body>
</html>
按“提交”按钮后,数据会被提交到modiUser3.jsp页面,modiUser3.jsp页面的源代码如下。
modiUser3.jsp
<%@ page contentType="text/html;charset=GB2312" %>
<%@ page import="java.sql.*,javax.naming.*,javax.sql.DataSource" %>
<html>
<body bgcolor="#DCDADA">
当前位置:用户管理-->修改用户信息<br>
<%//------接收请求参数------
String oaUserId=request.getParameter("oaUserId");
String oaUserName=request.getParameter("oaUserName");
String oaUserPassword=request.getParameter("oaUserPassword");
String oaUserTrueName=request.getParameter("oaUserTrueName");
String department=request.getParameter("department");
%>
<%//------如果接收数据有误------
if(oaUserName==null||oaUserPassword==null||
oaUserTrueName==null||department==null||
oaUserId==null||
oaUserName.length()==0||oaUserPassword.length()==0||
oaUserTrueName.length()==0||department.length()==0||
oaUserId.length()==0)
response.sendRedirect("modiUser1.jsp");
%>
<%//------构造将数据插入到数据库的SQL语句-----
String sqlStr=new String("update oaUser set oaUserName=?,"+
"oaUserPassword=?,oaUserTrueName=?,departmentId=? where "+
"oaUserId=?");
%>
<%//------得到数据库连接------
Context initCtx = new InitialContext();
Context ctx = (Context) initCtx.lookup("java:comp/env");
Object obj = (Object) ctx.lookup("jdbc/sqlserver");
DataSource ds = (javax.sql.DataSource)obj;
Connection conn = ds.getConnection();
%>
<%//------将数据更新到数据库------
PreparedStatement preSQLupdate=conn.prepareStatement(sqlStr);
oaUserName=new String(oaUserName.getBytes("ISO-8859-1"));
preSQLupdate.setString(1,oaUserName);
oaUserPassword=new String(oaUserPassword.getBytes("ISO-8859-1"));
preSQLupdate.setString(2,oaUserPassword);
oaUserTrueName=new String(oaUserTrueName.getBytes("ISO-8859-1"));
preSQLupdate.setString(3,oaUserTrueName);
preSQLupdate.setInt(4,Integer.parseInt(department));
preSQLupdate.setInt(5,Integer.parseInt(oaUserId));
int i=preSQLupdate.executeUpdate();
if(i==0)//更新失败
response.sendRedirect("modiUser1.jsp");
else
out.print("更新用户数据成功!");
if(conn!=null)
conn.close();
%>
</body>
</html>
这个页面首先是接收传的数据,再构造SQL语句,再作数据更新操作。
“修改您的密码”功能任一登录的用户都可以使用,功能为修改当前用户的登录密码。点击系统操作界面左边的系统功能菜单中的“用户管理”→“修改您的密码”,即可出现如图14-19所示的界面。
图14-19 修改当前用户密码
这个界面为modiPass1.jsp页面的显示效果。modiPass1.jsp页面的源代码如下。
modiPass1.jsp
<%@ page contentType="text/html;charset=GB2312" %>
<!------系统交互javascript----->
<script language="JavaScript">
<!--
function checkData() {
if(form1.oaUserPassword.value.length==0){
alert("原密码不能为空");
return false;
}
if(form1.newPassword.value.length==0){
alert("新密码不能为空");
return false;
}
if(form1.reNewPassword.value.length==0){
alert("再次输入的新密码不能为空");
return false;
}if(form1.reNewPassword.value!=form1.newPassword.value){
alert("两次输入的密码不同");
return false;
} else return true;
}
-->
</script>
<html>
<body bgcolor="#DCDADA">
当前位置:用户管理-->修改当前用户密码<br>
<table border="1" CELLSPACING=0 CELLPADDING=0>
<form name="form1" action="modiPass2.jsp" method="post" onsubmit="return
checkData()">
<tr><td align="center" colspan="2">
修改当前用户密码
</td></tr>
<tr><td align="right">用户名:</td>
<td align="left">
<%=session.getAttribute("oaUserName")%>
</td></tr>
<tr><td align="right">原密码:</td>
<td align="left">
<input type="password" size="30" name="oaUserPassword">(*)
</td></tr>
<tr><td align="right">新密码:</td>
<td align="left">
<input type="password" size="30" name="newPassword">(*)
</td></tr>
<tr><td align="right">再输入一次新密码:</td>
<td align="left">
<input type="password" size="30" name="reNewPassword">(*)
</td></tr>
<tr><td align="center" colspan="2">
<input type="submit" value="提 交">
</td></tr>
</form>
</table>
</body>
</html>
程序中要求用户输入原密码,再输入两次新的密码。输入完后,按“提交”按钮即会用JavaScript语句作数据合法性检查,要求输入的密码不能为空,且两次输入的新密码要相同。如果数据检查通过,再将数据提交到modiPass2.jsp页面。modiPass2.jsp页面的源代码如下。
modiPass2.jsp
<%@ page contentType="text/html;charset=GB2312" %>
<%@ page import="java.sql.*,javax.naming.*,javax.sql.DataSource" %>
<html>
<body bgcolor="#DCDADA">
当前位置:用户管理-->修改当前用户密码<br>
<%//------session检查------
if(session.getAttribute("oaUserId")==null){
response.sendRedirect("login.jsp");
}else{
//------接收请求参数------
String newPassword=request.getParameter("newPassword");
String oaUserPassword=request.getParameter("oaUserPassword");
//------如果接收数据有误------
if(newPassword==null||newPassword.length()==0)
response.sendRedirect("modiPass1.jsp");
//------构造更新数据库的SQL语句-----
String sqlStr=new String("update oaUser set oaUserPassword=?"+
" where oaUserId=? and oaUserPassword=?");
//------得到数据库连接------
Context initCtx = new InitialContext();
Context ctx = (Context) initCtx.lookup("java:comp/env");
Object obj = (Object) ctx.lookup("jdbc/sqlserver");
DataSource ds = (javax.sql.DataSource)obj;
Connection conn = ds.getConnection();
//------更新数据------
PreparedStatement preSQLUpdate=conn.prepareStatement(sqlStr);
newPassword=new String(newPassword.getBytes("ISO-8859-1"));
preSQLUpdate.setString(1,newPassword);
preSQLUpdate.setLong(2,Long.parseLong(session.getAttribute("oaUserId").toString()));
oaUserPassword=new String(oaUserPassword.getBytes("ISO-8859-1"));
preSQLUpdate.setString(3,oaUserPassword);
int i=preSQLUpdate.executeUpdate();
if(i==0)//更新失败
response.sendRedirect("modiPass1.jsp");
else
out.print("更新当前用户密码成功!");
if(conn!=null)
conn.close();
}
%>
</body>
</html>
“公司部门管理”功能只有系统管理员才能使用,用于设置公司部门这一基础数据。点击系统操作界面左边的系统功能菜单中的“用户管理”→“公司部门管理”,即可出现如图14-20所示的界面。
图14-20 公司部门管理
这个界面中主要有两个表格。上面的表格用于增加一个部门时输入新的部门名称;下面的表格显示已有的部门,但不显示“系统管理员”这个部门,因为是系统内置的,不能删除。这个页面department.jsp的源代码如下。
department.jsp
<%@ page contentType="text/html;charset=GB2312" %>
<%@ page import="java.sql.*,javax.naming.*,javax.sql.DataSource" %>
<html>
<body bgcolor="#DCDADA">
当前位置:用户管理-->公司部门管理<br>
<table border="1" CELLSPACING=0 CELLPADDING=0>
<form name="form1" action="addDepartment.jsp" method="post" onsubmit="return
checkData()">
<tr><td align="left" colspan="3">
增加一个部门
</td></tr>
<tr><td align="right">请输入新的部门名称:</td>
<td align="left"><input type="text" size="30" name="departmentName"></td>
<td align="left"><input type="submit" value="提 交"></td>
</tr>
</form>
</table><br>
<table border="1" CELLSPACING=0 CELLPADDING=0 width=440>
<tr><td align="center">部门ID号</td><td align="center">部门名称</td>
<td align="center">删除?</td></tr>
<%
//------查询数据库的SQL语句------
String sqlStr="select * from department";
//------得到数据库连接------
Context initCtx = new InitialContext();
Context ctx = (Context) initCtx.lookup("java:comp/env");
Object obj = (Object) ctx.lookup("jdbc/sqlserver");
DataSource ds = (javax.sql.DataSource)obj;
Connection conn = ds.getConnection();
//------查询出数据------
java.sql.Statement sql1=conn.createStatement(ResultSet._
TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY);
java.sql.ResultSet rs1=null;
ResultSet rs=sql1.executeQuery(sqlStr);
while(rs.next()){
if(rs.getInt("departmentId")!=1){//不是系统管理员
%>
<tr><td align="center"><%=rs.getInt("departmentId")%></td>
<td align="center"><%=rs.getString("departmentName")%></td>
<td align="center">
<a href="delDepartment.jsp?departmentId=<%=rs.getInt("departmentId")%>">
删除</a></td></tr>
<%}
}
%>
</table><br>
</body>
</html>
如果是增加新的部门,输入部门名称,按“提交”按钮后,数据会被提交到addDepartment.jsp页面。addDepartment.jsp页面的源代码如下。
addDepartment.jsp
<%@ page contentType="text/html;charset=GB2312" %>
<%@ page import="java.sql.*,javax.naming.*,javax.sql.DataSource" %>
<html>
<body bgcolor="#DCDADA">
当前位置:用户管理-->增加新的公司部门<br>
<%//------接收请求参数------
String departmentName=request.getParameter("departmentName");
%>
<%//------如果接收数据有误------
if(departmentName==null||departmentName.length()==0)
response.sendRedirect("department.jsp");
%>
<%//------构造将数据插入到数据库的SQL语句-----
String sqlStr=new String("insert into department(departmentName) values(?)");
%>
<%//------得到数据库连接------
Context initCtx = new InitialContext();
Context ctx = (Context) initCtx.lookup("java:comp/env");
Object obj = (Object) ctx.lookup("jdbc/sqlserver");
DataSource ds = (javax.sql.DataSource)obj;
Connection conn = ds.getConnection();
%>
<%//------将数据插入到数据库------
PreparedStatement preSQLinsert=conn.prepareStatement(sqlStr);
departmentName=new String(departmentName.getBytes("ISO-8859-1"));
preSQLinsert.setString(1,departmentName);
int i=preSQLinsert.executeUpdate();
if(i==0)//插入失败
response.sendRedirect("department.jsp");
else
out.print("增加新的公司部门成功!");
if(preSQLinsert!=null)
preSQLinsert.close();
if(conn!=null)
conn.close();
%>
</body>
</html>
在图14-20所示的界面中,点击某条记录后的“删除”超链接,即会跳转到delDepartment.jsp页面,并传入departmentId参数,即部门ID号。delDepartment.jsp页面的源代码如下。
delDepartment.jsp
<%@ page contentType="text/html;charset=GB2312" %>
<%@ page import="java.sql.*,javax.naming.*,javax.sql.DataSource" %>
<html>
<body bgcolor="#DCDADA">
当前位置:用户管理-->删除公司部门信息<br>
<%//------接收请求参数------
String departmentId=request.getParameter("departmentId");
%>
<%//------如果接收数据有误------
if(departmentId==null||departmentId.length()==0)
response.sendRedirect("department.jsp");
%>
<%//------构造将数据插入到数据库的SQL语句-----
String sqlStr=new String("delete from department where departmentId="+departmentId);
%>
<%//------得到数据库连接------
Context initCtx = new InitialContext();
Context ctx = (Context) initCtx.lookup("java:comp/env");
Object obj = (Object) ctx.lookup("jdbc/sqlserver");
DataSource ds = (javax.sql.DataSource)obj;
Connection conn = ds.getConnection();
%>
<%//------将数据插从数据库中删除------
java.sql.Statement sql=conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,
ResultSet.CONCUR_UPDATABLE);
int i=sql.executeUpdate(sqlStr);
if(i==0){//删除失败
response.sendRedirect("department.jsp");
}else{
out.print("删除公司部门信息成功!");
}
if(conn!=null)
conn.close();
%>
</body>
</html>
本系统的代码都经过了仔细的调试,但如果要作为商业应用的软件还需要读者再作相当工作量的完善。本系统主要有如下的可以改进的地方:
(1)用户角色仅有系统管理员与普通用户两种,种类有限,不够丰富,系统管理员承担的职责过多。
(2)安全性有待提高。如后台管理中的所有页面都要作会话检查,构造SQL语句使用“?”来传递参数,而不要构造静态的SQL语句,以防止SQL注入攻击等。
(3)功能还不够强大,仅有消息管理、审批管理、用户管理等模块,办公自动化系统的许多常见的功能还没有,如邮件服务、公文流转等。
(4)没有采用框架技术,业务逻辑也没有进行封装,所有的业务逻辑都在JSP页面中实现,因此代码的可读性和可维护性都比较差。
根据具体的使用场合的情况,可能还有很多需要改进的内容,读者可以根据实际情况完善需求,改造程序以形成新的版本,也可以和本书的作者联系沟通,提出改进意见。
第15章 基于Struts+JSTL改进办公自动化系统
【本章专家知识导学】
在第14章中实现的系统采用了动态页面这种单一而又简单的系统架构,没有采用JavaBean,也没有采用Struts这样的框架技术,所有的业务逻辑都在JSP页面中,这样Java代码和HTML代码、JavaScript代码夹杂在一起,因此代码的可读性和可维护性都比较差。这一章中将采用Struts框架技术,在JSP页面中辅以JSTL技术,将达到如下几个目标:
1.用Struts框架来架设系统的整体架构,将表示逻辑、业务逻辑、数据验证逻辑清晰的分开来。
2.在JSP页面中结合JSTL和Struts标签,使JSP页面中不出现一句Java代码。
15.1 架构设计
系统的整体架构如图15-1所示。
图15-1 系统的整体架构图
这个图与Strtus框架技术的原理图基本相似。JSP页面用于展现信息,用到的技术有JSP、HTML、JavaScript、JSTL、Struts HTML标签。JSP主要用到了JSP的动作指令、page语句以;JavaScript用于作交互时的数据检查、页面特效等需要在客户端完成的一些功能;JSTL为标准标签库中的标签,用于动态展现数据;Sruts HTML标签用于表示HTML表单中的各个元素,这样就可以相应地建议ActionForm组件,以封装HTML表单中的数据,并在ActionForm组件编写代码实现对数据的合法性检查和校验。
业务逻辑主要位于Struts框架中的Action组件中。对于各种业务功能,如果有表单数据的,则先用ActionForm组件封装提交的表单并作数据检查,检查通过后才会提交到Action组件中;没有表单数据的,直接执行Action组件中的相关代码。
ActionForm组件和Action组件如果需要操作数据库中的数据,均采用JDBC连接数据库,并用SQL语句操作。
数据库中的数据结构保持第14章中的设计不变。
系统目录的设置情况如图15-2所示。
图15-2 系统目录的设置情况
WebRoot目录为应用的目录,下面有5个子目录。img目录下存放到系统用到的所有的图片文件;info目录下是系统信息中心的信息管理功能JSP页面;man目录下是系统的审批管理功能JSP页面;user目录下是用户管理功能JSP页面。
WEB-INF目录下的内容对用户来说是不可见的,有3个子目录和2个配置文件。web.xml配置文件是当前Web应用的配置文件;struts-config.xml是Struts框架的配置文件。WEB-INF目录下的classes下是所有类的字节码文件,action子目录中存放Action类,formBean子目录中存放ActionForm类。lib目录中存放到系统要用到的JAR组件包。tlds目录中存放了系统要用到的各种标签的tld文件。
(1)数据分页技术
由于在JSP页面中已有Java代码,数据分页的功能需要在Action类中完成,并把数据结果返回给JSP页面来显示。但Action类并不知道当前要显示哪一页的数据,因此需要稿Action类中传入页码。Action类中可以得到页面的request对象,以得到表单或QueryString中的参数值。
还是还有一个问题没有解决,向JSP页面中返回Result对象是可以,在JSP页面中也可以用Java语句循环输出Result对象中的数据。但是JSP页面中又出现Java语句了,而且JSTL中并没有提供循环输出Result对象的标签,怎么办呢?办公自动化系统中采用的办法是在在Action类中将Result对象转换成相应的object二维数组,然后再在JSP页面中用<c:forEach>标签输出数据。
实现数据分页的具体代码请参见本章的后续内容。
(2)Struts框架技术
Struts框架技术的基本原理在本书的前面章节中已有详细的解说,在办公自动化系统中将有表单的页面的表单数据对应封装为一个ActionForm类,并在ActionForm类中作数据验证,如果通过验证才会提交到作业务处理的Action类。表单中的数据项相同的页面,则可共用一个ActionForm类,甚至可以共用一个Action类。
Struts的配置信息均写在struts-config.xml文件中,这个文件的内容如下。
struts-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<struts-config>
<!--formBean组件配置-->
<form-beans>
<form-bean name="loginFormBean" type="formBean.loginFormBean"/>
<form-bean name="addInfoFormBean" type="formBean.addInfoFormBean"/>
<form-bean name="addThingFormBean" type="formBean.addThingFormBean"/>
<form-bean name="addArraFormBean" type="formBean.addArraFormBean"/>
<form-bean name="arraMeFormBean" type="formBean.arraMeFormBean"/>
<form-bean name="addUserFormBean" type="formBean.addUserFormBean"/>
<form-bean name="modiUserFormBean" type="formBean.modiUserFormBean"/>
<form-bean name="modiPassFormBean" type="formBean.modiPassFormBean"/>
<form-bean name="addDepartFormBean" type="formBean.addDepartFormBean"/>
</form-beans>
<!--============action-mappings配置============-->
<action-mappings>
<!-- ====用户管理模块====-->
<!-- 登录系统-->
<action path="/checkLogin" name="loginFormBean"
type="action.loginAction"
validate="true" input="/user/login.jsp">
</action>
<!-- 进入增加一个新用户页面-->
<action path="/enterAddUser"
type="action.enterAddUserAction">
</action>
<!-- 增加一个新用户-->
<action path="/addUser" name="addUserFormBean"
type="action.addUserAction"
validate="true" input="/user/addUser1.jsp">
<forward name="addUserSuccessJSP" path="/user/addUser2.jsp"/>
</action>
<!-- 显示用户列表-->
<action path="/viewUserList"
type="action.viewUserListAction">
</action>
<!-- 进入修改用户信息页面-->
<action path="/viewUserInfo"
type="action.viewUserInfoAction">
</action>
<!-- 修改用户信息-->
<action path="/modiUser" name="modiUserFormBean"
type="action.modiUserAction"
validate="true" input="/user/modiUser2.jsp">
<forward name="modiUserSuccessJSP" path="/user/modiUser3.jsp"/>
</action>
<!-- 删除一个用户-->
<action path="/delUser"
type="action.delUserAction">
<forward name="delUserSuccessJSP" path="/user/delUser.jsp"/>
</action>
<!-- 修改用户密码-->
<action path="/modiPass" name="modiPassFormBean"
type="action.modiPassAction"
validate="true" input="/user/modiPass1.jsp">
<forward name="modiPassSuccessJSP" path="/user/modiPass2.jsp"/>
<forward name="modiPassJSP" path="/user/modiPass1.jsp"/>
</action>
<!-- 进入公司部门管理页面-->
<action path="/viewDepartList"
type="action.viewDepartListAction">
</action>
<!-- 增加一个部门-->
<action path="/addDepart" name="addDepartFormBean"
type="action.addDepartAction"
validate="true" input="/user/department.jsp">
</action>
<!-- 删除一个部门-->
<action path="/delDepart"
type="action.delDepartAction">
</action>
<!-- ====消息管理模块====-->
<!-- 查看信息列表-->
<action path="/viewInfo"
type="action.viewInfoAction">
</action>
<!-- 查看具体信息-->
<action path="/viewInfo2"
type="action.viewInfoAction2">
</action>
<!-- 删除一条信息-->
<action path="/delInfo"
type="action.delInfoAction">
<forward name="delInfoJSP" path="/info/delInfo.jsp"/>
</action>
<!-- 增加一条信息-->
<action path="/addInfo" name="addInfoFormBean"
type="action.addInfoAction"
validate="true" input="/info/addInfo1.jsp">
<forward name="addInfo1JSP" path="/info/addInfo1.jsp"/>
<forward name="addInfo2JSP" path="/info/addInfo2.jsp"/>
</action>
<!-- ====审批事务管理模块====-->
<!-- 增加一个审批事务-->
<action path="/addThing" name="addThingFormBean"
type="action.addThingAction"
validate="true" input="/man/addThing1.jsp">
<forward name="addThing1JSP" path="/man/addThing1.jsp"/>
<forward name="addThing2JSP" path="/man/addThing2.jsp"/>
</action>
<!-- 查看审批事务列表-->
<action path="/viewThingList"
type="action.viewThingListAction">
</action>
<!-- 查看我的审批事务列表-->
<action path="/viewMeThingList"
type="action.enterThingToMeAction">
</action>
<!-- 删除一个审批事务-->
<action path="/delThing"
type="action.delThingAction" >
</action>
<!-- 查看具体的审批事务信息-->
<action path="/viewThing"
type="action.viewThingAction">
</action>
<!-- 安排审批-->
<action path="/thingArra"
type="action.thingArraAction">
</action>
<!-- 进入增加一个审批过程的页面-->
<action path="/enterAddArra"
type="action.enterAddArraAction">
</action>
<!-- 增加一个审批过程-->
<action path="/addArra" name="addArraFormBean"
type="action.addArraAction"
validate="true" input="/man/thingArra2.jsp">
</action>
<!-- 删除一个审批过程-->
<action path="/delArra"
type="action.delArraAction" >
</action>
<!-- 进入审批页面-->
<action path="/enterArraMe"
type="action.enterArraMeAction" >
<forward name="enterArraMeJSP" path="/man/thingToMe2.jsp"/>
</action>
<!-- 审批-->
<action path="/arraMe" name="arraMeFormBean"
type="action.arraMeAction"
validate="true" input="/man/thingToMe2.jsp">
<forward name="arraMeSuccessJSP" path="/man/thingToMe3.jsp"/>
</action>
</action-mappings>
<!-- ====消息资源配置====-->
<message-resources parameter="errors_zh"/>
<!-- ====全局转发配置====-->
<global-forwards>
<forward name="firstJSP" path="/index.jsp"/>
<forward name="viewInfoJSP" path="/info/viewInfo1.jsp"/>
<forward name="viewInfodo" path="/viewInfo.do"/>
<forward name="viewInfo2JSP" path="/info/viewInfo2.jsp"/>
<forward name="viewThingListJSP" path="/man/thingArra1.jsp"/>
<forward name="viewThingListdo" path="/viewThingList.do"/>
<forward name="viewThingJSP" path="/man/manRecord.jsp"/>
<forward name="enterAddArraJSP" path="/man/thingArra2.jsp"/>
<forward name="enterAddArrado" path="/enterAddArra.do"/>
<forward name="viewMeThingListdo" path="/viewMeThingList.do"/>
<forward name="viewMeThingListJSP" path="/man/thingToMe1.jsp"/>
<forward name="enterAddUserJSP" path="/user/addUser1.jsp"/>
<forward name="viewUserListJSP" path="/user/modiUser1.jsp"/>
<forward name="viewUserListdo" path="/viewUserList.do"/>
<forward name="modiUserJSP" path="/user/modiUser2.jsp"/>
<forward name="enterDepartJSP" path="/user/department.jsp"/>
<forward name="enterDepartdo" path="/viewDepartList.do"/>
</global-forwards>
</struts-config>
在后续的各个功能模块的详细解说中,可参考上述配置来找到相应的ActionForm类和Action类。办公自动化系统还用到了Struts框架中的HTML标签以及需要对action作出映射,需要在web.xml文件中作出配置。web.xml文件内容如下。
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app>
<!-- 标准actionServlet配置-->
<servlet>
<servlet-name>action</servlet-name>
<servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
<init-param>
<param-name>config</param-name>
<param-value>/WEB-INF/struts-config.xml</param-value>
</init-param>
<!-- 数字越低表明启动越早-->
<load-on-startup>2</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>action</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
<!--Web应用首页设置-->
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<!-- struts标签库相关配置-->
<taglib>
<taglib-uri>/WEB-INF/tlds/struts-html.tld</taglib-uri>
<taglib-location>/WEB-INF/tlds/struts-html.tld</taglib-location>
</taglib>
</web-app>
办公自动化系统中所有的报错信息都保存在一个“.properties”文件中,文件内容如下。
errors.propertyes
#错误提示文本
#---------------------------------------------
DBOPError=数据库操作失败!
#---------------------------------------------
oaUserNameIsNull=用户名输入为空!
oaUserPasswordIsNull=用户密码输入为空!
nameOrPassError=用户或密码输入有误!
passNotEqual=两次输入的密码不同!
oaUserTrueNameIsNull=用户真实姓名输入为空!
departmentIsNull=没有选择所属部门!
passwordError=密码有误!
departmentNameIsNull=部门名称输入为空!
#---------------------------------------------
infoTitleIsNull=信息标题输入为空!
infoContentIsNull=信息内容输入为空!
#---------------------------------------------
thingTitleIsNull=审批事务标题输入为空!
thingContentIsNull=审批事务内容输入为空!
#---------------------------------------------
thingIdIsNull=事务ID号为空!
oaUserIdIsNull=用户ID号为空!
#---------------------------------------------
thingContentIsNull=审批内容为空!
.properties文件中的中文文字要作转换,否则会被显示成乱码。转换中文文字的命令如下。
native2ascii -encoding gb2312 errors.properties >errors_zh.properties
转换后的代码如下。
errors_zh.properties
#\u9519\u8bef\u63d0\u793a\u6587\u672c
#---------------------------------------------
DBOPError=\u6570\u636e\u5e93\u64cd\u4f5c\u5931\u8d25\uff01
#---------------------------------------------
oaUserNameIsNull=\u7528\u6237\u540d\u8f93\u5165\u4e3a\u7a7a\uff01
oaUserPasswordIsNull=\u7528\u6237\u5bc6\u7801\u8f93\u5165\u4e3a\u7a7a\uff01
nameOrPassError=\u7528\u6237\u6216\u5bc6\u7801\u8f93\u5165\u6709\u8bef\uff01
passNotEqual=\u4e24\u6b21\u8f93\u5165\u7684\u5bc6\u7801\u4e0d\u540c\uff01
oaUserTrueNameIsNull=\u7528\u6237\u771f\u5b9e\u59d3\u540d\u8f93\u5165\u4e3a\u7a7a\uff01
departmentIsNull=\u6ca1\u6709\u9009\u62e9\u6240\u5c5e\u90e8\u95e8\uff01
passwordError=\u5bc6\u7801\u6709\u8bef\uff01
departmentNameIsNull=\u90e8\u95e8\u540d\u79f0\u8f93\u5165\u4e3a\u7a7a\uff01
#---------------------------------------------
infoTitleIsNull=\u4fe1\u606f\u6807\u9898\u8f93\u5165\u4e3a\u7a7a\uff01
infoContentIsNull=\u4fe1\u606f\u5185\u5bb9\u8f93\u5165\u4e3a\u7a7a\uff01
#---------------------------------------------
thingTitleIsNull=\u5ba1\u6279\u4e8b\u52a1\u6807\u9898\u8f93\u5165\u4e3a\u7a7a\uff01
thingContentIsNull=\u5ba1\u6279\u4e8b\u52a1\u5185\u5bb9\u8f93\u5165\u4e3a\u7a7a\uff01
#---------------------------------------------
thingIdIsNull=\u4e8b\u52a1ID\u53f7\u4e3a\u7a7a\uff01
oaUserIdIsNull=\u7528\u6237ID\u53f7\u4e3a\u7a7a\uff01
#---------------------------------------------
thingContentIsNull=\u5ba1\u6279\u5185\u5bb9\u4e3a\u7a7a\uff01
系统登录模块主要有3个页面:login.jsp、index.jsp,以及与之相关的配置、ActionForm组件、Action组件。
(1) 登录功能
login.jsp页面为登录页面,源代码如下。
login.jsp
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib uri="/WEB-INF/tlds/struts-html.tld" prefix="html"%>
<%@ page contentType="text/html;charset=GB2312" %>
<%@ page isELIgnored="false"%>
<html:html>
<body bgcolor="#DCDADA" topmargin="80">
<table border="0" CELLSPACING=0 CELLPADDING=0 align="center">
<tr><td align="center" colspan="2">
<IMG src="${pageContext.request.contextPath}/img/csai.gif">
</td></tr>
<html:form action="/checkLogin.do" method="post">
<tr><td align="center" colspan="2">
登录希赛OA系统
</td></tr>
<tr><td align="center" colspan="2">
<font color="red"><html:errors/></font>
</td></tr>
<tr><td align="right">用户名:</td>
<td align="left">
<html:text size="19" property="oaUserName"/>(*)
</td></tr>
<tr><td align="right">密码:</td>
<td align="left">
<html:password size="20" property="oaUserPassword"/>(*)
</td></tr>
<tr><td align="center" colspan="2">
<html:submit value="登录"/>
</td></tr>
</html:form>
</table>
</body>
</html:html>
这个页面中再也看不到Java代码了。数据校验工作在ActionForm类中完成。登录时输入用户名和密码的表单使用了Struts HTML标签中的<html:form>、<html:text>、<html:password>、<html:submit>。在struts-config.xml相关的配置有如下内容:
<?xml version="1.0" encoding="UTF-8"?>
<struts-config>
<!--formBean组件配置-->
<form-beans>
……
<form-bean name="loginFormBean" type="formBean.loginFormBean"/>
……
</form-beans>
<!--============action-mappings配置============-->
<action-mappings>
……
<!-- ====用户管理模块====-->
<!-- 登录系统-->
<action path="/checkLogin" name="loginFormBean"
type="action.loginAction"
validate="true" input="/user/login.jsp">
</action>
……
</action-mappings>
<!-- ====消息资源配置====-->
<message-resources parameter="errors_zh"/>
<!-- ====全局转发配置====-->
<global-forwards>
……
<forward name="firstJSP" path="/index.jsp"/>
……
</global-forwards>
</struts-config>
从以上配置可以看出,表单对应的ActionForm类为formBean.loginFormBean类;登录对应的Action类为action.loginAction;消息资源在errors_zh.properties文件中;当数据验证通过后,页面将重定向到index.jsp页面。
formBean.loginFormBean类的源代码如下。
loginFormBean.java
package formBean;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.servlet.http.HttpServletRequest;
import javax.sql.DataSource;
import org.apache.struts.action.ActionErrors;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.action.ActionMessage;
/**
* @author dengziyun
* 封装登录表单数据的FormBean
*/
public class loginFormBean extends ActionForm{
String oaUserName;//用户名
String oaUserPassword;//密码
/**
* 重置所有属性的值
*/
public void reset(ActionMapping arg0, HttpServletRequest request) {
oaUserName=null;
oaUserPassword=null;
}
/**
* 验证表单中的数据
*/
public ActionErrors validate(ActionMapping arg0, HttpServletRequest request) {
ActionErrors errors=new ActionErrors();
//------用户名非空校验------
if(oaUserName==null||oaUserName.trim().length()<1)
errors.add("oaUserNameIsNull",new ActionMessage("oaUserNameIsNull"));
//------密码非空校验------
if(oaUserPassword==null||oaUserPassword.trim().length()<1)
errors.add("oaUserPasswordIsNull",new
ActionMessage("oaUserPasswordIsNull"));
//------到数据库中检验------
try{
//------得到数据库连接------
Context initCtx = new InitialContext();
Context ctx = (Context) initCtx.lookup("java:comp/env");
Object obj = (Object) ctx.lookup("jdbc/sqlserver");
DataSource ds = (javax.sql.DataSource)obj;
Connection conn = ds.getConnection();
//------查询数据------
String sqlStr=new String("select * from oaUser where oaUsername=? "+
" and oaUserPassword=?");
PreparedStatement preSQLSelect=conn.prepareStatement(sqlStr,_
ResultSet.TYPE_SCROLL_SENSITIVE, _
ResultSet.CONCUR_READ_ONLY);
oaUserName=new String(oaUserName.getBytes("ISO-8859-1"));
preSQLSelect.setString(1,oaUserName);
oaUserPassword=new String(oaUserPassword.getBytes("ISO-8859-1"));
preSQLSelect.setString(2,oaUserPassword);
ResultSet rs=preSQLSelect.executeQuery();
if(rs.next()){//找到数据
request.getSession().setAttribute("oaUserName",_
rs.getString("oaUserName"));
request.getSession().setAttribute("oaUserTrueName",_
rs.getString("oaUserTrueName"));
request.getSession().setAttribute("oaUserId",rs.getString("oaUserId"));
request.getSession().setAttribute("departmentId",_
rs.getString("departmentId"));
}else{//找不到数据
errors.add("nameOrPassError",new ActionMessage("nameOrPassError"));
}
//------关闭数据库连接------
if(conn!=null)
conn.close();
}catch(Exception e){
errors.add("DBOPError",new ActionMessage("DBOPError"));
}
return errors;
}
public String getOaUserName() {
return oaUserName;
}
public void setOaUserName(String oaUserName) {
this.oaUserName = oaUserName;
}
public String getOaUserPassword() {
return oaUserPassword;
}
public void setOaUserPassword(String oaUserPassword) {
this.oaUserPassword = oaUserPassword;
}
}
在loginFormBean类中,有两个属性:oaUserName和oaUserPassword,这两个属性的名字和表单中的两个输入域的名字(见login.jsp页面中Struts HTML标签的property属性)相同,并均有setXxxx()方法和getXxxx()方法,以利于和JSP页面交互数据。在loginFormBean类的validate()方法中作数据校验,先是作用户名非空校验,再作密码非空校验,如果有误则往errors对象中加入错误消息,错误消息的文字从errors_zh.properties文件中去取,可是系统怎么知道到errors_zh.properties文件中去取呢?程序员并没有指出啊?这是因为在struts-config.xml的消息资源配置中就已经把errors_zh.properties文件配置成消息资源文件了,系统自然会知道从此文件中去取消息内容。
验证用户名和密码是否正确需要到数据库的oaUser表中去查找,一旦查找到记录表示验证通过,再通过如下的语句在session中记住用户名、用户真实姓名、用户ID号,用户所在的部门ID号,以方便其它程序使用。
request.getSession().setAttribute("oaUserName",rs.getString("oaUserName"));
request.getSession().setAttribute("oaUserTrueName",rs.getString("oaUserTrueName"));
request.getSession().setAttribute("oaUserId",rs.getString("oaUserId"));
request.getSession().setAttribute("departmentId",rs.getString("departmentId"));
在数据库的oaUser表中匹配记录时,构造了带“?”参数的SQL语句,在SQL查询时需要使用PreparedStatement对象,再用setString()方法设置参数的值。如果用户名或密码有误则往errors对象中加入消息,因为validate()方法返回的errors对象非null,Struts框架认为数据校验没有通过会自动回转到login.jsp,login.jsp页面中的<html:errors/>标签,即会输出errors对象中的错误消息。
SQL语句中使用带“?”参数的SQL语句的好处是可以防范SQL注入攻击,如果在login.jsp页面中的用户名和密码输入框中输入“1' or '1'='1”是不能登录成功的。
(2) 系统首页
系统登录成功后进入首页index.jsp,如图15-3所示。
图15-3 系统首页
首页index.jsp使用了框架技术,将页面分成三个部分:首部的框架中放置使用OA系统的企业的logo,系统的名称等;左边的框架中菜单是系统的功能菜单;右边的框架中是操作的具体内容,进入系统时显示的就是firstPage.jsp,此后作功能操作时显示的是具体功能操作的内容。
首页index.jsp的源代码如下。
index.jsp
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page contentType="text/html;charset=GB2312" %>
<%@ page isELIgnored="false"%>
<c:if test="${empty sessionScope.oaUserId}">
<c:redirect url="user/login.jsp"/>
</c:if>
<html>
<head>
<title>希赛网络办公自动化系统</title>
</head>
<frameset framespacing="1" rows="69,*" frameborder="0" name="all">
<frame name="banner" scrolling="no" noresize target="contents" src="banner.htm">
<frameset cols="178,*">
<frame name="menu" target="main" src="menu.jsp" marginwidth="0" marginheight="0" scrolling="auto" noresize>
<frame src="firstPage.jsp" name="main" scrolling="auto">
</frameset>
<noframes>
<body>
</body>
</noframes>
</frameset>
</html>
代码中首先做了session检查,看session变量oaUserId的值是否为null,如果为null表示尚未登录,将跳转到login.jsp页面。框架menu的target属性值设置为main,则表示目标框架为main,当点击框架menu中的超链接时,改变了框架main中包含的网页。
框架menu中的menu.jsp页面的源代码如下:
menu.jsp
<%@ page contentType="text/html;charset=GB2312" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page isELIgnored="false"%>
<html>
<head>
<SCRIPT LANGUAGE="JavaScript">
<!--
var bV=parseInt(navigator.appVersion);
var NS4=(document.layers) ? true : false;
var IE4=((document.all)&&(bV>=4))?true:false;
var ver4 = (NS4 || IE4) ? true : false;
function expandIt(){return}
function expandAll(){return}
function nomsg(){self.status="";}
if(ver4){
document.write("<SCRIPT LANGUAGE=\"JavaScript\" SRC=\"rsmenu.js\"></SCRIPT>");
}
//-->
</SCRIPT>
</head>
<body leftMargin="0" topMargin="0" marginheight="0" marginwidth="0" bgcolor="#DCDADA">
<span class="label"></span><br>
<!---------- begin OUTLINE ----------->
<!--上方全部展开/关闭-->
<DIV style="margin-left: 20px">
<FONT STYLE="font-size: 12pt">
您好,<%=session.getAttribute("oaUserTrueName")%><br>
操作功能菜单
</FONT></A>
</div>
<DIV ID="elOneParent" class="parent" style="margin-left: 20px">
<TABLE BORDER=0 CELLSPACING=0 CELLPADDING=0 WIDTH=101><tr>
<td class="label">
<A HREF="#" onClick="expandIt('elOne'); return false">
信息中心
</a></td></tr></table>
</DIV>
</table>
<!--相应的子菜单-->
<DIV ID="elOneChild" class="child" style="margin-left: 24px">
<TABLE BORDER=0 CELLSPACING=0 CELLPADDING=0 WIDTH=126>
<tr><td>
<A HREF="${pageContext.request.contextPath}/viewInfo.do" target="main">
<li>查看信息</A><BR>
<c:if test="${sessionScope.departmentId==1}">
<A HREF="info/addInfo1.jsp" target="main"><li>发布信息</A><BR>
</c:if>
</td></tr>
</table>
</DIV>
<DIV ID="elTwoParent" class="parent" style="margin-left: 20px">
<TABLE BORDER=0 CELLSPACING=0 CELLPADDING=0 WIDTH=101><tr><td class="label">
<A HREF="#" text-decoration:none;" onClick="expandIt('elTwo'); return false">
行政审批
</a></td></tr></table>
</DIV>
</table>
<!--相应的子菜单-->
<DIV ID="elTwoChild" class="child" style="margin-left: 24px">
<TABLE BORDER=0 CELLSPACING=0 CELLPADDING=0 WIDTH=126>
<tr><td>
<A HREF="man/addThing1.jsp" target="main"><li>发起审批事务</A><BR>
<c:if test="${sessionScope.departmentId==1}">
<A HREF="viewThingList.do" target="main"><li>安排审批事务</A><BR>
</c:if>
<A HREF="viewMeThingList.do" target="main"><li>待审批事务</A><BR>
</td></tr>
</table>
</DIV>
<!--父菜单-->
<DIV ID="elFiveParent" class="parent" style="margin-left: 20px">
<TABLE BORDER=0 CELLSPACING=0 CELLPADDING=0 WIDTH=101><tr><td class="label">
<A HREF="#" text-decoration:none;" onClick="expandIt('elFive'); return false">
用户管理
</a></td></tr></table>
</DIV>
</table>
<!--相应的子菜单-->
<DIV ID="elFiveChild" class="child" style="margin-left: 24px">
<TABLE BORDER=0 CELLSPACING=0 CELLPADDING=0 WIDTH=126>
<tr><td>
<c:if test="${sessionScope.departmentId==1}">
<A HREF="enterAddUser.do" target="main"><li>增加新用户</A><BR>
<A HREF="viewUserList.do" target="main"><li>修改用户信息</A><BR>
</c:if>
<A HREF="user/modiPass1.jsp" target="main"><li>修改您的密码</A><BR>
<c:if test="${sessionScope.departmentId==1}">
<A HREF="viewDepartList.do" target="main"><li>公司部门管理</A><BR>
</c:if>
</td></tr>
</table>
</DIV>
<!--父菜单-->
<DIV ID="elTwoParent" class="parent" style="margin-left: 20px">
<TABLE BORDER=0 CELLSPACING=0 CELLPADDING=0 WIDTH=101><tr><td class="label">
<A HREF="user/login.jsp" target="_parent">
退出系统
</a></td></tr></table>
</table>
</DIV>
<!---------- end OUTLINE ----------->
<br>
<SCRIPT LANGUAGE="JavaScript1.2">
<!--
if(NS4){
firstEl = "elOneParent";
firstInd = getIndex(firstEl);
showAll();
arrange();
}
//-->
</SCRIPT>
</body>
</html>
menu.jsp中显示的是系统的功能菜单。系统主要基于菜单来作权限控制。如果部门ID号不是1表示是普通用户,是1表示是系统管理员。普通用户不能使用发布消息、安排审批事务、增加新用户、修改用户信息、公司部门管理等功能。实际上就是根据如下的判断来实现的:
<c:if test="${sessionScope.departmentId==1}">
… …
</c:if>
main框架中第一个页面firstPage.jsp的源代码如下。
firstPage.jsp
<%@ page contentType="text/html;charset=GB2312" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ page isELIgnored="false"%>
<jsp:useBean id="newDate" class="java.util.Date"></jsp:useBean>
<html>
<body bgcolor="#DCDADA">
<br><br><br><br><br>
现在时间是:
<fmt:formatDate value="${newDate}"
pattern="yy'年'MM'月'dd'日'HH'时'mm'分'"/>
<br>
欢迎访问希赛网络办公自动化系统!<br>
本系统供希赛图书的作者和读者使用,版权所有,违者必究!
</body>
</html>
在信息中心模块中可以查看系统发布的各种信息,如会议通知、工作简讯等;可以发布信息,发布信息的功能由系统管理员来完成。
(1) 查看信息
查看信息的界面如图15-4所示。
图15-4 查看信息
查看信息功能页面viewInfo1.jsp作了分页处理。viewInfo1.jsp页面详细的源代码如下。
viewInfo1.jsp
<%@ page contentType="text/html;charset=GB2312" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<%@ page isELIgnored="false"%>
<html>
<body bgcolor="#DCDADA">
当前位置:信息中心-->查看信息<br>
<table border="1" CELLSPACING=0 CELLPADDING=0>
<tr><td colspan="3" align="center">
查看信息
(共${requestScope.allRecordCount}条,当前页${requestScope.pageRecordCount}条)
<c:if test="${requestScope.diPage!=1}">
<a href='viewInfo.do?diPage=1'>首页</a>
<a href='viewInfo.do?diPage=${requestScope.diPage-1}'>上一页</a>
</c:if>
<c:if test="${requestScope.diPage!=requestScope.pageCount&&requestScope.pageCount!=0}">
<a href='viewInfo.do?diPage=${requestScope.diPage+1}'>下一页</a>
<a href='viewInfo.do?diPage=${requestScope.pageCount}'>尾页</a>
</c:if>
</td>
</tr>
<tr><td>信息标题</td><td>查看?</td>
<c:if test="${sessionScope.departmentId!=null}">
<c:if test="${sessionScope.departmentId==1}">
<td>删除?</td>
</c:if>
</c:if>
</tr>
<c:forEach items="${requestScope.rsArray}" var="record">
<tr><td>[${fn:substring(record[3],0,10)}]${record[1]}</td>
<td><a href="#" onclick="window.open('viewInfo2.do?infoId=${record[0]}')">查看</a></td>
<c:if test="${sessionScope.departmentId!=null}">
<c:if test="${sessionScope.departmentId==1}">
<td><a href="delInfo.do?infoId=${record[0]}">删除</a></td>
</c:if>
</c:if>
</tr>
</c:forEach>
</table>
</body>
</html>
页面代码这么简单就作了分页处理?是的,因为分页的数据读取数据的功能都放在Action类中了,JSP页中要做的事情仅仅是显示当前页的数据。在menu.jsp菜单中点击“查看信息”超链接时,链接地址为:
${pageContext.request.contextPath}/viewInfo.do
在struts-config.xml文件中相关的配置为:
… …
<!-- ====消息管理模块====-->
<!-- 查看信息列表-->
<action path="/viewInfo" type="action.viewInfoAction">
</action>
… …
因此在点击“查看信息”超链接时,会在Action类中作出处理,查找出当前页的数据,并返回给JSP页面。Action类的代码如下。
viewInfoAction.java
package action;
import java.sql.Connection;
import java.sql.ResultSet;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.sql.DataSource;
import org.apache.struts.action.Action;
import org.apache.struts.action.ActionErrors;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.action.ActionMessage;
/**
* @author dengziyun
* 进入查看信息页面的的Action类
*/
public class viewInfoAction extends Action{
/**
* 在此方法中处理HTTP请求,并作响应
*/
public ActionForward execute(ActionMapping arg0, ActionForm arg1,
HttpServletRequest request, HttpServletResponse reponse) throws Exception {
int diPage=1;//当前页码
int pageSize=10;//每页显示的记录条数
int allRecordCount=0;//记录总条数
int pageRecordCount=0;//当前页记录条数
int pageCount=0;//总页数
ActionErrors errors=new ActionErrors();
String diPageStr=request.getParameter("diPage");
//------没有传来当前页码,则默认为1------
if(diPageStr==null||diPageStr.trim().length()<1)
diPage=1;
//------防止传来的数据有误,默认为1------
try{
diPage=Integer.parseInt(request.getParameter("diPage"));
}catch(Exception e){
diPage=1;
}
//------查询出数据,将查询结果数据集写入到response中------
try{
//------得到数据库连接------
Context initCtx = new InitialContext();
Context ctx = (Context) initCtx.lookup("java:comp/env");
Object obj = (Object) ctx.lookup("jdbc/sqlserver");
DataSource ds = (javax.sql.DataSource)obj;
Connection conn = ds.getConnection();
//------查询数据------
//------构造查询的SQL语句------
//sqlStr1为查询到当前页数据的SQL语句
//sqlStr2为查询到总记录条数的SQL语句
String sqlStr1="select top "+diPage*pageSize+" * from info where "+
"infoId not in (select top "+(diPage-1)*pageSize+" infoId from"+
" info order by infoId desc) order by infoId desc";
String sqlStr2="select count(*) as allCount from info";
//------得到总页数、总记录条数与当前页记录条数------
java.sql.Statement sql1=conn.createStatement(ResultSet._
TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY);
java.sql.ResultSet rs1=null;
rs1=sql1.executeQuery(sqlStr2);
if(rs1!=null){
//---得到总页数、总记录条数与当前页记录条数---
rs1.next();
allRecordCount=rs1.getInt("allCount");
if(allRecordCount%pageSize==0)
pageCount=allRecordCount/pageSize;
else
pageCount=(int)(allRecordCount/pageSize)+1;
if(diPage==pageCount||pageCount==0)//最后一页或没有数据
pageRecordCount=allRecordCount-(diPage-1)*pageSize;
else
pageRecordCount=pageSize;
}
//------得到当前面数据------
java.sql.ResultSet rs2=null;
java.sql.Statement sql2=conn.createStatement(ResultSet._
TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
rs2=sql2.executeQuery(sqlStr1);
Object[][] rsArray=new Object[pageRecordCount][4];
int i=0;
while(rs2.next()){
rsArray[i][0]=rs2.getString("infoId");
rsArray[i][1]=rs2.getString("infoTitle");
rsArray[i][2]=rs2.getString("infoContent");
rsArray[i][3]=rs2.getString("infoAddTime");
i++;
}
//------将所有数据写入到request------
request.setAttribute("diPage",new Integer(diPage));
request.setAttribute("pageSize",new Integer(pageSize));
request.setAttribute("allRecordCount",new Integer(allRecordCount));
request.setAttribute("pageRecordCount",new Integer(pageRecordCount));
request.setAttribute("pageCount",new Integer(pageCount));
request.setAttribute("rsArray",rsArray);
//------关闭数据库连接------
if(conn!=null)
conn.close();
}catch(Exception e){
e.printStackTrace();
errors.add("DBOPError",new ActionMessage("DBOPError"));
saveErrors(request,errors);
}
return arg0.findForward("viewInfoJSP");
}
}
从以上代码可以看出,在Action类中作了数据分页处理,返回的数据仅是当前页的数据,这样大大减少了数据流量,并且向JSP页面返回了diPage(当前页码)、pageSize(每页记录条数)、allRecordCount(总记录条数)、pageRecordCount(当前页记录条数)、pageCount(总页数)、rsArray(当前页数据二维数组),返回的方法是将这些数据写入到request对象中,这样在JSP页面中就可以用${requestScope.XXXX}表达式来得到值了,二维数组则可以<c:forEach>迭代来输出值。
(2) 发布信息模块
发布信息页面addInfo1.jsp的界面如图15-5所示。
addInfo1.jsp
图14-9 发布信息
信息标题和信息内容都是必输的内容,输入完成后,按“提交”按钮即可提交表单。发布信息页面addInfo1.jsp的源代码如下。
addInfo1.jsp
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib uri="/WEB-INF/tlds/struts-html.tld" prefix="html"%>
<%@ page contentType="text/html;charset=GB2312" %>
<%@ page isELIgnored="false"%>
<html:html>
<body bgcolor="#DCDADA">
当前位置:信息中心-->发布信息<br>
<table border="1" CELLSPACING=0 CELLPADDING=0>
<html:form action="/addInfo.do" method="post">
<tr><td align="center" colspan="2">
发布一条新的信息
</td></tr>
<tr><td align="center" colspan="2">
<font color="red"><html:errors/></font>
</td></tr>
<tr><td align="right">信息标题:</td>
<td align="left">
<html:text size="40" property="infoTitle"/>
</td></tr>
<tr><td align="right">信息内容:</td>
<td align="left">
<html:textarea property="infoContent" rows="10" cols="40"/>
</td></tr>
<tr><td align="center" colspan="2">
<html:submit value="提 交"/>
</td></tr>
</html:form>
</table>
</body>
</html:html>
上述代码中使用了Struts HTML标签,表单为<html:form>标签,输入信息的标题使用了<html:form>标签,输入信息内容使用了<html:textarea>标签。
struts-config.xml文件中相关的配置如下。
<?xml version="1.0" encoding="UTF-8"?>
<struts-config>
<!--formBean组件配置-->
<form-beans>
……
<form-bean name="addInfoFormBean" type="formBean.addInfoFormBean"/>
……
</form-beans>
<!--============action-mappings配置============-->
<action-mappings>
……
<!-- ====消息管理模块====-->
……
<!-- 增加一条信息-->
<action path="/addInfo" name="addInfoFormBean"
type="action.addInfoAction"
validate="true" input="/info/addInfo1.jsp">
<forward name="addInfo1JSP" path="/info/addInfo1.jsp"/>
<forward name="addInfo2JSP" path="/info/addInfo2.jsp"/>
</action>
……
</action-mappings>
<!-- ====消息资源配置====-->
<message-resources parameter="errors_zh"/>
… …
</struts-config>
可见对应的ActionForm类为addInfoFormBean,对应的Action类为addInfoAction。addInfoFormBean类的代码如下。
addInfoFormBean.java
package formBean;
import javax.servlet.http.HttpServletRequest;
import org.apache.struts.action.ActionErrors;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.action.ActionMessage;
/**
* @author dengziyun
* 封装输入信息表单数据的FormBean
*/
public class addInfoFormBean extends ActionForm{
String infoTitle;//信息标题
String infoContent;//信息内容
/**
* 重置所有属性的值
*/
public void reset(ActionMapping arg0, HttpServletRequest request) {
infoTitle=null;
infoContent=null;
}
/**
* 验证表单中的数据
*/
public ActionErrors validate(ActionMapping arg0, HttpServletRequest request) {
ActionErrors errors=new ActionErrors();
//------信息标题非空校验------
if(infoTitle==null||infoTitle.trim().length()<1)
errors.add("infoTitleIsNull",new ActionMessage("infoTitleIsNull"));
//------信息内容非空校验------
if(infoContent==null||infoContent.trim().length()<1)
errors.add("infoContentIsNull",new ActionMessage("infoContentIsNull"));
return errors;
}
public String getInfoContent() {
return infoContent;
}
public void setInfoContent(String infoContent) {
this.infoContent = infoContent;
}
public String getInfoTitle() {
return infoTitle;
}
public void setInfoTitle(String infoTitle) {
this.infoTitle = infoTitle;
}
}
这个ActionForm类中作了基本的数据校验工作,即非空校验。如果数据通过校验,则进入addInfoAction类,addInfoAction类代码如下。
addInfoAction.java
package action;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.sql.DataSource;
import org.apache.struts.action.Action;
import org.apache.struts.action.ActionErrors;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.action.ActionMessage;
/**
* @author dengziyun
* 增加信息的Action类
*/
public class addInfoAction extends Action{
/**
* 在此方法中处理HTTP请求,并作响应
*/
public ActionForward execute(ActionMapping arg0, ActionForm arg1,
HttpServletRequest request, HttpServletResponse reponse) throws Exception {
ActionErrors errors=new ActionErrors();
//------接收请求参数------
String title=request.getParameter("infoTitle");
String content=request.getParameter("infoContent");
//------构造SQL语句------
String sqlStr=new String("insert into info(infoTitle,infoContent"+
") values(?,?)");
//------插入数据------
try{
//------得到数据库连接------
Context initCtx = new InitialContext();
Context ctx = (Context) initCtx.lookup("java:comp/env");
Object obj = (Object) ctx.lookup("jdbc/sqlserver");
DataSource ds = (javax.sql.DataSource)obj;
Connection conn = ds.getConnection();
//------将数据插入到数据库------
PreparedStatement preSQLinsert=conn.prepareStatement(sqlStr);
title=new String(title.getBytes("ISO-8859-1"));
preSQLinsert.setString(1,title);
content=new String(content.getBytes("ISO-8859-1"));
preSQLinsert.setString(2,content);
int i=preSQLinsert.executeUpdate();
if(i==0)//插入失败
return arg0.findForward("addInfo1JSP");
//------关闭数据库连接------
if(conn!=null)
conn.close();
}catch(Exception e){
e.printStackTrace();
errors.add("DBOPError",new ActionMessage("DBOPError"));
saveErrors(request,errors);
System.out.println(e);
return arg0.findForward("addInfo1JSP");
}
return arg0.findForward("addInfo2JSP");
}
}
简单地说就这个Action类做的事情的过程就是是“得到request数据→构造insert SQL语句→执行insert SQL语句”。
限于篇幅,这里就不再一一详细列举出所有模块的源代码了,具体的实现读者可参见随书光盘中的内容,光盘中有完整的源代码。本章的前面所述内容已能体现出系统的关键技术,再辅以光盘中的代码,即可方便读者对Struts、JSTL技术的学习,接近项目实战,有兴趣的读者可自行对系统作些满足特定需求的改进,或和作者联系提出改进意见。
本章基于Struts和JSTL技术对第14章中实现的办公自动化系统作了全面的改进,用Struts框架来架设系统的整体架构,将表示逻辑、业务逻辑、数据验证逻辑清晰的分了开来,在JSP页面中结合了JSTL和Struts HTML标签,使JSP页面中不出现一句Java代码。
系统能给读者提供了接近真实工程的学习与练习环境。本书的配套光盘中有系统的所有源代码,以方便读者学习使用。