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的isErrorrPage属性为true时,该对象才可以使用。常用的方法有getMessage()和printStackTrace()等。
- out:javax.servlet.jsp.JspWriter的实例,该实例代表JSP页面的输出流,用于输出内容,形成HTML页面。
- page:代表页面本身,通常没有太大用处。也就四Servlet中的this,其类型就是生成的Servlet类,能用page的地方就可用this。
- pageContextt:javax.servlet.jsp.PaggeContext的实例,该对象代表该JSP页面上下文,使用该对象可以访问页面中的共享数据。常用方法有getServletContext()和getServletConfig()等。
- request:javax.servlet.http.HttpSeervletRequest的实例,该对象封装了一次请求,客户端的请求参数都被封装在该对象里。这是一个常用的对象,获取客户端请求参数必须使用该对象。常用的方法有getParameter(String paramName),getParameterValues(String paramName),setAttribute(String attrName, Object attrValue),getAttribute(String attrName)和setCharacterEncoding(String env)等。
- response:javax.servlet.httpHttpServletResponse 的实例,代表服务器对客户端的响应。通常很少使用该对象直接响应,而是使用out对象,除非需要生成非字符响应。而response对象常用于重定向,常用的方法有getOutputStream()、sendRedirect(java.lang.String location)等。
- session:javax.servlet.http.HttpSession的实例,该对象代表一次会话。当客户端浏览器与站点建立连接时,会话开始;当客户端关闭浏览器时,会话结束。常用的方法有geAttribute(String attrName)、setAttribute(String attrName, Object attrValue)等。
-
注意
- 由于JSP内置对象都是在_jspService()方法中完成初始化,因此只能在JSP脚本、JSP输出表达式中使用这些内置对象。若在JSP声明中使用它们,则系统将提示找不到这些变量。
1.application对象
我们常把基于Web应用称为 B/S(Browser/Server)架构的应用,其实Web应用一样是C/S(Client/Server)结构的应用,只是这种应用的服务器是Web服务器,客户端是浏览器。
对于大部分浏览器而言,它通常负责完成:
- 向远程服务器发送请求
- 读取远程服务器返回的字符串数据。
- 负责根据字符串数据渲染出一个网页。
Web服务器则负责接收客户端请求,每当接收到客户端连接请求后,Web服务器应该是用单一的线程为该客户端提供服务:接受请求数据、送回响应数据。
Web应用架构总是有个客户端发送请求,服务器接收到请求后送回相应的数据,所以这种架构称作“请求响应”架构。对于每次客户端请求,Web服务器大致需要完成一下几个步骤:
- 启动单独的线程。
- 使用I/O流读取用户的请求数据。
- 从请求数据中解析参数。
- 处理用户请求。
- 生成响应数据。
- 使用I/O流向客户端发送请求。
上面的1、2和6步是通用的,可由Web服务器来完成。对于3、4和5步,Web服务器会调用Servlet的_jspService()方法来完成,当我们编写JSP页面时,页面里的静态内容、JSP脚本都会转换成_jspService()方法的执行代码,这些代码负责完成解析参数、处理请求、生成响应等业务功能,而Web服务器则负责完成多线程、网络通信等底层功能。
Web应用里的JSP页面、Servlet等程序都将由Web服务器来调用,JSP、Servlet之间的交换数据通过4个类似于Map的结构,分别是application、session、request、page来实现。几乎所有Web服务器(包括Java、ASP、PHP、Ruby等)都会提供这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脚本、声明部分访问。
application代表Web应用本身,因此使用application来操作Web应用相关数据。application通常有如下两个作用:
在整个Web应用的多个JSP、Servlet之间共享数据。
访问Web应用的配置参数。
- 实现JSP间共享数据
- 获得Web应用配置参数
实现JSP间共享数据
在如下代码中,先把counter属性put到application中
<!-- JSP声明 -->
<%!
int i;
%>
<!-- 将i值自加后放入application的变量内 -->
<%
application.setAttribute("counter",String.valueOf(++i));
%>
<!-- 输出i值 -->
<%=i%>
其他JSP页面可直接访问application对象的counter属性值
<!-- 直接输出application 变量值 -->
<%=application.getAttribute("counter")%>
如果还没有初始化第一个JSP,而直接通过第二个JSP来访问application的counter属性,会返回null。
注意
application不仅可以用于两个JSP页面之间共享数据,还可以用于Servlet和JSP之间来共享数据。application可以理解成一个Map对象,任何JSP、Servlet都可以把某个变量放入application中保存,并为之制定一个属性名;该应用里的其他JSP、Servlet可以根据该属性名来得到这个变量。|
示例代码:
@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=gb2312");
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>");
}
}
由于Servlet中并没有application内置对象,下需要通过下面的代码获取ServletContext实例:
ServletContext sc = getServletConfig().getServletContext();
out.println(sc.getAttribute("counter"));
这句代码来获取ServletContext实例,每个Web应用只有一个SerletContext实例,在JSP页面中可通过application内置对象访问。而Servlet必须通过代码来获取。
注意
该Servlet类同样需要编译成class文件才可食用,实际上该Servlet还是用了@WebServlet Annotation进行部署。编译Servlet时可能由于没有添加环境变量出现异常,如果安装了Java EE 6 SDK,只需将Java EE 6 SDK路径的javaee.jar文件添加到CLASSPATH环境变量中;如果没有安装Java EE SDK,可以将Tomcat7的lib路径下的jsp-api.jar、servlet-api.jar两个文件添加到CLASSPATH环境变量中。
书上是这样说的,但我用的是Tomcat8,不知道为什么不需要添加CLASSPATH,也许运行Tomcat8的时候已经把它加到里面去了。
虽然使用application(即ServletContext实例)可以方便多个JSP、Servlet共享数据,但不要仅仅因为这个就把共享数据放入application中。通常应该把Web应用的状态数据放入application中。
获得Web应用配置参数
application还有一个重要用处: 可用于获得Web应用的配置参数。
比如把连接数据库的相关配置写到web.xml中,通过application内置对象获取相应的属性值。在web.xml文件中使用<context-param.../>
元素配置,每个<context-param.../>
元素配置一个参数,该元素下有如下两个子元素:
- param-name : 配置Web参数名
- param-value : 配置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>123456</param-value>
</context-param>
在JSP调用的脚本如下,通过调用application对象的getInitParameter(String paramName)方法获得属性值:
<%
//从配置参数中获取驱动
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" border="1" width="480">
<%
//遍历结果集
while(rs.next())
{
%>
<tr>
<td><%=rs.getString(1)%></td>
<td><%=rs.getString(2)%></td>
</tr>
<%
}
%>
config对象
config对象代表当前JSP配置信息,对于JSP页面而言,通常无须配置,因此也不存在配置信息。但在Servlet中用处较大,因为Servlet需要在web.xml文件中进行配置,可指定配置参数。看如下代码,使用了cofig的一个getServletName()方法,由于所有的JSP页面都有相同的名字:jsp,所以执行会输出:jsp。
<%@ page contentType="text/html; charset=GBK" language="java" errorPage="" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>测试config内置对象</title>
<meta name="website" content="http://www.crazyit.org" />
</head>
<body>
<!-- 直接输出config的getServletName的值 -->
<%=config.getServletName()%>
</body>
</html>
实际上,我们也可以在web.xml文件中配置JSP(比较少用),这样就可以为JSP页面指定配置信息,并可为JSP页面另外设置一个URL。
config对象是ServletConfig的实例,该接口用于获取配置参数的方法是getInitParameter(String paramName)。下面的代码示范如何在页面中使用config来获取JSP配置参数。
<%@ page contentType="text/html; charset=GBK" language="java" errorPage="" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>测试config内置对象</title>
<meta name="website" content="http://www.crazyit.org" />
</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的参数,值为yeeku -->
<init-param>
<param-name>name</param-name>
<param-value>yeeku</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 URL-->
<servlet-name>config</servlet-name>
<url-pattern>/config</url-pattern>
</servlet-mapping>
上面把 .jsp页面配置成名为config的Serlet,并将该Servlet映射到/config处,我们就可以通过/config来访问该JSP页面。运行截图:
当然,其实我们也可以直接访问JSP页面。
但是,如果希望JSP页面可以获取web.xml配置文件中的配置信息,则必须通过为JSP配置的路径来访问页面,因为只有这样访问JSP页面才会让配置参数起作用。运行结果:
name配置参数的值:null
age配置参数的值:null
exception对象
exception对象时Throwable的实例,代表JSP脚本中产生的错误和异常,是JSP页面异常机制的一部分。
在JSP脚本中无需处理异常,即使该异常是checked异常。事实上,JSP脚本包含的所有可能出现的异常都可交给错误处理页面来处理。如下图:
这是典型的一场捕捉处理块。在JSP页面中,一般JSP页面脚本只执行JSP页面代码,而异常处理页面负责错误页面处理代码。在异常处理代码中,可以看到一个异常对象,该对象就是内置对象exception。
注意
exception对象仅在异常处理页面中才有效。
打开普通的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 {
response.setContentType("text/html; charset=GBK");
pageContext = _jspxFactory.getPageContext(this, request, response,
"", true, 8192, true);
...
} catch (java.lang.Throwable t) {
if (!(t instanceof javax.servlet.jsp.SkipPageException)){
out = _jspx_out;
if (out != null && out.getBufferSize() != 0)
try { out.clearBuffer(); } catch (java.io.IOException e) {}
if (_jspx_page_context != null)
_jspx_page_context.handlePageException(t);
else
throw new ServletException(t);
}
} finally {
_jspxFactory.releasePageContext(_jspx_page_context);
}
}
由于JSP脚本和静态HTML部分都转换成_jspService()方法里的执行代码,这些脚本已经处于try语句块中。一旦try语句块捕捉到JSP脚本的一场,并且_jspx_page_context不为null,,就会有该对象来处理异常。
_jspx_page_context对一场的处理:如果该页面的page指令制定了errorPage属性,则将请求forward到errorPage属性制定的页面,否则使用系统页面输出一场信息。
注意
由于只有JSP脚本、输出表达式才对应与_jspService()方法里的代码,所以这两部分的代码无需处理checked异常。但JSP的声明部分仍然需要处理checked一场,JSP的异常处理机制对JSP声明不起作用。
在JSP的一场处理机制中,一个异常处理页面可以处理多个JSP页面脚本部分的异常。异常处理页面通过page编译指令的errorPage属性指定。
<!-- 通过errorPage属性指定异常处理页面 -->
<%@ page contentType="text/html; charset=GBK" language="java" errorPage="error.jsp" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title> JSP脚本的异常机制 </title>
<meta name="website" content="http://www.crazyit.org" />
</head>
<body>
<%
int a = 6;
int c = a / 0;
%>
</body>
</html>
以上代码执行后会抛出一个ArithmeticException,则JSP异常机制将会转发到error.jsp页面,error.jsp页面代码如下:
<%@ page contentType="text/html; charset=GBK" language="java" isErrorPage="true" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title> 异常处理页面 </title>
<meta name="website" content="http://www.crazyit.org" />
</head>
<body>
异常类型是:<%=exception.getClass()%><br/>
异常信息是:<%=exception.getMessage()%><br/>
</body>
</html>
error.jsp在page指令的isErrorPage属性被设为true,则可以通过exception对象来访问上一个页面所出现的异常。请求throwEx.jsp页面,运行截图如下:
打开error.jsp页面生成的Servlet类,在_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;
java.lang.Throwable exception = org.apache.jasper.runtime.JspRuntimeLibrary.getThrowable(request);
if (exception != null) {
response.setStatus(javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
...
}
注意
只有当page指令中的isErrorPage属性设为true,才可访问exception内置对象。
out对象
out对象代表一个页面输出流,通常用于在页面上输出变量值及常量。一般在使用输出表达式的地方,都可以使用out对象来打到同样效果。
<%
//注册数据库驱动
Class.forName("com.mysql.jdbc.Driver");
//获取数据库连接
Connection conn = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/javaee","root","123456");
//创建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>");
}
%>
从Java的语法上看,上面的程序更容易理解,out十个页面输出流,负责输出页面表格及所有内容,但使用out则需要编写更多代码,使用输出表达式更简洁。
注意
所有使用out的地方,都可以使用输出表达式来代替,而且使用输出表达式更加简洁。<%=…%>表达式的本质就是out.write(…);
pageContext对象
这个对象代表页面上下文,该对象主要用于访问JSP之间的共享数据。使用pageContext可访问page、request、session、application范围的变量。
pageContext是PageContext类的实例,它提供了如下两个方法来访问page、request、session、application范围的变量。
getAttribute(String name):取得page范围内的name属性。
getAttribute(String name, String scope):取得指定范围内的name属性,其中scope可以是如下4个值。
- PageContext.PAGE_SCOPE
- PageContext.REQUEST_SCOPE
- PageContext.SESSION_SCOPE
- PageContext.APPLICATION_SCOPE
这四个值分别对应page、request、session和application四个范围。
与getAttribute()方法相对应,PageContext也提供了2个对应的setAttribute(String name, String value)和setAttribute(String name, String value, Scope scope)方法,用于设置属性值和将属性值放到对应的page
<%
//使用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/>");
%>
上图使用pageContext获取个属性所在的范围,其中这些范围获取的都是整型变量,这些整形变量分别对应如下4个生存范围。
1:对应page生存范围
2:对应request生存范围
3:对应session生存范围
4:对应application生存范围
pageContext对象还包含如下方法:
- ServletRequest getRequest():获取request对象
- ServletResponse getResponse():获取response对象
- ServletConfig getServletConfig(): 获取config对象
- ServletContext getServletContext:获取application对象
- HttpSession getSession(): 获取session对象
如果获取到pageContext对象,就可通过以上方法获取其他内置对象。
request对象
request对象是JSP中重要的对象之一,每个request对象封装这一次用户请求,并且所有的请求参数都被封装在request对象中,因此request对象是获取请求参数的重要途径。
1. 获取请求头/请求参数(header/parameters)
Web应用是请求/相应架构的应用,浏览器发送请求是通常会附带一些请求头,还包含请求参数发送给服务器,服务端负责解析请求头/请求参数的是JSP或Servlet,而JSP和Servlet去的请求参数的途径就是request。request是HttpServletRequest接口的实例,他提供如下几个方法来获取请求参数(parameters):
String getParameter(String paramName)
:获取paramName请求参数的值。Map getParameterMap()
:获取所有请求参数名和参数值所组成的Map对象。Enumeration getParameterNames()
: 获取所有请求参数名所组成的Enumeration对象。String[] getParameterValues(String name)
: paramName请求参数的值,当请求参数有多个值时,该方法将返回多个值所组成的数组。HttpServletRequest提供了如下方法来访问请求头(Headers):
String getHeader(String name)
:获取指定请求头的值。java.util.Enumeration<String> getHeaderNames()
:获取所有请求头的名称。java.util.Enumeration<String> getHeaders(String name)
:获取指定请求头的多个值。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元素设置method属性为post,则表示发送POST请求。
下面是表单页面的代码:
<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>
这个页面没有动态的JSP部分,他只是包含一个手机请求参数的表单,且该表单的action为request1.jsp,这表明提交表单时请求将headers和parameters发送到request1.jsp页面;本次请求method为post,说明提交表单将发送POST请求。
在该页面输入相应信息后,单击“提交”按钮,表单域所代表的请求参数将通过request对象的getParameter()方法来获得。
提示
并不是每个表单域都会生成请求参数的,而是有name属性的表单域才会生成请求参数。表单域和请求参数的关系遵循一下4点:
- 每个有name属性的表单域对应一个请求参数。
- 如果有多个表单域有相同的name属性,则多个表单域只生成一个请求参数,只是改参数有多个值。
- 表单域的name属性指定请求参数名,value制定请求参数值。
- 如果某个表单与设置了disabled=”disabled”属性,则该表单域不在生成请求参数。
request1.jsp页面对应代码如下:
<body>
<%
//获取所有请求头的名称
Enumeration<String> headerNames = request.getHeaderNames();
while(headerNames.hasMoreElements())
{
String headerName = headerNames.nextElement();
//获取每个请求、及其对应的值
out.println(
headerName + "-->" + request.getHeader(headerName) + "<br/>");
}
out.println("<hr/>");
//设置解码方式,对于简体中文,使用gb2312解码
request.setCharacterEncoding("gb2312");
//下面依次获取表单域的值
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>
上述代码示范了如何获取请求头、请求参数,在获取表单域对应的请求参数值之前,先设置request编码的字符集request.setCharacterEncoding("gb2312");
如果POST请求的请求参数里包含飞袖字符,则必须在获取请求参数之前先调用setCharacterEncoding()方法设置编码的字符集。由于本次发送请求的表单页采用gb2312字符集,所以在request1.jsp页面先执行request.setCharacterEncoding("gb2312");
在form.jsp点击提交按钮,跳转到request1.jsp就会出现下图运行效果:
如果需要传递的参数是普通字符串,而且仅需传递少量参数,可以选择GET方式发送请求参数,GET方式发送的请求参数被附加到地址栏的URL之后,地址栏的URL将变成如下形式:
url?param1=val1¶m2=val2¶m3=val3&…paramN=valN
URL和多个参数用“?”分隔,而多个参数之间用“&”分隔。
下面代码示范如何通过request获取GET请求参数值:
<body>
<%
//获取name请求参数的值
String name = request.getParameter("name");
//获取gender请求参数的值
String gender = request.getParameter("gender");
%>
<!-- 输出name变量值 -->
您的名字:<%=name%><hr/>
<!-- 输出gender变量值 -->
您的性别:<%=gender%><hr/>
</body>
不难看出,request获取POST请求参数的代码和获取GET请求参数完全一样。但是GET请求必须在地址栏增加请求参数,如下图:
如果请求参数里包含非西欧字符,即使通过先调用setCharacterEncoding()来设置request编码的字符集,这种做法也是行不通。
下面代码示范如何解决中文编码的问题:
<body>
<%
//获取请求里包含的查询字符串
String rawQueryStr = request.getQueryString();
out.println("原始查询字符串为:" + rawQueryStr + "<hr/>");
//使用URLDecoder解码字符串
String queryStr = java.net.URLDecoder.decode(
rawQueryStr , "gbk");
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/>");
}
%>
疑问1
我做了实验,代码如下:
<body>
<%
//获取请求里包含的查询字符串
String rawQueryStr = request.getQueryString();
out.println("原始查询字符串为:" + rawQueryStr + "<hr/>");
//使用URLDecoder解码字符串
String queryStr = java.net.URLDecoder.decode(
rawQueryStr , "gbk");
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/>");
}
request.setCharacterEncoding("gb2312");
//获取name请求参数的值
String name = request.getParameter("name");
//获取gender请求参数的值
String gender = request.getParameter("gender");
%>
<!-- 输出name变量值 -->
您的名字:<%=name%><hr/>
<!-- 输出gender变量值 -->
您的性别:<%=gender%><hr/>
</body>
第一次,输入url和请求参数,按下回车,运行结果如下:
不符合预期的结果
- 紫色方框里的请求参数没有变成特定编码符号,还是保留中文
- 红色方框里的经过编码转换,应该显示中文
- 橙色方框里没有经过编码转换,应该显示乱码
第二次,我输入url和请求参数,按下F5,运行结果就和预期相同:
第三次,使用form表单提交参数:
<body>
<form id="form1" method="get" action="request3.jsp">
用户名:<br/>
<input type="text" name="name"><hr/>
性别:<br/>
男:<input type="radio" name="gender" value="男">
女:<input type="radio" name="gender" value="女"><hr/>
<input type="submit" value="提交">
<input type="reset" value="重置">
</form>
</body>
在页面运行效果如下:
点击提交按钮,向request3.jsp发送GET请求
在request3.jsp运行结果如下:
- 紫色方框里的请求参数转换为相应的非西欧字符编码
- 红色方框里的请求参数值经过编码转换能显示中文
- 橙色方框里的请求参数值没有经过编码转换显示乱码
结论
通过form表单提交非西欧字符请求参数,这种做法符合预期结果;
通过直接在浏览器地址栏输入,这种做法可能由于浏览器内部的实现,导致没有达到预期效果,暂时不知道什么原理,先保留这个疑问。
疑问2
资料说可以通过先将字符串转为字节数组,然后再对字节数组重新编码成字符串的方法,但是我测试却不成功。
<body>
<%
//获取name请求参数的值
String rawName = request.getParameter("name");
//获取gender请求参数的值
String rawGender = request.getParameter("gender");
//将请求参数值使用ISO-8859-1字符串分解成字节数组
byte[] rawBytes1 = rawName.getBytes("ISO-8859-1");
byte[] rawBytes2 = rawGender.getBytes("ISO-8859-1");
//将字节数组重新解码成字符串
String name = new String(rawBytes1, "gb2312");
String gender = new String(rawBytes2, "gb2312");
%>
<!-- 输出name变量值 -->
您的名字:<%=name%><hr/>
<!-- 输出gender变量值 -->
您的性别:<%=gender%><hr/>
</body>
运行结果如下:
url上的请求参数已成功转码,但是在页面的代码却转码不成功
2.操作request范围的属性
HttpServletRequest还包含如下两个方法,用于设置和获取request范围的属性。
- setAttribute(String attName, Object attValue):将attValue设置成request范围的属性值,参数名为attName。
- Object getAttribute(String attrName):获取request范围的参数名为:attrName的属性。
如果使用forward请求时,请求的参数和请求属性都不会丢失。可想而知,include请求时,也不会丢失。因为include指令是引入另一个jsp页面的代码。以下为示例代码,第一个页面draw.jsp负责提交请求数据:
<body>
<!-- 取钱的表单 -->
<form method="post" action="first.jsp">
取钱:<input type="text" name="balance">
<input type="submit" value="提交">
</form>
</body>
该页面向first.jsp页面请求后,balance参数将被提交到first.jsp页面,下面是first.jsp页面代码:
<body>
<%
//获取请求的钱数
String bal = request.getParameter("balance");
//将钱数的字符串转换成双精度浮点数
double money = Double.parseDouble(bal);
//对取出的钱进行判断
if (money < 500)
{
out.println("转账" + money + "块");
out.println("账户减少" + money);
}
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>
first.jsp页面首先获取请求的转账数,然后对请求的钱进行判断。如果请求的钱小于500,则允许直接转账;否则将请求转发到second.jsp。转发之前创建了一个List对象,并设置成request范围的info属性。
接下来在second.jsp页面中,不仅获取了请求的balance参数,还会获取request范围的info属性。second.jsp 页面的代码如下。
<body>
<%
//取出请求参数
String bal = request.getParameter("balance");
double money = Double.parseDouble(bal);
//取出request范围内的info属性
List<String> info = (List<String>)request.getAttribute("info");
for (String tmp : info)
{
out.println(tmp + "<br/>");
}
out.println("转账" + money + "块");
out.println("账户减少" + money);
%>
</body>
运行结果如下:
提交数据页
处理请求页
3.执行forward或include动作指令
request还有一个功能就是执行forward和include,即代替JSP所提供的forward和include动作指令。其实request对象也可以执行forward。
HttpServletRequest类提供了一个getRequestDispatcher(String path)方法,其中path就是希望frward或者include的目标路径,该方法返回RequestDispatcher,该对象提供了如下两个方法。
- forward(ServletRequest request, ServletResponse response):执行forward动作指令。
- include(ServletRequest request, ServletResponse response):执行include动作指令。
示例代码如下:
getRequestDispatcher("/a.jsp").forward(request,response);
getRequestDispatcher("/a.jsp").include(request, response);
注意
使用request的getRequestDispatcher(String path)方法时,该path字符串必须以斜线开头。
response对象
response代表服务器对客户端的响应。大部分的时候,程序无须使用response来相应客户端请求,通常out对象即可完成。out对象代表页面输出流,用来生成一些简单的响应。
但out是JspWriter的实例对象,JspWriter是Writer的子类,Wirter是字符流,无法输出非字符内容。如果需要在JSP页面动态生成一幅位图,或者输出一个PDF文档,使用out作为响应对象将无法完成,此时必须使用response对象作为响应输出。
除此之外,还可以使用response对象来重定位请求,以及向客户端增加Cookie。
1.response响应生成非字符响应
对于需要生成非字符响应的情况,应该是用response来响应客户端请求。下面的JSP页面将在客户端生成一张图片。response是HttpServletResponse接口的实力,该接口提供了一个getOutputStream()方法,该方法返回响应输出字节流。
img.jsp代码如下:
%@ page contentType="image/jpeg" 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 , "jpg" , response.getOutputStream());
%>
在第一行编译指令中,page的属性设置为:image/jpeg,这表明服务器相应是一张JPG图片。接着创建了一个BufferedImage对象(代表图像),并获取该BufferedImage的Graphics对象(代表画笔),然后通过Graphics向BufferedImage中绘制图像,最后一行代码将直接将BufferedImage作为响应发送给客户端。运行效果如下:
也可以在其他页面中使用img标签来显示这个图片页面,代码如下:
<img src="img.jsp">
使用这种临时生成图片的方式就可以很容易地实现网页上的图形验证码功能。不仅如此,使用response生成非字符相应还可以直接生成PDF文件、Excel文件,这些文件可以直接作为报表使用。
2.重定向
重定向是response的另一个用处,与forward不同的是,重定向会丢失所有的请求参数和request范围的属性,因为重定向将生成第二次请求,与前一次请求在不同的一个request范围内,所以发送一次请求的请求参数和request范围的属性全部丢失。
HttpServletResponse提供了一个sendRedirect(String path)方法,该方法用于重定向到path资源,及重新向path资源发送请求。
下面JSP页面将使用response执行重定向:
<%@ page contentType="text/html; charset=GBK" language="java" errorPage="" %>
<%
//生成页面响应
out.println("====");
//重定向到forward-result.jsp页面
response.sendRedirect("redirect-result.jsp");
%>
以上页的那行代码:
response.sendRedirect("redirect-result.jsp");
用于执行重定向,想改页面发送请求时,请求会被重定向到redirect-result.jsp页面。例如,在地址栏中输入
http://localhost:8090/jspObject/doRedirect.jsp?name=lee
然后按下回车键,运行效果如下图:
此时地址栏已经发生改变,说明请求已经改变。在执行重定向动作时,地址栏的URL会变成重定向的目标URL。
-
注意
- 重定向会丢失所有的请求参数,使用重定向的效果,雨灾地址栏里重新输入新地址,再按回车键的效果完全一样,即发送了第二次请求。
从表面上来看,forward动作和redirect动作有些相似,:他们都可以将请求传递到另一个页面。但实际上forward和redirect之间存在较大差异,其中最明显的就是:forward动作执行的一个是同一个请求,redirect动作执行的是第二次请求。
forward转发 | redirect重定向 |
---|---|
执行forward后依然是上一次请求 | 执行redirect后生成第二次请求 |
3.增加Cooke
Cookie通常用于网站记录客户的某些信息,比如客户的用户名及客户的喜好等。一旦用户下次登陆,网站可以获取到客户的相关信息,根据这些客户信息,昂站可以对客户提供更友好的服务。Cookie与session的不同之处在于:session会随浏览器的关闭而失效,但Cookie会一直存放在客户端机器上,除非超出Cooke的生命期限。
由于安全性的原因,使用Cookie客户端浏览器必须支持Cookie才行。客户端浏览器完全可以设置禁用Cookie。
增加Cookie也是使用response内置对象完成的,response对象提供如下方法:
void addCookie(Cookie cookie)
: 增加Cookie。
在增加Cookie之前,首先要创建Cookie对象。增加Cookie应按照一下步骤:
- 创建Cookie实例,Cookie的构造器为
Cookie(String name, String value)
- 设置Cookie的生命周期,即该Cookie在多长时间内有效。
- 向客户端写Cookie
以下代码用于向客户端写入Cookie:
<%
//获取请求参数
String name = request.getParameter("name");
out.println(name);
String keyword = request.getParameter("keyword");
out.println(keyword);
//以获取到的请求参数为值,创建一个Cookie对象
Cookie c1 = new Cookie("username" , name);
Cookie c2 = new Cookie("keyword", keyword);
//设置Cookie对象的生存期限
c1.setMaxAge(20);//20秒
c2.setMaxAge(20);//20秒
//向客户端增加Cookie对象
response.addCookie(c1);
response.addCookie(c2);
%>
在浏览器地址栏输入GET请求URL
如果浏览器没有组织Cookie,执行该页面后,网站就会向客户端机器写入Cookie,该Cookie将在客户端硬盘上一直存在,知道超出Cookie的生存期限,这里设置为20秒
.
访问客户端Cookie使用request对象,request对象提供了getCookie()方法,将返回客户端机器上所有Cookie数组。从另一个JSP页面访问,即可获取Cookie,代码如下:
<%
//获取本站在客户端上保留的所有Cookie
Cookie[] cookies = request.getCookies();
//遍历客户端上的每个Cookie
for (Cookie c : cookies)
{
out.println(c.getName() + ":" + c.getValue());
}
%>
运行结果如下,其中JSESSIONID可能是默认的一个Cookie:
注意
使用Cookie对象必须设置其生存期限,否则Cookie将会岁浏览器的关闭而自动消失。
默认情况下,Cookie值不允许出现中文字符,如果我们需要值为中文内容的Cookie,可以借助java.net.URLEncoder先对中文字符进行编码,将编码后的结果设为Cookie之。当程序要读取Cookie时,则应该先读取,然后使用java.net.URLDecoder对其进行解码。
一下程序示范如何存入值为中文的Cookie。
<%
//以编码后的字符串为值,创建一个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()));
}
}
%>
- 在response对象add入Cookie前,先使用java.net.URLEncoder对中文进行编码;
- 在request对象读取Cookie的中文值前,先使用java.net.URLDecoder对中文进行解码。
session对象
session
对象也是一个常用的对象,该对象代表一次用户会话。一次用户会话的含义是:从客户端浏览器链接服务器开始,到客户端浏览器与服务器断开为止,这个过程就是一次会话。
session通常用于跟踪用户的绘画信息,如判断用户是否登陆系统,或者在购物车应用中,用于跟踪用户购买的商品等。
session范围内的属性可以在多个页面之间跳转共享。一旦关闭浏览器,即session结束,session范围内的属性将全部丢失。
session对象是HttpSession的实例,HttpSession有如下两个常用的方法。
- setAttribute(String attName, Object attVallue): 设置session范围内attName属性的值为attValue。
- getAttribute(String attName):返回session范围内attName属性的值。
下面代码示范一个购物车应用,一下是商品的JSP页面代码:
<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>
以下是页面运行效果
下面是处理请求页面的代码:
<%
//取出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 PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title> 购买的物品列表 </title>
<meta name="website" content="http://www.crazyit.org" />
</head>
<body>
您所购买的物品:<br/>
书籍:<%=itemMap.get("书籍")%>本<br/>
电脑:<%=itemMap.get("电脑")%>台<br/>
汽车:<%=itemMap.get("汽车")%>辆
<p><a href="shop.jsp">再次购买</a></p>
</body>
</html>
运行结果如下:
只有把浏览器所有页面都关闭,session的属性值才会消失。上面做了一个累加的实验,可以看出来。
注意
- 考虑到session本身的目的,通常只应该吧与用户绘画状态相关的信息放入session范围内。不要仅仅为了两个页面之间交换信息,就将该信息放入session范围内,这样会消耗空间。如果仅仅为了两个页面交换信息,通常可以将信息放入request范围内,然后forward请求即可。
- 还有一点需要指出,session机制通常用于保存客户端的状态信息,这些状态信息需要保存到Web服务器的硬盘上,所以要求session里的属性值必须是可持续化的,否则将会引发不可序列化的异常。(session的属性值可以是任何可序列化的Java对象)