3.1 JSP概述

3.1 JSP概述

JSP相较Servlet的优点

**JSP页面本质上是一个Servlet**。然而,用JSP页面开发比使用Servlet更容易,主要有两个原因。

  • 首先,不必编译JSP页面;
  • 其次,JSP页面是一个以.jsp为扩展名的文本文件,可以使用任何文本编辑器来编写它们。

JSP在哪里运行

JSP页面在JSP容器中运行,一个Servlet容器通常也是JSP容器。例如,Tomcat就是一个Servlet/JSP容器。

在JSP第一次被请求时 容器要做什么

当一个JSP页面第一次被请求时,Servlet/JSP容器主要做以下两件事情:
(1)把JSP页面转换成JSP页面实现类,该实现类是一个实现javax.servlet.jsp.JspPage接口或其子接口javax.servlet.jsp.HttpJspPage的Java类。而javax.servlet.jsp.JspPage接口又是javax.servlet.Servlet的子接口,这使得**每一个JSP页面都是一个Servlet**。该实现类的类名由Servlet/JSP容器生成。如果出现转换错误,则相关错误信息将被发送到客户端。
(2)如果转换成功,Servlet/JSP容器随后编译该Servlet类,并装载和实例化该类,并像其他正常的Servlet一样执行生命周期操作。

在JSP页面的后续请求时 容器要做什么

对于同一个JSP页面的后续请求,Servlet/JSP容器会先检查JSP页面是否被修改过。

  • 如果该JSP页面被修改过,则该JSP页面会被重新转换为Servlet、然后编译并执行。
  • 如果该JSP页面没有被修改过,则执行已经在内存中的JSP Servlet

这样一来,一个JSP页面的第一次调用的实际花费总比后续调用的花费多,因为第一次调用涉及转换和编译。

如何减少第一次调用JSP页面时的花费

为了解决这个问题,可以执行下列动作之一:

  • 配置应用程序,使所有的JSP页面在应用程序启动时被调用,也就是在引用程序启动时就转换和编译,而不是在第一次请求时被调用。
  • 预编译JSP页面,并将其部署为Servlet

JSP自带的API包

JSP自带的API包含4个包,如下所示:

  • javax.servlet.jsp。这个包包含了用于Servlet/JSP容器将JSP页面翻译为Servlet的核心类和接口。其中的两个重要成员是JspPageHttpJspPage接口。所有的JSP页面实现类必须实现JspPageHttpJspPage接口。在HTTP环境下,实现HttpJspPage接口是显而易见的选择。
  • javax.servlet.jsp.tagext。这个包中包含了用于开发自定义标签的类型。
  • javax.el。这个包中提供了统一表达式语言的API
  • javax.servlet.jsp.el。这个包提供了一组必须由Servlet/JSP容器支持,以便在JSP页面中使用表达式语言的类。

除了javax.servlet.jsp.tagext,我们很少直接使用JSP API。事实上,编写JSP页面时,我们更关心Servlet API,而非JSP API。当然,我们还需要掌握JSP语法,本章后续会进一步说明。开发JSP容器或JSP编译器时,JSP API已被广泛使用。
可以在以下网址查看JSP API:
https://docs.oracle.com/javaee/7/api/index.html?javax/servlet/jsp/package-summary.html

模板数据和语法元素

JSP页面可以包含模板数据和语法元素。这里,语法元素是一些具有特殊意义的JSP转换符。例如,“<%”是一个元素,因为它表示在JSP页面中的Java代码块的开始。“%>”也是一个元素,因为它是Java代码块的结束符。除去语法元素外的一切是模板数据模板数据会原样发送给浏览器。例如,**JSP页面中的HTML标记和文字都是模板数据**。

Demo

第一个JSP页面

下面是一个名为welcome.jsp的JSP页面。这个页面仅仅向用户发送一个Welcome。

1
2
3
4
5
6
<html>
<head><title>Welcome</title></head>
<body>
Welcome
</body>
</html>

在Tomcat中,welcome.jsp页面在第一次请求时被翻译成名为welcome_jsp的Servlet。你可以在Tomcat工作目录下的子目录中找到生成的Servlet,该Servlet继承
自org.apache.jasper. runtime.HttpJspBase抽象类,并实现了javax.servlet.jsp.HttpJspPage接口,org.apache.jasper. runtime.HttpJspBase继承于javax.servlet.http.HttpServlet类。
下面是为welcome.jsp生成的Servlet。

JSP页面对应的Servlet

JSP页面会被编译成Servlet,并发布到Tomcat安装目录下的\work\Catalina\localhost\项目名\包名\类名_jsp.java,我这里是D:\dev\apache-tomcat-8.5.35\work\Catalina\localhost\app03a\org\apache\jsp\welcome_jsp.java,打开这个文件,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
/*
* Generated by the Jasper component of Apache Tomcat
* Version: Apache Tomcat/8.5.35
* Generated at: 2019-03-26 08:24:51 UTC
* Note: The last modified time of this file was set to
* the last modified time of the source file after
* generation to assist with modification tracking.
*/
package org.apache.jsp;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*;
public final class welcome_jsp extends org.apache.jasper.runtime.HttpJspBase
implements org.apache.jasper.runtime.JspSourceDependent,
org.apache.jasper.runtime.JspSourceImports {
private static final javax.servlet.jsp.JspFactory _jspxFactory =
javax.servlet.jsp.JspFactory.getDefaultFactory();
private static java.util.Map<java.lang.String,java.lang.Long> _jspx_dependants;
private static final java.util.Set<java.lang.String> _jspx_imports_packages;
private static final java.util.Set<java.lang.String> _jspx_imports_classes;
static {
_jspx_imports_packages = new java.util.HashSet<>();
_jspx_imports_packages.add("javax.servlet");
_jspx_imports_packages.add("javax.servlet.http");
_jspx_imports_packages.add("javax.servlet.jsp");
_jspx_imports_classes = null;
}
private volatile javax.el.ExpressionFactory _el_expressionfactory;
private volatile org.apache.tomcat.InstanceManager _jsp_instancemanager;
public java.util.Map<java.lang.String,java.lang.Long> getDependants() {
return _jspx_dependants;
}
public java.util.Set<java.lang.String> getPackageImports() {
return _jspx_imports_packages;
}
public java.util.Set<java.lang.String> getClassImports() {
return _jspx_imports_classes;
}
public javax.el.ExpressionFactory _jsp_getExpressionFactory() {
if (_el_expressionfactory == null) {
synchronized (this) {
if (_el_expressionfactory == null) {
_el_expressionfactory = _jspxFactory.getJspApplicationContext(getServletConfig().getServletContext()).getExpressionFactory();
}
}
}
return _el_expressionfactory;
}
public org.apache.tomcat.InstanceManager _jsp_getInstanceManager() {
if (_jsp_instancemanager == null) {
synchronized (this) {
if (_jsp_instancemanager == null) {
_jsp_instancemanager = org.apache.jasper.runtime.InstanceManagerFactory.getInstanceManager(getServletConfig());
}
}
}
return _jsp_instancemanager;
}
public void _jspInit() {
}
public void _jspDestroy() {
}
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");
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("<html>\r\n");
out.write("<head><title>Welcome</title></head>\r\n");
out.write("<body>\r\n");
out.write("Welcome\r\n");
out.write("</body>\r\n");
out.write("</html>");
} catch (java.lang.Throwable t) {
if (!(t instanceof javax.servlet.jsp.SkipPageException)){
out = _jspx_out;
if (out != null && out.getBufferSize() != 0)
try {
if (response.isCommitted()) {
out.flush();
} else {
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实现类的主体方法

正如我们在上面的代码中看到的,JSP页面的主体是_jspService方法,该方法中响应输出如下:

1
2
3
4
5
6
7
8
9
10
11
public void _jspService(final javax.servlet.http.HttpServletRequest request, final javax.servlet.http.HttpServletResponse response)
throws java.io.IOException, javax.servlet.ServletException {
......
out.write("<html>\r\n");
out.write("<head><title>Welcome</title></head>\r\n");
out.write("<body>\r\n");
out.write("Welcome\r\n");
out.write("</body>\r\n");
out.write("</html>");
......
}

这个_jspService方法被定义在HttpJspPage,并被HttpJspBaseservice方法调用。HttpJspBase类中的service方法如下所示:

1
2
3
4
5
public final void service(HttpServletRequest request,
HttpServletResponse response) throws ServletException,
IOException {
_jspService(request, response);
}

一个JSP页面不同于一个Servlet的另一个方面是,JSP页面不需要添加注解或在部署描述符中配置来映射URL。在应用程序目录中的每一个JSP页面可以直接在浏览器中输入路径页面访问。

app03a应用程序的结构非常简单,由一个空的WEB-INF目录和welcome.jsp页面构成,如下图所示:
这里有一张图片
可以通过如下URL访问welcome.jsp页面:
http://localhost:8080/app03a/welcome.jsp

说明

添加新的JSP界面后,无须重启Tomcat

显示今日日期

todaysDate.jsp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@page import="java.util.Date"%>
<%@page import="java.text.DateFormat"%>
<html>
<head><title>显示当前日期</title></head>
<body>
<%
DateFormat dateFormat =
DateFormat.getDateInstance(DateFormat.LONG);
String s = dateFormat.format(new Date());
out.println("当前日期: " + s);
%>
</body>
</html>

上面的JSP页面最终编译成:
E:\apache-tomcat-8.5.35\work\Catalina\localhost\app03a\org\apache\jsp\todaysDate_jsp.java
这个Servlet,查看这个Servlet,可以看到_jspService方法中的响应输出部分如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void _jspService(final javax.servlet.http.HttpServletRequest request, final javax.servlet.http.HttpServletResponse response)
throws java.io.IOException, javax.servlet.ServletException {
......
out.write("<html>\r\n");
out.write("<head><title>显示当前日期</title></head>\r\n");
out.write("<body>\r\n");
DateFormat dateFormat =
DateFormat.getDateInstance(DateFormat.LONG);
String s = dateFormat.format(new Date());
out.println("当前日期: " + s);
out.write("\r\n");
out.write("</body>\r\n");
out.write("</html>");
......
}

现在可以通过如下URL访问todaysDate.jsp页面:
http://localhost:8080/app03a/todaysDate.jsp
显示效果如下:
这里有一张图片
todaysDate.jsp页面发送了几个HTML标签和字符串"当前日期: "以及今天的日期到浏览器。
请注意两件事情。首先,Java代码可以出现在JSP页面中的任何位置,并通过<%%>包括起来,这个<%...%>块被称为scriplet。其次,可以使用page指令的import属性导入在JSP页面中使用的Java类型,如果没有导入的类型,必须在代码中写Java类的全路径名称。