Presentation is loading. Please wait.

Presentation is loading. Please wait.

第五章. 服务器端开发技术- Servlet 对外经济贸易大学信息学院.

Similar presentations


Presentation on theme: "第五章. 服务器端开发技术- Servlet 对外经济贸易大学信息学院."— Presentation transcript:

1 第五章. 服务器端开发技术- Servlet 对外经济贸易大学信息学院

2 第五章. 服务器端开发技术- Servlet 第一节.Servlet概述 第二节.Servlet的开发环境及程序结构
第四节.会话跟踪 第五节.JDBC访问数据库

3 第一节 Servlet概述 1. Servlet的作用
Servlet技术是使用Java技术开发Web应用的基础,后面讲述的JSP也是在它的基础上发展起来的。Servlet技术支持创建动态的Web页面,即页面内容根据用户输入或其他变量数据而改变的Web页面(动态页面),这就极大地增强了应用开发的能力。 Servlet是一个基于Java语言实现的服务器端组件,它可以被用来扩充Web服务器的功能。Servlet可以简单地看作一个Java类。与一般类的不同之处是,这个类的开发有一些规范,而且最终该Java类运行在一个服务器端的Servlet容器内,通过容器提供会话管理和对象生命周期管理。

4 2. Servlet的优点 Servlet效率更高、更容易编程、功能更强、更安全和具有良好的可移植性,因而成为主流的动态网站技术。 Servlet最大的优势在于它的高性能,Servlet采用了和CGI不同的运行方式,一个Servlet在第一次初始化时装载并驻留在内存中,以后直接从内存中运行,不像CGI程序那样每次都要重新装入内存。其次,在默认情况下,Servlet以单实例多线程的方式工作,一个新请求到达后,Servlet实例开启一个新的线程来服务于这个请求,而不像CGI需要开启一个进程。Servlet继承了Java的所有优势,包括易升级以及平台无关性,在进行程序编写的时候可以调用Java所提供的大量的API。

5 3. Servlet的基本工作原理 一个Servlet在服务器端由称为Servelt容器(也有称为servlet引擎)的程序来解释执行,可以把Servlet理解成Server上的applet,Servlet在被调用后会被动态地载入到容器由容器解释执行,这样通过Servlet包含的功能就扩展了Web服务器的功能。Servlet可以在服务器端接收客户端的请求并给出响应,客户端的请求和Servlet的响应的典型通过HTTP协议来完成。一个客户端程序(Web浏览器或者其他的可以连接上Internet的程序)会访问Web服务器并发出请求,这个请求最终被运行在Web服务器上的Servlet容器接收,并交给Servlet处理,Servlet通过HTTP协议将它的响应通过容器转发到客户端。

6 Web服务器和Servlet容器可以是两个独立的松耦合的应用程序(图表5-2),也可以是紧密集成的一个应用程序(图表5-3)。从图表5-2和图表5-3显示了一个典型的基于Servlet和JSP(一种Servlet的变体)的Web应用程序,包括若干Servlet或者JSP文件,也可能包含许多HTML文件和图片等其他资源文件。客户端对于Servet的访问先到达Web Server,如果访问的是HTML文件和图片这样的静态资源文件,Web Server会直接处理并给出响应,而如果访问的是Servlet,那么通过Servlet容器去执行Servet并给出响应。浏览器或者其他应用程序一般通过HTTP协议访问Web Server。典型的,浏览器通过HTTP协议访问Web Server, Web Server返回给浏览器HTML格式的数据。而客户端应用程序可以通过HTTP协议访问服务器端的应用,服务器端的应用程序通过Web Server可以返回给客户端应用程序HTML格式的数据、XML格式的数据或者应用约定格式的数据。

7

8 第五章. 服务器端开发技术- Servlet 第一节.Servlet概述 第二节.Servlet的开发环境及程序结构
第四节.会话跟踪 第五节.JDBC访问数据库

9 第二节 Servlet的开发环境及程序结构
1.开发环境安装及测试 (1)开发环境概述 进行Servlet开发所需要的基本环境是JSDK(Java Servlet Development Kit)、一个支持Servlet的Web服务器、集成开发工具。集成开发工具提供了一个集成的开发环境,使用它可以编辑、编译和调试Servlet代码;JSDK包含了编译Servlet应用程序所需要的Java类库以及相关的文档;支持Servlet的Web服务器用于发布开发好的Servlet程序并进行测试。另外,如果Web应用程序需要访问数据库,那么需要安装数据库服务器(见JDBC访问数据库一节)。

10 开发工具有很多,比如JBuilder、Eclipse、Visual Cafe、 WebLogic Workshop、JRUN和Microsoft Visual J++等。在本书中采用了JBuilder进行编辑、编译和调试Servlet。 JSDK包含了编译Servlet应用程序所需要的Java类库以及相关的文档。一般安装完一个支持Servlet的Web Server后,他们都在自己的安装目录下有一个lib的目录,该目录下包含JSDK包,也可以去Sun公司的网站下载JSDK开发包。 常用的支持Servet的容器有Resin、Apache Tomcat、IBM Websphere、BEA WebLogic Server等等。在实际进行Java Web项目实施的时候,我们可以采用的商业java Web服务器有IBM WebSphere,Bea Web Logic 等。它们的功能齐全而强大,支持所有的java 服务容器标准,适合成品商业Web应用的发布。但是由于它们是商业服务器,价格昂贵,而且对系统资源要求极高。特别是配置复杂,开发的难度很大,尤其不适合初学者。本教材面向的是学校的教学,在选择开发软件环境的时候必须考虑学校和学生的实际条件。因为Resin对资源要求不高,配置比较容易,学校实验室和学生本人都有条件使用Resin。因此,在本书关于Servle和JSP的开发中,都将采用Resin 作为Web服务器。其下载Resin的网址如下:

11 (2) Resin的安装 安装Resin的步骤如下: 安装JDK 在安装Resin之前要先安装JDK(建议版本在1.4以上)。 拷贝Resin目录 安装完JDK之后如果要安装Resin,只需要把Resin文件夹 (下载后的Resin压缩文件展开后的文件夹) 拷贝到机器的特定目录下就可以了,不需要其他配置操作。 启动RESIN 拷贝完毕后,要启动RESIN直接执行httpd.exe即可。 测试 启动后,即可测试是否工作正常,方法是在浏览器里面输入

12 Resin安装完毕后的目录结构大致如下图:

13 从上图可以看出,Resin安装在d:\resin-2. 1. 16文件夹下,版本号是2. 1. 16。其中在子文件夹bin下的httpd

14 (3) Resin环境测试 安装好Resin后,我们可以启动Resin服务器,方式是执行httpd.exe即可,启动后屏幕显示结果如下图,仔细确认屏幕,如果其上没有出错提示,那么表示Resin已经启动。启动后屏幕显示结果 如下:

15 为验证Resin是否工作正常,我们需要打开浏览器测试一下,在地址栏输入

16 2.配置Web应用 一般一个Web应用发布时都会有一个自己发布区,这个发布区往往对应一个物理的目录,所有发布的html文件、图片文件、样式表、Servlet类、JSP文件和其他一些描述性文件等都要放到这个目录及其子目录下。Rein安装好后,我们需要做一些配置,才能达到这些目标,配置需要修改Resin服务器的配置文件。 (1)Resin的配置文件 Resin的配置文件是resin.conf,位于安装目录的conf目录下(比如D:\resin \conf)。resin.conf是一个XML格式的配置文件,这个配置文件由很多标记组成。其中有两个标记对非常重要,它们是<resource-ref>……</resource-ref>标记对和<http-server>……</http-server>标记对,配置经常需要修改它们。

17 <resource-ref>……</resource-ref>标记对
<resource-ref>……</resource-ref>标记是用来配置Resin所使用的资源,一般用来声明管理资源,如数据库驱动程序等。典型的是访问数据库的配置,下例是Resin中对于JDBC访问数据库的配置: <resource-ref> <res-ref-name>jdbc/test</res-ref-name> <res-type>javax.sql.DataSource</res-type> <init-param driver-name="com.MySql.jdbc.Driver"/> <init-param url="jdbc:MySql://localhost:3306/test?useUnicode=true&characterEncoding=gb2312"/> <init-param user="root"/> <init-param password=""/> <init-param max-connections="20"/> <init-param max-idle-time="30"/> </resource-ref> jdbc/test是外部使用

18 <http-server></http-server>标记对
在<http-server></http-server>标记对中的配 置和resin的Java Web 服务器有关。找到<web- app>,在<app-dir></app-dir>标记对里的id属性和 app-dir子标记非常重要。id属性表示该应用的 Web路径。如<web-app id=´/shopping´>,表示该 应用在Web上访问的时候应该用 /shopping/来访问。app-dir属性表示该应用的实际 路径。如: <app-dir>d:\resin\doc\test</app-dir> 表示该应用在d:\resin\doc\test目录下。

19 (2)配置自己的Web应用 假设我们有一个welcome.html的网页、一个counter.jsp的JSP文件和一个类名为RequestInfoExample.class的Servlet,现在要发布它们到一个支持Java的Web服务器上,访问的URL分别为:

20 修改Resin的配置文件 为了做到这点,需要修改Resin的配置文件resin.conf,在该配置文件中的<http-server></http-server>中需要增加一个和其他<web-app></web-app>并列的标记,指定id=‘/myweb’,同时在<web-app>的子标记中指定<app-dir>D:\resin \myweb</app-dir>,其他标记和值暂时忽略不变。该配置文件内容如下: <web-app id='/myweb'> <app-dir>D:\resin \myweb</app-dir> <classpath id='WEB-INF/classes' compile='false'/> <session-config> <session-max>4096</session-max> <session-timeout>30</session-timeout> <enable-cookies>true</enable-cookies> <enable-url-rewriting>true</enable-url-rewriting> </session-config> <path-mapping url-regexp='^/~([^/]*)' real-path='/home/$1/public_html/'/> </web-app> 修改配置文件后,一定记住要重新启动Resin,以便使新的修改被重新加载从而生效。

21 HTML网页的发布及测试 使用记事本或者网页编辑工具编辑如下html代码并保存到前面配置的D:\resin \myweb目录下,文件名为welcome.html。welcome.html内容如下: <html> <body> Welcome, have a nice day. </body> </html> 打开浏览器,在地址栏输入

22 JSP的发布及测试 同样,使用记事本或者网页编辑工具编辑如下JSP代码并保存到前面配置的D:\resin \myweb目录下,文件名为counter.jsp。counter.jsp,其内容如下: page contentType="text/html;charset=GBK" language="java" %> <% Integer count = null; synchronized (application) { count = (Integer) application.getAttribute("basic.counter"); if (count == null) count = new Integer(0); count = new Integer(count.intValue() + 1); application.setAttribute("basic.counter", count); } %> <html> <head><title>Counter</title></head> <body bgcolor=#ffffff> <h1>欢迎,访问次数: <%= count %></h1> </body> </html> 打开浏览器,在地址栏输入

23 3.第一个Servlet (1)编写及编译Servlet Java Servlet API是一个标准的Java扩展程序包,和Servlet相关的有javax.servlet和javax.servlet.http两个Java包。对于想开发基于客户自定义协议的开发者,应该使用javax.servlet包中的类与接口;对于仅利用HTTP协议与客户端进行交互的开发者,则只需要使用javax.servlet.http包中的类与接口进行开发即可。

24 Servlet的结构 下面一个例子RequestInfoExample.java打印前端请求参数,代码如下: import javax.servlet.*; import javax.servlet.http.*; import java.io.*; import java.util.*; public class RequestInfoExample extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { response.setContentType("text/html"); PrintWriter out = response.getWriter(); out.println("<html>"); out.println("<body>"); out.println("<head>"); out.println("<title>Request Information Example</title>"); out.println("</head>"); out.println("<h3>Request Information Example</h3>"); out.println("Method: " + request.getMethod()); out.println("<br>Request URI: " + request.getRequestURI()); out.println("<br>Protocol: " + request.getProtocol()); out.println("<br>PathInfo: " + request.getPathInfo()); out.println("<br>Remote Address: " + request.getRemoteAddr()); out.println("</body>"); out.println("</html>"); } public void doPost(HttpServletRequest request, HttpServletResponse response) doGet(request, response);

25 有关该servlet程序结构说明∶ 一般基于HTTP协议的servlet要引入javax.servlet和 javax.servlet.http包;HelloServlet从类HttpServlet 派生, HttpServlet为基于HTTP协议的servlet提供 了基本的支持; HttpServletRequest对象包含了客 户端请求的信息,可以通过该参数取得客户端的 一些信息(例如IP地址、浏览器类型等)以及HTTP 请求类型(例如GET、HEAD、POST、PUT等); HttpServletResponse对象用于完成Servlet与客户端 的交互,通过调用HttpServletResponse. getWriter 方法取得向客户端进行输出的输出流(PrintWriter的对象),向客户端发送HTML页面。编写了doGet方法,对于HTML POST 请求,调用Servlet 的doPost( )方法。

26 编译Servlet(RequestInfoExample.java)
利用Jbuilder对Servlet代码进行编译,在编译的时候一定记住要在Jbuilder中设置JSDK的路径,否则会找不到Servlet编译需要的类。编译后会生成RequestInfoExample.class类文件,该文件要发布到Resin服务器的myweb应用中,以便客户端调用。

27 (2)配置Resin以调用Servlet Servlet编译后要发布到Resin服务器的指定目录下,然后通过URL去调用,就象调用HTML文件和JSP文件一样。Servlet的发布稍微麻烦一些,主要有两个工作: 把类编译后的类文件拷贝到特定目录 如果要作为myweb应用的一部分发布,那么需要拷贝RequestInfoExample.class到D:\resin \myweb\WEB-INF\classes目录下。如果没有该目录,手工创建即可。 指定要发布的Servlet 需要在Resin配置文件resin.conf里指定要发布的Servlet ,包括要让客户端调用的Servlet,调用的映射地址,并且重新启动Resin。一般Resin的配置文件发生改动,或者发布的Servlet类有变化时都需要重新启动Resin。

28 为了调用RequestInfoExample Servlet, resin配置文件还针对myweb应用增
加下面黑体字的内容: <web-app id='/myweb'> <app-dir>D:\resin \myweb</app-dir> <servlet servlet-name='RequestInfoName' servlet-class='RequestInfoExample'/> <servlet-mapping url-pattern='/RequestInfo' servlet-name='RequestInfoName'/> <classpath id='WEB-INF/classes' compile='false'/> <session-config> <session-max>4096</session-max> <session-timeout>30</session-timeout> <enable-cookies>true</enable-cookies> <enable-url-rewriting>true</enable-url-rewriting> </session-config> <path-mapping url-regexp='^/~([^/]*)' real-path='/home/$1/public_html/'/> </web-app>

29 这段代码中的servlet这一段声明了你要调用的
Servlet的Java类是RequestInfoExample(servlet-class='RequestInfoExample',不带.class扩展名);同时指定它的逻辑名字是RequestInfoName(servlet-name= RequestInfoName),逻辑名字和类名不同(当然也可以相同);servlet-mapping声明逻辑名字为RequestInfoName的Servet访问时候可以通过地址/ RequestInfo访问,地址/RequestInfo和Servelt的类名可以相同也可以不同。 在上述配置下,可以使用如下URL去访问该Servlet:

30 可以看出配置文件中的url-pattern='/RequestInfo' 和<web-app id='/myweb'>共同决定了Servlet的调用URL(/myweb/RequestInfo),特别注意要在/RequestInfo前要加上应用的路径(/myweb)以及主机名及端口( 使用配置可以让URL中访问Servlet时候的路径和Servlet类的名字无关,这样即使Servlet的类的名字修改了,对外访问的URL也可以不变,这样带来了很大的灵活性。 当然,访问的时候,也可以直接通过类名或者逻辑名字访问Servlet,访问URL如下: (类名访问,访问方式2)

31 第五章. 服务器端开发技术- Servlet 第一节.Servlet概述 第二节.Servlet的开发环境及程序结构
第四节.会话跟踪 第五节.JDBC访问数据库

32 第三节 Servlet基础 1.Servlet的生命周期 一个Java servlet具有一个生命周期,这个生命周期定义了
一个Servlet如何被载入并被初始化,如何接收请求并作出对请求的响应,如何被从服务中清除。Servlet的生命周期被javax.servlet.Servlet这个接口所定义。 所有的Java Servlet都会直接地或间接地执行javax.servlet.Servlet接口,这样它才能在一个Servlet引擎中运行。Servlet引擎是Web 服务器按照Servlet API定制的扩展。Servlet引擎提供网络服务,能够理解MIME请求,并提供一个运行Servlet的容器。 理解并掌握Servlet的生命周期极为重要,只有理解了Servlet的生命周期里涉及的方法、方法的作用和方法的调用时机,才能掌握Servlet编程。javax.servlet.Servlet接口定义了在Servlet的生命周期中特定时间以及特定顺序被调用的方法(下图)。

33 (1)Servlet的解析、载入和初始化 Web容器(含Servlet引擎)在启动时或者在一个Servlet被请求时载入一个Servlet并对Servlet进行初始化,在这一过程中,Servlet可以读取一些固定存储的数据、初始化JDBC的连接以及建立与其他资源的连接。 在初始化过程中,javax.servlet.Servlet接口的init方法提供了Servlet的初始化信息,Servlet可以利用init方法对自己进行配置。 init方法获得了一个Servlet配置对象(ServletConfig)。这个对象在Servlet引擎中执行,并允许Servlet通过它获取相关参数。这个对象使得Servlet能够访Servlet环境对象(ServletContext),通过这个对象,Servlet从Servlet引擎获取环境信息。

34 (2)Servlet处理请求 Servlet被初始化之后,它可以处理来自客户端的请求,每一个来自客户端的请求都被描述成一个ServletRequest对象,而Servlet的响应被描述成一个ServletResponse对象。当客户端发出请求时,Servlet引擎传递给Servlet一个ServletRequest对象和一个ServletResponse对象,这两个对象作为参数传递到service()方法中。 (3)Servlet的卸载

35 当Servlet引擎决定卸载一个Servlet时(如引擎被关闭等),这个引擎必须允许Servlet释放正在使用的资源并存储有关资料。为了完成以上工作,引擎会调用Servlet的destroy()方法。 Servlet引擎可以自由的在任何时候使用或清除一个Servlet。卸载一个Servlet之前,Servlet引擎会等待所有的service()方法完成或超时结束。一个Servlet被卸载时,引擎将不会给Servlet发送任何请求。引擎必须释放Servlet并完成无用存储单元的收集。

36 2. Servlet相关接口和类 Servlet相关的接口和类共有3个: Servlet接口, GenericServlet类和HttpServlet类。其中Servlet接口定义了所有的Servlet的基本功能,作为一个接口,其他的Servlet(GenericServlet和HttpServlet)必须实现其中的方法。这些方法包括了Servlet的生命周期方法以及得到启动信息和自身描述信息的方法。GenericServlet抽象类实现了Servlet接口,定义了一个通用的,与协议无关的Servlet。HttpServlet定义了一个抽象类,它继承了GenericServlet抽象类,支持基于HTTP协议的Servlet,一般自写的Servlet都是为了处理HTTP请求的,所以应当继承HttpServlet类。

37 (1)Servlet接口 Servlet接口定义了所有的Servlet的基本功能,以及生命周期等方法。其方法见下面的内容:

38 public void init(ServletConfig config) throws ServletException;
Servlet引擎会在Servlet实例化之后,置入服务之前精确地调用init方法。在调用service方法之前,init方法必须成功退出。 public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException; Servlet引擎调用这个方法以允许Servlet响应请求。这个方法在Servlet未成功初始化之前无法调用。在Servlet被初始化之前,Servlet引擎能够封锁未决的请求。在一个Servlet对象被卸载后,直到一个新的Servelt被初始化,Servlet引擎不能调用这个方法。

39 public void destroy();
当一个Servlet被从服务中去除时,Servlet引擎调用这 个方法。在这个对象的service方法所有线程未全部退 出或者没被引擎认为发生超时操作时,destroy方法不 能被用。 public ServletConfig getServletConfig(); 返回一个ServletConfig对象,作为一个Servlet的开发 者,你应该通过init方法存储ServletConfig对象以便这 个方法能返回这个对象。GenericServlet在执行这个接 口时,已经这样做了。 public String getServletInfo(); 允许Servlet向主机的Servlet运行者提供有关它本身的 信息。返回的字符串应该是纯文本格式而不应有任何 标志(例如HTML,XML等)。

40 (2)GenericServlet类 GenericServlet 类是最基本的类之一,这个类的存在使得编写Servlet更加方便。程序员要编写Servlet 应用程序,一般要继承GenericServlet 类或它的子类HttpServlet 类。它提供了一个简单的方案来执行有关Servlet生命周期的方法以及在初始化时对ServletConfig对象和ServletContext对象进行说明。该类包含的方法如下:

41 init等生命周期方法的作用和前面介绍的Servlet接
时会使用。 public ServletConfig getServletConfig() 返回一个通过这个类的init方法产生ServletConfig 对象的说明。 public ServletContext getServletContext() 这是一个简便的途径,它将会调用ServletConfig 对象的同名的方法。

42 (3)HttpServlet类 HttpServlet 是从GenericServlet 继承而来,因此它具有GenericServlet 类似的方法和对象,HttpServlet是使用Servlet编程直接用到的类,它支持HTTP 的post 和 get 等方法。具体方法如下:

43 init() 方法的使用 在 Servlet 的生命期中,仅执行一次 init() 方法。它是在服务器装入 Servlet 时 执行的。 可以配置服务器,以在启动服务器或客户机首次访问 Servlet 时装 入 Servlet。 无论有多少客户机访问Servlet,都不会重复执行 init() 。 service() 方法使用 service() 方法是 Servlet 的核心。每当一个客户请求一个HttpServlet 对象,该 对象的service() 方法就要被调用,而且传递给这个方法一个“请求” (ServletRequest)对象和一个“响应”(ServletResponse)对象作为参数。 在 HttpServlet 中已经实现了 service() 方法,该方法会根据请求调用与 HTTP 请 求的方法相应的 doXXX 功能。 destroy() 方法 destroy() 方法仅执行一次,即在服务器停止且卸装Servlet 时执行该方法。典 型的,将 Servlet 作为服务器进程的一部分来关闭。缺省的 destroy() 方法通 常是符合要求的,但也可以覆盖它,典型的是管理服务器端资源。例如,如 果 Servlet 在运行时会累计统计数据,则可以编写一个 destroy() 方法,该方 法用于在未装入 Servlet 时将统计数字保存在文件中。另一个示例是关闭数 据库连接。

44 GetServletConfig()方法
GetServletConfig()方法返回一个ServletConfig 对 象,该对象用来返回初始化参数和ServletContext。 ServletContext 接口提供有关servlet 的环境信息。 GetServletInfo()方法 GetServletInfo()方法是一个可选的方法,它提供 有关servlet 的信息,如作者、版本、版权。

45 (4)使用HttpServlet类进行Servlet开发
创建一个 HTTP Servlet,通常涉及下列四个步骤: 扩展 HttpServlet 抽象类。 重载适当的方法。 如覆盖(或称为重写)service()方法或者doGet() 和doPost()方法。 获取信息 如果有 HTTP 请求信息的话,获取该信息。用请求对象(HttpServletRequest的对象,见后面HttpServletRequest的说明)来检索 HTML 表单所提交的数据或 URL 上的查询字符串。“请求”对象含有特定的方法以检索客户机提供的信息。 生成 HTTP 响应。 用响应对象(HttpServletResponse的对象,见后面的HttpServletResponse的说明)生成响应,并将它返回到发出请求的客户机上。

46 一个servlet样例(ServletSample.java)如下:
import java.io.*; import java.util.*; import javax.servlet.*; import javax.servlet.http.*; // 第一步: 扩展 HttpServlet 抽象类。 public class ServletSample extends HttpServlet { // 第二步:重写service方法 public void service (HttpServletRequest request, HttpServletResponse response) throws ServletException,IOException { System.out.println("in");//调试用,判断是否进入该函数 String myName = ""; // 第三步:获取HTTP 请求信息 myName = request.getParameter("myName"); if ((myName == "") || (myName==null)) myName = "guest"; // 第四步:生成 HTTP 响应。 PrintWriter out = response.getWriter(); response.setContentType("text/html;charset=GBK" ); out.println("<head><title>测试例子</title></head>"); out.println("<body>"); out.println("<h1>这是一个Servlet测试例子</h1>"); out.println ("<p>" + myName + " 你好, 这是一个仅给出HTML响应的Servlet例子."); out.println ( "<p>" + "请输入 out.println("</body></html>"); System.out.println("html ok"); //调试用,判断是否前面语句正常结束 out.flush(); }

47 上述ServletSample类继承了HttpServlet 抽象类,同时它覆盖了父类的service ()方法。在重写的service( )方法中,获取HTTP 请求中的一个任选的参数(myName)。然后设置设定响应的内容的类型,并从响应对象(response)获取PrintWriter对象(out),然后使用 PrintWriter 的 print() 和 println() 方法给请求的客户端程序响应一个HTML格式的内容。 将Servlet的类ServletSample发按照前面介绍的技术来发布到myweb应用中,需要调用 Servlet 或 Web 应用程序,可以使用下列任一种方法:由 URL 调用、在 <FORM> 标记中调用。

48 URL 调用 Servlet() 在发布Servlet的Resin服务器上打开浏览器,输入如下URL 如果在其他机器上,上述URL的localhost可以换成发布Servlet的Resin服务器的 主机名。 在 <FORM> 标记中指定 Servlet 可以在 <FORM> 标记中调用 Servlet。HTML 格式使用户能在 Web 页面(即 从浏览器)上输入数据,并向 Servlet 提交数据。例如: <html> <body> <FORM METHOD="get" ACTION=" "> <OL> <INPUT TYPE="radio" NAME="myName" VALUE="Zhang">Zhang Jun<BR> <INPUT TYPE="radio" NAME="myName" VALUE="Admin"> Administrator<BR> </OL>   <input type="submit" value="确定"> </FORM> </body> </html>

49 3.请求/响应类及接口 当一个servlet接收来自客户端的调用请求(通过 service,doXXX方法), 它接收两个对象: 一个是 ServletRequest和ServletResponse。ServletRequest类概括从 客户端到服务器之间的联系, 该接口 可以获取到这样一 些信息如由客户端传送的阐述名称,客户端使用的协议, 产 生请求并且接收请求的服务器远端主机名等。 ServletResponse类概括从servlet返回客户端的联系,用于 Servlet向客户发送响应。HttpServletRequest接口继承了 ServletRequest接口,他可以让 servlet获取更多的HTTP协议 特性数据。HttpServletResponse接口继承了ServletResponse 接口,它为Servlet返回HTTP回应给客户端提供了支持。下 图给出这四个接口里面定义的方法,接下来将介绍这些接 口里面的常用的基础方法,其他未介绍的方法请参考JSDK帮 助,要特别注意,对于父接口的方法介绍同样适用于子接 口。

50 四个接口里定义的方法

51 ServletRequest public String getParameter(String name) 以String对象返回指定的参数的值,如果这个参数不存在返回空值。例如,在一个HTTP Servlet中,这个方法会返回一个指定的查询语句产生的参数的值或一个被提交的表单中的参数值。如果一个参数名对应着几个参数值,这个方法只能返回通过getParameterValues方法返回的数组中的第一个值。因此,如果这个参数有(或者可能有)多个值,你只能使用getParameterValues方法,典型的如通过多选按钮提交Form。 public String[] getParameterValues(String name); 通过String对象的数组返回指定参数的值,如果这个参数不存在,该方法返回一个空值。 public Enumeration getParameterNames() 返回所有参数名的String对象列表,如果没有输入参数,该方法返回一个空值。 public void setAttribute(String name, Object object) 在请求中添加一个属性,这个属性可以被其他可以访问这个请求对象的对象使用。 public Object getAttribute(String name) 返回请求中指定属性的值,如果这个属性不存在,就返回一个空值。这个方法允许访问一些不提供给这个接口中其他方法的请求信息以及其他Servlet放置在这个请求对象内的数据。 public Enumeration getAttributeNames() 返回包含在这个请求中的所有属性名的列表。

52 (2) HttpServletRequest
该接口继承了ServletRequest接口,在ServletRequest接口的方法基础上,需要进一步掌握以下2个方法: public Cookie[] getCookies() 返回一个数组,该数组包含这个请求中当前的所有Cookie。如果这个请求中没有Cookie,返回一个空数组。 public HttpSession getSession() public HttpSession getSession(boolean create) 返回与这个请求关联的当前的有效的session。如果调用这个方法时没带参数,那么在没有session与这个请求关联的情况下,将会新建一个session。如果调用这个方法时带入了一个布尔型的参数,只有当这个参数为真时,session才会被建立。为了确保session能够被完全维持。Servlet开发者必须在响应被提交之前调用该方法。 getCookies和getSession方法分别用于支持Cookie和Session,在后面将详细介绍它们的使用。

53 (3) ServletResponse public PrintWriter getWriter throws IOException 这个方法返回一个PringWriter对象用来记录格式化的响应实体。如果要反映使用的字符编码,必须修改响应的MIME类型。在调用这个方法之前,必须设定响应的content类型。如果没有提供这样的编码类型,会抛出一个UnsupportedEncodingException。 public void setContentType(String type) 这个方法用来设定响应的content类型。这个类型以后可能会在另外的一些情况下被隐式地修改,这里所说的另外的情况可能当服务器发现有必要的情况下对MIME的字符设置。 为了保证成功地设定响应头的content类型,在响应被提交到输出流之前必须调用这个方法。 public String getCharacterEncoding() 返回MIME实体的字符编码。这个字符编码可以是指定的类型,也可以是与请求头域所反映的客户端所能接受的字符编码最匹配的类型。在HTTP协议中,这个信息被通过Accept-Charset传送到Servlet引擎。 public ServletOutputStream getOutputStream() throws IOException 返回一个记录二进制的响应数据的输出流。 public void setContentLength(int length) 设置响应的内容的长度,这个方法会覆盖以前对内容长度的设定。为了保证成功地设定响应头的内容长度,在响应被提交到输出流之前必须调用这个方法。

54 (4) HttpServletResponse
HttpServletResponse描述一个返回到客户端的HTTP回应。通过该接口可以利用HTTP协议规定的头信息。它继承了ServletResponse接口,在ServletResponse接口的方法基础上,需要进一步掌握以下几个方法: public void addCookie(Cookie cookie) 在响应中增加一个指定的Cookie。可多次调用该方法以定义多个Cookie。为了设置适当的头域,该方法应该在响应被提交之前调用。 public void sendRedirect(String location) throws IOException; 使用给定的路径,给客户端发出一个临时转向的响应。给定的路径必须是绝对URL。

55 (5) Servlet综合示例 要求达到的功能 : 用户在界面上输入的姓名(文本框)和选择要购买的商品(多选按钮)后点击提交按钮,提交给后台Servlet处理 Servlet对用户的点击的商品的价格给出响应,响应的内容为显示该用户的名字,同时显示用户购买的商品以及商品总价 假设页面上显示的商品包括MP3,DV,Radio,Pen,这4种商品的价钱在Servlet里面用变量维护。

56 HTML页面 : 要完成这样的功能,考虑使用一个HTML页面(commodity.html)完成商品选择和提交。HTML文件里面包含一个Form,Form里面包含一个文本框,同时包括多选按钮来显示已有4种商品供用户选择。页面外观参考如下:

57 Servlet : 另外,一个Servlet(CommodityServlet)接收Form提交的请求并进行处理,在该Servlet中获取用户输入的姓名,同时获取选择的商品,通过商品名查找该商品的价格,然后给用户响应,响应内容及格式参考下图。

58 commodity.html : <HTML> <body> <br> <center> <form action="/myweb/servlet/CommodityServlet" method="post"> <table width="50%" align="center" > <tr > <td width="35%" >名字</td> <td ><input type="text" name="name" size="20" ></td> </tr> <tr ><td colspan="2" >商品如下:</td></tr> <tr> <td ><input name="goods" type="checkbox" value="MP3">MP3</td> <td ><input name="goods" type="checkbox" value="DV"> DV</td> <td ><input name="goods" type="checkbox" value="Radio">收音机</td> <td ><input name="goods" type="checkbox" value="Pen">钢笔</td> </table> <input type="submit" name="submit" value="提交"> </form> </center> </body> </HTML> 注意commodity.html提交的两个参数为name和goods,其中goods可能会同时提交多个值。

59 import javax.servlet.*; import javax.servlet.http.*;
CommodityServlet.java : import java.io.*; import java.util.*; import javax.servlet.*; import javax.servlet.http.*; // 第一步: 扩展 HttpServlet 抽象类。 public class CommodityServlet extends HttpServlet { // 第二步:重写service方法 public void service (HttpServletRequest request, HttpServletResponse response)throws ServletException,IOException { String userName = ""; String inputCommodity[]=null; //用户提交的商品数组 double totalPrice=0; String[] commidty=null; //现有的商品数组 double[] price=null; //现有的商品对应的价格 commidty=new String[4]; price=new double[4]; commidty[0]="MP3"; price[0]=245; commidty[1]="DV"; price[1]=1225; commidty[2]="Radio"; price[2]=48; commidty[3]="Pen"; price[3]=120; PrintWriter out = response.getWriter(); // 第三步:获取HTTP 请求信息 userName = request.getParameter("name"); inputCommodity=request.getParameterValues("goods"); if ((userName == "") || (userName==null)) userName = "guest"; // 第四步:生成 HTTP 响应。 response.setContentType("text/html;charset=GBK" ); out.println("<head><title>商品汇总</title></head>"); out.println("<body>"); out.println("姓名:"+userName); if(inputCommodity==null) //如果没有选择商品就提示并结束方法执行 out.println("<br>请回去选择商品."); return; } out.println("<br>购买的商品如下:");

60 for(int i=0;i<inputCommodity.length;i++)
{ out.print("<br>"+inputCommodity[i]); for(int j=0;j<commidty.length;j++) { if (inputCommodity[i].equals(inputCommodity[j])) { totalPrice+=price[j]; out.println("\t\t单价 "+price[j]); break; } out.println("<br><hr>总价为"+totalPrice); out.println("</body></html>"); out.flush();

61 (6)汉字乱码处理 经常在访问一些JSP或者Servlet的程序 时,在浏览器一端会发现如下问题: 汉字都成了 ’?’; 浏览器中看到的 Servlet 页面中的汉字都成了乱码; JAVA 应用程序界面中的汉字都成了方块; Jsp/Servlet 页面无法显示 GBK 汉字; Jsp/Servlet 不能接收 form 提交的汉字; JSP/Servlet 数据库读写无法获得正确的内容。

62 要明白这些问题的原因,需要了解一些编码知识。英文字符一般是以一个字节来表示的,最常用的编码方法是 ASCII 。但一个字节最多只能表示128个字符(最高位没有使用),而汉字成千上万,所以现在都以双字节来表示汉字,为了能够与英文字符分开,每个字节的最高位一定为1,这样双字节最多可以表示64K个字符。我们经常碰到的编码方式有 GB2312、GBK、BIG5、UNICODE 等。 GB2312 码是中华人民共和国国家标准汉字信息交换用编码,是一个由中华人民共和国国家标准总局发布的关于简化汉字的编码,通行于中国大陆地区及新加坡,简称国标码。两个字节中,第一个字节(高字节)的值为区号值加32(20H),第二个字节(低字节)的值为位号值加32(20H),用这两个值来表示一个汉字的编码。GBK 是 GB2312 的扩展,它兼容GB2312。BIG5主要用于台湾等使用繁体汉字的地区。UNICODE 码是微软提出的解决多国字符问题的多字节等长编码,它对英文字符采取前面加“0”字节的策略实现等长兼容。如 “A” 的 ASCII 码为0x41,UNICODE 就为0x00,0x41。利用特殊的工具各种编码之间可以互相转换。 Java 编程语言默认的编码方式是 UNICODE,而我们通常使用的数据库及文件都是基于 GB2312 编码的。为此,在Java编程过程中,向网页输出中文字符串的时候(如用out.println(string), string 是含中文的字符串), 必须作 UNICODE 到 GBK 的转换。通过合理设置请求和响应的字符集可以解决乱码问题。

63 下面通过一个例子来掌握中文的处理。 import java.io.*; import javax.servlet.*; import javax.servlet.http.*; public class ChineseProcess extends HttpServlet { public void service (HttpServletRequest request, HttpServletResponse response) throws ServletException,IOException { String myName=null; PrintWriter out=response.getWriter(); //从HTTP请求的头信息中获取客户端的语言设置 String clientLanguage = request.getHeader("Accept-Language"); System.out.println("clientLanguage="+clientLanguage); //简体中文浏览器 if ( clientLanguage.equals("zh-cn") ) request.setCharacterEncoding("GBK"); response.setContentType("text/html; charset=GBK"); } //繁体中文浏览器 else if ( clientLanguage.equals("zh-tw") ) request.setCharacterEncoding("BIG5"); response.setContentType("text/html; charset=BIG5");

64 else if ( clientLanguage.equals("jp") )
{ request.setCharacterEncoding("SJIS"); response.setContentType("text/html; charset=SJIS"); } //缺省认为是英文浏览器 else { request.setCharacterEncoding("ISO "); response.setContentType("text/html; charset=ISO "); myName = request.getParameter("name"); if ((myName.equals("")) || (myName==null)) myName = "成吉思汗"; System.out.println("\n myName="+myName); out.println("<head><title>中文例子</title></head>"); out.println("<body>"); out.println("<h1>简单的中文(例子)</h1>"); out.println ("<p>收到请求name为" + myName);

65 在Servlet里读取请求和给出响应的时候,一般要先从HTTP请求的头信息中获取客户端的语言设置(request
在Servlet里读取请求和给出响应的时候,一般要先从HTTP请求的头信息中获取客户端的语言设置(request.getHeader(“Accept-Language”),然后根据客户端不同语言设置去设置请求对象request的字符编码(request.setCharacterEncoding(“GBK”))以及响应对象response的的内容类型中的字符集(response.setContentType(“text/html; charset=GBK”))。然后在浏览器输入URL为

66 第五章. 服务器端开发技术- Servlet 第一节.Servlet概述 第二节.Servlet的开发环境及程序结构
第四节.会话跟踪 第五节.JDBC访问数据库

67 第四节 会话跟踪 1.会话跟踪概述 HTTP是一个没有状态的协议。在客户端(浏览器)和服务器的一次典型的交互过程中,先由客户发出请求(请求中携带一些数据),请求及其数据通过网络传输到服务器,服务器读出数据并处理,进而会给出客户端处理的响应,响应中携带服务器返回给客户端的数据(对于浏览器客户端,一般将响应数据包含到页面中,返回给浏览器一个HTML格式的数据)。一次交互(一次请求和响应)结束后,伴随这次请求相关的数据全部失效,在后续的请求中无法使用。但是,在实际应用中,许多数据需要在多次交互中被共享和跟踪,多次交互密切相关,往往这多次交互共同完成一个实际的业务。一般将客户端和服务器的多次密切相关的交互称作一次会话,一次会话中包含一次或者多次请求和响应,这多次请求和响应之间可以共享数据,它们共同完成了特定的业务功能。许多Web应用,都需要识别一个连续的来自远端的客户机的请求。一个典型的例子如下: 在实现一个在线购物网站的时候,一般要实现一个购物车去保存客户的购物信息。当客户把商品加入购物车时(一次交互),服务器如何才能知道购物车里原先有些什么(前面的交互也把商品放到购物车里了),然后给客户一个累积的购物信息响应呢?当用户从选择商品的页面转到输入信用卡号和送达地址的页面时,服务器如何才能记住用户买了些什么呢? 针对这种需求,许多会话跟踪的技术被提出,如URL改写、隐藏表单域、Cookie和Session技术。

68 2. URL改写 URL 改写是把一些标识会话的数据(会话标识)附加到每个URL的后面,服务器每次处理请求的时候都要读取会话标识并把它和对应的会话数据关联起来。使用URL改写需要程序实现者在服务器端生成会话标识,并附加到返回给客户端的URL的后面。这样服务器端的程序要进行许多简单但单调冗长的处理。另外,还必须十分小心地保证每个URL后面都附加了必要的信息。 URL 是通过使用 HTTP GET 构造的,它可能包含几对参数和值。比如,用户标识1008(名字为zhangwei)的用户登录了服务器,服务器给该用户返回的网页的所有连接中都多包含两个请求参数(也可以包含更多的其他参数),格式如下:

69 其中,uid=1008&name=zhangwei用来标识该用户,每次该用户的请求都会多发送两个参数uid=1008&name=zhangwei,通过这两个参数可以检索该用户关联的其它数据。
URL 改写是一种好的解决方案,特别是在用户禁用Cookie(见后)的情况下,它是最佳解决方案,但要考虑到以下问题: 必须确保将信息附加到每个引用的网站的 URL; 附加参数带来了隐私问题,也许不希望所跟踪的实际数据是可见的; 用户可以离开会话并使用书签返回,会丢失会话信息。

70 3.隐藏表单域 HTML表单中可以包含下面这样的输入域:<input type="hidden" name="session" value="...">。这意味着,当表单被提交时,隐藏域的名字和数据也被包含到GET或POST数据里,我们可以利用这一机制来维持会话信息。然而,这种方法有一个很大的缺点,它要求所有页面都是动态生成的,因为整个问题的核心就是每个会话都要有一个唯一标识符。 隐藏式表单字段存储关于会话的信息。稍后可以使用 HTTPServletRequest 对象检索隐藏的数据。提交表单时,数据包括在 Get 或 Post 请求中。 由于只能在动态生成的页面上使用隐藏表单字段,所以它们的使用范围很有限。另外,人们可以通过查看 HTML 源码来看到隐藏表单字段所隐藏的数据,因此它有安全性漏洞。

71 4. Cookie Cookie 也是会话跟踪的最常用方法,Cookie是Web服务器发送给Web浏览器的内容较小的纯文本信息,以文本文件的形式存放在客户端的计算机中,这些Cookie一般用于保存用户的访问状态,当用户再次访问同一个网站的时候,浏览器会将这些Cookie信息发送回服务器,使得服务器能够恢复用户上一次的访问状态。使用Cookie的根本目的是为了在用户访问期间实现不同页面之间的数据传输,以解决HTTP无状态的问题。 Servlet API 提供了一个Cookie 类,封装了对Cookie 的一些操作。Servlet 可以创建一个新的Cookie,设置它的关键字、值及有效期等属性,然后把Cookie 设置在HttpServletResponse 对象中发回浏览器。

72 Cookie的工作原理 客户的HTTP 请求到达服务器。 服务器创建Cookie,并作为响应头域的一部分返回用户。 浏览器收到包含Cookie 的响应后,会把Cookie 的内容用“关键字/值” 对的形式写入到一个客户端专为存放Cookie 的文本文件中。 浏览器会把Cookie 及随后产生的请求发给相同的服务器, 服务器可以再次读取Cookie 中存放的Cookie 在程序实现的时候,可以对Cookie的有效期进行设置,过期的Cookie 浏览器不会再发送给服务器。

73 (2) Cookie类的方法

74 getComment/setComment 用来获取/设置Cookie的注释。 getName/setName
  getValue/setValue 获取/设置Cookie的值。如前所述,名字和值实际上是我们始终关心的两个方面。不过也有一些例外情况,比如把名字作为逻辑标记(也就是说,如果名字存在,则表示true)。 getMaxAge/setMaxAge 获取/设置Cookie过期之前的时间,以秒计。如果不设置该值(默认值为-1),则Cookie只在当前会话内有效,即在用户关闭浏览器之前有效,而且这些Cookie不会保存到磁盘上。参见下面有关LongLivedCookie的说明。 getComment/setComment 用来获取/设置Cookie的注释。 getName/setName 获取/设置Cookie的名字。本质上,名字和值是我们始终关心的两个部分。由于HttpServletRequest的getCookies方法返回的是一个Cookie对象的数组,因此通常要用循环来访问这个数组查找特定名字,然后用getValue检查它的值。 getPath/setPath 获取/设置Cookie适用的路径。如果不指定路径,Cookie将返回给当前页面所在目录及其子目录下的所有页面。这里的方法可以用来设定一些更一般的条件。例如,someCookie.setPath("/"),此时服务器上的所有页面都可以接收到该Cookie。 getSecure/setSecure 获取/设置一个boolean值,该值表示是否Cookie只能通过加密的连接(即SSL)发送。 getVersion/setVersion 获取/设置Cookie所遵从的协议版本。

75 (3) Cookie的使用 在程序实现的时候,要把Cookie发送到客户端,Servlet先要调用new Cookie(name,value)用合适的名字和值创建一个或多个Cookie,通过Cookie.setXXX设置各种属性,通过response.addCookie(cookie)把Cookie加入应答头。 要从客户端读入Cookie,Servlet应该调用request.getCookies(),getCookies()方法返回一个Cookie对象的数组。在大多数情况下,程序实现者需要用循环访问该数组的各个元素寻找指定名字的Cookie,然后对该Cookie调用getValue方法取得与指定名字关联的值。 使用Cookie步骤一般如下:

76 创建Cookie 调用Cookie对象的构造函数可以创建Cookie。Cookie对象的构造函数有两个字符串参数:Cookie名字和Cookie值。名字和值都不能包含空白字符以及下列字符: [ ] ( ) = , " / : ; 创建和设置Cookie属性 把Cookie加入待发送的应答头之前,可以查看或设置Cookie的各种属性。如在应答头中设置Cookie,Cookie可以通过HttpServletResponse的addCookie方法加入到Set-Cookie应答头。如下: Cookie userCookie = new Cookie("user", "zhang"); response.addCookie(userCookie); 读取Cookie属性 从客户端读取Cookie时调用的是HttpServletRequest的getCookies方法。该方法返回一个与HTTP请求头中的内容对应的Cookie对象数组。得到这个数组之后,一般是用循环访问其中的各个元素,调用getName检查各个Cookie的名字,直至找到目标Cookie。然后对这个目标Cookie调用getValue,根据获得的结果进行其他处理。

77 上述处理过程在实际程序实现中会经常会遇到,为使用方便,可以实现一个较通用的getCookieValue方法。只要给出Cookie对象数组、Cookie名字和默认值,getCookieValue方法就会返回匹配指定名字的Cookie值,如果找不到指定Cookie,则返回默认值。该函数是ServletUtilities.java的一部分。getCookieValue通过循环依次访问Cookie对象数组的各个元素,寻找是否有指定名字的Cookie,如找到,则返回该Cookie的值;否则,返回参数中给出的默认值。getCookieValue能够在一定程度上简化Cookie值的提取。下面是其代码。

78 Public. static. String. getCookieValue(Cookie[]
Public static String getCookieValue(Cookie[] cookies,String cookieName, String defaultValue) for(int i=0; i<cookies.length; i++) Cookie cookie =cookies[i];  if(cookieName.equals(cookie.getName()))  return(cookie.getValue());  return(defaultValue);  另外,一个比较常用的需求是希望Cookie能够在浏览器退出的时候自动保存 下来。下面的PersistantCookie类实现了这个功能,在需要此功能时候,可 以使用该类取代标准的Cookie类。 PersistantCookie.java代码如下: Import javax.servlet.http.*;  Public class PersistantCookie extends Cookie {  public static final int SECONDS_PER_YEAR = 60*60*24*365;  public PersistantCookie (String name, String value) super(name,value);  setMaxAge(SECONDS_PER_YEAR);  } } 

79 实例 实例1 testCookie.java : 下面给出一个例子(testCookie.java),该例可向用户的计算机写
入两个Cookie,一个名称为myCookie1,值为swimming,另一个名称 为myCookie2,值为cooking。其代码如下: import javax.servlet.*; import javax.servlet.http.*; import java.io.*; public class testCookie extends HttpServlet { public void service(HttpServletRequest rq,HttpServletResponse rp)throws ServletException,IOException    { Cookie c;   c=new Cookie("myCookie1","swimming");    c.setMaxAge(100*60);    rp.addCookie(c);    c=new Cookie("myCookie2","cooking");    c.setMaxAge(-1);    } }

80 实例2 Login.java: 下面是另一个例子(Login.java),例子演示了用户登录的过程,如果用户输入了用户名和口令,按照用户名和口令登录,并且生成Cookie。如果没有输入用户名和口令,则检查Cookie,看Cookie是否包含用户名和口令,如果有Cookie,使用Cookie里的值登录。其中用户名和口令在程序中用数组临时保存。另外,例子中使用了前面介绍的类ServletUtilities提供的静态方法去读取Cookie的值。 Login.java代码如下: import javax.servlet.*; import javax.servlet.http.*;import java.io.*; public class Login extends HttpServlet { public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException,IOException Cookie[] cookies; String[] userList={"zhang","li","wang"}; String[] pwdList={"zhang","li","wang"}; boolean userok=false,pwdok=false;

81 String user = request.getParameter("user");
String pwd = request.getParameter("pwd"); if((user==null)&&(pwd==null)) //没有用户,口令,看有无cookies { cookies = request.getCookies(); user = ServletUtilities.getCookievalue(cookies, "user", "test"); pwd = ServletUtilities.getCookievalue(cookies, "user", "test"); } else//输入了用户口令,生成cookie Cookie c; c=new Cookie("user",user); c.setMaxAge(100*60); response.addCookie(c); c=new Cookie("pwd",pwd); for(int i=0;i<userList.length;i++) if(user.equalsIgnoreCase(userList[i])) userok=true; break; for (int i=0;i<pwdList.length;i++) if(pwd.equalsIgnoreCase(pwdList[i])) pwdok=true; PrintWriter out = response.getWriter(); out.println("<html>"); out.println("<head><title>ServletCookie</title></head>"); out.println("<body>"); if(userok && pwdok) { out.println("ok"); } else out.println("error"); out.println("</body>"); out.println("</html>"); ServletUtilities代码如下: class ServletUtilities{ public static String getCookievalue(Cookie[] cookies, String cookieName, String defaultvalue) { for(int i=0; i<cookies.length; i++) { Cookie cookie = cookies[i]; if (cookieName.equals(cookie.getName())) return(cookie.getValue()); return(defaultvalue);

82 2. Session (1)会话的特点 和前面介绍的会话跟踪相比,这里讲的会话是狭义的会话。每次新创建的会话都有一个唯一的标识串,称为会话ID,会话ID被保存在Web服务器中,其它会话信息都在会话ID的索引下进行保存和读取。一次会话从创建到被删除的时间称为会话生存期,会话生存期的长短由会话管理机制决定。这和Cookie机制不同,Cookie是将用户访问的状态信息通过Web浏览器保存在客户的计算机中,而会话通过会话管理机制存放在Web服务器中。会话的实现一般是一个基于Cookie或者URL改写机制,如果浏览器支持Cookie,则使用Cookie;如果浏览器不支持Cookie或者Cookie功能被关闭,则自动使用URL改写方法。

83 (2) HttpSession 接口 Java Servlet使用HttpSession 接口用来管理会话信息,通过这个接口,Servlet引擎可以有效地跟踪用户的会话, 也无需直接处理Cookie或附加到URL后面的信息(见前面的Cookie和URL改写)。HttpSession 接口允许 Servlet: 保存数据到Session中 读取和管理Session中数据 确保信息可以在用户的多个页面请求中共享 在Servlet程序设计中,Servlet设计者可以先获得一个HttpSession(见HttpServletRequest 定义的getSession方法)的对象,然后把一个包含各种数据的Java对象保存到会话中,这个对象和一个唯一的名字关联。需要的时候,可以通过对象的唯一的名字从会话中取出这个对象,对象都可以被处理同一会话的所有Servlet使用。以下是HttpSession定义的方法:

84 常用重要方法的说明: setAttribute():使用指定的名称,将对象绑定到这个会话。 getAttribute():返回绑定到此会话的对象(带有指定名称)。 isNew():如果客户机还不知道会话,则返回 true。如果客户机已经禁用了 Cookie,则会话在每个请求上都是新的。 getId():返回包含分配给这个会话的唯一标识的字符串。在使用 URL 改写以标识会话时比较有用。 setMaxInactiveInterval():指定在 Servlet 使该会话无效之前客户机多次请求间的间隔时间。负的时间表示会话永远不会超时。 invalidate():终止当前会话,并解开与它绑定的对象。

85 (3)使用HttpSession的实例 下面给出一个使用HttpSession完成一个购物车的Servlet例子。 程序说明 该Servlet使用HttpSession实现会话跟踪。程序中首先要获 得会话对象,HttpServletRequest.getSession()对象返回与请求相关的当前HttpSession 对象,并且当该对象不存在时就新创建一个对象。ServletShoppingCart(购物车Servlet)接收请求,并创建eShoppingCart购物车对象,购物车对象管理各种商品(ShoppingCartCommodity)。ServletShoppingCart.java、eShoppingCart.java和ShoppingCartCommodity.java三个文件分别定义了这3个类。 当执行该Servlet的时候,可以在浏览器地址栏输入 ServletShoppingCart调用,如果没有传入参数,该Servlet会返回一个购物页面,点击页面上的商品链接后可以完成购物并显示购物汇总信息。关于该Servlet的service方法更详细的说明参看源代码注释。

86 购物页面 购物汇总信息

87 ServletShoppingCart.java代码如下:
import javax.servlet.*; import javax.servlet.http.*; import java.io.*; public class ServletShoppingCart extends HttpServlet { static final private String CONTENT_TYPE = "text/html; charset=GBK"; protected String m_command = null; public void service (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException //读取command参数 m_command = request.getParameter("command"); response.setContentType(CONTENT_TYPE); PrintWriter out = response.getWriter(); //如果没有传入command参数或者command参数等于空串, //显示购物页面,页面中只有三种商品 //可以点击返回的物页面的链接购物,该链接继续调用该Servlet, //每个链接在调用时都传入参数command,id,num,price,和name参数 if((m_command==null)||(m_command.equals(""))){ out.println("<a href=\"ServletShoppingCart?command=buy&id=1&num=1 &price=4500&name=DV\">DV 4500元</a>"); out.println("<br><a href=\"ServletShoppingCart?command=buy&id=2&num=1 &price=235&name=CD\">CD 235元</a>"); out.println("<br><a href=\"ServletShoppingCart?command=buy&id=3&num=1 &price=785&name=MP3\">MP3 735元</a>"); // 获得会话对象,如果是第一次访问,还没有session对象,那么创建一个新的 HttpSession sess = request.getSession(true); eShoppingCart sCart=null; //如果是新的会话,创建一个保存购物信息的eShoppingCart对象, //并在以后保存到会话中,如果会话对象已经存在,即不是新的会话, //那么从会话中取出以前保存的eShoppingCart类型的对象 if (sess.isNew()) sCart=new eShoppingCart(); System.out.println("finished creating ShoppingCart!"); } out.println("</body></html>"); return; } else { sCart=(eShoppingCart)sess.getAttribute("ShoppingCart"); System.out.println("got ShoppingCart!"); //给前端的部分响应 out.println("<html>"); out.println("<head><title>ServletShoppingCart</title></head>"); out.println("<body>"); if(m_command.equals("buy"))//客户购物用,购买某一类商品 String strID = null; long strNum = 0; float strPrice = 0; String strName = null; //读取购物信息 strID = (String)request.getParameter("id"); strNum= Integer.parseInt((String)request.getParameter("num")); strPrice= Float.parseFloat((String)request.getParameter("price")); strName= (String)request.getParameter("name"); //在服务器控制台打印读入的参数,方便调试 System.out.print("\n"+strID);System.out.print("\t"+strNum); System.out.print("\t"+strPrice);System.out.println("\t"+strName); //创建商品对象,并放入sCart购物车对象 ShoppingCartCommodity commdity=new ShoppingCartCommodity(strID,strNum,strPrice,strName); sCart.add(strID,commdity); //将购物车里的商品信息返回给客户度 out.println("<p>您购买的商品如下:</p>"); out.println(sCart.toString()); out.println("<br>"); //将购物车对象保存的会话对象 sess.setAttribute("ShoppingCart",sCart);

88 //Get next Map.Entry object Map.Entry entry = (Map.Entry)I.next();
eShoppingCart.java代码如下: class eShoppingCart { HashMap commodityList; public eShoppingCart() commodityList=new HashMap(); } void add(String id,ShoppingCartCommodity commodity) commodityList.put(id,commodity); void remove(long id) commodityList.remove(new Long(id)); void removeAll( ) commodityList.clear(); void modifyQuantity(long id,long newq) ShoppingCartCommodity commodityold=null,commoditynew=null; commodityold= (ShoppingCartCommodity)commodityList.get(new Long(id)); commoditynew=new ShoppingCartCommodity(commodityold.id,newq, commodityold.price, commodityold.name); commoditynew.totalPrice=newq*commoditynew.price; commoditynew.desc=commodityold.desc; commodityList.put(new Long(id),commoditynew); public String toString()//toString用于显示 String str=""; Iterator I = commodityList.entrySet().iterator(); str="<hr><table>"; float total=0; while (I.hasNext()) { //Get next Map.Entry object Map.Entry entry = (Map.Entry)I.next(); ShoppingCartCommodity commodity=(ShoppingCartCommodity)(entry.getValue()); str+=commodity.toString() ; total=total+commodity.getTotalPrice(); } str=str+"</table>"; str=str+ "<hr><br><br>"+ "总价钱:"+total+"元"; return str; public float getTotalPrice() { Iterator I = commodityList.entrySet().iterator(); float total=0; return total;

89 ShoppingCartCommodity.java代码如下:
class ShoppingCartCommodity{ String id; long num; String name; //下面三个属性用于显示,在toString中用到 float price; float totalPrice; String desc; public ShoppingCartCommodity(String id,long num,float price,String name ) { this.id=id; this.num=num; this.price=price; totalPrice=num*price; this.name=name; } public String toString()//toString生成用于显示的表格行 String str=null; str="<tr><td width=180>"+"商品编号:"+id+"</td>"; str=str+"<td width=180>"+"商品名称:"+name+"</td>"; str=str +"<td width=210><input name=txtNum type=text size = 10 value=\""+num+"\">套(个)</td>"; str=str+" <td width=180>价格:"+totalPrice+"</td></tr><br>"; return str; public float getTotalPrice() return num*price;

90 第五章. 服务器端开发技术- Servlet 第一节.Servlet概述 第二节.Servlet的开发环境及程序结构
第四节.会话跟踪 第五节.JDBC访问数据库

91 第五节 JDBC访问数据库 1. JDBC概述 JDBC(Java DataBase Connectivity)是Java访问数据库的一种技术标准,许多数据库厂商为其数据库提供了JDBC驱动程序,这使得开发者可以编写几乎完全不依赖数据库的代码。另外,JavaSoft 和 Intersolv 已开发了一种称为 JDBC-ODBC Bridge 的产品,可使得开发者可以连接还没有直接提供的JDBC驱动程序的数据库。 JDBC可以帮助开发者建立与数据库的连接、发送 SQL语句给数据库去执行和获取处理数据库执行结构做进一步的处理。

92 根据驱动程序工作原理的不同,Java 程序连接数据库的方法实际上
有四种: JDBC-ODBC 桥和 ODBC 驱动程序 这是一个本地解决方案,因为 ODBC 驱动程序和桥代码必须出现在用户的每台机器中,从根本上说这是一个临时解决方案。 本机代码和 Java 驱动程序 也是一个本地解决方案(该平台上的 Java 可调用的本机代码),用来取代 ODBC 和 JDBC-ODBC 桥。 JDBC 网络的纯 Java 驱动程序 这种类型的驱动程序将 JDBC 转换为与 DBMS 无关的网络协议,之后这种协议又被某个服务器转换为一种 DBMS 协议。网络服务器中间件能够将它的纯 Java 客户机连接到多种不同的数据库上。所用的具体协议取决于提供者。通常,这是最为灵活的 JDBC 驱动程序。在这种情况下,中间件软件提供商可提供网络服务器中间件。 本机协议 Java 驱动程序 这种类型的驱动程序将JDBC调用直接转换为DBMS 所使用的网络协议。这将允许从客户机机器上直接调用 DBMS 服务器,是 Intranet 访问的一个很实用的解决方法。由于许多这样的协议都是专用的,因此数据库提供者自己将是主要来源。

93 2. JDBC环境 (1) MySql数据库Server安装 如果应用程序需要访问数据库,那么需要安装数据库服务器。下载适合的数据库(比如Oracle,MicroSoft SQL Server, MySql等)服务器并安装,在本书的例子都采用了MySql数据库,数据库服务器版本为4.0.20a,安装比较简单,执行Setup安装程序按照提示安装即可。使用JDBC访问MySql需要下载对应的数据库厂商提供自己的数据库JDBC驱动程序,这里使用的驱动程序文件名为MySql-connector-java stable-bin.jar。注意JDBC驱动程序一方面在开发环境Jbuilder中会使用,需要将它放到JBulider的环境变量中,以便编译源代码使用;另一方面,如果开发的是Servlet程序,那么需要发布到Web Server中,要把MySql-connector-java stable-bin.jar放入Resin的安装目录的lib目录中,以便发布后在通过浏览器调用Servlet的时候使用。

94 最后,为操作数据库方便,可以安装一个图形化的MySql客户端访问工具(如MySql ControlCenter),通过该工具可以以图形界面的方式管理MySql数据库Server,其参考界面如下:

95 (2) Resin配置 要在Resin中让Servlet使用JDBC访问MySql数据库Server,除了上面提到的要将 JDBC驱动程序文件(MySql-connector-java stable-bin.jar)放入Resin的安装目录的lib目录中外,还需要修改Resin的配置文件(配置修改后要重新启动Resin以生效)。 在Resin的配置文件resin.conf文件中的<resource-ref>……</resource-ref>标记是用来配置Resin使用的资源,是典型的访问数据库的配置,下例是数据库语法示例如下: <resource-ref> <res-ref-name>jdbc/test</res-ref-name> <res-type>javax.sql.DataSource</res-type> <init-param driver-name="com.MySql.jdbc.Driver"/> <init-param url="jdbc:MySql://localhost:3306/test?useUnicode=true&characterEncoding=gb2312"/> <init-param user="root"/> <init-param password=""/> <init-param max-connections="20"/> <init-param max-idle-time="30"/> </resource-ref>

96 (3)常用访问数据库类 所有与数据库有关的类和方法都在 java.sql 包中,因此在使用 JDBC 的程序中必须加入 “import java.sql.* ”。下表中列出常用的和访问操作数据库相关的类。

97 Class类 JDBC 要连接 ODBC 数据库,必须首先加载驱动程序,Class类可以完成这个功能。该类的静态方法forName(String driver)用于加载驱动程序。 例如: Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"); //加载了JDBC-ODBC 桥驱动程序。 Class.forName("com.mysql.jdbc.Driver"); //加载了MySql的JDBC驱动程序。

98 (2) DriverManager类 DriverManager类管理JDBC驱动程序。使用JDBC驱动程序之前,必须先将驱动程序加载(Class.forName方法)并向DriverManager注册后才可以使用,它同时提供方法来建立与数据库的连接。静态方法getConnection返回一个连接,基于该连接可以访问数据库。该方法定义如下。 Static Connection getConnection(String url,String user,String password) throws SQLException; 其中特别注意url参数的语法,URL 语法可能因数据库类型的不同而变化极大。url的语法格式是: jdbc:subprotocol:subname 其中第一组字符代表连接协议,并且始终是 jdbc。还可能有一个子协议,在此处,子协议被指定为 odbc。它规定了一类数据库的连通性机制。如果要连接其它机器上的数据库服务器,可能也要指定该机器和一个子目录: jdbc:bankServer//doggie/elliott 最后,可能要指定用户名和口令,作为连接字符串的一部分: jdbc: bankServer //doggie/elliot;UID=sa;PWD=admin 下面给出一个获取连接的例子: String url = "jdbc:odbc:accessdb"; Connection con = DriverManager.getConnection(url); 注意上面使用的accessdb是在windows操作系统下 ODBC 设置面板中配置的数据源名称。

99 (3) Connection类 Connection类负责维护数据库程序和数据库之间的连接。通过可 以Connection可以获取Statement和PreparedStatement类的对象进而 执行sql语句。常用的方法如下: Statement createStatement() throws SQLException; Statement createStatement(int resultSetType, int resultSetConcurrency)throws SQLException 这两个具有不同参数的方法用于创建Statement对象。 其中resultSetType取值包括: TYPE_FORWARD_ONLY 结果集不可滚动 TYPE_SCROLL_INSENSITIVE 结果集可滚动,不反映数据库的变化 TYPE_SCROLL_SENSITIVE 结果集可滚动,反映数据库的变化 resultSetConcurrency取值包括: CONCUR_READ_ONLY 不能用结果集更新数据 CONCUR_UPDATABLE 能用结果集更新数据

100 PreparedStatement prepareStatement(String sql) throws SQLException
boolean getAutoCommit() throws SQLException 返回Connection类对象的AutoCommit状态 void setAutoCommit(boolean autoCommit) throws SQLException 设定Connection类对象的AutoCommit状态 void commit() throws SQLException 确定执行对数据库新增、删除或修改记录的操作 void rollback() throws SQLException 取消执行对数据库新增、删除或修改记录的操作 void close() throws SQLException 关闭Connection对象对数据库的连接 boolean isClosed() throws SQLException 测试是否已经关闭Connection类对象对数据库的联机 一旦连接到数据库,就可以请求表名以及表列的名称和内容等信 息,而且可以运行 SQL 语句来查询数据库或者添加或修改其内 容。可用来从数据库中获取信息的对象有:

101 (4) ResultSet类 ResultSet 类是 JDBC 中最重要的类之一,它负责存储查询数据库的结果,可以通过Statement 对象或者PreparedStatement对象的查询方法执行获取记录集.。ResultSet类提供一系列的方法对数据库进行新增、删除和修改操作。也负责维护一个记录指针(Cursor),记录指针指向数据表中的某个记录,通过适当的移动记录指针,可以随心所欲的存取数据库,加强程序的效率。 有时候,在使用 ResultSet 之前,需要知道它包含多少个列。此信息存储在 ResultSetMetaData 对象中,getMetaData方法可以得到该对象。

102 常用方法如下: boolean absolute(int row) throws SQLException //移动记录指针到指定的记录 void beforeFirst() throws SQLException //移动记录指针到第一笔记录之前 void afterLast() throws SQLException //移动记录指针到最后一笔记录之后 boolean first() throws SQLException //移动记录指针到第一笔记录 boolean last() throws SQLException //移动记录指针到最后一笔记录 boolean next() throws SQLException //移动记录指针到下一笔记录 boolean previous() throws SQLException //移动记录指针到上一笔记录 void deleteRow() throws SQLException //删除记录指针指向的记录 void moveToInsertRow() throws SQLException //移动记录指针以新增一笔记录 void moveToCurrentRow() throws SQLException //移动记录指针到被记忆的记录 void insertRow() throws SQLException //新增一笔记录到数据库中 void updateRow() throws SQLException //修改数据库中的一笔记录 void update类型(int columnIndex,类型 x) throws SQLException //修改指定字段的 值 其中类型可以是Java数据类型,和数据库字段相关。 int get类型(int columnIndex) throws SQLException //取得指定字段的值 ResultSetMetaData getMetaData() throws SQLException //取得ResultSetMetaData类对象

103 (5) ResultSetMetaData类
int getColumnCount() throws SQLException 取得ResultSet类对象的字段个数 int getColumnDisplaySize() throws SQLException 取得ResultSet类对象的字段长度 String getColumnName(int column) throws SQLException 取得ResultSet类对象的字段名称 String getColumnTypeName(int column) throws SQLException 取得ResultSet类对象的字段类型名称 String getTableName(int column) throws SQLException 取得ResultSet类对象的字段所属数据表的名称 boolean isCaseSensitive(int column) throws SQLException 测试ResultSet类对象的字段是否区分大小写 boolean isReadOnly(int column) throws SQLException 测试ResultSet类对象的字段是否为只读

104 (6) Statement类 提供如下常用方法: ResultSet executeQuery(String sql) throws SQLException 使用SELECT命令对数据库进行查询 int executeUpdate(String sql) throws SQLException 使用INSERT\DELETE\UPDATE对数据库进行新增、删除和修改操作 void close() throws SQLException 关闭Statement类对象对数据库的连接 (7) PreparedStatement类

105 PreparedStatement类和Statement类的不同之处在于PreparedStatement类对象会将传入的SQL命令事先编好等待使用,当有单一的SQL指令比多次执行时,用PreparedStatement类会比Statement类有效率。它提供如下常用方法: ResultSet executeQuery() throws SQLException 使用SELECT命令对数据库进行查询。 int executeUpdate() throws SQLException 使用INSERT\DELETE\UPDATE对数据库进行新增、删除和修改操作。 ResultSetMetaData getMetaData() throws SQLException 取得ResultSet类对象有关字段的相关信息。 void setInt(int parameterIndex,int x) throws SQLException 设定整数类型数值给PreparedStatement类对象的IN参数。 void setFloat(int parameterIndex,float x) throws SQLException 设定浮点数类型数值给PreparedStatement类对象的IN参数。 void setNull(int parameterIndex,int sqlType) throws SQLException 设定NULL类型数值给PreparedStatement类对象的IN参数。 void setString(int parameterIndex,String x) throws SQLException 设定字符串类型数值给PreparedStatement类对象的IN参数。 void setDate(int parameterIndex,Date x) throws SQLException 设定日期类型数值给PreparedStatement类对象的IN参数 。 void setTime(int parameterIndex,Time x) throws SQLException 设定时间类型数值给PreparedStatement类对象的IN参数。

106 (8) DataSource类 JDBC API 为连接池提供了一个客户端的接口javax.sql.DataSource, 通常就是应用代码用来请求一个缓冲了的 数据库连接的东西。一般采用它来获得连接(getConnection方法),进而访问数据库。示例代码片段如下: m_dsn = "jdbc/uibeec"; conn = DBAccess.getConnection(bean.m_dsn); //其中DBAccess类中提供了静态方法getConnection。 public static Connection getConnection(String dsn) throws Exception { InitialContext initCtx = null; Connection conn = null; try { if (m_ds == null) initCtx = new InitialContext(); m_ds = (DataSource)initCtx.lookup("java:comp/env/" + dsn); } conn = m_ds.getConnection(); return conn; catch(Exception ex) return null; finally if (initCtx != null) initCtx.close();

107 4.三种不同数据库连接方法 (1)通过JDBC-ODBC 连接数据库 JDBC 要连接 ODBC 数据库,必须首先加载 JDBC-ODBC 桥驱动程序 Class.forName("sun.jdbc.odbc.JdbcOdbcDriver")语句加载驱动程序,并创建该类的一个实例。然后,要连接一个特定的数据库,必须创建 Connection 类的一个实例,并使用 URL 语法连接数据库。注意使用的数据库名是在 ODBC 设置面板中输入的数据源名称。 下面给出一个例子,该例子中JdbcOdbc类在构造函数中通过jdbc-odbc访问数据库,将表中的内容打印出来,而JdbcOdbc_test类的main方法中创建了JdbcOdbc对象(调用了构造函数)。注意程序中的黑体部分的作用。

108 import java.net.URL; import java.sql.*; import java.util.*; class JdbcOdbc_test{ public static void main(String[] s) { JdbcOdbc tmp=new JdbcOdbc(); } class JdbcOdbc ResultSet results; ResultSetMetaData rsmd; DatabaseMetaData dma; Connection con; int numCols, i; //-- public JdbcOdbc() String url = "jdbc:odbc:accessdb"; String query = "SELECT ModuleCode, NodeCode, SerialNo FROM nms_module "; try //加载 JDBC-ODBC 桥驱动程序 Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"); //连接数据库 con = DriverManager.getConnection(url,"ccpdba","ccpdba"); //获取数据库的元数据 dma =con.getMetaData(); System.out.println("Connected to:"+dma.getURL()); System.out.println("Driver "+dma.getDriverName()); catch (Exception e) {System.out.println(e);} //执行查询 try { Statement stmt = con.createStatement(); results = stmt.executeQuery("SELECT * FROM nms_module;"); {System.out.println("query exception");} dumpResults("--Contents of nms_module column--"); //尝试实际的 SQL 语句 try { Statement stmt = con.createStatement(); results = stmt.executeQuery(query); } catch (Exception e) e.printStackTrace(); dumpResults("--Results of Query--"); private void dumpResults(String head) //这是打印列标头和每列的内容的 //通用方法 System.out.println(head); //从元数据中获取列数 rsmd = results.getMetaData(); numCols = rsmd.getColumnCount(); //打印列名 for (i = 1; i<= numCols; i++) System.out.print(rsmd.getColumnName(i)+" "); System.out.println(); //打印列内容 boolean more = results.next(); while (more) for (i = 1; i <= numCols; i++) System.out.print(results.getString(i)+" "); more = results.next(); catch(Exception e) {System.out.println(e.getMessage());}

109 (2)通过DriverManager连接数据库
下例给出一个使用DriverManager获取连接,并往数据库插入一条记录的一个servlet。该Servlet可以被一个HTML网页里面的Form调用。因此例子共有连个文件,一个是HTML文件(register.html)用于用户输入注册信息。另一个是Java Servlet(JDBCServletExample),里面使用JDBC读取前端的Form里面提交来的参数,插入一条记录,完成用户注册过程。 该例子使用Mysql数据库,要求在数据库里建一个表Person, 可以用create table person(name varchar(20), sex varchar(8),birthday varchar(10), varchar(30))语句建立表。访问数据库的时候,一定将JDBC的驱动程序放到服务器的类路径里。每执行一次新用户注册,数据表Person就会多一条新记录。

110 register.html源代码 : <HTML> <body> <br> <center> <form action="/myweb/servlet/JDBCServletExample" method="post"> <table width="50%" border="0" cellspacing="1" cellpadding="3" align="center"> <tr > <td width="15%" >名字</td> <td ><input type="text" name="name" size="20" ></td> </tr> <td width="15%" >性别</td> <td ><input CHECKED name="sex" type="radio" value="male">男 <input name="sex" type="radio" value="female"> 女</td> <td width="15%" >出生日期</td> <td ><input type="text" name="birthday" size="20" ></td> <td width="15%" >电子邮箱</td> <td ><input type="text" name=" " size="20" ></td> </table> <input type="submit" name="submit" value="提交"> </form> </center> </body> </HTML> 该页面执行结果如下图,点击提交按钮后,将输入提交给 JDBCServletExample Servlet。

111 JDBCServletExample.java代码 :
import java.sql.*; import java.io.*; import javax.servlet.*; import javax.servlet.http.*; public class JDBCServletExample extends HttpServlet { private Connection con; public void init() throws ServletException { super.init(); try{ String driverName="com.mysql.jdbc.Driver"; String userName="root"; //密码 String userPasswd=""; //数据库名 String dbName="test"; //表名 String tableName="person"; //连接字符串 String url="jdbc:mysql://localhost:3306/"+dbName+"?user="+username +"&password="+userPasswd; Class.forName(driverName).newInstance(); //获取连接,放入成员变量con con =DriverManager.getConnection(url); } catch(Exception e) e.printStackTrace(); public void service (HttpServletRequest request, HttpServletResponse response) throws ServletException,IOException { //读取前端Form提交来的参数 String name=request.getParameter("name"); String sex=request.getParameter( "sex"); String birthday=request.getParameter("birthday"); String =request.getParameter(" "); String reg="insert into person values(?,?,?,?)"; try{ PreparedStatement stmt=con.prepareStatement(reg); stmt.setString(1,name); stmt.setString(2,sex); stmt.setString(3,birthday); stmt.setString(4, ); stmt.executeUpdate();//执行update语句 System.out.println("insert ok!"); } catch(Exception e) e.printStackTrace();

112 (3)通过DataSource接口访问数据库
通过DriverManager连接数据库,一个数据库连接对象均对应一个物理连接,而数据库的连接的建立和关闭比较消耗系统资源,在Web及多层结构的应用程序架构中这种消耗资源的动作对系统的性能影响比较明显。 在Web或者多层结构的应用程序中通过连接池技术可以使系统的性能明显得到改善,连接池技术通过重用已有的数据库连接来代替新建一个数据库连接。通过这种方式,应用程序可以减少对数据库连接操作,尤其在多层环境中多个客户端可以通过共享少量的物理数据库连接来满足系统需求,这样可以提高系统性能。使用数据库连接池技术的使应用程序必须通过DataSource对象(一个实现javax.sql.DataSource接口的实例)的方式代替原有通过DriverManager类来获得数据库连接的方式。一个实现javax.sql.DataSource接口的类可以支持也可以不支持数据库连接池,但是两者获得数据库连接的代码基本是相同的。

113 5.数据库综合示例 我们已经学习了 JDBC 的所有基本功能,现在我们可以编写一个较 完整的Servlet应用,该程序完成客户信息的管理,testJDBC接收 html表单上来的各种请求,然后根据请求对该数据库执行增加、删 除、查询等操作,完成客户信息的管理。在运行此应用前,需要发 布该应用(方法见前述),并需要使用如下sql语句创建数据表 customer。 create table customer(id varchar(20), name varchar(20), pwd varchar(8), varchar(50), address varchar(40), zip varchar(6), province varchar(20), city varchar(20), tel varchar(20) )

114 此应用包含的文件以及功能见下表 :

115 CustomerListall.html :
<HEAD> <TITLE>CustomerList</TITLE> </HEAD> <BODY bgColor=#6F7A9E > <form name="CustomerList" method="post" action="/myweb/servlet/testJDBC?cmd=listall"> <input type="submit" name="cmdAdd" value="查询"> </form> <br> <a href=" target="_blank"> 查询所有</a> </BODY> </HTML> CustomerQuery.html : <HTML> <HEAD> <TITLE>CustomerQuery</TITLE> </HEAD> <BODY bgColor=#6F7A9E > <form name="CustomerQuery" method="post" action="/myweb/servlet/testJDBC?cmd=listby"> <table> <tr><td>ID</td><td> <input type="text" name="txtID" value="" size="32" maxlength="32"><td></tr> <tr><td>姓名</td><td><input type="text" name="txtName" value="" size="32" > </td></tr> <tr><td><input type="submit" name="cmdAdd" value="查找"></td><td></td></tr> </table> </form> </BODY> </HTML>

116 CustomerDelete.html : <HTML> <HEAD> <TITLE>CustomerDelete</TITLE> </HEAD> <BODY bgColor=#6F7A9E > <form name="CustomerDelete" method="post" action="/myweb/servlet/testJDBC?cmd=delete"> <table> <tr><td>ID</td><td> <input type="text" name="txtID" value="" size="32" maxlength="32"> <td></tr> <tr><td><input type="submit" name="cmdAdd" value="删除"></td><td></td></tr> </table> </form> </BODY> </HTML> CustomerUpdate.html : <HTML> <HEAD> <TITLE>CustomerUpdate</TITLE> </HEAD> <BODY bgColor=#6F7A9E > <form name="CustomerUpdate" method="post" action="/myweb/servlet/testJDBC?cmd=update"> <table> <tr><td>ID</td><td> <input type="text" name="txtID" value="" size="32" width =100 maxlength=5> <td></tr> <tr><td>姓名</td><td> <input type="text" name="txtName" value="" size="32" maxlength="32"> </td></tr> <tr><td>口令</td><td> <input type="text" name="txtPwd" value="" size="32" maxlength="32"> <tr><td> </td><td> <input type="text" name="txt " value="" size="32" maxlength="32"> <tr><td>地址</td><td> <input type="text" name="txtAddress" value="" size="132" maxlength="132"> <tr><td>邮编</td><td> <input type="text" name="txtZip" value="" size="32" maxlength="32"> <tr><td>省</td><td> <input type="text" name="txtProvince" value="" size="32" maxlength="32"> <tr><td>市</td><td> <input type="text" name="txtCity" value="" size="32" maxlength="32"> <tr><td>电话</td><td> <input type="text" name="txtTel" value="" size="32" maxlength="32"> <tr><td><input type="submit" name="cmdAdd" value="保存"></td><td></td></tr> </table> </form> </BODY> </HTML>

117 customeradd.html : <HTML> <HEAD> <TITLE>CustomerAdd</TITLE> </HEAD> <BODY bgColor=#6F7A9E > <form name="CustomerAdd" method="post" action="/myweb/servlet/testJDBC?cmd=add"> <table> <tr><td>ID</td><td> <input type="text" name="txtID" value="" size="32" width =100 maxlength=5><td></tr> <tr><td>姓名</td><td> <input type="text" name="txtName" value="" size="32" maxlength="32"></td></tr> <tr><td>口令</td><td> <input type="text" name="txtPwd" value="" size="32" maxlength="32"></td></tr> <tr><td> </td><td> <input type="text" name="txt " value="" size="32" maxlength="32"></td></tr> <tr><td>地址</td><td> <input type="text" name="txtAddress" value="" size="132" maxlength="132"></td></tr> <tr><td>邮编</td><td> <input type="text" name="txtZip" value="" size="32" maxlength="32"></td></tr> <tr><td>省</td><td> <input type="text" name="txtProvince" value="" size="32" maxlength="32"></td></tr> <tr><td>市</td><td> <input type="text" name="txtCity" value="" size="32" maxlength="32"></td></tr> <tr><td>电话</td><td> <input type="text" name="txtTel" value="" size="32" maxlength="32"></td></tr> <tr><td><input type="submit" name="cmdAdd" value="保存"></td><td></td></tr> </table> </form> </BODY> </HTML>

118 DBAccess.java : import java.sql.*; import javax.sql.*; import javax.naming.InitialContext; public class DBAccess { static DataSource m_ds = null; // 数据源 /** * 获得数据库连接。 * dsn 数据源标识 成功时返回一个数据库连接对象,失败时返回空对象。 */ public static Connection getConnection(String dsn) throws Exception InitialContext initCtx = null; Connection conn = null; try if (m_ds == null) initCtx = new InitialContext(); m_ds = (DataSource)initCtx.lookup("java:comp/env/" + dsn); } conn = m_ds.getConnection(); return conn; catch(Exception ex) return null; finally if (initCtx != null) initCtx.close(); * 关闭数据库连接以及相关的操作和结果集对象。 l 数据连接引用 操作对象引用 查询结果集 public static void closeConnection(Connection conn,Statement stmt,ResultSet rst) try{ if (rst != null) rst.close(); if (stmt != null) stmt.close(); if (conn != null) conn.close(); } catch (Exception e) { e.printStackTrace(); /** * 关闭数据库连接以及相关的操作和结果集对象。 * l 数据连接引用 2 操作对象引用 */ public static void closeConnection(Connection conn, Statement stmt) try

119 testJDBC.java : import javax.servlet.*; import javax.servlet.http.*; import java.io.*; import java.sql.*; import javax.sql.*; import java.util.*; import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.NamingException; public class testJDBC extends HttpServlet { static final private String CONTENT_TYPE = "text/html; charset=GBK"; //Initialize global variables public void init() throws ServletException } //Process the HTTP Get request public void doGet(HttpServletRequest request, HttpServletResponse response) try{ String cmd=request.getParameter("cmd"); System.out.println("cmd="+cmd); if(cmd.equals("update"))//修改 this.UpdateCustomer(request,response); else if(cmd.equals("add"))//追加 addCustomer(request,response); else if(cmd.equals("listall")) // 查询所有 listAllCustomer(request,response); //redirect("../servlet/BPMaster?bpid=org.uibe.ec.commoditymanagement.bControlCustomer&command=list"); else if(cmd.equals("listby")) // 根据id或者名字查询,维护人员用 { listCustomerByIDOrName(request,response); } else if(cmd.equals("delete")) // 根据id或者名字查询,维护人员用 DeleteCustomerByID(request,response); catch(Exception e) e.printStackTrace(); public void UpdateCustomer(HttpServletRequest request, HttpServletResponse response) PrintWriter out =null; try{ response.setContentType(CONTENT_TYPE); out= response.getWriter(); out.println("<html>"); out.println("<head><title>testJDBC</title></head>"); out.println("<body>"); printAllPara(request,response); String txtID=request.getParameter("txtID"); String txtName=request.getParameter("txtName"); String txtPwd=request.getParameter("txtPwd"); String txt =request.getParameter("txt "); String txtAddress=request.getParameter("txtAddress"); String txtZip=request.getParameter("txtZip"); String txtProvince=request.getParameter("txtProvince"); String txtCity=request.getParameter("txtCity"); String txtTel=request.getParameter("txtTel"); CustomerDBBean bean=new CustomerDBBean(); bean.InitInstance(); bean.update(txtID, txtName, txtPwd , txt , txtAddress, txtZip , txtProvince,

120 txtCity , txtTel ); } catch(Exception e) { e.printStackTrace(); out.println("<p>修改失败!</p>"); out.println("</body></html>"); return; out.println("<p>修改成功!</p>"); out.close(); public void gotoUpdate(HttpServletRequest request, HttpServletResponse response) public void addCustomer(HttpServletRequest request, HttpServletResponse response) throws Exception PrintWriter out =null; try{ response.setContentType(CONTENT_TYPE); out= response.getWriter(); out.println("<html>"); out.println("<head><title>testJDBC</title></head>"); out.println("<body>"); printAllPara(request,response); String txtID=request.getParameter("txtID"); String txtName=request.getParameter("txtName"); String txtPwd=request.getParameter("txtPwd"); String txt =request.getParameter("txt "); String txtAddress=request.getParameter("txtAddress"); String txtZip=request.getParameter("txtZip"); String txtProvince=request.getParameter("txtProvince"); String txtCity=request.getParameter("txtCity"); String txtTel=request.getParameter("txtTel"); CustomerDBBean.create(txtID, txtName, txtPwd , txt , txtAddress, txtZip , txtProvince, txtCity , txtTel ); } catch(Exception e) { e.printStackTrace(); out.println("<p>插入失败!</p>"); out.println("</body></html>"); return; out.println("<p>插入成功!</p>"); out.close(); public void listCustomerByIDOrName(HttpServletRequest request, HttpServletResponse response) PrintWriter out =null; Vector vc=null; String txtID=request.getParameter("txtID"); String txtName=request.getParameter("txtName"); try{ response.setContentType(CONTENT_TYPE); out= response.getWriter(); out.println("<html>"); out.println("<head><title>testJDBC</title></head>"); out.println("<body>"); CustomerDBBean bean=new CustomerDBBean(); bean.InitInstance(); vc=bean.findBy(txtID,txtName); for(int i=0;i<vc.size();i++) CustomerDBBean tbean=(CustomerDBBean)vc.elementAt(i); out.print(tbean.m_ID+ ","); out.print(tbean.m_Name+ ","); out.print(tbean.m_Province+ ","); out.print(tbean.m_City+ ","); out.print(tbean.m_Address+ ",");

121 out.print(tbean.m_Email+ ",");
out.print(tbean.m_Tel+ ","); out.print(tbean.m_Zip+ ","); out.print("<br>"); } catch(Exception e) { e.printStackTrace(); if(vc==null) out.println("<p>没有找到!</p>"); else out.println("<p>查找失败!</p>"); out.println("</body></html>"); return; out.println("<p>查找成功!</p>"); out.close(); public void DeleteCustomerByID(HttpServletRequest request, HttpServletResponse response) PrintWriter out =null; try{ response.setContentType(CONTENT_TYPE); out= response.getWriter(); out.println("<html>"); out.println("<head><title>testJDBC</title></head>"); out.println("<body>"); CustomerDBBean bean=new CustomerDBBean(); bean.InitInstance(); String id= request.getParameter("txtID"); if(id!=null) bean.remove(id); out.println("<p>删除失败!</p>"); out.println("</body></html>"); return; } out.println("<p>删除成功!</p>"); out.close(); public void listAllCustomer(HttpServletRequest request, HttpServletResponse response) { PrintWriter out =null; try{ response.setContentType(CONTENT_TYPE); out= response.getWriter(); out.println("<html>"); out.println("<head><title>testJDBC</title></head>"); out.println("<body>"); CustomerDBBean bean=new CustomerDBBean(); bean.InitInstance(); Vector vc=bean.findAll(); for(int i=0;i<vc.size();i++) CustomerDBBean tbean=(CustomerDBBean)vc.elementAt(i); out.print(tbean.m_ID+ ","); out.print(tbean.m_Name+ ","); out.print(tbean.m_Province+ ","); out.print(tbean.m_City+ ","); out.print(tbean.m_Address+ ","); out.print(tbean.m_ + ","); out.print(tbean.m_Tel+ ","); out.print(tbean.m_Zip+ ","); out.print("<br>"); catch(Exception e) e.printStackTrace(); out.println("<p>查找失败!</p>"); out.println("<p>查找成功!</p>");

122 out.println("</body></html>");
out.close(); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request,response); void printAllPara(HttpServletRequest request, HttpServletResponse response) Enumeration enum = request.getParameterNames(); String strKey=null; while(enum.hasMoreElements()) strKey = (String)enum.nextElement(); System.out.print ("*****strKey="+strKey+"********"); System.out.println(request.getParameter(strKey)); public void destroy()

123 m_sqlUpdate ="UPDATE customer SET "+ "id=?"+ ",name=?"+ ",pwd=?"+
CustomerDBBean.java : import java.sql.*; import java.util.*; import java.io.*; public class CustomerDBBean { // 数据源属性 public String m_dsn = ""; // 数据库表查询SQL属性 public String m_sqlSelect = ""; // 数据库表插入SQL属性 public String m_sqlInsert = ""; // 数据库表更新SQL属性 public String m_sqlUpdate = ""; // 数据库表删除SQL属性 public String m_sqlDelete = ""; String m_ID=null; String m_Name=null; String m_PWD=null; String m_ =null; String m_Address=null; String m_Zip=null; String m_Province=null; String m_City=null; String m_Tel=null; protected void InitInstance() // 初始化数据源 m_dsn = "jdbc/test"; // 初始化数据库表查询SQL m_sqlSelect = "SELECT "+ "ID,"+ "name,"+ "pwd,"+ " ,"+ "address,"+ "zip,"+ "province,"+ "city,"+ "tel "+ "FROM customer "; m_sqlInsert="insert into customer("+ "id,"+ "tel)"+ " VALUES(?,?,?,?,?,?,?,?,?)"; // 初始化数据库表查询SQL m_sqlUpdate ="UPDATE customer SET "+ "id=?"+ ",name=?"+ ",pwd=?"+ ", =?"+ ",address=?"+ ",zip=?"+ ",province=?"+ ",city=?"+ ",tel=?"+ " where id=?"; m_sqlDelete = "DELETE from customer WHERE id=?"; } //必须先调用InitInstance public static CustomerDBBean create( String id, String name, String pwd, String , String address, String zip, String province, String city, String tel ) //insert throws Exception { Connection conn = null; PreparedStatement stmt = null; try // 创建一个新的实例 CustomerDBBean bean = new CustomerDBBean(); bean.InitInstance(); // 建立数据库连接 conn = DBAccess.getConnection(bean.m_dsn); System.out.println("dsn="+bean.m_dsn); System.out.println("conn="+ conn); // 准备数据库操作描述 stmt = conn.prepareStatement(bean.m_sqlInsert); stmt.setString(1,id); stmt.setString(2,name); stmt.setString(3,pwd); stmt.setString(4, ); stmt.setString(5,address); stmt.setString(6,zip);

124 stmt.setString(7,province);
stmt.setString(8,city); stmt.setString(9,tel); System.out.println(bean.m_sqlInsert); // 执行查询 stmt.execute(); // 置属性 bean.m_ID=id; bean.m_Name=name; bean.m_PWD=pwd; bean.m_ = ; bean.m_Address=address; bean.m_Zip=zip; bean.m_Province=province; bean.m_City=city; bean.m_Tel=tel; return bean; } catch(Exception ex) { throw ex; finally DBAccess.closeConnection(conn, stmt); public static Vector findBy(String ID,String Name) throws Exception Connection conn = null; PreparedStatement stmt = null; ResultSet rst = null; Vector vDBBean = null; CustomerDBBean bean = null; try // 创建一个结果集 vDBBean = new Vector(); // 创建一个实例 bean = new CustomerDBBean(); bean.InitInstance(); // 建立数据库连接 conn = DBAccess.getConnection(bean.m_dsn); String whereclause=""; if (ID!=null && (!ID.trim().equals(""))) { whereclause=" where ID='"+ID+"'"; if(Name!=null && !Name.trim().equals("") ) whereclause=whereclause+ " and Name like '%"+Name+"'"; } else if(Name!=null && Name.trim()!="" && !Name.trim().equals("")) whereclause=" where Name like '%"+Name+"'"; // 准备数据库操作描述 String sqltmp=bean.m_sqlSelect + whereclause ; System.out.println("sqltmp="+sqltmp); stmt = conn.prepareStatement(sqltmp); // 执行查询 rst = stmt.executeQuery(); // 取数据 while(rst.next()) bean = new CustomerDBBean(); bean.m_ID=rst.getString(1); bean.m_Name=rst.getString(2); bean.m_PWD=rst.getString(3); bean.m_ =rst.getString(4); bean.m_Address=rst.getString(5); bean.m_Zip=rst.getString(6); bean.m_Province=rst.getString(7); bean.m_City=rst.getString(8); bean.m_Tel=rst.getString(9); vDBBean.addElement(bean); if(vDBBean.isEmpty()) return null;

125 return vDBBean; } catch(Exception ex) { throw ex; finally DBAccess.closeConnection(conn, stmt, rst); public static Vector findAll() throws Exception Connection conn = null; PreparedStatement stmt = null; ResultSet rst = null; Vector vDBBean = null; CustomerDBBean bean; try // 创建一个结果集 vDBBean= new Vector(); // 创建一个实例 bean = new CustomerDBBean(); bean.InitInstance() ; // 建立数据库连接 conn = DBAccess.getConnection(bean.m_dsn); if(conn==null) System.out.println("conn==null"); // 准备数据库操作描述 System.out.println("___________sql="+bean.m_sqlSelect); stmt = conn.prepareStatement(bean.m_sqlSelect); if(stmt==null) System.out.println("stmt==null"); rst = stmt.executeQuery(); // 取数据 while(rst.next()) bean.m_ID=rst.getString(1); bean.m_Name=rst.getString(2); bean.m_PWD=rst.getString(3); bean.m_ =rst.getString(4); bean.m_Address=rst.getString(5); bean.m_Zip=rst.getString(6); bean.m_Province=rst.getString(7); bean.m_City=rst.getString(8); bean.m_Tel=rst.getString(9); vDBBean.addElement(bean); } if(vDBBean.isEmpty()) return null; else return vDBBean; catch(Exception ex) { throw ex; finally DBAccess.closeConnection(conn, stmt, rst); public static void remove(String id) throws Exception Connection conn = null; PreparedStatement stmt = null; try // 创建一个新的实例 CustomerDBBean bean = new CustomerDBBean(); bean.InitInstance(); // 建立数据库连接 conn = DBAccess.getConnection(bean.m_dsn); System.out.println("dsn="+bean.m_dsn); System.out.println("conn="+ conn); // 准备数据库操作描述 stmt = conn.prepareStatement(bean.m_sqlDelete); stmt.setString(1,id); System.out.println(bean.m_sqlDelete);

126 // 执行查询 stmt.execute(); } catch(Exception ex) { throw ex; finally DBAccess.closeConnection(conn, stmt); public void update( String ID, String Name, String PWD, String , String Address, String Zip, String Province, String City, String Tel ) throws Exception //update Connection conn = null; PreparedStatement stmt = null; try // 创建一个新的实例 CustomerDBBean bean = new CustomerDBBean(); // 建立数据库连接 conn = DBAccess.getConnection(bean.m_dsn); // 准备数据库操作描述 stmt = conn.prepareStatement(bean.m_sqlUpdate); stmt.setString(1,ID ); stmt.setString(2,Name ); stmt.setString(3,PWD); stmt.setString(4, ); stmt.setString(5,Address); stmt.setString(6,Zip); stmt.setString(7,Province); stmt.setString(8,City); stmt.setString(9,Tel); stmt.setString(10,ID ); System.out.println("SQL:"+bean.m_sqlUpdate); // 执行 stmt.execute(); } catch(Exception ex) { throw ex; finally DBAccess.closeConnection(conn, stmt);


Download ppt "第五章. 服务器端开发技术- Servlet 对外经济贸易大学信息学院."

Similar presentations


Ads by Google