2.7.3 JSP Servlet的生命周期

JSP的本质就是Servlet,开发者编写的JSP页面将由web容器编译成对应的Servlet,当Servlet在容器中运行时,其实例的创建及销毁等都不是由程序员决定的,而是由Web容器进行控制的。
创建Servlet实例有两个时机。

  1. 客户端第一次请求某个Servlet时,系统创建该Servlet的实例:大部分的Servlet都是这种Servlet
  2. Web应用启动时立即创建Servlet实例,即load-on-startup这个Servlet

每个Servlet的运行都遵循如下生命周期

  1. 创建Servlet实例。
  2. Web容器调用Servletinit方法,对Servlet进行初始化
  3. Servlet初始化后,将一直存在于容器中,用于响应客户端请求。
    1. 如果客户端发送GET请求,容器调用ServletdoGet()方法处理并响应请求;
    2. 如果客户端发送POST请求,容器调用ServletdoPost()方法处理并响应请求。
    3. 或者统一使用service()方法处理来响应用户请求。
  4. web容器决定销毁Servlet时,先调用Servletdestroy方法,通常在关闭web应用之时销毁Servlet

Servlet的生命周期如图2.33所示。

2.7.2 Servlet的配置

编辑好的Servlet源文件并不能响应用户请求,还必须将其编译成class文件。将编译后的Firstservlet.class文件放在WEB-INF/classes路径下,如果Servlet有包,则还应该将class文件放在对应的包路径下(例如,本例的FirstServlet.class就放在WEB-INF/classes/lee路径下)。
如果需要直接采用javac命令来编译Servlet类,则必须将Servlet API接口和类添加到系统的CLASSPATH环境变量里。也就是将Tomcat 8.5安装目录下lib目录中servlet-api.jarjsp-api.ar添加到CLASSPATH环境变量中.

为了让Servlet能响应用户请求,还必须将Servlet配置在web应用中。配置Servlet时,需要修改web.xml文件。

配置Servlet的两种方式

Servlet3开始,配置Servlet有两种方式。

  • Servlet类中使用@WebServlet注解进行配置。
  • 通过在web.xml文件中进行配置。

@WebServlet注解属性

上面开发Servlet类时使用了@WebServlet注解修饰该Servlet类,使用@WebServlet时可指定如表2.2所示的常用属性。

属性 说明
asyncSupported 指定该Servlet是否支持异步操作模式。关于Servlet的异步调用请参考2.2节
displayName 指定该Servlet的显示名
initParams 用于为该Servlet配置参数
loadOnStartup 用于将该Servlet配置成load-on-startupServlet
name 指定该Servlet的名称
urlPatterns/value 这两个属性的作用完全相同。都指定该Servlet处理的URL

如果打算使用注解来配置Servlet,有两点需要指出。

  • 不要web.xml文件的根元素(<web-app>)中指定metadata-complete="true"“。
  • 不要在web.xml文件中配置该Servlet

使用web.xml配置Servlet步骤

如果打算使用web.xml文件来配置该Servlet,则需要配置如下两个部分。

  • 配置Servlet的名字:对应web.xml文件中的<servlet>元素。
  • 配置ServletURL:对应web.xml文件中的<Servlet-mapping>元素。这一步是可选的。但如果没有为Servlet配置URL,则该Servlet不能响应用户请求。

接下来的ServletFilterListener等相关配置,都会同时介绍使用web.xml配置、使用注解配置两种方式。但实际项目中只要采用任意一种配置方式即可,不需要同时使用两种配置方式。

web.xml中配置Servlet示例

因此,配置一个能响应客户请求的Servlet,至少需要配置两个元素。关于上面的FirstServlet的配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!-- 配置Servlet的名字 -->
<servlet>
<!-- 指定Servlet的名字,
相当于指定@WebServlet的name属性 -->
<servlet-name>firstServlet</servlet-name>
<!-- 指定Servlet的实现类 -->
<servlet-class>lee.FirstServlet</servlet-class>
</servlet>
<!-- 配置Servlet的URL -->
<servlet-mapping>
<!-- 指定Servlet的名字 -->
<servlet-name>firstServlet</servlet-name>
<!-- 指定Servlet映射的URL地址,
相当于指定@WebServlet的urlPatterns属性-->
<url-pattern>/aa</url-pattern>
</servlet-mapping>

如果在web.xm1文件中增加了如上所示的配置片段,则该ServletURL/aa
如果没有在web.xml文件中增加上面的配置片段,那么该Servlet类上的@WebServlet注解就会起作用,该ServletURL/firstServlet

form.jsp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<%@ page contentType="text/html; charset=UTF-8" language="java" errorPage="" %>
<!DOCtype html>
<html>
<head>
<title> 收集参数的表单页 </title>
</head>
<body>
<form id="form1" method="post" action="aa">
用户名:<br/>
<input type="text" name="name"><hr/>
性别:<br/>
男:<input type="radio" name="gender" value="男">
女:<input type="radio" name="gender" value="女"><hr/>
喜欢的颜色:<br/>
红:<input type="checkbox" name="color" value="红">
绿:<input type="checkbox" name="color" value="绿">
蓝:<input type="checkbox" name="color" value="蓝"><hr/>
来自的国家:<br/>
<select name="country">
<option value="中国">中国</option>
<option value="美国">美国</option>
<option value="俄罗斯">俄罗斯</option>
</select><hr/>
<input type="submit" value="提交">
<input type="reset" value="重置">
</form>
</body>
</html>

在表单域中输入相应的数据,然后单击“提交”按钮,效果如图2.32所

在这种情况下,ServletJSP的作用效果完全相同。

2.7 Servlet介绍

前面已经介绍过,JSP的本质就是Servlet,开发者把编写好的JSP页面部署在web容器中之后,web容器会将JSP编译成对应的Servlet。但直接使用Servlet的坏处是:Servlet的开发效率非常低,特别是当使用Servlet生成表现层页面时,页面中所有的HTML标签,都需采用Servlet的输出流来输出,因此极其烦琐。而且Servlet是标准的Java类,必须由程序员开发、修改,美工人员难以参与Servlet页面的开发。这一系列的问题,都阻碍了Servlet作为表现层的使用。
MVC规范出现后,Servlet的责任开始明确下来,仅仅作为控制器使用,不再需要生成页面标签,也不再作为视图层角色使用。

2.7.1 Servlet的开发

前面介绍的JSP的本质就是Servlet,Servlet通常被称为服务器端小程序,是运行在服务器端的程序,用于处理及响应客户端的请求.
Servlet是个特殊的Java类,这个Java类必须继承HttpServlet。每个Servlet都可以响应客户端的请求.

HttpServlet方法

相应特定请求的方法

Servlet提供不同的方法用于响应客户端请求:

  • doGet:用于响应客户端的GET请求。
  • dopost:用于响应客户端的POST请求。
  • doPut:用于响应客户端的PUT请求。
  • doDelete:用于响应客户端的DELETE请求。

事实上,客户端的请求通常只有GETPOST两种,Servlet为了响应这两种请求,必须重写doGeto0donoso两个方法。如果Servlet为了响应4种方式的请求,则需要同时重写上面的4个方法。

service方法

大部分时候,Servlet对于所有请求的响应都是完全一样的。此时,可以采用重写一个方法来代替上面的几个方法:只需重写service()方法即可响应客户端的所有请求
另外,HttpServlet还包含两个方法:

  • init(ServletConfig config):创建Servlet实例时,调用该方法的初始化Servlet资源
  • destroy():销毁Servlet实例时,自动调用该方法的回收资源

通常无须重写initdestroy两个方法,除非需要在初始化Servlet时,完成某些资源初始化的方法,才考虑重写init方法。如果需要在销毁Servlet之前,先完成某些资源的回收,比如关闭数据库连接等,才需要重写destroy方法。

不用为Servlet类编写构造器,如果需要对Servlet执行初始化操作,应将初始化操作放在Servletinit()方法中定义。如果重写了init(ServletConfig config)方法,则应在重写该方法的第一行调用super.init(config)。该方法将调用HttpServletinit方法.

下面提供一个Servlet的示例,该Servlet将获取表单请求参数,并将请求参数显示给客户端。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package lee;

import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.*;

// Servlet必须继承HttpServlet类
@WebServlet(name = "firstServlet", urlPatterns = { "/firstServlet" })
public class FirstServlet extends HttpServlet {
// 客户端的响应方法,使用该方法可以响应客户端所有类型的请求
public void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, java.io.IOException {
// 设置解码方式
request.setCharacterEncoding("GBK");
response.setContentType("text/html;charSet=GBK");
// 获取name的请求参数值
String name = request.getParameter("name");
// 获取gender的请求参数值
String gender = request.getParameter("gender");
// 获取color的请求参数值
String[] color = request.getParameterValues("color");
// 获取country的请求参数值
String national = request.getParameter("country");
// 获取页面输出流
PrintStream out = new PrintStream(response.getOutputStream());
// 输出HTML页面标签
out.println("<html>");
out.println("<head>");
out.println("<title>Servlet测试</title>");
out.println("</head>");
out.println("<body>");
// 输出请求参数的值:name
out.println("您的名字:" + name + "<hr/>");
// 输出请求参数的值:gender
out.println("您的性别:" + gender + "<hr/>");
// 输出请求参数的值:color
out.println("您喜欢的颜色:");
for (String c : color) {
out.println(c + " ");
}
out.println("<hr/>");
// 输出请求参数的值:national
out.println("您来自的国家:" + national + "<hr/>");
out.println("</body>");
out.println("</html>");
}
}

上面的Servlet类继承了HttpServlet类,表明它可作为一个Servlet使用。程序的粗体字代码定义了service方法来响应用户请求。对比该Servlet和2.6.6节中的request1.jsp页面,该Servletrequest1.jsp页面的效果完全相同,都通过HttpServletRequest获取客户端的form请求参数,并显示请求参数的值。
ServletJSP的区别在于:

  • Servlet中没有内置对象,原来JSP中的内置对象都必须由程序显式创建。
  • 对于静态的HTML标签,Servlet都必须使用页面输出流逐行输出。

这也正是前面介绍的,JSPServlet的一种简化,使用JSP只需要完成程序员需要输出到客户端的内容,至于JSP脚本如何嵌入一个类中,由JSP容器完成。而Servlet则是个完整的Java类,这个类的service方法用于生成对客户端的响应。
普通Servlet类里的service方法的作用,完全等同于JSP生成Servlet类的_jspService()方法。因此原JSP页面的JSP脚本、静态HTML内容,在普通Servlet里都应该转换成service方法的代码或输出语句;原JSP声明中的内容,对应为在Servlet中定义的成员变量或成员方法。

2.6.8 session对象

session对象也是一个非常常用的对象,这个对象代表一次用户会话。一次用户会话的含义是:从客户端浏览器连接服务器开始,到客户端浏览器与服务器断开为止,这个过程就是一次会话。
session通常用于跟踪用户的会话信息,如判断用户是否登录系统,或者在购物车应用中,用于跟踪用户购买的商品等。
session范围内的属性可以在多个页面的跳转之间共享。一旦关闭浏览器,即session结束,session范围内的属性将全部丢失。
session对象是Httpsession的实例,HttpSession有如下两个常用的方法。

session方法 描述
setAttribute(String attName, Object attValue) 设置session范围内attName属性的值为attValue.
getAttribute(String attName) 返回session范围内attName属性的值

shop.jsp

下面的示例演示了一个购物车应用,以下是陈列商品的JSP页面代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<%@ page contentType="text/html; charset=UTF-8" language="java" errorPage="" %>
<!DOCtype html>
<html>
<head>
<title> 选择物品购买 </title>
</head>
<body>
<form method="post" action="processBuy.jsp">
书籍:<input type="checkbox" name="item" value="book"/><br/>
电脑:<input type="checkbox" name="item" value="computer"/><br/>
汽车:<input type="checkbox" name="item" value="car"/><br/>
<input type="submit" value="购买"/>
</form>
</body>
</html>

这个页面几乎没有动态的JSP部分,全部是静态的HTML内容。该页面包含一个表单,表单里包含三个复选按钮,用于选择想购买的物品,表单由processBuy.jsp页面处理,其页面的代码如下。

processBuy.jsp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
<%@ page contentType="text/html; charset=UTF-8" language="java" %>
<%@ page import="java.util.*"%>
<%
//取出session范围的itemMap属性
Map<String,Integer> itemMap = (Map<String,Integer>)session
.getAttribute("itemMap");
//如果Map对象为空,则初始化Map对象
if (itemMap == null)
{
itemMap = new HashMap<String,Integer>();
itemMap.put("书籍" , 0);
itemMap.put("电脑" , 0);
itemMap.put("汽车" , 0);
}
//获取上个页面的请求参数
String[] buys = request.getParameterValues("item");
//遍历数组的各元素
for (String item : buys)
{
//如果item为book,表示选择购买书籍
if(item.equals("book"))
{
int num1 = itemMap.get("书籍").intValue();
//将书籍key对应的数量加1
itemMap.put("书籍" , num1 + 1);
}
//如果item为computer,表示选择购买电脑
else if (item.equals("computer"))
{
int num2 = itemMap.get("电脑").intValue();
//将电脑key对应的数量加1
itemMap.put("电脑" , num2 + 1);
}
//如果item为car,表示选择购买汽车
else if (item.equals("car"))
{
int num3 = itemMap.get("汽车").intValue();
//将汽车key对应的数量加1
itemMap.put("汽车" , num3 + 1);
}
}
//将itemMap对象放到设置成session范围的itemMap属性
session.setAttribute("itemMap" , itemMap);
%>
<!DOCTYPE html>
<html>
<head>
<title> 购买的物品列表 </title>
</head>
<body>
您所购买的物品:<br/>
书籍:<%=itemMap.get("书籍")%>本<br/>
电脑:<%=itemMap.get("电脑")%>台<br/>
汽车:<%=itemMap.get("汽车")%>辆
<p><a href="shop.jsp">再次购买</a></p>
</body>
</html>

以上页面中粗体字代码使用session来保证itemMap对象在一次会话中有效,这使得该购物车系统可以反复购买,只要浏览器不关闭,购买的物品信息就不会丢失,图2.31显示的是多次购买后的效果。

考虑session本身的目的,通常只应该把与用户会话状态相关的信息放入session范围内。不要仅仅为了两个页面之间交换信息,就将该信息放入session范围内。如果仅仅为了两个页面交换信息,可以将该信息放入request范围内,然后forward请求即可.

session里面的属性必须可序列化

关于session还有一点需要指出,session机制通常用于保存客户端的状态信息,这些状态信息需要保存到web服务器的硬盘上,所以要求session里的属性值必须是可序列化的,否则将会引发不可序列化的异常。
session的属性值可以是任何可序列化的Java对象。

2.6.7 response对象

response代表服务器对客户端的响应。大部分时候,程序无须使用response来响应客户端请求,因为有个更简单的响应对象out,它代表页面输出流,直接使用out生成响应更简单。
outJspWriter的实例,JspWriterWriter的子类,Writer是字符流,无法输出非字符内容假如需要在JSP页面中动态生成一幅位图、或者输出一个PDF文档,使用out作为响应对象将无法完成,此时必须使用response作为响应输出。
除此之外,还可以使用response来重定向请求,以及用于向客户端增加Cookie.

1. response响应生成非字符响应

对于需要生成非字符响应的情况,就应该使用response来响应客户端请求。下面的JSP页面将在客户端生成一张图片。

getOutputStream方法

responseHttpServletResponse接口的实例,该接口提供了一个getOutputStream()方法,该方法返回响应输出字节流.

img.jsp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<%-- 通过contentType属性指定响应数据是图片 --%>
<%@ page contentType="image/png" language="java"%>
<%@ page import="java.awt.image.*,javax.imageio.*,java.io.*,java.awt.*"%>
<%
// 创建BufferedImage对象
BufferedImage image = new BufferedImage(340 ,
160, BufferedImage.TYPE_INT_RGB);
// 以Image对象获取Graphics对象
Graphics g = image.getGraphics();
// 使用Graphics画图,所画的图像将会出现在image对象中
g.fillRect(0,0,400,400);
// 设置颜色:红
g.setColor(new Color(255,0,0));
// 画出一段弧
g.fillArc(20, 20, 100,100, 30, 120);
// 设置颜色:绿
g.setColor(new Color(0 , 255, 0));
// 画出一段弧
g.fillArc(20, 20, 100,100, 150, 120);
// 设置颜色:蓝
g.setColor(new Color(0 , 0, 255));
// 画出一段弧
g.fillArc(20, 20, 100,100, 270, 120);
// 设置颜色:黑
g.setColor(new Color(0,0,0));
g.setFont(new Font("Arial Black", Font.PLAIN, 16));
// 画出三个字符串
g.drawString("red:climb" , 200 , 60);
g.drawString("green:swim" , 200 , 100);
g.drawString("blue:jump" , 200 , 140);
g.dispose();
// 将图像输出到页面的响应
ImageIO.write(image , "png" , response.getOutputStream());
%>

以上页面的代码:

1
<%@ page contentType="image/png" language="java"%>

先设置了服务器响应数据是image/png,这表明服务器响应是一张PNG图片。
接着创建了一个BufferedImage对象(代表图像),并获取该BufferedImageGraphics对象(代表画笔),然后通过GraphicsBufferedImage中绘制图形,最后一行代码将直接将BufferedImage作为响应发送给客户端。

测试

请直接在浏览器中请求该页面,将看到浏览器显张图片,效果如图2.29所示。

jsp页面直接作为img标签的地址

也可以在其他页面中使用img标签来显示这个图片页面,代码如下:

1
<img src="img.jsp">

图形验证码

使用这种临时生成图片的方式就可以非常容易地实现网页上的图形验证码功能。不仅如此,使用response生成非字符响应还可以直接生成PDF文件、Excel文件,这些文件可直接作为报表使用。

2. 重定向

重定向会丢失请求数据

重定向是response的另外一个用处,与forward不同的是,重定向会丢失所有的请求参数和request范围的属性,因为重定向将生成第二次请求,与前一次请求不在同一个request范围内,所以发送一次请求的请求参数和request范围的属性全部丢失。

response.sendRedirect方法

HttpServletResponse提供了一个sendRedirect(String path)方法,该方法用于重定向到path资源,即重新向path资源发送请求。

doRedirect.jsp

下面的JSP页面将使用response执行重定向。

1
2
3
4
5
6
7
<%@ page contentType="text/html; charset=UTF-8" language="java" errorPage="" %>
<%
// 生成页面响应
out.println("====");
// 重定向到redirect-result.jsp页面
response.sendRedirect("redirect-result.jsp");
%>

以上页面的代码:

1
response.sendRedirect("redirect-result.jsp");

用于执行重定向,向该页面发送请求时,请求会被重定向到redirect-result.jsp页面。例如,在地址栏中输入htp:/Localhost888LjspObject/doRedirectJsp?name=Crazyit.org,然后按回车键,将看到如图2.30所示的效果。

注意地址栏的改变,执行重定向动作时,地址栏的URL会变成重定向的目标URL,而且重定向丢失了请求数据。

重定向会丢失所有的请求参数,使用重定向的效果,与在地址栏里重新输入新地址再按回车键的效果完全一样,即发送了第二次请求。

forward和redirect的区别

从表面上来看,forward动作和redirect动作有些相似:它们都可将请求传递到另一个页面。但实际上forwardredirect之间存在较大的差异,forwardredirect的差异如表2.1所示

转发(forward) 重定向(redirect)
执行forward后依然是上一次请求 执行redirect后生成第二次请求
forward的目标页面可以访问原请求的请求参数,因为依然是同一次请求,所有原请求的请求参数、request范围的属性全部存在 redirect的目标页面不能访问原请求的请求参数,因为是第二次请求了,所有原请求的请求参数、request范围的属性全部丢失
地址栏里请求的URL不会改变 地址栏改为重定向的目标URL。相当于在浏览器地址栏里输入新的URL后按回车键

3. 增加Cookie

Cookie通常用于网站记录客户的某些信息,比如客户的用户名及客户的喜好等。一旦用户下次登录,网站可以获取到客户的相关信息,根据这些客户信息,网站可以对客户提供更友好的服务。

cookie和session的区别

Cookiesession的不同之处在于:session会随浏览器的关闭而失效,但Cookie会一直存放在客户端机器上,除非超出Cookie的生命期限。

客户端可禁用cookie

由于安全性的原因,使用Cookie客户端浏览器必须支持Cookie才行。客户端浏览器完全可以设置禁用Cookie
增加Cookie也是使用response内置对象完成的,response对象提供了如下方法。

response对象方法 描述
void addCookie(Cookie cookie) 增加Cookie

在增加Cookie之前,必须先创建Cookie对象。

添加Cookie的步骤

增加Cookie请按如下步骤进行:

  1. 创建Cookie实例,Cookie的构造器为Cookie(String name, String value)
  2. 设置Cookie的生命期限,即该Cookie在多长时间内有效
  3. 向客户端写Cookie

程序示例 读写cookie

addCookie.jsp

看如下JSP页面,该页面可以用于向客户端写一个usernameCookie

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<%@ page contentType="text/html; charset=UTF-8" language="java" errorPage="" %>
<!DOCTYPE html>
<html>
<head>
<title> 增加Cookie </title>
</head>
<body>
<%
// 获取请求参数
String name = request.getParameter("name");
// 以获取到的请求参数为值,创建一个Cookie对象
Cookie c = new Cookie("username" , name);
// 设置Cookie对象的生存期限
c.setMaxAge(24 * 3600);
// 向客户端增加Cookie对象
response.addCookie(c);
%>
</body>
</html>

如果浏览器没有阻止Cookie,在地址栏输入:

1
http://localhost:8080/jspObject/addCookie.jsp?name=Trump

执行该页面后,网站就会向客户端机器写入一个名为usernameCookie,该Cookie将在客户端硬盘上一直存在,直到超出该Cookie的生存期限(本Cookie设置为24小时)。

读取cookie request.getCookies方法

访问客户端Cookie使用request对象。

  1. request对象提供了getCookies()方法,该方法将返回客户端机器上所有Cookie组成的数组,
  2. 然后遍历该数组的每个元素,找到希望访问的Cookie即可。

readCookie.jsp

下面是访问CookieJSP页面的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<%@ page contentType="text/html; charset=UTF-8" language="java" errorPage="" %>
<!DOCTYPE html>
<html>
<head>
<title> 读取Cookie </title>
</head>
<body>
<%
// 获取本站在客户端上保留的所有Cookie
Cookie[] cookies = request.getCookies();
// 遍历客户端上的每个Cookie
for (Cookie c : cookies)
{
// 如果Cookie的名为username,表明该Cookie是需要访问的Cookie
if(c.getName().equals("username"))
{
out.println(c.getValue());
}
}
%>
</body>
</html>

上面的代码就是通过request读取Cookie数组,并搜寻指定Cookie的关键代码,访问该页面即可读出刚才写在客户端的Cookie

使用Cookie对象必须设置其生存期限,否则Cookie将会随浏览器的关闭而自动消失
默认情况下,Cookie值不允许出现中文字符,如果需要值为中文内容的Cookie怎么办呢?同样可以借助于java.net.URLEncoder先对中文字符串进行编码,将编码后的结果设为Cookie值。当程序要读取Cookie时,则应该先读取,然后使用java.net.URLDecoder对其进行解码。

cnCookie.jsp

如下代码片段示范了如何存入值为中文的Cookie

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<%@ page contentType="text/html; charset=UTF-8" language="java" errorPage="" %>
<!DOCTYPE html>
<html>
<head>
<title> 中文Cookie </title>
</head>
<body>
<%
// 以编码后的字符串为值,创建一个Cookie对象
Cookie c = new Cookie("cnName"
, java.net.URLEncoder.encode("孙悟空" , "gbk"));
// 设置Cookie对象的生存期限
c.setMaxAge(24 * 3600);
// 向客户端增加Cookie对象
response.addCookie(c);

// 获取本站在客户端上保留的所有Cookie
Cookie[] cookies = request.getCookies();
// 遍历客户端上的每个Cookie
for (Cookie cookie : cookies)
{
// 如果Cookie的名为username,表明该Cookie是需要访问的Cookie
if(cookie.getName().equals("cnName"))
{
//使用java.util.URLDecoder对Cookie值进行解码
out.println(java.net.URLDecoder
.decode(cookie.getValue()));
}
}
%>
</body>
</html>

上面的程序中两行粗体字代码是存入值为中文的Cookie的关键:存入之前先用java.net.URLEncoder进行编码;读取时需要对读取的Cookie值用Java.net.URLDecoder进行解码。

2.6.6 request对象 3. 执行forward或include

request还有一个功能就是执行forwardinclude,也就是代替JSP所提供的forwardinclude动作指令。前面需要forward时都是通过JSP提供的动作指令进行的,实际上request对象也可以执行forward

RequestDispatcher对象的forward和include方法

HttpServletRequest类提供了一个getRequestDispatcher(String path)方法,其中path就是希望forward或者include的目标路径,该方法返回RequestDispatcher,该对象提供了如下两个方法:

RequestDispatcher对象方法 描述
forward(ServletRequest request, ServletResponse response) 执行forward
include(ServletRequest request, ServletResponse response) 执行include

requset执行include

如下代码行可以将a.jsp页面include到本页面中:

1
requset.getRequestDispatcher("/a.jsp").include(request,response);

requset执行forward

如下代码行则可以将请求forwarda.jsp页面:

1
request.getRequestDispatcher("/a.jsp").forward(request,response);

path必须以斜线开头

使用requestgetRequestDispatcher(String path)方法时,该path字符串必须以斜线开头。

2. 操作request范围的属性

HttpServletRequest还包含如下两个方法,用于设置和获取request范围的属性。

设置获取request作用域的属性 描述
setAttribute(String attName, Object attValue) attValue设置成request范围的属性。
Object getAttribute(String attName) 获取request范围的属性

forward请求是 请求参数 请求属性不会丢失

forward用户请求时,请求的参数和请求属性都不会丢失。

draw.jsp

看如下JSP页面,这个JSP页面是个简单的表单页,用于提交用户请求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<%@ page contentType="text/html; charset=UTF-8" language="java" errorPage="" %>
<!DOCTYPE html>
<html>
<head>
<title> 取钱的表单页 </title>
</head>
<body>
<!-- 取钱的表单 -->
<form method="post" action="first.jsp">
取钱:<input type="text" name="balance">
<input type="submit" value="提交">
</form>
</body>
</html>

first.jsp

该页面向first.jsp页面请求后,balance参数将被提交到first.jsp页面,下面是first.jsp页面的实现代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<%@ page contentType="text/html; charset=UTF-8" language="java" errorPage="" %>
<%@ page import="java.util.*" %>
<!DOCTYPE html>
<html>
<head>
<title> request处理 </title>
</head>
<body>
<%
// 获取请求的钱数
String bal = request.getParameter("balance");
// 将钱数的字符串转换成双精度浮点数
double qian = Double.parseDouble(bal);
// 对取出的钱进行判断
if (qian < 500)
{
out.println("给你" + qian + "块");
out.println("账户减少" + qian);
}
else
{
// 创建了一个List对象
List<String> info = new ArrayList<String>();
info.add("1111111");
info.add("2222222");
info.add("3333333");
// 将info对象放入request范围内
request.setAttribute("info" , info);
%>
<!-- 实现转发 -->
<jsp:forward page="second.jsp"/>
<%}%>
</body>
</html>

first.jsp页面首先获取请求的取钱数,然后对请求的钱数进行判断。如果请求的钱数小于500,则允许直接取钱;否则将请求转发到second.jsp。转发之前,创建了一个List对象,并将该对象设置成request范围的info属性。

second.jsp

接下来在second.jsp页面中,不仅获取了请求的balance参数,而且还会获取request范围的info属性。second.jsp页面的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<%@ page contentType="text/html; charset=UTF-8" language="java" errorPage="" %>
<%@ page import="java.util.*" %>
<!DOCTYPE html>
<html>
<head>
<title> request处理 </title>
</head>
<body>
<%
// 取出请求参数
String bal = request.getParameter("balance");
double qian = Double.parseDouble(bal);
// 取出request范围内的info属性
List<String> info = (List<String>)request.getAttribute("info");
for (String tmp : info)
{
out.println(tmp + "<br/>");
}
out.println("取钱" + qian + "块");
out.println("账户减少" + qian);
%>
</body>
</html>

如果页面请求的钱数大于500,请求将被转发到second.jsp页面处理,而且在second.jsp页面中可以获取到balance请求参数值,也可获取到request范围的info属性,这表明:forward用户请求时,请求参数和request范围的属性都不会丢失,即forward后还是原来的请求,并未再次向服务器发送请求

如果请求取钱的钱数为654,则页面的执行效果如图2.28所示。

2.6.6 request对象

request对象是JSP中重要的对象,每个request对象封装着一次用户请求,并且所有的请求参数都被封装在request对象中,因此request对象是获取请求参数的重要途径。
除此之外,request可代表本次请求范围,所以还可用于操作request范围的属性。

1. 获取请求头 获取请求参数

web应用是请求/响应架构的应用,浏览器发送请求时通常总会附带一些请求头,还可能包含一些请求参数发送给服务器,服务器端负责解析请求头凊请求参数的就是JSPServlet,而JSPServlet取得请求参数的途径就是request.

获取请求参数方法

requestHttpServletrequest接口的实例,它提供了如下几个方法来获取请求参数。

获取请求参数方法 描述
String getParameter(String paramName) 获取paramName请求参数的值。
Map getParameterMap() 获取所有请求参数名和参数值所组成的Map对象
Enumeration getParameterNames() 获取所有请求参数名所组成的Enumeration对象。
String[] getParameterValues(String name) paramName请求参数的值,当该请求参数有多个值时,该方法将返回多个值所组成的数组。

获取请求头方法

HttpServletRequest提供了如下方法来访问请求头。

  • String getHeader(String name):根据指定请求头的值。
  • java.util.Enumeration<String> GetHeaderNames():获取所有请求头的名称。
  • java.util.Enumeration<String> getHeaders(String name):获取指定请求头的多个值
  • int getIntHeader(String name):获取指定请求头的值,并将该值转为整数值。

请求头和请求参数的区别

对于开发人员来说,请求头和请求参数都是由用户发送到服务器的数据,区别在于:
请求头通常由浏览器自动添加,因此一次请求总是包含若干请求头;
请求参数则通常需要开发人员控制添加,让客户端发送。

GET请求POST请求

客户端发送的请求参数通常分两种情况。

GET方式的请求

直接在浏览器地址栏输入访问地址所发送的请求或提交表单发送请求时,该表单对应的form元素没有设置method属性,或设置method属性为get,这几种请求都是GET方式的请求。GET方式的请求会将请求参数的名和值转换成字符串,并附加在原URL之后,因此可以在地址栏中看到请求参数名和值。且GET请求传送的数据量较小,一般不能大于2KB

POST方式的请求

这种方式通常使用提交表单(由form元素表示)的方式来发送,且需要设置form元素的method属性为postPOST方式传送的数据量较大,通常认为POST请求参数的大小不受限制,但往往取决于服务器的限制,POST请求传输的数据量总比GET传输的数据量大。而且POST方式发送的请求参数以及对应的值放在HTML HEADER中传输,用户不能在地址栏里看到请求参数值,安全性相对较高。

对比上面两种请求方式,不难发现,通常应该采用POST方式发送请求。

几乎每个网站都会大量使用表单,表单用于收集用户信息,一旦用户提交请求,表单的信息将会提交给对应的处理程序,如果为form元素设置method属性为post,则表示发送POST请求。

form.jsp

下面是表单页面的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<%@ page contentType="text/html; charset=UTF-8" language="java" errorPage="" %>
<!DOCtype html>
<html>
<head>
<title> 收集参数的表单页 </title>
</head>
<body>
<form id="form1" action="request1.jsp">
用户名:<br/>
<input type="text" name="name"><hr/>
性别:<br/>
男:<input type="radio" name="gender" value="男">
女:<input type="radio" name="gender" value="女"><hr/>
喜欢的颜色:<br/>
红:<input type="checkbox" name="color" value="红">
绿:<input type="checkbox" name="color" value="绿">
蓝:<input type="checkbox" name="color" value="蓝"><hr/>
来自的国家:<br/>
<select name="country">
<option value="中国">中国</option>
<option value="美国">美国</option>
<option value="俄罗斯">俄罗斯</option>
</select><hr/>
<input type="submit" value="提交">
<input type="reset" value="重置">
</form>
</body>
</html>

这个页面没有动态的JSP部分,它只是包含一个收集请求参数的表单,且粗体字部分设置了该表单的actionrequestl.jsp,这表明提交该表单时,请求将发送到requestl.Jsp页面;粗体字代码还设置了methodpost,这表明提交表单将发送POST请求。
除此之外,表单里还包含1个文本框、2个单选框、3个复选框及1个下拉列表框,另外包括“提交”和“重置”两个按钮。页面的执行效果如图2.24所示。
在该页面中输入相应信息后,单击“提交”按钮,表单域所代表的请求参数将通过request对象的getParameter()方法来取得。

只有带有name属性的表单域才会生成请求参数

并不是每个表单域都会生成请求参数,而是有name属性的表单域才生成请求参数.关于表单堿和请求参数的关系遵循如下4点。

  • 每个有name属性的表单域对应一个请求参数。
  • 如果有多个表单域有相同的name属性,则多个表单域只生成一个请求参数,只是该参数有多个值。
  • 表单域的name属性指定请求参数名,value指定请求参数值。
  • 如果某个表单城设置了disabled=” ‘disabled“属性,则该表单域不再生成请求参数。

request1.jsp

上面的表单页向request1.jsp页面发送请求,request1.jsp页面的代码如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<%@ page contentType="text/html; charset=UTF-8" language="java" errorPage="" %>
<%@ page import="java.util.*" %>
<!DOCTYPE html>
<html>
<head>
<title> 获取请求头/请求参数 </title>
</head>
<body>
<%
// 获取所有请求头的名称
Enumeration<String> headerNames = request.getHeaderNames();
while(headerNames.hasMoreElements())
{
String headerName = headerNames.nextElement();
// 获取每个请求、及其对应的值
out.println(
headerName + "-->" + request.getHeader(headerName) + "<br/>");
}
out.println("<hr/>");

// 设置解码方式,对于简体中文,使用UTF-8解码
request.setCharacterEncoding("UTF-8"); // ①
// 下面依次获取表单域的值
String name = request.getParameter("name");
String gender = request.getParameter("gender");
// 如果某个请求参数有多个值,将使用该方法获取多个值
String[] color = request.getParameterValues("color");
String national = request.getParameter("country");
%>

<!-- 下面依次输出表单域的值 -->
您的名字:<%=name%><hr/>
您的性别:<%=gender%><hr/>
<!-- 输出复选框获取的数组值 -->
您喜欢的颜色:<%for(String c : color)
{out.println(c + " ");}%><hr/>
您来自的国家:<%=national%><hr/>
</body>
</html>

上述页面中的代码示范了如何获取请求头、请求参数.

请求参数的字符集

在获取表单域对应的请求参数值之前,先设置request编码的字符集(如①号代码所示).如果POST请求的请求参数里包含非西欧字符,则必须在获取请求参数之前先调用setCharacterEncoding()方法设置编码的字符集。

请求的字符集合表单页面的字符集一样

如果发送请求的表单页采用UTF-8字符集,该表单页发送的请求也将采用UTF-8字符集,所以本页面需要先执行如下方法:

  • request.setCharacterEncoding("UTF-8");:设置request编码所用的字符集。

测试

在表单提交页的各个输入域内输入对应的值,然后单击“提交”按钮,request1.jsp就会出现如图2.25所示的效果

如果需要传递的参数是普通字符串,而且仅需传递少量参数,可以选择使用GET方式发送请求参数,GET方式发送的请求参数被附加到地址栏的URL之后,地址栏的URL将变成如下形式:

1
url?param1=value1&param2=value2&......&paramN=valueN

URL和参数之间以“?”分隔,而多个参数之间以“&”分隔

request2.jsp

下面的JSP页面示范了如何通过request来获取GET请求参数值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<%@ page contentType="text/html; charset=UTF-8" language="java" errorPage="" %>
<!DOCTYPE html>
<html>
<head>
<title> 获取GET请求参数 </title>
</head>
<body>
<%
// 获取name请求参数的值
String name = request.getParameter("name");
// 获取gender请求参数的值
String gender = request.getParameter("gender");
%>
<!-- 输出name变量值 -->
您的名字:<%=name%><hr/>
<!-- 输出gender变量值 -->
您的性别:<%=gender%><hr/>
</body>
</html>

上面的页面中粗体字代码用于获取GET方式的请求参数,从这些代码不难看出:request获取POST请求参数的代码和获取GET请求参数代码完全一样
向该页面发送请求时直接在地址栏里增加一些GET方式的请求参数:

1
http://localhost:8080/jspObject/request2.jsp?name=川普&gender=女

执行效果如图2.26所示。

获取GET的请求参数值

细心的读者可能发现上面两个请求参数值都由英文字符组成,如果请求参数值里包含非西欧字符,那么是不是应该先调用setCharacterEncoding()来设置request编码的字符集呢?读者可以试一下。答案是不行,如果GET方式的请求值里包含了非西欧字符,则获取这些参数比较复杂.
下面的页面示范了如何获取GET请求里的中文字符。

request3.jsp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<%@ page contentType="text/html; charset=UTF-8" language="java" errorPage="" %>
<!DOCTYPE html>
<html>
<head>
<title> 获取包含非西欧字符的GET请求参数 </title>
</head>
<body>
<%
// 获取请求里包含的查询字符串
String rawQueryStr = request.getQueryString();
out.println("原始查询字符串为:" + rawQueryStr + "<hr/>");
// 使用URLDecoder解码字符串
String queryStr = java.net.URLDecoder.decode(
rawQueryStr , "UTF-8");
out.println("解码后的查询字符串为:" + queryStr + "<hr/>");
// 以&符号分解查询字符串
String[] paramPairs = queryStr.split("&");
for(String paramPair : paramPairs)
{
out.println("每个请求参数名、值对为:" + paramPair + "<br/>");
// 以=来分解请求参数名和值
String[] nameValue = paramPair.split("=");
out.println(nameValue[0] + "参数的值是:" +
nameValue[1]+ "<hr/>");
}
%>
</body>
</html>

上面的程序中粗体字代码就是获取GET请求里中文参数值的关键代码,为了获取GET请求里的中文参数值,必须借助于java.net.URLDecoder类。关于URLDecoderURLEncoder两个类的用法请参考疯狂Java体系的《疯狂Java讲义》的17.2节。
上面页面代码使用了UIF-8字符集进行解码,到底应该用哪种字符集来解码,这取决于浏览器。对于简体中文的环境来说,一般要么是UTF8字符集,要么是GBK字符集。
读者可以编写一个表单,并让表单以GET方式提交请求到request3jsp页面,将可看到如图2.27所示的效果
如果读者不想这样做,还可以在获取请求参数值之后对请求参数值重新编码。也就是先将其转换成字节数组,再将字节数组重新解码成字符串。例如,可通过如下代码来取得name请求参数的参数值。

1
2
3
4
5
6
//获取原始的请求参数值
String rawName = request.getParameter("name");
//将请求参数值使用Iso-8859-1字符串分解成字节数组
byte [] rawBytes = rawName.getBytes("ISO-8859-1");
//将字节数组重新解码成字符串
String name = new String(rawBytes,"UTF-8");

通过上面代码片段也可处理GET请求里的中文请求参数值。

2.6.5 pageContext对象

这个对象代表页面上下文,该对象主要用于访问JSP之间的共享数据。使用pageContext可以访问agerequestsessionapplication范围的变量。

getAttribute setAttribute方法

pageContextPageContext类的实例,它提供了如下两个方法来访问pagerequestsessionapplication范围的变量。

  • getAttribute(String name):取得page范围内的name属性。
  • getAttribute(String name, int scope):取得指定范围内的name属性,其中scope可以是如下4个值。
    • PageContext.PAGE_SCOPE:对应于page范围。
    • PageContext.REQUEST_SCOPE:对应于request范围。
    • PageContext.SESSION_SCOPE:对应于session范围。
    • PageContext.APPLICATION_SCOPE:对应于application范围

getAttribute()方法相对应,PageContext也提供了两个对应的setAttribute()方法,用于将指定变量放入pagerequestsessionapplication范围内。

pageContextTest.jsp

下面的JSP页面示范了使用pageContext来操作pagerequestsessionapplication范围内的变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<%@ page contentType="text/html; charset=UTF-8" language="java" errorPage="" %>
<!DOCTYPE html>
<html>
<head>
<title> pageContext测试 </title>
</head>
<body>
<%
// 使用pageContext设置属性,该属性默认在page范围内
pageContext.setAttribute("page","hello");

// 使用request设置属性,该属性默认在request范围内
request.setAttribute("request","hello");
// 使用pageContext将属性设置在request范围中
pageContext.setAttribute("request2","hello", pageContext.REQUEST_SCOPE);

// 使用session将属性设置在session范围中
session.setAttribute("session","hello");
// 使用pageContext将属性设置在session范围中
pageContext.setAttribute("session2","hello", pageContext.SESSION_SCOPE);

// 使用application将属性设置在application范围中
application.setAttribute("app","hello");
// 使用pageContext将属性设置在application范围中
pageContext.setAttribute("app2","hello", pageContext.APPLICATION_SCOPE);


//下面获取各属性所在的范围:
out.println("page变量所在范围:" +
pageContext.getAttributesScope("page") + "<br/>");
out.println("request变量所在范围:" +
pageContext.getAttributesScope("request") + "<br/>");
out.println("request2变量所在范围:"+
pageContext.getAttributesScope("request2") + "<br/>");
out.println("session变量所在范围:" +
pageContext.getAttributesScope("session") + "<br/>");
out.println("session2变量所在范围:" +
pageContext.getAttributesScope("session2") + "<br/>");
out.println("app变量所在范围:" +
pageContext.getAttributesScope("app") + "<br/>");
out.println("app2变量所在范围:" +
pageContext.getAttributesScope("app2") + "<br/>");
%>
</body>
</html>

以上页面使用pageContext将各变量分别放入pagerequestsessionapplication范围内,然后使用pageContext获取各变量所在的范围。

测试

浏览以上页面,可以看到如图2.23所示的效果。

page变量所在范围:1
request变量所在范围:2
request2变量所在范围:2
session变量所在范围:3
session2变量所在范围:3
app变量所在范围:4
app2变量所在范围:4

getAttributesScope方法

图2.23中显示了使用pageContext获取各属性所在的范围,其中这些范围获取的都是整型变量,这些整型变量分别对应如下4个生存范围。

整形变量值 对应生存范围
1 对应page生存范围
2 对应request生存范围。
3 对应session生存范围。
4 对应application生存范围。

pageContext对象获取其他内置对象的方法

不仅如此,pageContext还可用于获取其他内置对象,pageContext对象包含如下方法。

pageContext对象获取其他内置对象的方法 描述
ServletRequest getRequest() 获取request对象。
ServletResponse getResponse() 获取response对象。
ServletConfig getServletConfig() 获取config对象。
ServletContext getServletContext() 获取application对象。
HttpSession getSession() 获取session对象

2.6.4 out对象

out对象代表一个页面输出流,通常用于在页面上输出变量值及常量。一般在使用输出表达式的地方,都可以使用out对象来达到同样效果

outTest.jsp

看下面的JSP页面使用out来执行输出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<%@ page contentType="text/html; charset=GBK" language="java" errorPage="" %>
<%@ page import="java.sql.*" %>
<!DOCTYPE html>
<html>
<head>
<title> out测试 </title>
</head>
<body>
<%
// 注册数据库驱动
Class.forName("com.mysql.jdbc.Driver");
// 获取数据库连接
Connection conn = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/javaee","root","32147");
// 创建Statement对象
Statement stmt = conn.createStatement();
// 执行查询,获取ResultSet对象
ResultSet rs = stmt.executeQuery("select * from news_inf");
%>
<table bgcolor="#9999dd" border="1" width="400">
<%
// 遍历结果集
while(rs.next())
{
// 输出表格行
out.println("<tr>");
// 输出表格列
out.println("<td>");
// 输出结果集的第二列的值
out.println(rs.getString(1));
// 关闭表格列
out.println("</td>");
// 开始表格列
out.println("<td>");
// 输出结果集的第三列的值
out.println(rs.getString(2));
// 关闭表格列
out.println("</td>");
// 关闭表格行
out.println("</tr>");
}
%>
<table>
</body>
</html>

Java的语法上看,上面的程序更容易理解,out是个页面输出流,负责输出页面表格及所有内容,但使用out则需要编写更多代码。
所有使用out的地方,都可使用输出表达式来代替,而且使用输出表达式更加简洁。输出表达式<%=....%>的本质就是out.write();。通过out对象的介绍,读者可以更好地理解输出表达式的原理。