概述
JSP 全称是 Java Server Pages,它和 Servlet 技术一样,都是 SUN 公司定义的一种用于开发动态 web 资源的技术。
JSP 这门技术的最大的特点在于,写 JSP 就像在写 HTML,但它相比 HTML 而言,HTML 只能为用户提供静态数据,而 JSP 技术允许在页面中嵌套 Java 代码,为用户提供动态数据。
JSP 原理
案例:在 JSP 页面输出系统当前时间
<%@ page language="java" import="java.util.*" pageEncoding="ISO-8859-1"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>My JSP '1-showTime.jsp' starting page</title>
</head>
<body>
<font color="red">
<%
Date date = new Date();
String dateStr = date.toLocaleString();
out.write(dateStr);
%>
</font>
</body>
</html>
- 对 Server 中所有资源的访问都是由 Servlet 进行输出的
- 静态 web 资源由 DefaultServlet 进行输出
- 动态 web 资源 —— JSP
- 由 [JSP 翻译引擎(JspServlet)] 负责将 JSP 页面翻译生成对应的 Servlet
- 生成的 Servlet 放置在
tomcat/work/[Engine]/[virtualHost]/[webName]/
下 - 然后由生成的 Servlet 进行输出;JSP 页面在第一次被访问时会被 [JSP翻译引擎] 翻译成一个 Servlet,从此对这个 JSP 页面的访问都是由这个 Servlet 执行
service()
- 案例中 JSP 生成的 Servlet
- 类声明:
public final class _1_002dshowTime_jsp extends HttpJspBase implements JspSourceDependent
- 继承的 HttpJspBase 的类声明:
public abstract class HttpJspBase extends HttpServlet implements HttpJspPage
。也由此得证,JSP 生成的类是一个 Servlet - 既然是 Servlet,再看下
service()
部分源码:
- 类声明:
JSP 语法
JSP 模板元素
- JSP 页面中书写的 HTML 内容称作 "JSP的模板元素",在翻译过来的 Servlet 中直接被
out.write(…)
输出到 Browser 页面中了。
JSP 表达式
- 语法:
<%= Java表达式%>
- JSP脚本表达式中的变量或表达式后面不能有分号
- 在翻译过来的 Servlet 中,计算 [Java表达式] 的值后,被 out 输出到 Browser 上
<%= new Date().toLocaleString() %> -------------------↓------------------- out.print( new Date().toLocaleString());
JSP 脚本片段
- 语法:
<% 若干Java语句 %>
- 在翻译过来的 Servlet 中,直接被 copy 到了对应的位置
- 在一个 JSP 页面中可以有多个脚本片断,在两个或多个脚本片断之间可以嵌入文本、HTML 标记和其他 JSP 元素
- 多个脚本片段中的代码可以相互访问,犹如将所有的代码放在一对
<% %>
之中的情况 - 单个脚本片段中的 Java 语句可以是不完整的。但是,多个脚本片段组合后的结果必须是完整的 Java 语句
JSP 声明
- JSP 页面中编写的所有代码,默认会翻译到 Servlet 的
service()
中, 而 JSP 声明中的 Java 代码被翻译到_jspService()
的外面 - 语法:
<%! Java代码 %>
- JSP 声明可用于定义 JSP 页面转换成的 Servlet 程序的静态代码块、成员变量和方法
- 多个静态代码块、变量和函数可以定义在一个 JSP 声明中,也可以分别单独定义在多个 JSP 声明中
- JSP 隐式对象 的作用范围仅限于 Servlet 的
_jspService()
,所以在 JSP 声明中不能使用这些隐式对象 - Quiz:下面的代码有问题吗?
<% int i = 0; %> <%! public void method() { i++;} %> ------------------------------------------------------------------- <%! int i = 0; %> <%! public void method() { i++; response.getWriter().write(i);} %> <% method(); %>
JSP 注释
- JSP注释
- 语法:
<%-- 注释的内容 --%>
- 在 "JSP翻译引擎" 将 JSP 翻译成 Servlet 的过程中,会被丢弃;在翻译过来的 Servlet 中没有这些信息
- 语法:
- 其他注释
<% // Java注释 %>
:被当作 JSP脚本片段 被直接 copy 到了 Servlet 中,在".java"被编译成".class"文件的时候注释信息被丢弃
<!-- HTML注释 -->
:被当作 模板元素 被输出到了浏览器上,浏览器认识 HTML 注释,不予显示
JSP 指令
JSP 指令(directive)是为JSP引擎而设计的,它们并不直接产生任何可见输出,而只是告诉引擎如何处理 JSP 页面中的其余部分。在 JSP 2.0 规范中共定义了 3 个指令:① page 指令;② include 指令;③ taglib 指令
格式:<%@ 指令 属性名="值" ... %>
page 指令
用于定义 JSP 页面的各种属性,无论 page 指令出现在 JSP 页面中的什么地方,它作用的都是整个 JSP 页面。为了保持程序的可读性和遵循良好的编程习惯,page 指令最好是放在整个 JSP 页面的起始位置。
- language="java"
- extends=""
- 翻译过来的Servlet继承谁
- 这个类必须本身也是个Servlet
- import="{package1 package2 …}"
- 上面的语句也可以改写为使用多条 page 指令的 import 属性来分别引入各个包或类
- JSP 引擎自动导入如下包:
java.lang.*
,javax.servlet.*
,java.servlet.jsp.*
,javax.servlet.http.*
- session="true | false"
- 默认:true
- 若设置为 false
- 默认:true
- buffer="none | 8KB | sizeKB" 用来禁用 内置对象out 的缓冲区,或者指明它的缓冲区大小,默认 8KB
- autoFlush = "true | false"
- 当 out对象 的缓冲区满时,再写入的数据该如何处理
- true:先将缓冲区中的数据 flush 到 response缓冲区,然后再接受新数据
- false:满了再追加写入数据,则直接抛出异常
- isThreadSafe="false"
- 用来控制翻译过来的 Servlet 是否实现
SingleThreadModel<I>
- 取值:false,实现接口; true,不实现接口
- 用来控制翻译过来的 Servlet 是否实现
- errorPage=""
- 当 JSP 页面抛出异常的时候,用哪个页面进行提示
- 底层是请求转发
- isErrorPage="true | false"
- 默认:false
- 若设置为 true,exception 引用的就是出错的页面抛出的异常对象
- contentType
- JSP 乱码 ②
- 在翻译过来的Servlet中,如果要输出中文到 Browser,因为 out 相当于
response.getWriter()
,如果不指定编码,Server 发送数据时将字符转为字节时使用 ISO-8859-1,导致乱码 - 另外,除了控制 Server 以什么码表发送之外,还要控制 Browser 以什么码表打开,才能解决问题
- 在翻译过来的Servlet中,如果要输出中文到 Browser,因为 out 相当于
- 设置该属性相当于在翻译过来的 Servlet 中写上:
response.setContentType(...)
,从而可以解决上述乱码问题
- JSP 乱码 ②
- pageEncoding = "[characterSet]"
- 用来通知 [JSP翻译引擎] 在翻译时使用什么码表来读取 JSP 文件(只要设定过该属性,自动也就会去设置 Content-Type 属性)
- JSP 乱码 ①
- 当 JSP 页面第一次被访问到时,[JSP翻译引擎] 将 JSP 文件翻译成 Servlet。此时,如果不明确指定,则 [JSP翻译引擎] 默认在读取 JSP 文件时将使用 UTF-8 码表
- 如果当初存 JSP 时使用的码表和翻译时读取文件使用的码表不同,则在翻译的过程中就会产生乱码
- 在 MyEclipse 开发环境系下,文件的保存编码会随着 pageEncoding 的改变而改变
- isELIgnored = "true | false" 控制当前页面是否允许使用 EL 表达式
include 指令
用于引入其他 JSP 页面,如果使用 include 指令引入其他 JSP 页面,那么 [JSP翻译引擎] 将把这两个 JSP 翻译成一个Servlet
- 静态包含(只有 include 指令进行的包含是静态包含,其余包含都是动态包含): JSP 在被翻译成 Servlet 时,会把被包含的其他 JSP 的内容也都放在这个 Servlet 中
- 动态包含:被引入的 JSP 被各自翻译成 Servlet 之后,Servlet 都执行,然后输出结果做合并
taglib 指令
taglib 指令用于在 JSP 页面中导入标签库。具体使用详见 #7
JSP 内置对象
每个 JSP 页面在第一次被访问时,WEB 容器都会把请求交给 [JSP翻译引擎](即一个Java程序)去处理。 [JSP翻译引擎] 先将 JSP 翻译成一个 _ jspServlet(实质上也是一个 Servlet) ,然后按照 Servlet 的调用方式进行调用。
由于 JSP 第一次访问时会翻译成 Servlet,所以第一次访问通常会比较慢,但第二次访问, [JSP翻译引擎] 如果发现 JSP 没有变化,就不再翻译,而是直接调用,所以程序的执行效率不会受到影响。
[JSP翻译引擎] 在调用 JSP 对应的 _ jspServlet 时,会传递或创建 9 个与 web 开发相关的对象供 _ jspServlet 使用。JSP 技术的设计者为便于开发人员在编写 JSP 页面时获得这些 web 对象的引用,特意定义了 9 个相应的变量,开发人员在 JSP 页面中通过这些变量就可以快速获得这 9 大对象的引用。
- HttpServletRequest - request
- HttpServletResponse - response
- ServletConfig - config
- ServletContext - application
- Throwable - exception
- HttpSession - session
- HttpServlet.this - page
- JspWriter - out
- PageContext - pageContext
着重提下 out 和 pageContext。
out
out 隐式对象用于向客户端发送文本数据。
out 对象是通过调用 pageContext 对象的 getOut()
返回的,其作用和用法与 ServletResponse.getWriter()
返回的 PrintWriter 对象非常相似。
JSP 页面中的 out隐式对象 的类型为 JspWriter,JspWriter 相当于一种带缓存功能的 PrintWriter,设置 JSP 页面的 page 指令的 buffer 属性可以调整它的缓存大小,甚至关闭它的缓存。
这也就是 response.getWriter()
和 out 对象的不同之处。out 对象本身具有一个缓冲区,利用 out 写出的内容会先缓存在 out 缓冲区中,直到满足如下任何一个条件时,out 对象才去调用 ServletResponse.getWriter()
,并通过该方法返回的 PrintWriter 对象将 out 对象的缓冲区中的内容真正写入到 Servlet 引擎提供的 response 的缓冲区中,最终带到 Browser 的页面进行显示。
- 设置 page 指令的 buffer 属性关闭了 out 对象的缓存功能
- out 对象的缓冲区已满
- 整个 JSP 页面结束
注意:在 JSP 页面需要进行数据输出时,不要自己获取 response.getWriter().write()
,要使用 out 进行输出,防止既使用 out 又使用 response.getWriter()
而导致输出顺序错乱的问题。
这也是动态包含举的例子中打印结果错乱的原因。
pageContext
pageContext 对象是 JSP 技术中最重要的一个对象,它代表 JSP 页面的运行环境。
- 作为入口对象 获取其他 8 大隐式对象的引用
getException() getPage() getRequest() getResponse() getServletConfig() getServletContext() getSession() getOut()
- 还是一个域对象,4 大作用域的入口,可以操作 4 大作用域的域属性
- 作用范围:当前 JSP 页面
- 生命周期
- 当对 JSP 页面的访问开始时,创建代表当前 JSP 的 pageContext
- 当对当前 JSP 页面访问结束时,销毁代表当前 JSP 的 pageContext
- 作用:在当前JSP中共享数据
- 变量 & 方法
public void setAttribute(String name,Object value) public Object getAttribute(String name) public void removeAttribute(String name) public void setAttribute(String name, Object value,int scope) public Object getAttribute(String name, int scope) public void removeAttribute(String name, int scope) PageContext.APPLICATION_SCOPE PageContext.SESSION_SCOPE PageContext.REQUEST_SCOPE PageContext.PAGE_SCOPE findAttribute() 搜寻 4 大作用域中的属性,从最小的域向大的域找。 如果找到则返回该值,如果 4 大作用域中都找不到则返回一个 null
- 封装了 web 开发中经常涉及到的一些常用操作
pageContext.include("/index.jsp"); pageContext.forward("/index.jsp");
JSP 标签
JSP 标签也称之为 Jsp Action(JSP动作) 元素,它用于在 JSP 页面中提供业务逻辑功能,避免在 JSP 页面中直接编写 Java 代码,造成 JSP 页面难以维护。
常用的 3 个:
<jsp:include page="">
实现页面包含<jsp:forward page="">
实现请求转发<jsp:param name="" value="">
配合上面两个标签使用,在请求包含和请求转发时用来在路径后拼接一些请求参数
Tips
-
JSP 映射
<servlet> <servlet-name>index</servlet-name> <jsp-file>/scope.jsp</jsp-file> </servlet> <servlet-mapping> <servlet-name>index</servlet-name> <url-pattern>/jsp/*</url-pattern> </servlet-mapping>
-
JSP 最佳实践
把 Servlet 作为 web 应用中的控制器组件来使用,负责响应请求产生的数据,并把数据通过转发技术带给 JSP。而把 JSP 技术作为数据显示模板来使用 -
域的总结
- 4 个域
- [application域] ServletContext
- [session域] HttpSession
- [request域] HttpRequest
- [page域] pageContext
- 使用情景
- 如果一个数据只在当前 JSP 页面使用,可以使用 pageContext 域存储
- 如果一个数据除了在当前 Servlet 中使用,还要在请求转发时带到其他 Servlet 处理 / JSP 中显示,用request 域
- 如果一个数据除了现在要用,过一会还要用,用 session 域
- 如果一个数据除了自己要用,其他用户也要用,用 application 域
- 4 个域