JSP 脚本中包含9个内置对象,这 9 个内置对象都是 Servlet API 接口的实例,只是 JSP 规范对它们进行了默认初始化(由 JSP 页面对应 Servlet 的_jspService()方法来创建这些实例)。也就是说,它们已经是对象,可以直接使用。9个内置对象依次如下。
➢ application:javax.servlet.ServletContext 的实例,该实例代表 JSP 所属的 Web 应用本身,可用于 JSP 页面,或者在 Servlet 之间交换信息。常用的方法有 getAttribute(String attName)、setAttribute(String attName,String attValue)和 getInitParameter(String paramName)等。
➢ config:javax.servlet.ServletConfig 的实例,该实例代表该 JSP 的配置信息。常用的方法有 getInitParameter(String paramName)和 getInitParameternames()等方法。事实上,JSP 页面通常无须配置,也就不存在配置信息。因此,该对象更多地在 Servlet 中有效。
➢ exception: java.lang.Throwable 的实例,该实例代表其他页面中的异常和错误。只有当页面是错误处理页面,即编译指令 page 的 isErrorPage 属性为 true 时,该对象才可以使用。常用的方法 getMessage()和 printStackTrace()等。
➢ out:javax.servlet.jsp.JspWriter 的实例,该实例代表 JSP 页面的输出流,用于输出内容,形成 HTML 页面。
➢ page:代表该页面本身,通常没有太大用处。也就是 Servlet 中的 this,其类型就是生成的 Servlet 类,能用 page 的地方就可用 this。
➢ pageContext:javax.servlet.jsp.PageContext 的实例,该对象代表该 JSP 页面上下文,使用该对象可以访问页面中的共享数据。常用的方法有 getServletContext())和 getServletConfig()等。
➢ request: javax.servlet.http.HttpServletRequest 的实例,该对象封装了一次请求,客户端的请求参数都被封装在该对象里。这是一个常用的对象,获取客户端请求参数必须使用该对象。常用的方法有 getParameter(String paramName)、getParameterValues(String paramName)、setAttribute(String attrName,Object attrValue)、getAttribute(String attrName)和 setCharacterEncoding(String env)等。
➢ response: javax.servlet.http.HttpServletResponse 的实例,代表服务器对客户端的响应。通常很少使用该对象直接响应,而是使用 out 对象,除非需要生成非字符响应。而 response 对象常用于重定向,常用的方法有 getOutputStream()、sendRedirect(java.lang.String location)等。
➢ session:javax.servlet.http.HtpSession 的实例,该对象代表一次会话。当客户端浏览器与站点建立连接时,会话开始;当客户端关闭浏览器时,会话结束。常用的方法有:getAttribute(String attrName)、setAttribute(String attrName,Object attrValue)等。
进入 Tomcat 的 work\Catalina\localhost\jspPrinciple\org\apache\jsp 路径下,打开任意一个 JSP 页面对应生成的 Servlet 类文件,看到如下代码片段
public final class test_jsp extends org.apache.jasper.runtime.HttpJspBase
implements org.apache.jasper.runtime.JspSourceDependent,
org.apache.jasper.runtime.JspSourceImports {
···
// 用于响应用户请求的方法
public void _jspService(final javax.servlet.http.HttpServletRequest request, final javax.servlet.http.HttpServletResponse response)
throws java.io.IOException, javax.servlet.ServletException {
final java.lang.String _jspx_method = request.getMethod();
if (!"GET".equals(_jspx_method) && !"POST".equals(_jspx_method) && !"HEAD".equals(_jspx_method) && !javax.servlet.DispatcherType.ERROR.equals(request.getDispatcherType())) {
response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, "JSPs only permit GET POST or HEAD");
return;
}
final javax.servlet.jsp.PageContext pageContext;
javax.servlet.http.HttpSession session = null;
final javax.servlet.ServletContext application;
final javax.servlet.ServletConfig config;
javax.servlet.jsp.JspWriter out = null;
final java.lang.Object page = this;
javax.servlet.jsp.JspWriter _jspx_out = null;
javax.servlet.jsp.PageContext _jspx_page_context = null;
try {
response.setContentType("text/html; charset=UTF-8");
pageContext = _jspxFactory.getPageContext(this, request, response,
"", true, 8192, true);
_jspx_page_context = pageContext;
application = pageContext.getServletContext();
config = pageContext.getServletConfig();
session = pageContext.getSession();
out = pageContext.getOut();
···
}
}
}
几乎所有的 JSP 页面编译后 Servlet 类都有如上所示的结构,上面 Servlet 类的代码表明:request、response 两个对象是_jspService()方法的形参,当 Tomcat 调用该方法时会初始化这两个对象。而 page、pageContext、application、config、session、out 都是_jspService()方法的局部变量,由该方法完成初始化。
通过上面的代码不难发现 JSP 内置对象的实质:它们要么是_jspService()方法的形参,要么是_jspService()方法的局部变量,所以可以直接在 JSP 脚本(脚本将对应于 Servlet 的_jspService()方法部分)中调用这些对象,无须创建它们。
细心的读者可能已经发现了:上面的代码中并没有 exception 内置对象,这与前面介绍的正好相符:只有当页面的 page 指令的 isErrorPage 属性为 true 时,才可使用 exception 对象。也就是说,只有异常处理页面对应 Servlet 时才会初始化 exception 对象。
application 对象
在介绍 application 对象之前,先简单介绍一些 Web 服务器的实现原理。虽然绝大部分读者都不需要、甚至不曾想过自己开发 Web 服务器,但了解一些 Web 服务器的运行原理,对于更好地掌握 JSP 知识将有很大的帮助。
虽然常把基于 Web 应用称为 B/S (Browser/Server) 架构的应用,但其实 Web 应用一样是 C/S (Client/Server) 结构的应用,只是这种应用的服务器是 Web 服务器,而客户端是浏览器。
现在抛开 Web 应用直接看 Web 服务器和浏览器,对于大部分浏览器而言,它通常负责完成三件事情。
(1)向远程服务器发送请求。
(2)读取远程服务器返回的字符串数据。
(3)负责根据字符串数据渲染出一个丰富多彩的页面
Web 服务器则负责接收客户端请求,每当接收到客户端连接请求之后,Web 服务器应该使用单独的线程为该客户端提供服务:接收请求数据、送回响应数据。下图显示了 Web 服务器的运行机制。
如上图所示的应用架构总是先由客户端发送请求,服务器接收到请求后送回响应的数据,所以也将这种架构称做“请求/响应”架构。根据如上图所示的机制进行归纳,对于每次客户端请求而言,Web 服务器大致需要完成如下几个步骤。
① 启动单独的线程。
② 使用 I/O 流读取用户请求的二进制流数据。
③ 从请求数据中解析参数。
④ 处理用户请求。
⑤ 生成响应数据
⑥ 使用 IO 流向客户端发送请求数据。
在上面 6 个步骤中,第 1、2 和 6 步是通用的,可以由 Web 服务器来完成,但第 3、4 和 5 步则存在差异:因为不同请求里包含的请求参数不同,处理用户请求的方式也不同,所生成的响应自然也不同。那么 Web 服务器到底如何执行第 3、4 和 5 步呢?
实际上,Web 服务器会调用 Servlet 的_jspService()方法来完成第 3、4 和 5 步,编写 JSP 页面时,页面里的静态内容、JSP 脚本都会转换成_jspService()方法的执行代码,这些执行代码负责完成解析参数、处理请求、生成响应等业务功能,而 Web 服务器则负责完成多线程、网络通信等底层功能。
Web 服务器在执行了第 3 步解析到用户的请求参数之后,将需要通过这些请求参数来创建 HttpServletRequest、HttpServletResponse 等对象,作为调用_jspService()方法的参数,实际上一个 Web 服务器必须为 Servlet API 中绝大部分接口提供实现类。
从上面介绍可以看出,Web 应用里的 JSP 页面、Servlet 等程序都将由 Web 服务器来调用,JSP、Servlet 之间通常不会相互调用,这就产生了一个问题:JSP、Servlet 之间如何交换数据?
为了解决这个问题,几乎所有 Web 服务器(包括 Java、ASP、PHP、Ruby 等)都会提供4个类似 Map 的结构,分别是 application、session、request、page,并允许 JSP、Servlet 将数据放入这 4 个 Map 的结构中,并允许从这 4 个 Map 结构中取出数据。这4个 Map 结构的区别是范围不同。
➢ application:对于整个 Web 应用有效,一旦 JSP、Servlet 将数据放入 application 中,该数据将可以被该应用下其他所有的 JSP、Servlet 访问。
➢ session:仅对一次会话有效,一旦 JSP、Servlet 将数据放入 session 中,该数据将可以被本次会话的其他所有的 JSP、Servlet 访问。
➢ request:仅对本次请求有效,一旦 JSP、Servlet 将数据放入 request 中,该数据将可以被该次请求的其他 JSP、Servlet 访问。
➢ page: 仅对当前页面有效,一旦 JSP、Servlet 将数据放入 page 中,该数据只可以被当前页面的 JSP 脚本、声明部分访问。
就像现实生活中有两个人,他们的钱需要相互交换,但他们两个人又不能相互接触,那么只能让 A 把钱存入银行,而 B 从银行去取钱。因此可以把 application、session、request 和 page 理解为类似银行的角色。
把数据放入 application、session、request 或 page 之后,就相当于扩大了该数据的作用范围,所以认为 application、session、request 和 page 中的数据分别处于 application、session、request 和 page 范围之内。
JSP 中的 application、session、request 和 pageContext 4 个内置对象分别用于操作 application、session、request 和 page 范围中的数据。
application 对象代表 Web 应用本身,因此使用 application 来操作 Web 应用相关数据。application 对象通常有如下两个作用。
➢ 在整个 Web 应用的多个 JSP、Servlet 之间共享数据。
➢ 访问 Web 应用的配置参数。
1.让多个 JSP、Servlet 共享数据
application 通过 setAttribute(String attrName,Object value)方法将一个值设置成 application 的 atrName 属性,该属性的值对整个 Web 应用有效,因此该 Web 应用的每个 JSP 页面或 Servlet 都可以访问该属性访问属性的方法为 getAttribute(StringattrName)。
看下面的页面,该页面仅仅声明了一个整型变量,每次刷新该页面时,该变量值加 1,然后将该变量的值放入 application 内。下面是页面的代码。
<%@ page contentType="text/html; charset=UTF-8" language="java" errorPage=""%>
<!DOCTYPE html>
<html>
<head>
<title> application 测试 </title>
</head>
<body>
<!-- JSP 声明 -->
<%!
int i;
%>
<!-- 将 i 值自加后放入 application 的变量内 -->
<%
application.setAttribute("counter",String.valueOf(++i));
%>
<!-- 输出 i 值 -->
<%=i%>
</body>
</html>
以上页面的代码实现了每次刷新该页面时,变量i都先自加,并被设置为 application 的 counter 属性的值,即每次 application 中的 counter 属性值都会加1。
再看下面的 JSP 页面,该页面可以直接访问到 application 的 counter 属性值。
<%@ page contentType="text/html; charset=UTF-8" language="java" errorPage=""%>
<!DOCTYPE html>
<html>
<head>
<title> application 测试 </title>
</head>
<body>
<%=application.getAttribute("counter")%>
</body>
</html>
以上页面的代码直接输出 application 的 counter 属性值。虽然这个页面和 put-aptication.jsp 没有任何关系,但它一样可以访问到 application 的属性,因为 application 的属性对于整个 Web 应用的 JSP、Servlet 都是共享的。
在浏览器的地址栏中访间第一个 put-application.jsp 页面,经多次刷新后,看到如下图所示的页面。
访问 get-application.jsp 页面,也可看到类似于上图所示的效果,因为 get-application.jsp 页面可以访问 application 的 counter 属性值。
下面的 Servlet 代码示范了如何在 Servlet 中访问 application 里的变量。
package lee;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletContext;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet(name="get-application",urlPatterns={"/get-application"})
public class GetApplication extends HttpServlet{
public void service(HttpServletRequest request,HttpServletResponse response)throws IOException{
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
out.println("<html><head><title>");
out.println("测试 application");
out.println("</title></head><body>");
ServletContext sc = getServletConfig().getServletContext();
out.print("application 中当前的 counter 值为:");
out.println(sc.getAttribute("counter"));
out.println("</body></html>");
}
}
由于在 Serviet 中并没有 application 内置对象,所以上面程序显式获取了该 Web 应用的 ServletContext 实例,每个 Web 应用只有一个 ServletContext 实例,在 JSP 页面中可通过 application 内置对象访问该实例,Servlet 中则必须通过代码获取。程序访问、输出了application 中的 counter 变量。
将 Servlet 部署在 Web 应用中,在浏览器中访问 Servlet,出现如下图所示的页面。
最后要指出的是:虽然使用 application(即 ServletContext 实例)可以方便多个JSP、Servlet 共享数据,但不要仅为了 JSP、Servlet 共享数据就将数据放入 application 中!由于 application 代表整个 Web 应用,所以通常只应该把 Web 应用的状态数据放入 application 里。
2.获得 Web 应用配置参数
application 还有一个重要用处:可用于获得 Web 应用的配置参数。看如下 JSP 页面,该页面访问数据库,但访问数据库所使用的驱动、URL、用户名及密码都在 web.xml 中给出。
<%@ page contentType="text/html; charset=UTF-8" language="java" errorPage="" %>
<%@ page import="java.sql.*" %>
<!DOCTYPE html>
<html>
<head>
<title>application 测试</title>
</head>
<body>
<%
// 从配置参数中获取驱动
String driver = application.getInitParameter("driver");
// 从配置参数中获取数据库url
String url = application.getInitParameter("url");
// 从配置参数中获取用户名
String user= application.getInitParameter("user");
// 从配置参数中获取密码
String pass = application.getInitParameter("pass");
// 注册驱动
Class.forName(driver);
// 获取数据库连接
Connection conn = DriverManager.getConnection(url,user,pass);
// 创建 Statement 对象
Statement stmt = conn.createStatement ();
// 执行查询
ResultSet rs = stmt.executeQuery("select * from news_inf");
%>
<table bgcolor="#9999dd" borders="1" width="480">
<%
// 遍历结果集
while(rs.next()){
%>
<tr>
<td><%=rs.getString(1)%></td>
<td><%=rs.getString(2)%></td>
</tr>
<%
}
%>
</table>
</body>
</html>
上面的程序中的代码使用 application 的 getInitParameter(String paramName)来获取 Web 应用的配置参数,这些配置参数应该在 web.xml 文件中使用 context-param 元素配置,每个<context-param.../>元素配置一个参数,该元素下有如下两个子元素。
➢ param-name:配置 Web 参数名。
➢ param-value:配置 Web 参数值。
web.xml 文件中使用<context-param.../>元素配置的参数对整个 Web 应用有效,所以也被称为 Web 应用的配置参数。与整个 Web 应用有关的数据,应该通过 application 对象来操作。
为了给 Web 应用配置参数,应在 web.xml 文件中增加如下片段。
<!-- 配置第一个参数:driver -->
<context-param>
<param-name>driver</param-name>
<param-value>com.mysql.jdbc.Driver</param-value>
</context-param>
<!-- 配置第二个参数:url -->
<context-param>
<param-name>url</param-name>
<param-value>jdbc:mysql://localhost:3306/javaee</param-value>
</context-param>
<!-- 配置第三个参数:user -->
<context-param>
<param-name>user</param-name>
<param-value>root</param-value>
</context-param>
<!-- 配置第四个参数:pass -->
<context-param>
<param-name>pass</param-name>
<param-value>32147</param-value>
</context-param>
在浏览器中浏览 getWebParam.jsp 页面时,可看到数据库连接、数据查询完全成功。可见,使用 application 可以访间 Web 应用的配置参数。
config 对象
config 对象代表当前 JSP 配置信息,但 JSP 页面通常无须配置,因此也就不存在配置信息,所以 JSP 页面比较少用该对象。但在 Servlet 中则用处相对较人,因为 Servet 需要在 web.xml 文件中进行配置,可以指定配置参数。关于 Servlet 的使用将在下一篇介绍。
看如下 JSP 页面代码,该 JSP 代码使用了 config 的一个方法 getServletName()。
<%@ page contentType="text/html; charset=UTF-8" language="java" errorPage="" %>
<!DOCTYPE html>
<html>
<head>
<title>测试 config 内置对象</title>
</head>
<body>
<!-- 直接输出 config 的 getServletName 的值 -->
<%=config.getServletName()%>
</body>
</html>
上面的代码中直接输出了 config 的 getServletName()方法的返回值,所有的 JSP 页面都有相同的名字:jsp,所以代码输出为 jsp。
实际上,也可以在 web.xml 文件中配置 JSP(只是比较少用),这样就可以为 JSP 页面指定配置信息,并可为 JSP 页面另外设置一个 URL。
config 对象是 ServletConfig 的实例,该接口用于获取配置参数的方法是 getInitParameter(StringparamName)。下面的代码示范了如何在页面中使用 config 获取 JSP 配置参数。、
<%@ page contentType="text/html; charset=UTF-8" language="java" errorPage="" %>
<!DOCTYPE html>
<html>
<head>
<title>测试 config 内置对象</title>
</head>
<body>
<!-- 输出该 JSP 名为 name 的配置参数 -->
name 配置参数的值:<%=config.getInitParameter("name")%><br/>
<!-- 输出该 JSP 名为 age 的配置参数 -->
age 配置参数的值:<%=config.getInitParameter("age") %>
</body>
</html>
上面的代码中输出了 config 的 getInitParameter()方法返回值,它们分别获取 name、age 两个配置参数的值。
配置 JSP 也是在 web.xml 文件中进行的,JSP 被当成 Servlet 配置,为 Servlet 配置参数使用 init-param 元素,该元素可以接受 param-name 和 param-value 两个子元素,分别指定参数名和参数值。
在 web.xml 文件中增加如下配置片段,即可将 JSP 页面配置在 Web 应用中。
<servlet>
<!-- 指定 Servlet 名字 -->
<servlet-name>config</servlet-name>
<!-- 指定将哪个 JSP 页面配置成 Servlet -->
<jsp-file>/configTest2.jsp</jsp-file>
<!-- 配置名为 name 的参数,值为xiaowu007.com -->
<init-param>
<param-name>name</param-name>
<param-value>xiaowu123.com</param-value>
</init-param>
<!-- 配置名为 age 的参数,值为 30 -->
<init-param>
<param-name>age</param-name>
<param-value>30</param-value>
</init-param>
</servlet>
<servlet-mapping>
<!-- 指定将 config Servlet 配置到/config 路径 -->
<servlet-name>config</servlet-name>
<url-pattern>/config</url-pattern>
</servlet-mapping>
上面的配置文件片段中前半部分为该 Servlet(其实是 JSP)配置了两个参数:name 和 age。上面的配置片段把 configTest2.jsp 页面配置成名为 config 的 Servlet,并将该 Servlet 映射到/config 处,这就允许通过/config 来访问该页面。在浏览器中访问/config 看到如下图所示界面。
从上图中可以看出,通过 config 可以访问到 web.xml 文件中的配置参数。实际上,也可以直接访问 configTest2.jsp 页面,在浏览器中访问该页面将看到如下图所示界面。
对比上面两张图不难看出,如果希望 JSP 页面可以获取 web.xml 配置文件中的配置信息,则必须通过为该 JSP 配置的路径来访问该页面,因为只有这样访问 JSP 页面才会让配置参数起作用。
exception 对象
exception 对象是 Throwable 的实例,代表 JSP 脚本中产生的错误和异常,是 JSP 页面异常机制的一部分。
在 JSP 脚本中无须处理异常,即使该异常是 checked 异常。事实上,JSP 脚本包含的所有可能出现的异常都可交给错误处理页面处理。
看如右图所示的异常处理结构,这是典型的异常捕捉处理块。在 JSP 页面中,普通的 JSP 脚本只执行第一个部分——代码处理段,而异常处理页面负责第二个部分——异常处理段。在异常处理段中,可以看到有个异常对象,该对象就是内置对象 exception。
{/gird-item}
{gird-item}
{/gird-item}
打开普通 JSP 页面所生成的 Servlet 类,将可以发现如下代码片段
public void _jspService(final javax.servlet.http.HttpServletRequest request, final javax.servlet.http.HttpServletResponse response)
throws java.io.IOException, javax.servlet.ServletException {
···
try {
// 所有的 JSP 脚本、静态 HTML 都会转换成此部分代码
response.setContentType("text/html; charset=UTF-8");
···
out.write("</body>\r\n");
out.write("</html>");
} catch (java.lang.Throwable t) {
···
//处理该异常
if (_jspx_page_context != null) _jspx_page_context.handlePageException(t);
else throw new ServletException(t);
}
} finally {
//释放资源
_jspxFactory.releasePageContext(_jspx_page_context);
}
}
从上面代码中可以看出,JSP 脚本和静态 HTML 部分都将转换成_jspService()方法里的执行性代码——这就是 JSP 脚本无须处理异常的原因:因为这些脚本已经处于 try 块中。一旦 try 块捕提到 JSP 脚本的异常,并且_jspx_page_context 不为 null,就会由该对象来处理该异常。
_jspx_page_context 对异常的处理也非常简单:如果该页面的 page 指令指定了 errorPage 属性,则将请求 forward 到 errorPage 属性指定的页面,否则使用系统页面来输出异常信息。
在 JSP 的异常处理机制中,一个异常处理页面可以处理多个 JSP 页面脚本部分的异常。异常处理页面通过 page 指令的 errorPage 属性确定。
下面的页面再次测试了 JSP 脚本的异常机制。
<!-- 通过 errorPage 属性指定异常处理页面 -->
<%@ page contentType="text/html; charset=UTF-8" language="java" errorPage="error.jsp" %>
<!DOCTYPE html>
<html>
<head>
<title> JSP 脚本的异常机制 </title>
</head>
<body>
<%
int a = 6;
int c = a / 0;
%>
</body>
</html>
以上页面的代码将抛出一个 ArithmeticEception,则 JSP 异常机制将会转发到 error.jsp 页面,error.jsp 页面代码如下。
<%@ page contentType="text/html; charset=UTF-8" language="java" isErrorPage="true" %>
<!DOCTYPE html>
<html>
<head>
<title> 异常处理页面 </title>
</head>
<body>
异常类型是:<%=exception.getClass() %><br/>
异常信息是:<%=exception.getMessage() %>
</body>
</html>
以上页面 page 指令的 isErrorPage 属性被设为 true,则可以通过 exception 对象来访问上一个页面所出现的异常。在浏览器中请求 throwEx.jsp 页面,将看到如下图所示的界面。
打开 error.jsp 页面生成的 Serviet 类,在_jspService()方法中发现如下代码片段:
public void _jspService(final javax.servlet.http.HttpServletRequest request, final javax.servlet.http.HttpServletResponse response)
throws java.io.IOException, javax.servlet.ServletException {
final javax.servlet.jsp.PageContext pageContext;
javax.servlet.http.HttpSession session = null;
// 初始化 exception 对象
java.lang.Throwable exception = org.apache.jasper.runtime.JspRuntimeLibrary.getThrowable(request);
if (exception != null) {
response.setStatus(javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
···
}
从以上代码片段中可以看出,当 JSP 页面 page 指令的 isErrorPage 为 true 时,该页面就会提供 exception 内置对象。
out 对象
out 对象代表一个页面输出流,通常用于在页面上输出变量值及常量。一般在使用输出表达式的地方,都可以使用 out 对象来达到同样效果。
看下面的 JSP 页面使用 out 来执行输出。
<%@ page contentType="text/html; charset=UTF-8" language="java" errorPage="" %>
<%@ page import="java.sql.*" %>
<!DOCTYPE html>
<html>
<head>
<title> out 测试</title>
</head>
<body>
<%
// 注册数据库驱动
Class.forName("com.mysql.jabc.Driver");
// 获取数据库连接
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/javaee","root","root");
// 创建 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 则需要编写更多代码。
pageContext 对象
这个对象代表页面上下文,该对象主要用于访问 JSP 之间的共享数据。使用 pageContext 可以访问 page、request、session、application 范围的变量。
pageContext 是 PageContext 类的实例,它提供了如下两个方法来访问 page、request、sessionapplication 范围的变量。
➢ getAttribute(String name):取得 page 范围内的 name 属性。
➢ getAtribute(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()方法,用于将指定变量放入 page、request、session、application 范围内。
下面的 JSP 页面示范了使用 pageContext 来操作 page、request、session、application 范围内的变量。
<%@ 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","hel1o",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 将各变量分别放入 page、request、session、application 范围内,程序的代码还使用 pageContext 获取各变量所在的范围。
浏览以上页面,可以看到如下图所示的效果。
上图中显示了使用 pageContext 获取各属性所在的范围,其中这些范围获取的都是整型变量,这些整型变量分别对应如下 4 个生存范围。
1:对应 page 生存范围。
2:对应 request 生存范围
3:对应 session 生存范围。
4:对应 application 生存范围。
不仅如此,pageContext 还可用于获取其他内置对象,pageContext 对象包含如下方法。
➢ ServletRequest getRequest():获取 request 对象。
➢ ServletResponse getResponse():获取 response 对象
➢ ServletConfig getServletConfig():获取 config 对象。
➢ ServletContext getServletContext():获取 application 对象。
➢ HttpSession getSession():获取 session 对象。
因此一旦在 JSP、Servlet 编程中获取了 pageContext 对象,就可以通过它提供的上面方法来获取其他内置对象。
request 对象
request 对象是 JSP 中重要的对象,每个 request 对象封装着一次用户请求,并且所有的请求参数都被封装在 request 对象中,因此 request 对象是获取请求参数的重要途径。
除此之外,request 可代表本次请求范围,所以还可用于操作 request 范围的属性。
1.获取请求头/请求参数
Web 应用是请求/响应架构的应用,浏览器发送请求时通常总会附带一些请求头,还可能包含一些请求参数发送给服务器,服务器端负责解析请求头/请求参数的就是 JSP 或 Servlet,而 JSP 和 Servlet 取得请求参数的途径就是 request。request 是 HttpServletRequest 接口的实例,它提供了如下几个方法来获取请求参数。
➢ String getParameter(String paramName):获取 paramName 请求参数的值。
➢ Map getParameterMap():获取所有请求参数名和参数值所组成的 Map 对象。
➢ Enumeration getParameterNames():获取所有请求参数名所组成的 Enumeration 对象。
➢ String[] getParameterValues(String name):paramName 请求参数的值,当该请求参数有多个值时,该方法将返回多个值所组成的数组。
HttpServletRequest 提供了如下方法来访问请求头。
➢ String getHeader(String name): 根据指定请求头的值。
➢ java.util.Enumeration
➢ java.util.Enumeration
➢ int getIntHeader(String name):获取指定请求头的值,并将该值转为整数值。
对于开发人员来说,请求头和请求参数都是由用户发送到服务器的数据,区别在于请求头通常由浏览器自动添加,因此一次请求总是包含若干请求头;而请求参数则通常需要开发人员控制添加,让客户端发送请求参数通常分两种情况。
➢ GET 方式的请求:直接在浏览器地址栏输入访问地址所发送的请求或提交表单发送请求时,该表单对应的 form 元素没有设置 method 属性,或设置 method 属性为 get,这几种请求都是 GET 方式的请求。GET 方式的请求会将请求参数的名和值转换成字符串,并附加在原URL之后,因此可以在地址栏中看到请求参数名和值。且 GET 请求传送的数据量较小,一般不能大于 2KB。
➢ POST 方式的请求:这种方式通常使用提交表单(由 form HTML 元素表示)的方式来发送,目需要设置 form 元素的 method 属性为 post。POST 方式传送的数据量较大,通常认为 POST 请求参数的大小不受限制,但往往取决于服务器的限制,POST 请求传输的数据量总比 GET 传输的数据量大。而且 POST 方式发送的请求参数以及对应的值放在 HTML HEADER 中传输,用户不能在地址栏里看到请求参数值,安全性相对较高。
对比上面两种请求方式,不难发现,通常应该采用 POST 方式发送请求。
几乎每个网站都会大量使用表单,表单用于收集用户信息,一旦用户提交请求,表单的信息将会提交给对应的处理程序,如果为 form 元素设置 mehed 属性为 post,则表示发送 POST 请求。
下面是表单页面的代码
<%@ page contentType="text/html; charset=GBK" language="java" errorPage="" %>
<!DOCTYPE html>
<html>
<head>
<title>收集参数的表单页</title>
</head>
<body>
<form id="form1" method="post" 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 部分,它只是包含一个收集请求参数的表单,且设置了该表单的 action 为 request1.jsp,这表明提交该表单时,请求将发送到 request1.jsp 页面;还设置了 method 为 post,这表明提交表单将发送 POST 请求。
除此之外,表单里还包含 1 个文本框、2 个单选框、3 个复选框及 1 个下拉列表框,另外包括“提交”和“重置”两个按钮。页面的执行效果如下图所示。
在该页面中输入相应信息后,单击“提交”按钮,表单域所代表的请求参数将通过 request 对象的 getParameter()方法来取得。
上面的表单页向 request1.jsp 页面发送请求,request1.jsp 页面的代码如下
<%@ page contentType="text/html; charset=GBK" 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/>");
// 设置解码方式,对于简体中文,使用 GBK 解码
request.setCharacterEncoding("GBK"); // ①
// 下面依次获取表单域的值
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()方法设置编码的字符集。
如果发送请求的表单页采用 GBK 字符集,该表单页发送的请求也将采用 GBK 字符集,所以本页面需要先执行如下方法。
➢ setCharacterEncoding("GBK"):设置 request 编码所用的字符集。
在表单提交页的各个输入域内输入对应的值,然后单击“提交”按钮,request1.jsp 就会出现如下图所示的效果。
如果需要传递的参数是普通字符串,而且仅需传递少量参数,可以选择使用 GET 方式发送请求参数,GET 方式发送的请求参数被附加到地址栏的 URL 之后,地址栏的 URL 将变成如下形式:
url?paraml=valuel¶m2=value2&···paramN=valueN
URL 和参数之间以“?”分隔,而多个参数之间以“&”分隔。
下面的 JSP 页面示范了如何通过 request 来获取 GET 请求参数值。
<%@ page contentType="text/html; charset=GBK" language="java" errorPage="" %>
<!DOCTYPE html>
<html>
<head>
<title>获取 GET 请求参数</title>
</head>
<body>
<%
// 获取 name 请求参数的值
String name = request.getParameter("name");
// 获取 gender 请求参数的值
String gender = request.getParameter("gander");
%>
<!-- 输出 name 变量值 -->
您的名字:<%=name%><hr/>
<!-- 输出 gender 变量值 -->
您的性别: <%=gender%><hr/>
</body>
</html>
上面的页面中获取了 GET 方式的请求参数,从这些代码不难看出:request 获取 POST 请求参数的代码和获取 GET 请求参数代码完全一样。向该页面发送请求时直接在地址栏里增加一些 GET 方式的请求参数,执行效果如下图所示。
细心的读者可能发现上面两个请求参数值都由英文字符组成,如果请求参数值里包含非西欧字符,那么是不是应该先调用 setCharacterEncoding()来设置 request 编码的字符集呢?读者可以试一下。答案是不行,如果 GET 方式的请求值里包含了非西欧字符,则获取这些参数比较复杂。
下面的页面示范了如何获取 GET 请求里的中文字符。
<%@ page contentType="text/html; charset=GBK" 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 类。
读者可以编写一个表单,并让表单以 GET 方式提交请求到 request3.jsp 页面,将可看到如下图所示的效果。
如果读者不想这样做,还可以在获取请求参数值之后对请求参数值重新编码。也就是先将其转换成字节数组,再将字节数组重新解码成字符串。例如,可通过如下代码来取得 name 请求参数的参数值。
// 获取原始的请求参数值
String rawName = request.getParameter("name");
// 将请求参数值使用 ISO-8859-1 字符串分解成字节数组
byte[] rawBytes = rawName.getBytes("ISO-8859-1");
// 将字节数组查询解码成字符串
String name = new String(rawBytes,"UTF-8");
通过上面代码片段也可处理 GET 请求里的中文请求参数值。
2.操作 request 范围的属性
HtpServietRequest 还包含如下两个方法,用于设置和获取 request 范围的属性。
➢ setAtribute(String attName,Objcct attValue):将 attValue 设置成 request 范围的属性。
➢ Object getAttribute(String attName):获取 request 范围的属性。
当 forward 用户请求时,请求的参数和请求属性都不会丢失。看下一个 JSP 页面,这个 JSP 页面是个简单的表单页,用于提交用户请求。
<%@ 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 页面请求后,balance 参数将被提交到first.jsp 页面,下面是 first.jsp 页面的实现代码。
<%@ 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 页面中,不仅获取了请求的 balance 参数,而且还会获取 request 范围的 info 属性。second.jsp 页面的代码如下。
<%@ 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,则页面的执行效果如下图所示。
3.执行 forward 或 include
request 还有一个功能就是执行 forward 和 include,也就是代替 JSP 所提供的 forward 和 inchude 动作指令。前面需要 forward 时都是通过 JSP 提供的动作指令进行的,实际上 request 对象也可以执行 forward。
HttpServletReques 类提供了一个 getRequestDispatcher(String path)方法,其中 path 就是希望 forward 或者 include 的目标路径,该方法返回 RequestDispatcher,该对象提供了如下两个方法。
➢ forward(ServletRequest request,ServletResponse response):执行 forward。
➢ include(ServletRequest request,ServletResponse response): 执行 include。
如下代码行可以将 a.jsp 页面 include 到本页面中:
getRequestDispatcher("/a.jsp").include(request,response);
如下代码行则可以将请求 forward 到 a.jsp 页面:
getRequestDispatcher("/a.jsp").forward(request,response);
response 对象
response 代表服务器对客户端的响应。大部分时候,程序无须使用 response 来响应客户端请求,因为有个更简单的响应对象——out,它代表页面输出流,直接使用 out 生成响应更简单。
但 out 是 JspWriter 的实例,JspWriter 是 Writer 的子类,Writer 是字符流,无法输出非字符内容。假如需要在 JSP 页面中动态生成一幅位图、或者输出一个 PDF 文档,使用 out 作为响应对象将无法完成,此时必须使用 response 作为响应输出。
除此之外,还可以使用 response 来重定向请求,以及用于向客户端增加 Cookie。
1.response 响应生成非字符响应
对于需要生成非字符响应的情况,就应该使用 response 来响应客户端请求。下面的 JSP 页面将在客户端生成一张图片。response 是 HttpServletResponse 接口的实例,该接口提供了一个 getOutputStream()方法,该方法返回响应输出字节流。
<%-- 通过 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());
%>
以上页面的代码先设置了服务器响应数据是 image/png,这表明服务器响应是一张 PNG 图片。接着创建了一个 BufferedImage 对象(代表图像),并获取该 BufferedImage 的 Graphics 对象(代表画笔),然后通过 Graphics 向 BufferedImage 中绘制图形,最后一行代码将直接将 BufferedImage 作为响应发送给客户端。
请直接在浏览器中请求该页面,将看到浏览器显示一张图片,效果如下图所示。
也可以在其他页面中使用 img 标签来显示这个图片页面,代码如下:
<img src="img.jsp">
使用这种临时生成图片的方式就可以非常容易地实现网页上的图形验证码功能。不仅如此,使用 response 生成非字符响应还可以直接生成 PDF 文件、Excel 文件,这些文件可直接作为报表使用。
2.重定向
重定向是 response 的另外一个用处,与 forward 不同的是,重定向会丢失所有的请求参数和 request 范围的属性,因为重定向将生成第二次请求,与前一次请求不在同一个 request 范围内,所以发送一次请求的请求参数和 request 范围的属性全部丢失。
HttpServletResponse 提供了一个 sehdRedirect(String path)方法,该方法用于重定向到 path 资源,即重新向 path 资源发送请求。
下面的 JSP 页面将使用 response 执行重定向。
<%@ page contentType="text/html; charset=UTF-8" language="java" errorPage="" %>
<%
// 生成页面响应
out.println("====");
// 重定向到 request2.jsp 页面
response.sendRedirect("request2.jsp");
%>
以上页面 response.sendRedirect 用于执行重定向,向该页面发送请求时,请求会被重定向到 request2.jsp 页面。例如,在地址栏中输入 http://localhost:8080/jspObject/doRedirect.jsp?name=xiaowu&gender=男,然后按回车键,将看到如下图所示的效果。
注意地址栏的改变,执行重定向动作时,地址栏的 URL 会变成重定向的目标 URL。
从表面上来看,forward 动作和 rdirec 动作有些相似:它们都可将请传递到另一个页面。但实际上 fonward 和 redirect 之间存在较大的差异, forward 和 rediret 的差异如下表所示。
转发(forward) | 重定向(redirect) |
---|---|
执行 forward 后依然是上一次请求 | 执行 redirect 后生成第二次请求 |
forward 的目标页面可以访问原请求的请求参数,因为依然是同一次请求,所有原请求的请求参数、request 范围的属性全部存在 | redirect 的目标页面不能访问原请求的请求参数,因为是第二次请求了,所有原请求的请求参数、request 范围的属性全部丢失 |
地址栏里请求的 URL 不会改变 | 地址栏改为重定向的目标 URL。相当于在浏览器地址栏里输入新的 URL 后按回车键 |
3.增加Cookie
Cookie 通常用于网站记录客户的某些信息,比如客户的用户名及客户的喜好等。一旦用户下次登录,网站可以获取到客户的相关信息,根据这些客户信息,网站可以对客户提供更友好的服务。Cookie 与 session 的不同之处在于: session 会随浏览器的关闭而失效,但 Cookie 会一直存放在客户端机器上,除非超出 Cookie 的生命期限。
由于安全性的原因,使用 Cookie 客户端浏览器必须支持 Cookie 才行。客户端浏览器完全可以设置禁用 Cookie。
增加 Cookie 也是使用 response 内置对象完成的,response 对象提供了如下方法。
➢ void addCookie(Cookie cookie):增加 Cookie。
正如在上面的方法中见到的,在增加 Cookie 之前,必须先创建 Cookie 对象。增加 Cookie 请按如下步骤进行。
① 创建 Cookie 实例,Cookie 的构造器为 Cookie(String name,String value)。
② 设置 Cookie 的生命期限,即该 Cookie 在多长时间内有效。
③ 向客户端写 Cookie。
看如下 JSP 页面,该页面可以用于向客户端写一个 username 的 Cookie。
<%@ page contentType="text/html; charset=UTF-8" language="java" errorPage="" %>
<!DOCTYPE html>
<html>
<head>
<title>z增加 Cookie</title>
</head>
<body>
<%
// 获取请求参数
String name = request.getParameter("username");
// 以获取到的请求参数为值,创建一个 Cookie 对象
Cookie c = new Cookie("username",name);
// 设置 Cookie 对象的生存期限
c.setMaxAge(24* 3600);
// 向客户端增加 Cookie 对象
response.addCookie(c);
%>
</body>
</html>
如果浏览器没有阻止 Cookie,在地址栏输入 http://localhost:8080/jspObject/addCookie.jsp?name=xiaowu,执行该页面后,网站就会向客户端机器写入一个名为 usemame 的 Cookie,该 Cookie 将在客户端硬盘上一直存在,直到超出该 Cookie 的生存期限(本 Cookie 设置为 24 小时)。
访问客户端 Cookie 使用 request 对象,request 对象提供了 getCookies()方法,该方法将返回客户端机器上所有 Cookie 组成的数组,遍历该数组的每个元素,找到希望访问的 Cookie 即可。
下面是访问 Cookie 的 JSP 页面的代码。
<%@ 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 怎么办呢?同样可以借助于 java.net.URLEncoder 先对中文字符进行编码,将编码后的结果设为 Cookie 值。当程序要读取 Cookie 时,则应该先读取,然后使用 java.net.URLDecoder 对其进行解码。
如下代码片段示范了如何存入值为中文的 Cookie。
<%@ page contentType="text/html; charset=UTF-8" language="java" errorPage="" %>
<%
// 以编码后的字符串为值,创建一个 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 ()));
}
}
%>
上面的程序中 java.net.URLDecoder 是存入值为中文的 Cookie 的关键:存入之前先用 java.net.URLEncoder 进行编码;读取时需要对读取的 Cookie 值用 java.net.URLDecoder 进行解码。
session 对象
session 对象也是一个非常常用的对象,这个对象代表一次用户会话。一次用户会话的含义是:从客户端浏览器连接服务器开始,到客户端浏览器与服务器断开为止,这个过程就是一次会话。
session 通常用于跟踪用户的会话信息,如判断用户是否登录系统,或者在购物车应用中,用于跟踪用户购买的商品等。
session 范围内的属性可以在多个页面的跳转之间共享。一旦关闭浏览器,即 session 结束,session 范围内的属性将全部丢失。
session 对象是 HttpSession 的实例,HttpSession 有如下两个常用的方法。
➢ setAttribute(String attName,Obiect attValue):设置 session 范围内 attName 属性的值为 attValue。
➢ getAttribute(String attName):返回 session 范围内 attName 属性的值。
下面的示例演示了一个购物车应用,以下是陈列商品的 JSP 页面代码。
<%@ 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 页面处理,其页面的代码如下。
<%@ page contentType="text/html; charset=UTF-8" language="java" errorPage="" %>
<%@ 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>new document</title>
</head>
<body>
您所购买的物品: <br/>
书籍:<%=itemMap.get("书籍")%>本<br/>
电脑:<%=itemMap.get("电脑")%>台<br/>
汽车:<%=itemMap.get("汽车")%>辆
<p><a href="shop.jsp">再次购买</a></p>
</body>
</html>
以上页面的代码使用 session 来保证 itemMap 对象在一次会话中有效,这使得该购物车系统可以反复购买,只要浏览器不关闭,购买的物品信息就不会丢失,下图显示的是多次购买后的效果。
关于 session 还有一点需要指出,sssion 机制通常用于保存客户端的状态信息,这些状态信息需要保存到 Web 服务器的硬盘上,所以要求 session 里的属性值必须是可序列化的,否则将会引发不可序列化的异常。
评论