J2EE是一套规范,而Servlet/Jsp是J2EE规范的一部分,是Tomcat的主要实现部分。在最初的应用实践中,当用户向指定Servlet发送请求时,Servlet利用输出流动态生成HTML页面,这导致Servlet开发效率极为低下。JSP技术通过实现普通静态HTML和动态部分混合编码,使得逻辑内容与外观相分离,大大简化了表示层的实现,提高了开发效率。本文以JSP的本质是Servlet为主线,结合JSP转译后所得的Servlet,详细探讨了JSP的原理、执行过程、脚本元素、编译指令和动作指令,并给出了JSP使用的常见注意事项。
一. JSP 原理
1、JSP 简介
JSP (Java Server Pages) 是由 Sun 公司倡导、多家公司参与一起建立的一种动态网页技术标准。JSP 文件(*.jsp)实际上是在传统的网页HTML文件(*.htm,*.html)中插入Java程序段(Scriptlet)和 JSP标记(tag)所形成的。 JSP 的本质是 Servlet,当用户向指定 Servlet 发送请求时,Servlet 利用输出流动态生成 HTML 页面。当使用 Servlet 作为表现层(实际上,Servlet 最适合作控制器)时,由于表现层页面往往包括大量的 HTML标签、静态文本和格式等,导致 Servlet开发效率极为低下。如下图所示,JSP 的出现弥补了这种不足,它通过实现普通静态HTML和动态部分混合编码,使得逻辑内容与外观相分离,大大简化了表示层的实现。实际上,在静态的HTML页面中嵌入动态Java脚本即可完成JSP的开发。JSP并没有增加任何本质上不能用Servlet实现的功能,但是在JSP中编写静态HTML更加方便,因为不必再用println语句来输出每一行HTML代码。
2、JSP 的执行过程
(1)当客户第一次请求JSP页面时,JSP引擎会通过预处理把JSP文件中的静态数据(HTML文本)和动态数据(Java脚本)全部转换为Java代码。转换原则非常直观:对于HTML文本只是简单的用 out.println() 方法包裹起来,而对于Java脚本只是保留或做简单的处理;
(2)JSP引擎把生成的.java文件编译成Servlet类文件(.class)。对于Tomcat服务器而言,生成的类文件默认的情况下存放在\work目录;
(3)编译后的class对象被加载到容器中,并根据用户的请求生成HTML格式的响应页面。
为了更好地了解JSP的转译过程和转换原则,我们以下面这个JSP文件为例进行说明。
<%@ page language="java" import="java.util.*" pageEncoding="GB18030"%>
<%-- JSP脚本 --%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<base href="<%=basePath%>">
<title>Success</title>
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="expires" content="0">
<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
<meta http-equiv="description" content="This is my page">
<!--
<link rel="stylesheet" type="text/css" href="styles.css">
-->
</head>
<body>
Welcome! </br>
<%! private String declaration = "我是JSP声明..." ;%>
<%=declaration %>
<%-- 我是JSP注释... --%>
</body>
</html>
JSP引擎会把JSP文件中的静态数据(HTML文本)和动态数据(Java脚本)转换为如下Java代码:
public final class welcome_jsp extends org.apache.jasper.runtime.HttpJspBase
implements org.apache.jasper.runtime.JspSourceDependent {
// 动态部分: 通过JSP声明的变量转化为成员变量
private String declaration = "我是JSP声明..." ;
private static java.util.List _jspx_dependants;
public Object getDependants() {
return _jspx_dependants;
}
public void _jspService(HttpServletRequest request, HttpServletResponse response)
throws java.io.IOException, ServletException {
//JSP 内置对象
JspFactory _jspxFactory = null;
PageContext pageContext = null;
HttpSession session = null;
ServletContext application = null;
ServletConfig config = null;
JspWriter out = null;
Object page = this;
JspWriter _jspx_out = null;
PageContext _jspx_page_context = null;
try { // JSP异常处理机制
_jspxFactory = JspFactory.getDefaultFactory();
response.setContentType("text/html;charset=GB18030");
pageContext = _jspxFactory.getPageContext(this, request, response,
null, true, 8192, true);
_jspx_page_context = pageContext;
application = pageContext.getServletContext();
config = pageContext.getServletConfig();
session = pageContext.getSession();
out = pageContext.getOut();
_jspx_out = out;
out.write('\r');
out.write('\n');
// 动态部分: JSP脚本直接转换为Java代码
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
// 静态部分: 页面输出流输出静态HTML
out.write("\r\n");
out.write("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\r\n");
out.write("<html>\r\n");
out.write(" <head>\r\n");
out.write(" <base href=\"");
out.print(basePath);
out.write("\">\r\n");
out.write(" \r\n");
out.write(" <title>Success</title>\r\n");
out.write("\t<meta http-equiv=\"pragma\" content=\"no-cache\">\r\n");
out.write("\t<meta http-equiv=\"cache-control\" content=\"no-cache\">\r\n");
out.write("\t<meta http-equiv=\"expires\" content=\"0\"> \r\n");
out.write("\t<meta http-equiv=\"keywords\" content=\"keyword1,keyword2,keyword3\">\r\n");
out.write("\t<meta http-equiv=\"description\" content=\"This is my page\">\r\n");
out.write("\t<!--\r\n");
out.write("\t<link rel=\"stylesheet\" type=\"text/css\" href=\"styles.css\">\r\n");
out.write("\t-->\r\n");
out.write(" </head>\r\n");
out.write(" \r\n");
out.write(" <body>\r\n");
out.write(" Welcome! </br>\r\n");
out.write(" \t");
out.write("\r\n");
out.write(" \t");
// 动态部分: JSP 输出表达式转换为 out.print()
out.print(declaration );
out.write('\r');
out.write('\n');
out.write(' ');
out.write("\r\n");
out.write(" </body>\r\n");
out.write("</html>\r\n");
} catch (Throwable t) {
if (!(t instanceof SkipPageException)){
out = _jspx_out;
if (out != null && out.getBufferSize() != 0)
out.clearBuffer();
if (_jspx_page_context != null) _jspx_page_context.handlePageException(t);
}
} finally {
if (_jspxFactory != null) _jspxFactory.releasePageContext(_jspx_page_context);
}
}
}
结合以上示例,关于JSP本质和转移准则,我们可以得出以下几点结论:
1、org.apache.jasper.runtime.HttpJspBase 继承自 javax.servlet.http.HttpServlet,因此 JSP 的本质是 Servlet;
2、JSP 页面的静态部分(HTML内容)在 _jspService()方法中由页面输出流进行输出;
3、JSP引擎在处理 JSP页面的动态部分时,会将JSP声明的变量/方法转换为类的成员变量/方法;将JSP脚本按原顺序插入到_jspService()方法中;将JSP的输出表达式转换为 out.print() 进行输出;将JSP注释进行忽略。
在请求时期,容器加载编译后的Servlet类,并把响应结果返回至客户端。特别地,如果JSP页面已经被转换为Servlet类(jsp文件转换.java文件)且该Servlet类已经被编译(.java文件编译成.class文件)进而被加载(在第一次被请求时)了,这样再次请求此JSP页面时,将感觉不到延迟。
二. JSP 基本语法与脚本元素
JSP 的脚本元素(Scripting Element)包含三个部分:Scriptlet、Expression(表达式) 和 Declaration(声明)。
Scriptlet 元素
<%@ page language="java" import="java.util.*" contentType="text/html; charset=utf-8" pageEncoding="utf-8" errorPage="exception.jsp"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>test</title>
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="expires" content="0">
<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
<meta http-equiv="description" content="This is my page">
</head>
<body>
<% for(int i =0; i < 7; i++){
out.print("<font size='" + i + "'>");
%>
地球</br></font> <!-- 会打印七遍“地球”-->
<%}%>
</body>
</html>
JSP 提供了一种简单方法访问可用的Java变量和Java表达式,并生成页面HTML字符串。Expression 元素是以 <%= 开始,以%> 结尾的,其中内容包含一段合法Java的表达式。
Declaration 元素
Declaration元素用于声明在页面中初始化的变量、方法和类,特别需要注意以下几点:
(1)编译JSP时,Scriptlet 中定义的变量是_jspService()方法的局部变量,而使用 Declaration 所声明的变量为全局变量;
(2)每一个Declaration声明仅在一个页面中有效,如果要在每个页面都用到一些共同的声明,最好把它们写成一个单独的JSP网页,然后用<%@include%>或元素包含进来;
(3)Declaration元素必须是完整的Java语句,以分号结尾,并且不能产生任何输出。
此外,JSP注释的格式为 <%– –%>,与 HTML注释 (<!– –>)的区别在于: JSP注释不会输出到客户端。
<%@ page language="java" import="java.util.*" contentType="text/html; charset=utf-8" pageEncoding="utf-8" errorPage="exception.jsp"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>test</title>
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="expires" content="0">
<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
<meta http-equiv="description" content="This is my page">
</head>
<body>
<% for(int i =0; i < 7; i++){
out.print("<font size='" + i + "'>");
%>
地球</br></font> <!-- 会打印七遍“地球”-->
<%}%>
</body>
</html>
JSP 的三个编译指令
JSP指令负责发送消息到JSP引擎,不包含业务逻辑,不修改out流,只是告诉JSP引擎JSP页面应该如何编译。JSP指令的作用范围仅限于包含指令本身的JSP页面。JSP编译指令有三种类型:page指令、include指令和taglib指令。
1、Page 指令
Page指令定义了一些属性,通知关于JSP页面一般设置的Servlet引擎的属性。对于page指令,有以下几点需要注意:
(1)可以在一个页面中引用多个 Page指令,但是除了import属性能多次使用之外,其他的属性都只能用一次;
(2)Page指令可以放在JSP文件的任何地方,但是为了JSP程序的可读性及养成好的编程习惯,最好还是放在JSP文件的顶部;
(3)Page 指令的errorPage属性和isErrorPage属性共同组成了JSP的异常机制,因此 JSP 可以不用像Java那样处理异常,即使是checked异常。前者用于设置该jsp页面出现异常时所要转到的页面,如果没设定,容器将使用当前的页面显示错误信息;后者用于设置该jsp页面是否作为错误显示页面,默认是false,如果设置为true,容器则会在当前页面生成一个 exception 内置对象。
2、include 指令
include指令指出所编译的JSP页面要包含的文件名(以相对URL形式),实质上是页面的静态导入(复制),甚至它会把目标页面的其他编译指令也包含进来(动态include则不会),所以被包括的文件内容会成为当前JSP页面的一部分。需要注意的是,通过include指令包含的文件的操作会在转译时期完成,即将所包含的页面以页面输出流的形式添加到原JSP页面中,如下所示。
<%@ page language="java" import="java.util.*" pageEncoding="utf-8" errorPage=""%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>My JSP 'include.jsp' starting page</title>
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="expires" content="0">
<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
<meta http-equiv="description" content="This is my page">
</head>
<body>
This is my JSP page. <br>
</body>
<%@ include file="test.jsp"%>
</html>
使用include编译指令所包含的JSP文件被转译成如下的java片断:
out.write("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\r\n");
out.write("<html>\r\n");
out.write(" <head>\r\n");
out.write(" <title>test</title>\r\n");
out.write("\t<meta http-equiv=\"pragma\" content=\"no-cache\">\r\n");
out.write("\t<meta http-equiv=\"cache-control\" content=\"no-cache\">\r\n");
out.write("\t<meta http-equiv=\"expires\" content=\"0\"> \r\n");
out.write("\t<meta http-equiv=\"keywords\" content=\"keyword1,keyword2,keyword3\">\r\n");
out.write("\t<meta http-equiv=\"description\" content=\"This is my page\">\r\n");
out.write(" </head>\r\n");
out.write(" \r\n");
out.write(" <body>\r\n");
out.write(" \t");
for(int i =0; i < 7; i++){
out.println("<font size='" + i + "'>");
out.write("\r\n");
out.write(" \t地球</br></font>\r\n");
out.write(" ");
out.write("\r\n");
JSP中的七个动作指令
(1)、jsp:forward,jsp:include,jsp:param;
(2)、jsp:useBean,jsp:setProperty,jsp:getProperty;
1、forward 指令
执行页面转向,将请求的处理转发到下一个页面。使用 forward 指令进行请求转发时,其并不会重新向新页面发送请求,而只是采用新页面来对用户生成响应。也就是说,请求依然是一次请求,所以不会丢失请求参数,而且用户的请求地址也不会发生改变。此外,在使用forward 指令转发请求时可以增加额外的请求参数,格式如下:
<jsp:forward page="{relativeURL | <%=expression%>}">
<jsp:param value="***" name="***"/>
</jsp:forward>
forward指令被转译为Servlet时,会在其对应处添加以下代码片段:
if (true) {
_jspx_page_context.forward("所forward的页面地址" + (("所forward的页面地址").indexOf('?')>0? '&': '?') +
org.apache.jasper.runtime.JspRuntimeLibrary.URLEncode("额外参数名", request.getCharacterEncoding()) +
"=" + org.apache.jasper.runtime.JspRuntimeLibrary.URLEncode("对应参数值", request.getCharacterEncoding()));
return; // 不执行该指令后面的内容
}
结合以上说明,我们可以得出以下几个结论:
- forward指令使用同一个request;
- forward后的语句不会继续发送给客户端;
- 服务器内部转换 (转发的路径必须是同一个web容器下的url);
- 可以传递额外的参数。
2、include 指令
用于动态引入一个JSP页面,但它不会导入被include页面的编译指令,仅仅将被导入页面的body内容插入本页面。此外,在使用forward 指令转发请求时可以增加额外的请求参数,格式如下:
<jsp:include page="{relativeURL | <%=expression%>}">
<jsp:param value="***" name="***"/>
</jsp:include>
include 指令被转译为Servlet时,会在其对应处添加以下代码片段:
org.apache.jasper.runtime.JspRuntimeLibrary.include(request, response, "被导入页面的地址" +
(("被导入页面的地址").indexOf('?')>0? '&': '?') +
org.apache.jasper.runtime.JspRuntimeLibrary.URLEncode("额外参数名", request.getCharacterEncoding())+ "=" +
org.apache.jasper.runtime.JspRuntimeLibrary.URLEncode("对应参数值", request.getCharacterEncoding()),
out, false);
从以上代码片段我们可以看出,动态导入只是使用一个include()方法来插入目标页面的内容,而不是将目标页面完全融入当前页面中。因此,静态导入和动态导入有如下三点区别:
- 静态导入是被导入页面的代码完全融入;而动态导入则在Servlet中使用include方法引入被导入页面内容;
- 静态导入会导入被导入页面的编译指令;而动态导入只是插入被导入页面的body内容;
- 动态导入还可以增加额外的参数。
useBean、setProperty、getProperty 指令
useBean指令用于在JSP页面中初始化一个Java对象;setProperty 指令用于为 JavaBean 实例的属性设置值;getProperty 指令用于输出 JavaBean 实例的属性值。格式分别如下:
<jsp:useBean id="name" class="classname" scope="page | request | session | application"/>
<jsp:setProperty name="BeanName" property="propertyName" value="value"/>
<jsp:getProperty name="BeanName" property="propertyName"/>
useBean、setProperty、getProperty 指令在使用上是并列的(不是嵌套的),形式如下:
<jsp:useBean id="p" class="com.tju.rico.bean.Person" scope="application"/>
<jsp:setProperty property="name" name="p" value="rico"/>
<jsp:getProperty property="name" name="p"/></br>
形如上面的jsp代码被转译为Servlet时,会在其对应处添加以下代码片段:
//useBean 指令的转译
com.tju.rico.bean.Person p = null;
// 共享资源的序列化访问
synchronized (application) {
p = (com.tju.rico.bean.Person) _jspx_page_context.getAttribute("p", PageContext.APPLICATION_SCOPE);
if (p == null){
p = new com.tju.rico.bean.Person();
_jspx_page_context.setAttribute("p", p, PageContext.APPLICATION_SCOPE);
}
}
//内部调用了JavaBean的 setter
org.apache.jasper.runtime.JspRuntimeLibrary.introspecthelper(_jspx_page_context.findAttribute("p"),
"name", "rico", null, null, false);
//调用了JavaBean的 getter
out.write(org.apache.jasper.runtime.JspRuntimeLibrary.
toString((((com.tju.rico.bean.Person)_jspx_page_context.findAttribute("p")).getName())));
结合以上说明,我们可以在JSP中使用定义好的Bean,但是 Bean 必须具有以下特点:
- Bean类应该没有任何公共实例变量多态(Bean中可以不定义属性,但必须提供对应的 getter/setter),也就是说,不允许直接访问实例变量,变量名称首字母必需小写;
- 必须要有一个不带参数的构造器。在JSP元素创建Bean时会调用空构造器 (JSP创建bean的时候使用不带参数的构造器);
- 通过getter/setter方法来读/写变量的值,并且将对应的变量首字母改成大写。
三.JSP 九大内置对象概述及相关概念说明
- 由JSP规范提供,不需要使用者来实例化;
- 通过Web容器实现和管理;
- 所有JSP页面均可使用;
- 只有在脚本元素的表达式或代码段中才可使用(<%=使用内置对象%>或<%使用内置对象%>)而不能在JSP声明中使用,因为内置对象都是_jspService()方法的局部变量。
内置对象 | 说明 | 类型 | 作用域 |
pageContext | 页面上下文对象 | javax.servlet.jsp.PageContext | Page |
request | 请求对象 | javax.servlet.ServletRequest | Request |
session | 会话对象 | javax.servlet.http.HttpSession | Session |
application | 应用程序对象 | javax.servlet.ServletContext | Application |
response | 响应对象 | javax.servlet.ServletResponse | Page |
out | 页面输出对象 | javax.servlet.jsp.JspWriter | Page |
config | 配置对象 | javax.servlet.ServletConfig | Page |
exception | 异常对象 | javax.lang.Throwable | Page |
page | 页面对象 | java.lang.Object | Page |
1、JSP内置对象的实质
我们通过一个JSP实例来认识JSP内置对象的实质,JSP代码片段(isErrorPage=”true”表明这是一个异常处理页面,因为只有异常处理页面才有exception对象)。其JSP代码片段如下:
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8" isErrorPage="true"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
...
</html>
对应转译后的Java代码片段:
public final class exception_jsp extends org.apache.jasper.runtime.HttpJspBase
implements org.apache.jasper.runtime.JspSourceDependent {
...
public void _jspService(HttpServletRequest request, HttpServletResponse response)
throws java.io.IOException, ServletException {
//内置对象都是_jspService方法内部的局部变量
PageContext pageContext = null;
HttpSession session = null;
Throwable exception = org.apache.jasper.runtime.JspRuntimeLibrary.getThrowable(request);
if (exception != null) {
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
ServletContext application = null;
ServletConfig config = null;
JspWriter out = null;
Object page = this;
try {
_jspxFactory = JspFactory.getDefaultFactory();
response.setContentType("text/html;charset=UTF-8");
pageContext = _jspxFactory.getPageContext(this, request, response,null, true, 8192, true);
_jspx_page_context = pageContext;
application = pageContext.getServletContext();
config = pageContext.getServletConfig();
session = pageContext.getSession();
out = pageContext.getOut();
......
}
}
}
我们可以从上述JSP代码片段转译后的Java代码片段看出,request、response、session、application、out、pagecontext、config、page、exception 这九个内置对象都是_jspService()方法的局部变量(request 和 response 两个内置对象是该方法的形参,实质上也是它的局部变量) ,根据JSP转译规则,我们可以直接在JSP脚本(只有在脚本元素的表达式或代码段中才可使用,不能在JSP声明中使用)中使用这些对象,而无须创建它们。
特别地,只有设置isErrorPage=”true”的页面才是异常处理页面,而且只有异常处理页面对应的Servlet才会初始化 exception 对象。
一般地,我们称基于 Web 的应用为 B/S 应用,这些应用一般都是 请求/响应 架构的,即总是先由客户端发送请
求,服务器接收到请求并返回响应数据。现在,我们概述一下基于 请求/响应 架构的应用的运行机制,以及在其中扮
演重要角色的 浏览器 和 Web服务器 各自的作用,其示意图如下:
浏览器的作用主要有以下几个:
(1)向远程服务器发送请求;
(2)读取远程服务器返回的字符串数据;
(3)负责根据返回的字符串数据渲染出一个丰富多彩的页面。
Web服务器负责接收客户端请求,每当接收到客户端连接请求之后,Web服务器就会单独开启一个线程为该客户端提供服务。对于每次客户端的请求而言,Web 服务器大致需要完成如下几个步骤:
(1)为客户端请求启动单独的线程;
(2)使用 I/O 流读取用户请求的二进制流数据;
(3)从请求数据中解析请求参数;
(4)处理请求;
(5)生成响应数据;
(6)使用 I/O 流向客户端发送请求数据。
上面六个步骤中,第1、2和6步是通用的,由 Web服务器来完成,但第3、4和5步则存在差异。因为不同请求的请求参数不同,处理请求的方式也不同,所生成的响应自然也不同,那么Web服务器如何执行这些步骤呢?
实际上,Web服务器会调用Servlet的_jspService()方法来处理这些事情。我们知道,在转译时期,JSP中的静态页面和java脚本都会转换为_jspService()方法的执行代码,这些执行代码就负责完成解析请求参数、处理请求、生成响应等功能,而Web服务器则负责完成多线程、网络通信等底层实现。
Web 服务器执行到第三步得到请求参数后,会根据这些请求参数来创建 HttpServletRequest、HttpServletResponse 等对象,作为调用 _jspService()方法的参数。由此可见,一个Web服务器必须实现Servlet API中绝大部分接口提供实现类(也就是说,Web服务器要实现J2EE中的Servlet、JSP等标准)。
2、JSP/Servlet的通信与内置对象的作用域
为了解决这个问题,几乎所有的Web服务器都会提供四个类似Map的结构,即page、request、session 和 application,并允许JSP/Servlet在这四个类似Map的结构中存取数据,而这四个类似Map结构的数据的区别仅在于作用域不同。特别地,JSP中pageContext、request、session和application 四个内置对象分别用于操作page、request、session和application范围内的数据。
作用域是指变量的有效范围。我们通过一个例子进行简单说明,假设这样一个场景:当我们访问index.jsp 的时候,分别对pageContext、request、 session 和 application四个作用域中的整型变量进行累加。然后,从 index.jsp forward到test.jsp,并在test.jsp里再进行一次累加,然后显示出这四个整数来。从显示的结果来看,我们可以直观的看到:
- page 范围里的变量无法从index.jsp传递到test.jsp,只要页面跳转了,它们就不见了;
- request 范围里的变量可以跨越forward的前后的两页,但是只要刷新页面,它们就重新计算了;
- session 范围里的变量一直在累加,开始还看不出区别,但只要关闭浏览器,再次重启浏览器访问这个页面,它们就重新计算了;
- application 范围里的变量一直在累加,除非你重启tomcat,否则它们会一直变大。
(1) 如果我们把变量放到pageContext里,就说明这个变量的作用域是page,它的有效范围只在当前jsp页面里。也就是说,从把变量放到pageContext开始,到jsp页面结束,你都可以使用这个变量。
(2) 如果把变量放到request里,就说明这个变量的作用域是request,它的有效范围是当前请求周期。所谓请求周期,就是指从http请求发起,到服务器处理结束并返回响应的整个过程。在这个过程中可能使用forward的方式跳转了多个jsp页面,但由于仍然是同一个请求,因此在这些页面里,我们都可以使用这个变量。
(3) 如果把变量放到session里,就说明这个变量的作用域是session,它的有效范围是当前会话。所谓当前会话,就是指从用户打开浏览器开始,到用户关闭浏览器这中间的过程,这个过程可能包含多个请求和响应。也就是说,只要用户不关浏览器,服务器就有办法知道这些请求是一个人发起的,整个过程被称为一个会话(session),而放到会话中的变量,就可以在当前会话范围内使用。
(4) 如果把变量放到application里,就说明这个变量的作用域是application,它的有效范围是整个应用。所谓整个应用是指从应用启动,到应用结束。特别地,我们没有说“从服务器启动,到服务器关闭”,这是因为一个服务器可以部署多个应用,只要你结束了当前应用,这个变量就失效了。
与上述三个作用域不同的是,application 作用域里的变量的存活时间是最长的,如果不进行手工删除,它们就一直可以使用。此外,application里的变量可以被所有用户共用,也就是说,如果用户甲的操作修改了application中的变量,用户乙访问时得到的是修改后的值。这在其他作用域中是不会发生的,page、 request 和 session 都是完全隔离的,无论如何修改都不会影响其他用户。
3、application对象
1) 通过以下两个方法在整个Web应用的多个JSP、Servlet间共享Web应用状态层面的数据:
- setAttribute(String attrName, Object value)
- getAttribute(String attrName)
使用 getInitParameter(String attrName) 方法获取 Web 应用的配置参数,这些参数应该在 web.xml 文件中使用 <context-param></context-param>元素进行配置,每个元素配置一个参数,例如:
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<!-- Web 应用配置参数 -->
<context-param>
<param-name>admin</param-name>
<param-value>Rico</param-value>
</context-param>
<context-param>
<param-name>campus</param-name>
<param-value>NEU & TJU</param-value>
</context-param>
</web-app>
通过这种方式可以将一些配置信息放在web.xml文件中配置,从而避免使用硬编码的方式写在代码中,从而更好地提高程序代码的可移植性。
4、pageContext 对象
(1)、get/setAttribute(String name):取得取得/设置 page 范围内的 name 属性;
(2)、get/setAttribute(String name, int scope):取得/设置指定范围内的 name 属性,其中 scope 可以是如下四个值;
- public static final int PAGE_SCOPE = 1;
- public static final int REQUEST_SCOPE = 2;
- public static final int SESSION_SCOPE = 3;
- public static final int APPLICATION_SCOPE = 4;
5、request 对象
(1)获取请求参数/请求头
Web 应用是请求/响应架构的应用,浏览器发送请求时总会附带一些请求头,还可能包含一些请求参数发送给服务器。实际上,请求头和请求参数都是用户发送到服务器的数据,只不过前者通常由浏览器自动添加,而后者需要开发人员控制添加。服务器端负责解析请求头/请求参数的就是 JSP/Servlet,而 JSP/Servlet 获取请求参数的途径就是 request 对象,其提供如下几个方法来获取请求参数:
- getParameter(String name)
- getParameterMap()
- getParameterNames()
- getParameterValues(String name)
此外,客户端发送请求的方式一般有两种:
- GET 方式的请求:直接在浏览器地址栏输入访问地址所发送的请求或提交表单的默认请求参数发送方式;
- POST 方式的请求:以 post 方式提交表单,发送请求参数。该种方式一般将请求参数放在HTML HEADER 中传输,因此用户不能在地址栏看到请求参数值,安全性较高。
因此,一般建议以POST的方式发送请求。特别地,对于通过提交提交表单发送请求参数而言,需要注意两点:
- 只有具有name属性的表单域才生成请求参数;
- 若多个表单域有相同的name属性,则这些表单域只生成一个请求参数,只是该参数具有多个值。
(2)操作 request范围的属性
使用 request对象的如下两个方法可以设置/获取request范围的属性(特别注意的是,forward前后仍是同一个请求):
- setAttribute(String name, Object o)
- getAttribute(String name)
(3)执行 forward/include 操作
HttpServletRequest 提供了getRequestDispatcher(String path)方法去执行 forward/include 操作,也就是代替JSP所提供的 forward/include 动作指令,其中参数path就是希望 forward/include 的路径。具体方式如下:
request.getRequestDispatcher("目标路径").forward/include(request, response);
上述代码的语义就是
将请求forward到了目标页面或将目标页面包含到了当前页面
。需要注意的是,以该种方式进行 forward/include 时,
path参数必须以 “/” 开头
。
6、session 对象
- setAttribute(String attrName, Object value)
- getAttribute(String attrName)
特别地,通常只应该把与用户会话状态相关的信息放入session范围内,而不应该仅仅为了两个页面间交换信息,就将该信息放入session范围内。Session 的属性值可以是任何可序列化的 Java 对象。
7、 response 对象
(1) 重定向
请求重定向与 forward 不同,重定向会丢失所有的请求参数和 request范围内的属性。因为重定向将生成一个全新的请求,与前一次请求不在同一个 request 范围内,所以会丢失原有请求的所有请求参数和request范围内的属性。我们通过以下方式进行重定向:
response.sendRedirect("request/MyJsp.jsp");
特别需要注意的是,
请求重定向方法 sendRedirect() 的path参数不必以 “/” 开头(若以 “/” 开头,其代表的是 “http://localhost:8080/“)
。
(2) 操作Cookie
Cookie 通常用于网站记录客户的某些信息,例如客户的用户名等。一旦用户下次登录,网站就可以获取到客户的相关信息,根据这些客户信息,网站可以对客户提供更为友好的服务。Cookie 与 Session的最大不同在于:session会随着浏览器的关闭而失效,但Cookie会一直存放在客户端的机器上,除非超出Cookie的生命周期(Cookie的默认生命周期就是整个会话)。通常,如下面的例子所示,在客户端增加一个 Cookie 分为如下三个步骤:
- 创建Cookie实例,其构造器为 Cookie(String name, String value);
- 设置Cookie的生命周期;
- 向客户端增加Cookie。
// 获取请求参数
String name = request.getParameter("name");
// 以获取到的请求参数为值,创建一个Cookie对象
Cookie c = new Cookie("username" , name);
// 设置Cookie对象的生存期限
c.setMaxAge(24 * 3600);
// 向客户端增加Cookie对象
response.addCookie(c);
out.print(name);
访问客户端的Cookie时,需要使用request对象
。request对象提供了getCookies()方法,该方法将返回客户端机器上所有Cookie组成的数组,遍历该数组的每个元素,找到希望访问的Cookie即可,例如:
Cookie[] cookies = request.getCookies();
for(Cookie cookie : cookies){
out.print(cookie.getName() + " : " + cookie.getValue() + "</br>");
}
特别地,Cookie值不允许出现中文字符,如果需要值为中文内容的Cookie时,可以借助于 java.NET.URLEncoder/URLDecoder 进行转换。
8、config 对象
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<!-- Servlet 配置 -->
<servlet>
<servlet-name>config</servlet-name>
<servlet-class>com.edu.tju.rico.servlet.configServlet</servlet-class>
<init-param>
<param-name>name</param-name>
<param-value>Rico</param-value>
</init-param>
<init-param>
<param-name>age</param-name>
<param-value>24</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>config</servlet-name>
<url-pattern>/servlet/config</url-pattern>
</servlet-mapping>
</web-app>
9、exception 对象
exception 对象是Throwable的实例,代表JSP脚本中产生的异常和错误 ,是JSP页面异常机制的一部分,并且只有当isErrorPage属性设为true时才可以访问exception内置对象。我们知道,在JSP脚本中通常无需处理异常,即使是checked异常。事实上,JSP脚本所有可能出现的异常都可以交给错误处理页面处理。看下面非异常处理页面转译成Servlet后的例子(片段):
public void _jspService(HttpServletRequest request, HttpServletResponse response)
throws java.io.IOException, ServletException {
// 内置对象的声明
try {
// 内置对象的初始化
out.write("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\r\n");
out.write("<html>\r\n");
//... ...
out.write(" </body>\r\n");
out.write("</html>\r\n");
} catch (Throwable t) {
if (!(t instanceof SkipPageException)){
out = _jspx_out;
if (out != null && out.getBufferSize() != 0)
out.clearBuffer();
if (_jspx_page_context != null) _jspx_page_context.handlePageException(t);
}
} finally {
if (_jspxFactory != null)
_jspxFactory.releasePageContext(_jspx_page_context);
}
我们可以看到,
之所以在JSP脚本中通常无需处理异常,包括checked异常,是因为当JSP被转译成Servlet后,其静态HTML和动态java脚本都将置于 try 语句块中
。特别地,
exception 对象只有在异常处理页面中才有效
。看下面异常处理页面转译成Servlet后的例子(片段):
public void _jspService(HttpServletRequest request, HttpServletResponse response)
throws java.io.IOException, ServletException {
// 内置对象的声明
PageContext pageContext = null;
//...
PageContext _jspx_page_context = null;
Throwable exception = org.apache.jasper.runtime.JspRuntimeLibrary.getThrowable(request);
if (exception != null) {
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
try {
// 内置对象的初始化
pageContext = _jspxFactory.getPageContext(this, request, response,
null, true, 8192, true);
//...
//使用内置对象pageContext给_jspx_page_context赋值
_jspx_page_context = pageContext;
out.write("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\r\n");
out.write("<html>\r\n");
... ...
out.write("</html>\r\n");
} catch (Throwable t) {
if (!(t instanceof SkipPageException)){
out = _jspx_out;
if (out != null && out.getBufferSize() != 0)
out.clearBuffer();
if (_jspx_page_context != null) _jspx_page_context.handlePageException(t);
}
} finally {
if (_jspxFactory != null) _jspxFactory.releasePageContext(_jspx_page_context);
}
}
如上面代码片段所示,
一旦 try 语句块 捕获到JSP脚本的异常,并且 _jspx_page_context 不为 null,就会由该对象来处理异常
。实际上,该对象的处理逻辑也很简单:如果该页面的page指令指定了errorPage属性,则将请求forward到errorPage属性所指定的页面,否则使用系统页面来输出异常信息即可。特别地,JSP声明部分仍然需要处理checked异常。
本文内容主要转载自:
http://blog.csdn.net/justloveyou_/article/details/55824500
http://blog.csdn.net/justloveyou_/article/details/57154560