3. java环境配置

如果你满足以下任一条件,请继续阅读,否则请跳过此后的部分,进入下一章:第 1 章 开始编写jsp。

1. 没用过java,没配置过java环境的朋友。

2. 用过java,但没有使用过类似Tomcat服务器的朋友。

按照我们的直接感官认识,如果我们要写jsp,就需要一个支持jsp的程序来运行它,我们在这里选用的是Tomcat,而tomcat需要安装java开发环境。反过来就是我们准备的顺序,先是java然后tomcat。

3.1. 安装jdk

jdk是Java Development toolkit(Java开发工具包),我们需要的就是下载,安装,进行需要的配置。

本教程支持版本号:JDK 6 Update 5

下载JDK 6 Update 5。网址 http://java.sun.com/javase/downloads/index.jsp。

选择右侧的download。

选择accept接受sun的下载协议。

接受协议后,就可以选择71.39M的下载包,进行下载。

下载完毕,得到jdk-6u5-windows-i586-p.exe。

双击jdk-6u5-windows-i586-p.exe进行安装,默认的安装路径是:C:\Program Files\Java\jdk1.6.0_05\。

安装完成后,C:\Program Files\Java\jdk1.6.0_05\目录结构如下。 9 / 148

接下来设置系统的环境变量(Windows xp)

我的电脑(单击鼠标右键)->属性->高级->环境变量

选择新建,添加两个环境变量,JAVA_HOME和PATH。

其中JAVA_HOME的值是刚刚的安装路径,在这里是C:\Program Files\Java\jdk1.6.0_05\,如果安装到其他路径下,需要按实际情况进行修改。

PATH环境变量要引用刚才设置的JAVA_HOME,使用%JAVA_HOME%的写法,这一部分会自动替换成JAVA_HOME对应的值。

添加完成后的效果,可以看到path中的%JAVA_HOME%自动替换成安装路径了。

在cmd中执行javac,出现下图中的使用说明,表示设置成功。

注意

如果觉得自己完全是照上面的步骤进行了设置,但是运行javac总是报错的同志,重新按照一下几点进行检查。

 修改了环境变量以后,记得重开一个cmd窗口进行测试,老的cmd窗口不会使用新修改的环境变量。

 windows中设置目录要使用“\”,而不是“/”。如果使用了“/”会导致找不到路径的错误。

 如果还是不行,就去jdk安装目录下去找一个叫javac.exe的可执行文件,把这个文件所在的目录路径写到path中,把这个路径的上一级目录设置成JAVA_HOME的值。

3.2. 配置tomcat服务器

简要说几句,tomcat是apache基金会开发的java服务器,因为它是免费的,很多公司里都使用它。所以用它学会了jsp,以后工作上也用得到。

本教程支持版本号:apache-tomcat-5.5.26。

下载apache-tomcat-5.5.26.zip。网址 http://tomcat.apache.org/download-55.cgi#5.5.26

下载core里的zip项,这是绿色免安装版本,如果你希望使用安装版本,也可以选择Windows Excutable。

将下载文件,解压至c:\apache-tomcat-5.5.26目录,目录结构如下:

请确认安装了jdk,并配置了对应的JAVA_HOME环境变量。

双击运行bin目录下的startup.bat

看到:“server startup in 4062 ms”,说明tomcat启动成功。

关闭tomcat,可以直接关闭cmd窗口,或使用shutdown.bat

到此为止,准备工作都已结束,我们可以在tomcat下编写jsp了。

第 1 章 开始编写jsp

注意

因为本章包含了对http部分原理的介绍,建议读者不要略过这部分。

如果你不满足以下任一条件,请继续阅读,否则请跳过此后的部分,进入下一章:第 2 章 让jsp说hello。

1. 了解jsp的基本组成,可以编写简单的jsp。

2. 了解tomcat下jsp的放置位置,以及如何访问对应路径下的jsp。

3. 了解http基本原理,以及jsp在http下是如何发挥效用的。

1.1. 开篇第一个jsp

JSP是Java Server Page的缩写,现在先让我们编写一个简单的jsp页面,看看jsp究竟能干什么?

<%=new java.util.Date()%>

随便找一个文本编辑器来编写第一个jsp吧,如果实在没有顺手的编辑器,那么也可以用windows自带的记事本,新建一个test.jsp文件,把上面的代码复制到文件里,保存即可。

如果嫌麻烦,也可以直接使用文档附带的演示代码,位置在lingo-sample/01-01/test.jsp。

注意

对于第一次用记事本写代码的朋友,有一点需要特别注意,默认情况下windows不会显示扩展名,如果直接在记事本里将文件名改为test.jsp,那么另存的文件名会变成test.jsp.txt,记事本会自作聪明的为你加上.txt的扩展名,为了避免这种问题,需要在保存的时候,在文件名两端加上双引号,就像这样"test.jsp"。

建议设置系统文件夹属性,把文件的扩展名显示出来,这样我们更容易看出是否在文件名上出现问题。如果找不到这个设置,请打开“我的电脑” -> 选择上方菜单里的“工具” -> 选择弹出菜单中的“文件夹选项” -> 选择第二个标签“查看”,在下边的配置里有一项“隐藏已知文件类型的扩展名”,将它前面的对勾取消,点击确定,就可以看到文件的全名了。

得到了我们的第一个jsp文件之后,让我们把它复制到tomcat/webapp/ROOT目录下。现在检查一下tomcat是不是已经启动了,如果还没启动,需要先去启动tomcat,在看到tomcat正常启动的提示之后,就可以打开浏览器,输入网址 http://localhost:8080/test.jsp查看执行效果。

页面上的显示应该与下面类似:

Fri Feb 29 00:54:20 CST 2008

看到了这些字样,也就说明我们写的第一个jsp已经成功执行了,现在咱们看到的并不是谁预先写好的文字,而是使用服务器实时计算出的当前时间,如果不信你可以多刷新几次页面,时间内容会不断改变,显示的永远是当前服务器的时间。

好的,现在就引出了一个问题,jsp是如何把我们需要的数据发送给我们的。

1.2. B/S结构,请求与响应

B/S结构,既浏览器(Browser)/服务器(结构),用浏览器查看jsp写的页面就算非常简单的B/S结构了。

先看看在我们访问http://localhost:8080/test.jsp的时候究竟发生了什么:

1. 首先,浏览器解析我们输入的网址,查找服务器的位置。

咱们这里使用了http://localhost:8080/,浏览器就会以http协议,去访问localhost的8080端口,localhost是本机的别名,8080是tomcat的默认端口,即使现在不太理解也没有关系,只要知道http://localhost:8080/是你这台机器上运行的tomcat就可以了。

2. 找到了服务器的位置,浏览器会向服务器发送一个请求(request),这个请求包含着http协议规定格式的数据,现在咱们不需要去计较细节,先把注意力集中在流程上。

3. 接下来,服务器接收请求,分析请求中包含的数据。这个分析过程也是定义在http协议中的,像我们这里请求的是/test.jsp这个jsp页面,服务器就会去webapp/ROOT目录下去查找这个test.jsp,然后对它进行解析,运行。

4. 在服务器的操作结束后,会生成一个响应(response),并把这个响应发送回客户机器的浏览器。

5. 现在浏览器接收了响应,开始进行解析与运行,最后把结果显示给用户,这就成为我们最后看到的结果。

这就是http协议的基本流程了,像我们看到的一样,浏览器与服务器之间完全是依靠请求和响应联系起来的。这就像是在打乒乓球,浏览器发过一个球来,服务器接到球,反手再打回去。双方队员之间不会有其他接触,所有的交流就是那颗小球。

这也暗示了http中另一个重要的特性:短连接,无状态。

 短连接是指:请求响应一次,服务器就关闭与浏览器之间的网络连接。

 无状态是指,任意两次请求响应之间,没有直接的联系。

浏览器发出一个请求,服务器才能返回一个响应。一个请求对应一个响应,每个过程都是完全独立的。并且服务器端是被动的,只能接收请求,然后向请求的原发地发送响应,如果没有请求,服务器没办法凭空发一个响应出去,因为它不知道客户的机器在什么地方。浏览器和服务器之间也不会拉一根电话线,随时保证畅通,每次请求处理完之后,服务器就会立刻忘掉上次请求的信息。这样做的好处是处理简单,连接用完就断,不会浪费资源,坏处是在进行复杂操作的时候,因为没法保证用户当前的状态,只好把表示状态的信息不断的在浏览器和服务器之前传来传去,造成了操作的复杂。

一个请求一个响应,构筑了http协议的基础,jsp则是专门管辖服务器的部分,这样我们就可以把jsp的功能锁定在第二步和第三步了。处理请求,返回响应,这便是jsp的所有工作。

再重复一遍,jsp只负责服务器的操作,浏览器上的任何东西都与其无关,显示图片,显示文字,点击按钮,弹出窗口,这些都是浏览器的工作职能。并且,因为http的无状态性,jsp一旦返回了响应,就再不管其他的了。它绝对不会也不能直接对浏览器造成什么影响,它所能做是把响应这颗球打出去,剩下的就全看浏览器了。浏览器会发生什么事情,jsp也无从得知,如果想让jsp做些什么事情,唯一的办法就是发送一个请求。

重复第三遍,浏览器和服务器基本就是两个孤岛,两地居民的交流只能通过飞鸽传书来实现。在一封信到达之前,本地人是没办法得知对面的消息的,每当浏览器这边岛上的居民想要什么东西,他们就把需要的东西写到信里,用信鸽传递给服务器那边的岛上,服务器收到信鸽的消息,会按照上边所写的准备好,再放飞回去,等浏览器等到他们要的信鸽,就可以把东西放下来随便用了。两地居民直接接触的只有信鸽,他们从信鸽上那到他们想要的东西,对面的人长什么样子,是人是鬼他们全不在意,只要大家都会用信鸽就好了,http就是这样一只大家都会用的信鸽了。

希望现在大家已经对http有了清晰的认识,如果不了解它的运行原理,带着一脑子糨糊学jsp,那可是太可怕了,最怕的就是把jsp和浏览器混为一谈,认为是浏览器在运行jsp。实际上jsp是完全不知道浏览器的存在,它只是根据http形式的请求,发送http形式的响应,如果对面的浏览器能解析http形式的响应,就能显示出页面来,幸运的是目前市面上的浏览器都能解析http形式的响应,所以我们只要制造符合标准的数据,当作响应发送出去就行了。

其实一切都是这么简单,只要能了解它的运行原理,我们就可以在自己的舞台上大施拳脚,不用为涉及不到的领域胡乱苦恼了。

下一章,我们就可以研究如何使用jsp了。

第 2 章 让jsp说hello

注意

为了保证循序渐进的学习状态,我们这里无可奈何的使用了一些在正式工作时明令禁止的代码,请大家在阅读此章务求领会精神,千万不要死记硬背,以防遗憾终身。

如果你不满足以下任一条件,请继续阅读,否则请跳过此后的部分,进入下一章:第 3 章 请求的跳转与转发。

1. 了解从request中获得参数的方法。

2. 了解一些jsp指令(directive)。

3. 了解中文乱码问题。

2.1. 另一个简单jsp

上一篇举的例子很单纯,无论谁向服务器发送请求,服务器都只计算当前系统时间,然后把这个时间制作成http响应发还给浏览器。

可惜这种单向的响应没办法实现复杂的业务,比如像这样:

客户在这个页面输入自己的名字,然后提交:

服务器会对你说:“你好啊,XXX。”

返回刚才的页面,再输入另外一个名字:

提交以后就变成这样:

呵呵,神奇吧,服务器知道你叫什么名字,而且还会向你问好。

虽然咱们都知道这只是从请求中获得了用户名,加上欢迎信息再发送回浏览器,但最终用户会感到更亲切。下面就让我们来看一下如何从请求获得参数吧。

首先用户输入页面会是这样的:

<form action="test.jsp">

username : <input type="text" name="username" />

<br />

<input type="submit">

</form>

这里是一个简单的form,里面只有一个名叫username的文本框,点击提交之后它的值就会提交到后台服务器上的test.jsp。那么在jsp里,咱们如何得到用户输入的username呢?

Hello <%=request.getParameter("username")%>

呼呼,原来就这么简单,前面的Hello 是一成不变的,后面紧接<%%>包含的java代码。

只要是<%%>中间的部分就会被当成java代码执行,咱们就来看看jsp里是通过什么途径获得请求中的参数吧。

首先是request,翻译过来就是请求,它被称作jsp九大默认对象之一,与http请求相关的操作都是通过request实现的,你完全可以把它看作是浏览器发送过来的http请求。

请求里就包含了我们需要获得的参数,你想要知道username的值吗?好的,只要调用getParameter("username")就可以获得了,你交给request一个参数名,它会返回对应的参数值,因为http协议的限制,获得的参数值都是字符串,不

过在参数不存在的时候,也会返回null,所以一定要记住在使用之前判断是否为null,免得频繁出现NullPointerException。

好了,现在我们把得到的参数值输出即可,你可以对语句前面的等号感到疑惑,它的意思就是把参数值输出到页面上,你也可以使用另一种方法:

Hello <%out.print(request.getParameter("username"));%>

上一种方法可以看作是它的简化形式,请注意如果使用第一种方法,是不需要在java代码最后加上分号的,而第二种方法必须加上分号。因为第一种方法是特殊的简化写法,第二种方法是标准的java代码,稍微留意一下就可以区分了。

例子在lingo-sample/02-01/,将目录复制到tomcat的webapp目录下,启动tomcat后可以访问http://localhost:-01/进行测试。

2.2. 中文乱码

你有没有发现,我们的例子中没有中文?

并不是我们不想使用中文,那是因为在jsp中使用中文是一个历史悠久的大问题,虽然等你解决了之后会觉得它是那么简单,但对于没经验的新手来说,中文乱码问题足以搞得他们焦头烂额了。

现在我们就要带领你闯过这道关,消灭掉请求和响应中可能出现的乱码,还中文英雄本色。

2.2.1. 先解决响应中的乱码

何为响应中的乱码?把页面中的“username”改成“用户名”你就知道了。

所谓响应中的乱码,就是显示页面上的乱码,因为页面数据是从服务器一端放入响应(response)中,然后发送给浏览器,如果响应中的数据无法被正常解析,就会出现乱码问题。

为什么英文就没有问题呢?因为在iso-8859-1,gb2312, utf-8以及任意一种编码格式下,英文编码格式都是一样的,每个字符占8位,而中文就麻烦了,在gb2312下一个中文占16位,两字节,而在utf-8下一个中文要占24位,三字节。浏览器在不知道确定编码方式的情况下,就会把这些字符从中间截断,再显

示的时候就乱掉了。所以,想要解决乱码问题,就是要告诉浏览器我们到底使用了什么样的编码方式。

为了获得正常显示的中文,需要注意以下几步:

1. 因为服务器要先从本地读取jsp文件,然后经过处理后写入响应,所以我们首先要知道的就是jsp文件的编码格式。从问题的源头着手解决。

在咱们用的windowxp下,文件默认的编码格式是gb2312。

2. 我们要在http的响应(response)中添加编码信息,使用如下方式:

<%@ page contentType="text/html; charset=gb2312"%>

这段要放在jsp页面的第一行,用来指定响应的类型和编码格式,contentType为text/html就是html内容,charset表示编码为gb2312。这样浏览器就可以从响应中获得编码格式了。

这种<%@ %>的形式叫做jsp指令(directive),现在接触到的是page指令,还有include和taglib指令,我们会在后面陆续讲到。

3. 还需要在html中指定编码格式。

<head>

<meta http-equiv="Content-Type" content="text/html; charset=gb2312" />

<title>title</title>

</head>

meta部分用来指定当前html的编码格式,注意这一段要放在head标签中,并且放到head标签的最前面,如果不是最前面ie下可能会出现问题,尤其是在title中有中文的情况下。

完成了以上三段检验,我们才能保证输出的jsp页面会正常显示中文。

例子在lingo-sample/02-02/下,正常显示的页面index.jsp,还留了一个乱码页面index_wrong.jsp。

2.2.2. POST乱码

先把form里加上method="POST",让form提交的时候使用POST方式。

发送请求的时候,使用的编码是iso-8859-1,意味着只有英文是有效字符,这个限制是因为当初指定http标准的成员都来自英语国家,所以如果使用默认的方式从请求获取数据,中文一定会全部变成乱码。

如果不信,你可以在刚才的例子里输入中文,然后提交:

提交结果就会变成这样:

怎么解决呢?我们要jsp最前面加上一条java语句,设置请求的字符编码。

<%

request.setCharacterEncoding("gb2312");

%>

于是,那些乱码都正常了:

例子在lingo-sample/02-03/下。

2.2.3. GET乱码

直接点击超链接,form的默认提交方式都是GET。

POST方式下的解决方式还算简单,因为POST方式下提交的数据都是以二进制的方式附加在http请求的body部分发送,只需要在后台指定编码格式就足矣解决。

GET方式下会将参数直接附加到url后面,这部分参数无法使用request.setCharacterEncoding()处理,结果就是get形式的所有中文都变成了乱码。

这时再也没有简便方法了,只能对这些中文一个一个进行转换,使用new String(bytes, "gb2312")进行转码。


<%

String username = request.getParameter("username");

byte[] bytes = username.getBytes("iso-8859-1");

String result = new String(bytes, "gb2312");

out.print(result);

%>


如我们所见,先从request中获得参数,接着把字符串按照iso-8859-1编码打散成byte数组,然后用gb2312编码组合成新字符串,最后打印出来就是正常的中文了。

写在一起就变成了:

<%=new String(new String(request.getParameter("username").getBytes("iso-8859-1"), "gb2312")%>

这样做的缺点,是从请求中取得的所有中文都需要转码,非常烦琐。

所以大家千万不要像这样<a href="test.jsp?username=测试">测试</a>,把中文参数写到超链接中,form尽量使用method="POST",这样只需要设置request.setCharacterEncoding()就可以应付中文乱码问题。

例子在lingo-sample/02-04/下。

经历了这些只有非英语体系国家才能遇到的波折之后,我们的jsp终于可以接受请求中的参数,并正常响应了。下面将要进行更复杂的请求响应流程。

第 3 章 请求的跳转与转发

注意

应用广泛,而又没几个人明白的东西上场了,这章就只讨论这一个问题。

如果你确认自己明白下面这个问题,请直接进入下一章:第 4 章 四个作用域。

1. forward, redirect的区别。

3.1. 范例

这次用户可以在首页选择自己喜欢的颜色,进入对应的页面。

选择绿色,会进入绿色界面

选择红色,会进入红色界面

好的,这里我们会看到四个页面:

1. index.jsp中选择颜色,点击按钮后提交到test.jsp。

2. test.jsp取得用户选择的颜色,根据颜色值显示对应的页面。

3. 如果选择了红色,就显示red.jsp。

4. 如果选择了绿色,就显示green.jsp。

在这里例子里,index.jsp,red.jsp,green.jsp中的内容都是一样的,所有的玄机都在test.jsp中。

现在面临的问题是如何在test.jsp决定实现red.jsp或者green.jsp,我们可以在forward和redirect中任选其一。

3.2. 如果用forward

test.jsp中需要这样写:

<%@ page contentType="text/html; charset=gb2312"%>

<%

String color = request.getParameter("color");

if ("red".equals(color)) {

request.getRequestDispatcher("red.jsp").forward(request, response);

} else if ("green".equals(color)) {

request.getRequestDispatcher("green.jsp").forward(request, response);

}

%>

略过取得参数与比较参数值不提,只关注forward的部分:

request.getRequestDispatcher("red.jsp").forward(request, response);

首先调用request的getRequestDispatcher()方法,获得对应red.jsp的转发器,然后调用forward()方法执行请求转发。结果用户看到的就是red.jsp中的结果了,一个红色的页面。

这里请大家注意一下浏览器的url地址:

选择红色页面时:

选择绿色页面时:

于是,无论转发至red.jsp还是green.jsp,地址栏上显示的都是test.jsp。

这是为什么呢?通过下面的流程图会让我们容易理解:

1. 浏览器向test.jsp发送请求。

2. test.jsp计算客户选择的颜色,将请求转发至red.jsp。

3. red.jsp返回响应给浏览器。

28 / 148

这下知道为什么浏览器的地址没有变化了吧?因为浏览器只是执行了对test.jsp的请求,test.jsp到red.jsp的部分是在服务器内执行的,浏览器并不知道服务器里到底发生了什么,它只知道自己获得的响应是test.jsp发回来的,甚至不知道服务器还有个red.jsp。

这就是请求转发forward了。例子见lingo-sample/03-01/。

3.3. 如果用redirect

test.jsp中需要这样写:

<%@ page contentType="text/html; charset=gb2312"%>

<%

String color = request.getParameter("color");

if ("red".equals(color)) {

response.sendRedirect("red.jsp");

} else if ("green".equals(color)) {

response.sendRedirect("green.jsp");

}

%>

略过取得参数与比较参数值不提,只关注redirect的部分:

response.sendRedirect("red.jsp");

response翻译过来就是响应,代表着http响应。调用response的sendRedirect("red.jsp")方法,将页面重定向到red.jsp。

再请大家注意一下浏览器的url地址:

选择红色页面时:

选择绿色页面时:

与forward不同,url地址一直在变化,红色的时候显示red.jsp,绿色的时候显示green.jsp。

再看一下流程图吧:

1. 浏览器向test.jsp发送请求。

2. test.jsp计算客户选择的颜色,向浏览器发送一个页面重定向(redirect)的响应,响应中包含red.jsp的url地址。

30 / 148

3. 浏览器根据页面重定向(redirect)响应中的red.jsp地址,再次向服务器发送请求,这次请求的就是red.jsp了。

4. red.jsp执行,返回响应。

31 / 148

redirect会触发另一个请求响应流程,第二次请求的时候是由浏览器发起对red.jsp的请求,所以url地址改变了。

这就是页面重定向redirect了。例子见lingo-sample/03-02/。

3.4. forward和redirect的问题

3.4.1. 绝对路径与相对路径

1. 如果咱们使用的URL网址是以“/”开头的,那么这个网址就叫做绝对路径。

2. 如果咱们使用的URL网址不是“/”开头的,那么这个网址就叫做相对路径。

3.4.1.1. 相对路径

在相对路径上,两者的表现是相同的。

看看lingo-sample/03-03/这个例子,如果我们去请求relative/forward.jsp或redirect.jsp,然后从这里再跳转向它下面的result/result.jsp会怎样呢?

1. forward的例子:

<%request.getRequestDispatcher("result/result.jsp").forward(request, response);%>

这里的相对路径就是result/result.jsp。

因为刚刚请求的test.jsp是在/03-03/relative/下,所以我们的当前路径就是/03-03/relative/,执行forward的时候会寻找当前路径下的result/result.jsp,找到之后便转发请求。

2. redirect的例子:

<%response.sendRedirect("result/result.jsp");%>

这里的相对路径也是result/result.jsp。

因为刚刚请求的test.jsp是在/03-03/relative/下,所以我们的当前路径就是/03-03/relative/,执行redirect的时候会把当前路径加上result/result.jsp,把结果作为重定向的地址发送给浏览器,浏览器再去请求/03-03/relative/result/result.jsp,从而得到响应。

3.4.1.2. 绝对路径

问题出现了,绝对路径在forward和redirect中出现了差别,还是刚才的情况,但使用绝对路径的时候写法便不同了。

1. forward的例子:

<%request.getRequestDispatcher("/relative/result/result.jsp").forward(request, response);%>

这里的绝对路径就是/relative/result/result.jsp。

在本地测试时,forward把http://localhost:-03/当作根路径,在它的基础上计算绝对路径。

这是由jsp的部署方式决定的,webapp里可以放好多项目,为了让这些项目可以互不影响、独立运行,不能让请求从一个项目直接在服务器内部转移到另一个项目。为了防止出现这种情况,在执行forward的时候干脆把项目的路径当作根目录,开发者看不到其他项目,也就不会出现问题了。

2. redirect的例子:

<%response.sendRedirect("/03-03/absolute/result/result.jsp");%>

这里的绝对路径却是/03-03/absolute/result/result.jsp。

在本地测试时,redirect把http://localhost:8080/当作根路径,在它的基础上计算绝对路径。

因为redirect会让浏览器重新发起一个新请求,所以不会搅乱服务器里多个项目之间的关系,也就不需要对它做限制,如果需要在多个项目之间进行跳转,就只能使用redirect。不过因为重新发起了新的请求,上次请求的那些数据都会丢失,如果有什么重要的数据,记得要重新设置。

3.4.2. forward导致找不到图片

找不到图片,找不到js脚本,找不到css样式表,都属于这个问题。

要演示这个问题,是非常容易的,只需要满足两个条件:

1. forward前后的jsp页面不在一个目录下。

2. forward后的jsp页面里使用相对路径引用一些资源,图片,js脚本,css样式表什么的。

03-04里就模拟了这样一个环境,你进入http://localhost:-04/,选择“有问题的”:

打开03-04可以看到如下的目录结构:

|--+ 03-04

|--- index.jsp

|--- test.jsp

|--+ result

|--- success.jsp

|--- failure.jsp

|--- lingo.png

刚才咱们看到的页面是failure.jsp,它里边显示图片的部分是:

<img src="lingo.png" />

这时候就有疑问了,lingo.png和failure.jsp明明在同一个目录下,为什么无法显示。

现在请在无法显示的图片上,点击鼠标右键,选择属性,让我们看一下图片的请求地址:

图片的位置本来在http://localhost:-04/result/lingo.png,但请求的地址却是http://localhost:-04/lingo.png。问题就是丢掉了中间的/result。

再试一次index.jsp上的“没问题的”:

这次我们看到的页面是success.jsp,它里边显示图片的部分是:

<img src="result/lingo.png" />

结果手工加上result这段路径后就可以显示图片了。

这个问题还要追溯到浏览器对html的处理方式,在html里包含的图片,css样式表,js脚本,视频等等外部资源,都需要浏览器再次向服务器发起请求。

如果这些外部资源使用了相对路径,浏览器就会在当前请求路径的基础上,加上相对路径拼接出完整的http请求,发送给服务器。这个例子中,我们请求http://localhost:-04/test.jsp,浏览器得到的当前路径就是http://localhost:-04/,failure.jsp中图片的相对路径是lingo.png,那么拼接的结果是http://localhost:-04/lingo.png。

不要怪浏览器太傻,是因为使用forward的时候浏览器并不清楚这些改变。它一直认为,既然自己请求的是test.jsp,返回的自然就是test.jsp的内容,那么再使用test.jsp当作当前路径去计算相对路径当然没有问题。是我们欺骗了浏览器,在服务器偷偷改变了请求流向,返回了其他页面的内容。

清楚了以上的请求流程,就知道如何应对这种问题了。

1. 第一种方法:不要在不同目录之间使用forward做请求转发,保证当前路径不发生变化。

2. 第二种方法:像上例一样修改图片路径,或全部改为绝对路径。

请根据实际需要进行选择。

第 4 章 四个作用域

注意

这里介绍的主要是作用域,但实际中是不允许在jsp写些么多代码的,如果你工作了还这样写,要么这个公司的水平有问题,要么你就要被大骂一顿了。请务必领会精神,不要死记硬背。

如果你不满足以下任一条件,请继续阅读,否则请跳过此后的部分,进入下一章:第 5 章 结合javabean实现CRUD。

1. 了解四个作用域(scope)的用处。

2. 了解el(Expression Language)。

4.1. 何为作用域

先让我们看看效果:

大概流程是这样的,我们访问04-01/index.jsp的时候,分别对pageContext, request, session, application四个作用域中的变量进行累加。(当然先判断这个变量是不是存在,如果变量不存在,则要把变量初始化成1。)计算完成后就从index.jsp执行forward跳转到test.jsp。在test.jsp里再进行一次累加,然后显示出这四个整数来。

从显示的结果来看,我们可以直观的得出结论:

1. page里的变量没法从index.jsp传递到test.jsp。只要页面跳转了,它们就不见了。

2. request里的变量可以跨越forward前后的两页。但是只要刷新页面,它们就重新计算了。

3. session和application里的变量一直在累加,开始还看不出区别,只要关闭浏览器,再次重启浏览器访问这页,session里的变量就重新计算了。

37 / 148

4. application里的变量一直在累加,除非你重启tomcat,否则它会一直变大。

而作用域规定的是变量的有效期限。

1. 如果把变量放到pageContext里,就说明它的作用域是page,它的有效范围只在当前jsp页面里。

从把变量放到pageContext开始,到jsp页面结束,你都可以使用这个变量。

2. 如果把变量放到request里,就说明它的作用域是request,它的有效范围是当前请求周期。

所谓请求周期,就是指从http请求发起,到服务器处理结束,返回响应的整个过程。在这个过程中可能使用forward的方式跳转了多个jsp页面,在这些页面里你都可以使用这个变量。

3. 如果把变量放到session里,就说明它的作用域是session,它的有效范围是当前会话。

所谓当前会话,就是指从用户打开浏览器开始,到用户关闭浏览器这中间的过程。这个过程可能包含多个请求响应。也就是说,只要用户不关浏览器,服务器就有办法知道这些请求是一个人发起的,整个过程被称为一个会话(session),而放到会话中的变量,就可以在当前会话的所有请求里使用。

4. 如果把变量放到application里,就说明它的作用域是application,它的有效范围是整个应用。

整个应用是指从应用启动,到应用结束。我们没有说“从服务器启动,到服务器关闭”,是因为一个服务器可能部署多个应用,当然你关闭了服务器,就会把上面所有的应用都关闭了。

application作用域里的变量,它们的存活时间是最长的,如果不进行手工删除,它们就一直可以使用。

与上述三个不同的是,application里的变量可以被所有用户共用。如果用户甲的操作修改了application中的变量,用户乙访问时得到的是修改后的值。这在其他scope中都是不会发生的,page, request, session都是完全隔离的,无论如何修改都不会影响其他人的数据。

我们使用public Object getAttribute(String name)获得变量值,使用public void setAttribute(String name, Object value)将变量值保存到对应作用域中。举个pageContext的例子就是:

// page

Integer countPage = (Integer) pageContext.getAttribute("countPage");

if (countPage == null) {

pageContext.setAttribute("countPage", 1);

} else {

pageContext.setAttribute("countPage", countPage + 1);

}

这里先从pageContext中取出名为countPage的整数,因为返回的都是java.lang.Object类型,所以需要强制转换成我们需要的整形。这里取得的变量如果不存在就会返回null,通过判断countPage == null来辨别变量是否存在,如果不存在就设置为1,如果存在就进行累加,最后使用setAttribute()方法将修改后的变量值放入pageContext。

将其中的pageContext换成request, session, application就可以操作其他三个作用域中的变量。

在显示这些变量值的时候,我们没有写<%=pageContext.getAttribute("countPage")%>而是使用了${countPage}的形式,这种${}的形式叫做el表达式,是jsp-2.0规范的一部分,tomcat里正好可以使用。

使用el有以下几个好处:

1. 代码量小,并且不需要使用尖括号。

2. 支持从pageContext, request, session, application中取值,它会自动检查四个作用域,不需要特别指定。

3. 如果变量不存在,会输出空字符串"",而不是null,省去了手工判断的工作。

4.2. 例子:在线列表

我们做一个新手级的在线用户列表,原理是这样:

1. 用户登录,并把登录使用的用户名保存到session中,通过session中是否存在用户名判断用户是否已登录。

session可以在整个会话过程中保存用户信息,不必每次刷新页面都重新登录。

2. 用户登录后,将用户名添加到application中的在线用户列表。

用户注销时,讲用户名从application中的在线列表删除。

只要服务器还在运行着,application就会保存所有登录用户的信息,所有用户都可以看到这个在线用户列表。

可以尝试一下lingo-sample/04-02/中的例子:

1. 进入登录页面,登陆一个用户。

2. 登录成功既看到已登录的用户名,和当前的在线用户列表。

3. 再登录一个用户.

4. 然后就可以看到在线用户列表增加了,可以看到里面包含上次登录的用户和当前登录的用户。

这时,如果第一个用户刷新页面,也会看到在线用户列表中变成两个人。

5. 现在任何一个用户点击注销,将返回登录页面。另一个用户刷新页面会发现在线用户列表减少了。

让我们从登录页面index.jsp开始,复习一下目前学到的知识。

index.jsp中显示的是用户登录表单,为了显示index.jsp中包含的中文,需要加上<%@ page contentType="text/html; charset=gb2312"%>,这里使用的文件编码是默认的gb2312。

<form action="login.jsp" method="post">

用户名:<input type="text" name="username" />

<br />

<input type="submit" value="登录" />

</form>

在这个form里我们可以输入一个username的值,提交的url是login.jsp,使用post方法是为了更简单的解决中文问题。在填写了用户名之后,点击登录按钮,将数据提交到login.jsp。

login.jsp中进行的是对用户名的操作,包括获得请求中的用户名,将用户名添加到session和在线用户列表中。

<%@ page import="java.util.*"%>

<%

request.setCharacterEncoding("gb2312");

// 取得登录的用户名

String username = request.getParameter("username");

// 把用户名保存进session

session.setAttribute("username", username);

// 把用户名放入在线列表

List onlineUserList = (List) application.getAttribute("onlineUserList");

// 第一次使用前,需要初始化

if (onlineUserList == null) {

onlineUserList = new ArrayList();

application.setAttribute("onlineUserList", onlineUserList);

}

onlineUserList.add(username);

// 成功

response.sendRedirect("result.jsp");

%>

中文编码设置和获得请求参数都已经熟识了。在获得在线用户列表时,先获得application中的onlineUserList,强制转换成List类型。如果onlineUserList并不存在,我们还需要先对它做初始化,并添加到application里。这时有一个小技巧,因为onlineUserList已经放在application中了,将username添加进去后,不必再使用setAttribute()也可以达到修改在下用户列表的效果。

因为此处用到的List和ArrayList都是定义在java.util包内的工具类,如果不希望写成全类名java.util.List, java.util.ArrayList的形式,就需要使用<%@ page import="java.util.*"%>做声明,当然也可以写成<%@ page import="java.util.List,java.util.ArrayList"%>,具体情况就任君选择了。

登录成功后,使用redirect的方式跳转到result.jsp页面,result.jsp页面中显示的是当前登录用户和在线用户列表的信息。

先看一下页面中使用的jsp指令(directive),<%@ page contentType="text/html; charset=gb2312" import="java.util.*"%>,为了处理中文和使用import,可以把这两部分写在一起。

显示当前登陆名时,使用了el表达式:

<h3>您好:${username} [<a href="logout.jsp">注销</a>]</h3>

显示在线用户列表的时候使用了循环:

<%

List onlineUserList = (List) application.getAttribute("onlineUserList");

for (int i = 0; i < onlineUserList.size(); i++) {

String onlineUsername = (String) onlineUserList.get(i);

%>

<tr>

<td><%=onlineUsername%></td>

</tr>

<%

}

%>

这里的循环体可能会令人感到费解,其实它与下面的写法是等价的:

<%

List onlineUserList = (List) application.getAttribute("onlineUserList");

for (int i = 0; i < onlineUserList.size(); i++) {

String onlineUsername = (String) onlineUserList.get(i);

out.println(" <tr>");

out.println(" <td>" + onlineUsername + "</td>");

out.println(" </tr>");

}

%>

只需要理解代码的含义就可以了,从application里获得onlineUserList,然后循环输出所有的用户名。application是公用的,所以可以看到每个登录的用户。

点击注销的时候,会跳转到logout.jsp,这里负责用户注销和从在线用户列表去除已登录用户。

<%@ page import="java.util.*"%>

<%

// 取得登录的用户名

String username = (String) session.getAttribute("username");

// 销毁session

session.invalidate();

// 从在线列表中删除用户名

List onlineUserList = (List) application.getAttribute("onlineUserList");

onlineUserList.remove(username);

// 成功

response.sendRedirect("index.jsp");

%>

这次我们从session中获得登录名,因为请求中没有包含任何数据。session.invalidate()这个方法给我们提供了一条销毁session的捷径,不需要一条一条删除session中的数据,invalidate()会直接销毁session,session里边所有的数据也就消失了。

在线用户列表的操作很直观,从application中获得onlineUserList,然后remove(username)就可以从中去除当前登录用户。最后使用redirect跳转到index.jsp这个登录页面。整个应用的流程也就结束了。

整个应用的功能很单纯,之所以把它叫做“新手级”,是因为它只能用于演示。等待用户去点击注销才去操作在线用户列表存在着很大的漏洞,实际使用中,用户很可能因为个人或网络原因没有进行注销就退出系统,这样会导致用户列表不能删除,就这样一直增长下去。

解决这个问题的方法超出了目前掌握的知识,我们将在后面的章节进行介绍。

第 5 章 结合javabean实现CRUD

注意

这里介绍的是在jsp中使用自己写的javabean,不过这种写法也仅仅适用于小型应用,只打算学两下jsp玩玩的朋友可以到此为止了,完成了这章就不必继续下去了,此后难度会加大不少。

如果你不满足以下任一条件,请继续阅读,否则请跳过此后的部分,进入下一章:第 6 章 贴近servlet

1. 了解如何在jsp中使用自定义的javabean。

2. 了解一些jsp动作(action)。

3. 使用jdbc操作数据库

5.1. 概念和命名方式

需要提及的两个名词概念:

1. CRUD是Create(创建)、Read(读取)、Update(更新)和Delete(删除)的缩写,一般应用有这四项也就足够了。

我们这里的例子是对联系人信息进行CRUD操作。

2. javabean是把一些操作集合在一起写成一个java类,想要进行什么操作直接调用这个类里的方法就行。

咱们这里使用javabean的地方有两处,一个是链接数据库并进行CRUD操作,另一个把每条数据都写 成一个类。

对于CRUD应用,有一些大家默认的命名来表示不同的操作。

1. list.jsp。读取所有信息并显示到页面上,这个是CRUD中Read(读取)。

2. create.jsp。进入添加联系信息的页面,等待用户输入信息。

save.jsp。接收用户提交的信息,添加到数据库中。

这两步对应CRUD中的Create(创建)。

3. edit.jsp。进入修改联系信息的页面,等待用户修改信息。

update.jsp。接收用户提交的信息,修改数据库中对应的信息。

这两步对应CRUD中的Update(更新)。

4. remove.jsp。删除用户选择的信息。这步对应CRUD中的Delete(删除)。

下面我们将按照用户浏览的顺序对这些页面进行介绍。

5.2. Read(读取)

启动服务器,访问http://localhost:-01/就会看到联系信息列表。

看一下05-01目录里边的7个jsp页面,其中6个页面都已经介绍了,只剩index.jsp。

index.jsp是tomcat默认的索引页面,在用户访问http://localhost:-01/的时候会自动执行index.jsp,但我们更希望用户能直接进入list.jsp页面看到所有的联系方式,所以在index.jsp里使用forward跳转到list.jsp。

<%@ page contentType="text/html; charset=gb2312"%>

<jsp:forward page="list.jsp"/>

第一行是我们曾经讲过的设置中文编码。第二行叫做jsp action(jsp动作),它的写法和html标签很相像,有了它们我们可以节省很多java代码。比如,这个jsp动作就与下面的代码功能相同。

<%

request.getRequestDispatcher("list.jsp").forward(request, response);

%>

从长度来看,jsp动作明显占有绝对优势,在单独使用forward的时候,建议大家优先考虑<jsp:forward page="list.jsp"/>的写法。

现在来看list.jsp里的内容,我们是如何获得这些联系信息,并把这些联系信息显示到页面上。

为了便于操作,我们将所有对数据库的操作都封装到anni.ContactDao中,这就是所谓的javabean了。现在我们想要获得所有联系信息时,只要创建一个ContactDao的实例,然后调用contactDao.getAll()获得装满联系信息的List列表就好了。

在创建ContactDao实例的时候,我们使用了另一个jsp动作:jsp:useBean,它就写在list.jsp的第二行。

<jsp:useBean class="anni.ContactDao" id="contactDao" scope="application"/>

看到这里,可能有朋友提问了,既然只是创建一个对象的实例,为什么不用new呢,那要比这样写的代码少许多,也更容易理解,为什么我们还要执意使用jsp:useBean?只是为了尝试新技术吗?

这里我提醒大家注意一下标签中的scope="application",application正是我们介绍过的四个作用域之一,既然有了这个属性就说明事情没有new这么简单了,实际上正因为scope的属性,这段jsp:usebean实际上等价于下面的代码。

<%

anni.ContactDao contactDao = (anni.ContactDao) application.getAttribute("contactDao");

if (contactDao == null) {

contactDao = new anni.ContactDao();

application.setAttribute("contactDao", contactDao);

}

%>

它会先去scope定义的作用域application中取得contactDao对应的对象,这个contactDao正是标签中定义的id,转换的对象类型则是标签中class属性的值。

好的,我们先从application中获得contactDao对应的对象,然后判断得到的是否为null,如果为null说明此变量还没有初始化,这时就要使用new创建一个对象实例并放入application中。最后我们得到的就是这个contactDao实例。

现在我们得到了一个contactDao实例,并把它放到application作用域中,供所有用户公用,通过使用jsp:useBean,我们下面就可以直接使用它获得需要的数据。

<%

List list = contactDao.getAll();

for (int i = 0; i < list.size(); i++) {

pageContext.setAttribute("contact", list.get(i));

pageContext.setAttribute("row", i % 2 != 0 ? "odd" : "even");

%>

<tr class="${row}" onmouseover="this.className='highlight';" onmouseout="this.className='${row}';">

<td>${contact.username}</td>

<td>${contact.sex}</td>

<td>${contact.email}</td>

<td>${contact.qq}</td>

<td>${contact.descn}</td>

<td><a href="edit.jsp?id=${contact.id}">修改</a> | <a href="remove.jsp?id=${contact.id}">删除</a></td>

</tr>

<%

}

%>

首先我们调用contactDao.getAll()获得联系信息的List列表,然后使用for循环将这些信息都输出到页面。

下面的操作比较有趣,我们每获得一条信息就把它放入pageContext中。之所以这样做,是为了在下面使用el表达式显示信息。

el表达式的一个特点就是必须放到作用域里才能调用,如果使用Contact contact = (Contact) list.get(i);而不放到pageContext中,后面的${contact.username}就无法找到contact了。

这里还要提及el表达式的进一步用法,${contact.username}实际上得到的是contact.getUsername()返回的结果。这里有一个默认的转换规则,假设有一个getUsername()方法,我们先要去掉开头的get,然后将get后的那个字母小写,得到的username就是与el表达式中对应的部分。

有趣的是${contact.username}仅仅与getUsername()方法对应,无论contact有没有String username这个变量。如果我们想使用${contact.nameAndSex},只要写一个public String getNameAndSex()方法返回我们想要的数据即可,不需要添加String nameAndSex;变量。

另一个放到pageContext中的row就很直观了,我们根据行数的奇偶来决定当前行使用的css样式,这样就可以显示出斑马线的效果了,这项功能并非必要,只是为了娱乐。

5.3. Create(创建)

选择list.jsp中的“添加联系信息”,即进入create.jsp添加信息页面。

create.jsp中没有包含java,它提供给用户一个输入信息的表单,用户填写过信息后就能点击提交按钮,将数据提交给save.jsp处理。

save.jsp中与create.jsp相反,里边只有处理数据的java代码,没有显示的内容。

<jsp:useBean class="anni.ContactDao" id="contactDao" scope="application"/>

<jsp:useBean class="anni.Contact" id="contact"/>

<jsp:setProperty name="contact" property="*"/>

<%

contactDao.save(contact);

response.sendRedirect("list.jsp");

%>

jsp:useBean的用处我们已经了解了,先从application中取出contactDao,再创建一个contact。在创建contact的时候没有指定scope,默认情况下只会使用new创建这个局部变量,不会对任何作用域产生影响。

jsp:setProperty是新事物了,它的作用就是为某个javabean设置数据。之前我们已经使用jsp:useBean创建了一个contact实例,现在我们通过name="contact"设置这个实例的数据,property可以指定一个属性,比如property="username",也可以使用星号(*)批量设置所有可以找到的属性,这个jsp动作实际上与下面的代码等价。

contact.setUsername(request.getParameter("username"));

contact.setSex(request.getParameter("sex"));

contact.setEmail(request.getParameter("email"));

contact.setQq(request.getParameter("qq"));

contact.setDescn(request.getParameter("descn"));

这些数据都是由create.jsp提交过来的,只要它们的名称与contact中的方法对应(这次是set开头的方法了),jsp:setProperty就可以自动为它们进行赋值,转换的规则与get的方法名是类似的。

通过这一系列的jsp动作,我们得到的contact中已经设置好了用户刚刚填写的数据,现在只要进行保存就好了。

contactDao.save(contact);

response.sendRedirect("list.jsp");

contactDao.save()也已经封装好了,直接调用便完成添加功能。操作完成后记得要调用sendRedirect,将页面重定向到list.jsp,查看添加后的结果。

5.4. Update(更新)

点击列表右侧的“修改”,对这一行显示的联系信息进行修改。

虽然页面的布局与create.jsp基本相同,但为了预先显示需要修改的数据,我们需要根据请求中的id值,去数据库中查找对应的联系信息,再显示到jsp中。

点击“修改”的时候,id便附加到url后了。

<a href="edit.jsp?id=${contact.id}">修改</a>

edit.jsp中获得id的值,根据id从contactDao获得对应的联系信息contact,再将contact放到pageContext供后面的el表达式使用。

<jsp:useBean class="anni.ContactDao" id="contactDao" scope="application"/>

<%

String id = request.getParameter("id");

Contact contact = contactDao.get(Long.parseLong(id));

pageContext.setAttribute("contact", contact);

%>

在html中显示信息的时候,直接使用el表达式,如果属性值不存在,el也会自动输出空字符串"",这对我们来说都是非常便捷的。

<input type="text" name="username" value="${contact.username}" /> 51 / 148

edit.jsp中还有一点儿隐藏的玄机,表单中定义了一个名叫id的隐藏属性,这样服务器才能知道我们需要修改那一条数据。

<input type="hidden" name="id" value="${contact.id}" />

既然是隐藏的,我们便不能在页面上看到它,但在提交的时候它还会与其他数据一起发送到服务器。

修改信息之后,点击提交发送请求。

update.jsp中与save.jsp相似,唯一不同的是这次我们调用的是contactDao.update()而不是contactDao.save()。

contactDao.update(contact);

update()会根据id的值修改数据库中对应的数据,而不是添加一条新数据,这从跳转后的list.jsp可以看出来。

5.5. Delete(删除)

点击列表右侧的“删除”,就会删除这条数据。

remove.jsp与修改数据时一样,都需要传递一个id来指定要操作哪一条记录。

<a href="remove.jsp?id=${contact.id}">删除</a>

remove.jsp依然没有任何显示,仅仅使用java操作。

<jsp:useBean class="anni.ContactDao" id="contactDao" scope="application"/>

<%

String id = request.getParameter("id");

contactDao.remove(Long.parseLong(id));

response.sendRedirect("list.jsp");

%>

因为只需要id和contactDao,remove.jsp的代码十分单纯,首先从request中获得id,然后删除id对应的记录,最后页面重定向到list.jsp。删除便成功了。

5.6. 用jdbc操作数据库

虽然有人说JDBC是Java Database Bridge Connection(java数据库桥接)的缩写,但sun公司一直没有承认这种解释。不过jdbc确实是一种桥接方式,所有服务器厂商都为jdbc提供对应自己数据库的驱动,我们只要学会使用jdbc中的类和方法,就可以通过它操作任何一款数据库了。

这次我们使用的是一个名叫hsqldb的嵌入型数据库,它是使用java编写的,把hsqldb-1.8.0.7.jar放到WEB-INF/lib/目录下就可以使用了。

现在看我们是如何连接数据库的,hsqldb-1.8.0.7.jar中已经为我们提供了jdbc驱动,为了方便调用我们将jdbc的配置封装在anni.DbUtils中。

1. Class.forName("org.hsqldb.jdbcDriver");加载jdbc驱动。

Class.forName()是惯用写法,可以强制加载指定的类,org.hsqldb.jdbcDriver是hsqldb驱动的名称,只需要记忆即可。

2. 与数据库建立连接。

DriverManager.getConnection("jdbc:hsqldb:res:/hsqldb/contact", "sa", "");

三个参数分别是连接数据库使用的url,登录用户名和密码。

url以jdbc:hsqldb:开头,表明它将使用hsqldb的驱动,后面的res:/hsqldb/contact是hsqldb的一种连接方式,它将去classpath下的hsqldb目录中读取名为contact的数据库文件作为初始配置,在这里classpath就是指的WEB-INF/classes/,你可以在WEB-INF/classes/hsqldb/下看到两个数据库文件,contact.properties和contact.script。

3. 数据库连接十分消耗系统资源,一定要记得在使用完成后关闭,一旦忘记关闭,资源很快就会耗尽,你会得到一连串无法连接数据库的错误。

anni.DbUtils中我们提供了一个close()方法来关闭连接。

完整的anni.DbUtils代码在WEB-INF/src/DbUtils.java。

数据库contact的表结构写在WEB-INF/sql/import.sql中。

create table contact (

id bigint,

username varchar(100),

sex varchar(100),

email varchar(100),

qq varchar(100),

descn varchar(200)

);

与之对应的anni.Contact的结构大致如下。

package anni;

public class Contact {

private Long id;

private String username;

private String sex;

private String email;

private String qq;

private String descn;

// getter and setter

}

javabean中的属性与数据库中的字段一一对应,习惯上将属性定义为private,并配上对应的getter与setter方法,这样就构成了一个典型的javabean。anni.Contact的源代码在WEB-INF/src/Contact.java。

anni.ContactDao是完成CRUD操作的主体,在这里集合了anni.Contact和anni.DbUtils为jsp提供调用的方法。

1. Read(读取)。

/**

* 获得所有联系簿.

*

* @return contact列表

*/

public List<Contact> getAll() throws Exception {

Connection conn = null;

Statement state = null;

ResultSet rs = null;

List<Contact> list = new ArrayList<Contact>();

try {

conn = DbUtils.getConn();

state = conn.createStatement();

rs = state.executeQuery("select * from contact");

while (rs.next()) {

Contact contact = new Contact();

contact.setId(rs.getLong("id"));

contact.setUsername(rs.getString("username"));

contact.setSex(rs.getString("sex"));

contact.setEmail(rs.getString("email"));

contact.setQq(rs.getString("qq"));

contact.setDescn(rs.getString("descn"));


list.add(contact);

}

} finally {

DbUtils.close(rs, state, conn);

}

return list;

}

第一步,使用DbUtils.getConn()建立与数据库的连接Connection。

第二步,从Connection创建一个Statement。

第三步,使用Statement执行sql查询语句,返回查询结果集ResultSet。

第四步,将ResultSet中的数据转换成Contact队列。

第五步,关闭数据库的连接,并返回Contact队列作为结果。


Connection -> Statement -> ResultSet -> close()是一个查询功能的基本结构。

这里使用的sql语句会获得数据库中所有的联系信息,所以我们循环读取ResultSet最后得到一个Contact队列。另一个方法public Contact get(Long id)中会根据指定的主键获得一条对应记录,虽然依然返回ResultSet,但这次ResultSet中只包含一条数据,所以最终只会获得一个Contact对象。

2. Create(创建)

/**

* 向数据库插入一条数据.

*

* @param contact 联系信息

*/

public void save(Contact contact) throws Exception {

Connection conn = null;

PreparedStatement state = null;

try {

conn = DbUtils.getConn();

state = conn.prepareStatement("insert into contact(username,sex,email,qq,descn) values(?,?,?,?,?)");

state.setString(1, contact.getUsername());

state.setString(2, contact.getSex());

state.setString(3, contact.getEmail());

state.setString(4, contact.getQq());

state.setString(5, contact.getDescn());


state.executeUpdate();

} finally {

DbUtils.close(null, state, conn);

}

<p