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
的核心类和接口。其中的两个重要成员是JspPage
和HttpJspPage
接口。所有的JSP
页面实现类必须实现JspPage
或HttpJspPage
接口。在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
|
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
,并被HttpJspBase
的service
方法调用。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
类的全路径名称。