摘要:
J2EE 是一套规范,而 Servlet/Jsp 是 J2EE 规范的一部分,是 Tomcat 的主要实现部分。在最初的应用实践中,当用户向指定 Servlet 发送请求时,Servlet 利用输出流动态生成 HTML 页面,这导致 Servlet 开发效率极为低下。JSP 技术通过实现普通静态 HTML 和动态部分混合编码,使得逻辑内容与外观相分离,大大简化了表示层的实现,提高了开发效率。本文以 JSP 的本质是 Servlet 为主线,结合 JSP 转译后所得的 Servlet,详细探讨了 JSP 的原理、执行过程、脚本元素、编译指令和动作指令,并给出了 JSP 使用的常见注意事项。
1、J2EE 与 Tomcat
J2EE 是由 SUN 公司开发的一套企业级应用规范。所谓规范 (Specification) 指的是一系列接口,不包含具体实现,我们可以通过 (以 J2EE 6 为例) 来了解该规范。 J2EE 主要由十三种核心技术规范组成,这些规范包括:
2、Web Application
符合 J2EE 规范的 Web Application 实际上是一个目录,如下图所示。classes 和 lib 两个文件夹都适用于保存 Web 应用 所需的 Java 类文件,区别是 classes 保存单个 *.class 文件;而 lib 保存打包后的 jar 文件。
2、JSP 的执行过程
当用户访问一个 JSP 页面时,容器对 JSP 页面的处理通常可分为两个时期:转译时期(Translation Time)和 请求时期(Request Time)。在转译时期,JSP 网页被转译成 Servlet 类,然后被编译成 class 文件;在请求时期,容器加载编译后的 Servlet 类,并把响应结果返回至客户端,整个流程可分为三个步骤,流程图如下:
(1). 当客户第一次请求 JSP 页面时,JSP 引擎会通过预处理把 JSP 文件中的静态数据(HTML 文本)和动态数据(Java 脚本)全部转换为 Java 代码。转换原则非常直观:对于 HTML 文本只是简单的用 out.println() 方法包裹起来,而对于 Java 脚本只是保留或做简单的处理;
(2). JSP 引擎把生成的. java 文件编译成 Servlet 类文件(.class)。对于 Tomcat 服务器而言,生成的类文件默认的情况下存放在 \ work 目录;
(3). 编译后的 class 对象被加载到容器中,并根据用户的请求生成 HTML 格式的响应页面。
1) 转译时期
为了更好地了解 JSP 的转译过程和转换原则,我们以下面这个 JSP 文件为例进行说明。
- <%@ page language="java" import="java.util.*" pageEncoding="GB18030" %>
- <%-- JSP脚本 --%>
- <% String path=r equest.getContextPath(); String basePath=r equest.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 本质和转移准则,我们可以得出以下几点结论:
2) 请求时期
在请求时期,容器加载编译后的 Servlet 类,并把响应结果返回至客户端。特别地,如果 JSP 页面已经被转换为 Servlet 且该 Servlet 被编译进而被加载(在第一次被请求时),这样再次请求此 JSP 页面时,将感觉不到延迟。
三. JSP 基本语法与脚本元素
JSP 的脚本元素 (Scripting Element) 包含三个部分:Scriptlet、Expression(表达式) 和 Declaration(声明)。
- <%@ 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>
Declaration 元素用于声明在页面中初始化的变量、方法和类,特别需要注意以下几点:
此外,JSP 注释 的格式为 <%– –%>,与 HTML 注释 (<!– –>) 的区别在于: JSP 注释不会输出到客户端。
四. JSP 的三个编译指令
JSP 指令负责发送消息到 JSP 引擎,不包含业务逻辑,不修改 out 流,只是告诉 JSP 引擎 JSP 页面应该如何编译。JSP 指令的作用范围仅限于包含指令本身的 JSP 页面。JSP 编译指令有三种类型:page 指令、include 指令和 taglib 指令,其语法如下:
<%@directive attribute="attribute value"%>
1、page 指令
Page 指令定义了一些属性,通知关于 JSP 页面一般设置的 Servlet 引擎的属性。对于 page 指令,有几点需要注意:
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 的七个动作指令
JSP 的动作指令与编译指令不同,编译指令是通知 Servlet 引擎的消息,而动作指令只是运行时的动作。编译指令在将 JSP 编译成 Servlet 时起作用;而动作指令通常可以替换成 JSP 脚本,它只是 JSP 脚本的标准化写法。现将这七个动作指令按使用方式分为两组 (动作指令 jsp:plugin 暂不细表):
- <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; // 不执行该指令后面的内容
- }
结合以上说明,我们可以得出以下几个结论:
- include page="{relativeURL | <%=expression%>}">
- param value="***" name="***"/>
- 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() 方法来插入目标页面的内容,而不是将目标页面完全融入当前页面中。因此,静态导入和动态导入有如下三点区别:
- id="name" class="classname" scope="page | request | session | application"/>
- name="BeanName" property="propertyName" value="value"/>
- name="BeanName" property="propertyName"/>
useBean、setProperty、getProperty 指令在使用上是并列的(不是嵌套的),形式如下:
- id="p" class="com.tju.rico.bean.Person" scope="application"/>
- property="name" name="p" value="rico"/>
- property="name" name="p"/>
形如上面的 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 必须具有以下特点:
六. 注意事项
1、JSP 中 out.write()、out.print() 和 out.println() 的区别
out 对象的类型是 JspWriter,而 JspWriter 继承了 java.io.Writer 类。上述三个方法区别如下:
2、JSP/Servlet 中的四种编码方式的作用与区别
关于这块内容的介绍详见我的博文。
关于 JSP 更多的介绍,特别是内置对象等内容见我的下一篇博客。
关于 JSP 中文乱码更多的介绍,包括 JSP 的执行过程与编码设定、页面乱码、参数乱码、表单乱码、源文件乱码 等知识,见我的另外两篇博客: 和 。
来源: http://www.bubuko.com/infodetail-1962094.html