JSP是Java Server Page的缩写,。它是Servlet的扩展,其作用是简化网站创建过程和维护动态网站。本章将介绍JSP的运行机制和语法、JSP包含其他Web组件的方法,以及把请求转发给其他Web组件的方法,本章还介绍了JSP的异常处理。w
1.比较HTML、Servlet和JSP
静态HTML文件,以及Servlet和JSP都能向客户端返回HTML页面。下面结合具体的例子来解释这三者的区别。
1.1 静态HTML文件
以一个简单的HTML文件为例:hello.html
<html>
<head>
<meta charset="UTF-8">
<title>HelloPage</title>
</head>
<body>
<b>Hello~</b>
</body>
</html>
当浏览器请求访问localhost:8080/hello.html时,Web服务器会读取本地文件系统中的hello.html文件中的数据,把它作为请求正文发送给浏览器,hello.html文件事先已经存在于文件系统中,每次客户端请求访问该文件,客户端得到的内容都是相同的。
1.2 用Servlet动态生成HTML文件
假定根据应用需求,要求Web应用在运行时能根据客户端的username请求参数来动态生成与参数匹配的HTML文档。显然,内容一成不变的hello.html无法完成这一任务,而具有动态生成HTML文档的servlet可以完成这一任务。
HelloServlet.java
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
public class HelloServlet extends HttpServlet {
@Override
public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String username = req.getParameter("username");
if (username == null) username = "Stranger";
PrintWriter out = resp.getWriter();
out.println("Hi,"+username);
out.close();
}
}
这种情况下,浏览器请求访问HelloServlet,这时会运行HelloServlet的一个实例,返回一个动态的HTML文档。
开发人员尽管可以通过Servlet来动态生成文档,但是必须要以java代码的形式来输出,即通过PrintWriter对象来一行行地打印HTML的内容。在Servlet中编写用于生成网页内容的程序代码不仅烦琐而且难以理解。尤其对于内容庞大并且布局非常复杂的页面,编程工作将会非常烦琐。
1.3 用JSP动态生成HTML页面
上面我们看到,HTML静态文档和Servlet各有优缺点,而JSP的出现就是吸收了两者的优点而摒弃了二者的缺点,大大地简化了动态生成网页的工作。
在传统的HTML文件(html、htm)中加入Java程序片段(Java Scriptlet)和JSP标记,就构成了JSP文件。
<html>
<head>
<title>Hello.jsp Page</title>
</head>
<body>
<b>Hello,<%=request.getParameter("username")%></b>
</body>
</html>
hello.jsp中的<%=request.getParameter("username")%>采用了JSP的语法,其作用是向网页上输出request.getParameter("username")方法的返回值。
浏览器访问localhost:8080/hello.jsp?username=Tomcat。当Servlet容器接收到客户端的要求访问特定JSP文件的请求时,容器按照以下流程来处理客户端请求:
- 查找JSP文件对应的Servlet,如果已经存在,就调用它的服务方法。
- 如果JSP对应的Servlet还不存在,就解析系统文件中的JSP文件,把它翻译成Servlet源文件,接着把Servlet源文件编译成Servlet类,然后再初始化并运行Servlet。后面把这个过程简称为编译JSP文件。
在一般情况下,把JSP文件翻译成Servlet源文件及编译Servlet源文件的过程仅在客户端首次调用JSP文件时发生。在Web应用处于运行时的状态下,如果原始的jsp文件被更新,多数Servlet容器能检测到所做的更新,继而自动生成新的Servlet源文件并进行编译,然后运行新生成的Servlet。
Tomcat把JSP生成的Servlet源文件和类文件放在work目录下,在开发和调试Web应用阶段,通常情况下,如果开发人员修改了JSP文件,Tomcat会重新编译JSP,并覆盖原文件。在少数情况下,如果更新JSP文件后通过浏览器看到的仍然是旧的网页,有可能是因为Tomcat使用的还是work目录下的旧Servlet类文件,此时可以手工删除work目录下的相关Servlet文件,确保Tomcat重新编译修改后的JSP文件。
从容器处理JSP的过程可以看出,JSP虽然形式上像HTML文件,但本质上是Servlet。前面1、2章介绍的Servlet的功能与特性也都适用于JSP。JSP中的java程序片段可以完成与Servlet同样的功能,例如动态生成网页、操纵数据库、把请求转发给其他Web组件以及发送Email等。但是,由于JSP和Servlet形式上的不一样,所以它们在Web应用中有着不同的分工。
JSP技术的出现,使得把应用中的HTML文档和业务逻辑代码有效分离成为可能。通常,JSP负责动态生成HTML文档,而业务逻辑由其他可重用的组件,如JavaBean或其他Java程序来实现。JSP可通过Java程序片段来访问这些业务逻辑。下图显示了JSP访问服务器端可重用的业务组件的模型,其中JavaBean表示业务组件。后续章节在介绍Web应用的MVC设计模式时,还会介绍JSP和Servlet的分工细节。
JSP本质上就是Servlet,因此JSP可以访问Servlet API中的接口和类,此外,JSP还可以访问JSP API中的接口和类。JSP API主要位于javax.servlet.jsp包及子包中,其文档可以从JSP官方网站上下载。 Tomcat中集成了jsp-api.jar。
2. JSP语法
虽然JSP本质上就是Servlet,但是JSP有着不同于Java编程语言的专门的语法,该语法的特点是,尽可能地用标记来取代Java程序代码,使整个JSP文件在形式上不像Java程序,而像标记文档。
如,在JSP中导入Java包的指令:
<%@page import="java.io.*,java.util.Hashtable" %>
再例如设置相应正文的类型:<%@page contentType="text/html; ISO-8859-1" %>
在JSP文件(扩展名为.jsp)中除了可以直接包含HTML文本,还可以包含以下内容:- JSP指令(或成为指示语句)。
- JSP声明。
- Java程序片段(Scriptlet)。
- Java表达式。
- JSP隐含对象。
- 还可以包含纯文本。只需要通过<%@ page contentType="text/plain" %>把相应类型设置为文本。
2.1 JSP指令(Directive)
JSP指令(在"<%@"和"%>"内)用来设置和整个jsp网页相关的属性,如网页的编码方式和脚本语言等。JSP指令的一般语法形式为: <%@ 指令名 属性="值"%>
常用的三种指令为page、include和taglib。下面分别讲述page和include指令,taglib指令在本书后续章节(自定义JSP标签)中详解。
1. page指令
page指令可以指定所使用的编程语言、与JSP对应的Servlet所实现的接口、所扩展的类及导入的软件包等。其一般语法形式为: <%@ page 属性1="值1" 属性2="值2"%>
page指令的属性 | 描述 | 举例 |
---|---|---|
language | 指定文件中的程序代码所使用的编程语言,目前仅java为有效值和默认值。该指令作用于整个文件,多次使用该指令,只有第一次使用是有效的。 | <%@ page language="java"%> |
method | 指定Java程序片段所属的方法的名称,Java程序片段会成为指定方法的主体,默认的方法是service()方法。当多次使用该指令时,只有第一次是有效的。该属性的有效值包括:service,doGet和doPost等。 | <%@ page method="doPost"%> |
import | 指定导入的Java软件包或类名列表,该列表用逗号分隔。在JSP文件中,可以多次使用该指令来导入不同的包。 | <%@ page import="java.io.*,java.util.Hashtable"%> |
content_type | 指定响应结果的MIME类型,默认MIME类型为“text/html”,默认字符编码为"ISO-8859-1"。当多次使用该指令时,只有第一次使用是有效的。 | <%@ page content_type="text/html;charset=GB2312"%> |
session="true|false" | 指定JSP页是否使用Session,默认为true。 | <%@ page sesson="true"%> |
errorPage="error_url" | 指定当发生异常时,客户请求被转发到哪个网页 | <%@ page errorPage="errorPage.jsp"%> |
isErrorPage="true|false" | 表示此JSP页是否为处理异常的网页 | <%@ page isErrorPage="true"%> |
2.include指令
JSP可以通过include指令来包含其他文件的内容,被包含的文件可以是JSP文件或HTML文件。语法为:
<%@ include file="目标组件的绝对或相对URL" %>
在开发网站时,如果多数网页都包含相同的内容,可以把这部分相同的内容单独放到一个文件中,其他的JSP文件通过
include语句将它包含进来。
用include指令来包含其他文件被看作静态包含,本章后面的章节还会介绍动态包含,以及和静态包含的区别。
2.2 JSP声明
JSP声明用于声明与JSP对应的Servlet类的成员变量和方法,语法如下:
<%! declaration;{declaration;}%>,例如:
<%! int v1 = 0 ;%>
<%! int v2,v3,v4;%>
<%!String v5 = "Hello";
static int v6;%>
<%!
public String amethod(int i ){
if (i<3) return "<3";
else return ">3";
}
%>
以上v1v2v3v4v5都是实例变量,v6是静态变量,amethod(int i )方法为实例方法。每个JSP声明只在当前JSP文件中有效,如希望在多个JSP文件中都包含这些声明,可以把这些声明语句写到一个单独的JSP文件中,然后在其他JSP文件中用include指令把这个JSP文件包含进来。
2.3 java程序片段(Scriptlet)
在JSP文件中,可以在"<%"和"%>"标记之间直接嵌入任何有效的java程序代码,这种嵌入的程序片段成为Scriptlet。如果在page指令中没有制定method属性,那么这些程序片段默认为与JSP对应的Servlet类的service()方法中的代码块。例如,
<%
String gender = request.getParameter("gender");
if (gender.equals("female")){
%>
She is a girl.
<% }else{%>
He is a boy.
<% } %>
一下代码块等价于以下Servlet的service()方法:public class HelloServlet extends HttpServlet {
@Override
public void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
PrintWriter out = resp.getWriter();
String gender = req.getParameter("gender");
if (gender.equals("female")){
out.println("She is a girl.");
}else {
out.println("He is a boy.");
}
}
}
2.4 Java表达式
Java表达式的标记为"<%="和"%>"。如果在JSP文件的模板文本中使用该标记,那么它能把表达式的值输出到网页上。表达式中的int或float类型的值都自动转换成字符串再进行输出。
<html>
<head>
<title>hitCounter</title>
</head>
<body>
<H1>
You hit the page:
<%! int hitcount = 1;%>
<% int count = 0;
count ++;
%>
<%=hitcount++%>
times.
<%="count="+count%>
</H1>
</body>
</html>
上面的jsp文件中有<%!%>表示的JSP声明、<%%>表示的Java程序片段和<%=%>表示的Java表达式。运行该页面,然后刷新数次,会看到hitcount的数值一直在变化,而count的值一直为1.这是因为,通过<%!%>来声明的变量相当于Servlet中我们设置的一个实例变量,而<%%>中的程序片段中声明的变量是service()方法中的局部变量,相当于一个是在类描述中声明,一个是在类的service()方法中声明的。
2.5 隐含对象
Servlet可以访问由Servlet容器提供的ServletContext、ServletRequest和ServletResponse等对象。在JSP的程序片段中,这些对象称为隐含对象,每个对象都被固定的引用变量引用,JSP不需要做任何变量声明就可以直接通过固定的引用变量来引用这些对象。
隐含对象的引用变量 | 隐含对象的类型 |
---|---|
request | HttpservletRequest |
response | HttpServletResponse |
pageContext | javax.servlet.jsp.PageContext |
application | ServletContext |
out | javax.servlet.jsp.JspWriter |
config | javax.servlet.ServletConfig |
page | java.lang.Object(相当于Java中的this关键字) |
session | javax.servlet.http.HttpSession |
exception | java.lang.Exception |
例如我们之前看到的,在JSP中可以直接通过request变量来获取HTTP请求中的请求参数:
<% String username = request.getParameter("username");
out.println(username);%>
再例如,通过ServletContext隐含对象向Web应用范围内存放一个username属性,application变量引用该对象。<% application.setAttribute("username",username);%>
3. JSP的生命周期
JSP和Servlet的一个区别在于,Servlet容器必须先把JSP编译成Servlet类,然后才能运行它。JSP的声明周期包括以下阶段:
- 解析阶段:容器解析JSP的代码,如果有语法错误,就会向客户端返回错误信息。
- 翻译阶段:容器把jsp文件翻译成Servlet源文件。
- 编译阶段:容器编译Servlet源文件,生成Servlet类,。
- 初始化阶段:加载与JSP对应的Servlet类,创建其实例,并调用它的初始化方法。
- 运行时阶段,调用与JSP对应的Servlet实例的服务方法。
- 销毁阶段:调用于JSP对应的Servlet实例的销毁方法,然后销毁该实例。
其中解析、翻译和编译仅仅发生在JSP文件被客户端首次请求访问、JSP文件被更新、或与JSP文件对应的Servlet类的类文件被手工删除之时。