上文中提到,Tomcat会处理JSP和Servlet两种类型的文件,那么我们先来看看这两种类型文件是什么,有什么作用,然后再从Tomcat的架构角度去看请求是怎么被处理的,本篇文章重点探讨JSP的整体运行机制。

JSP概念简介

前文我们说过有静态和动态两种网站模式,其实我们大多数的网站都是动态网站,所以不能依赖单纯的Html去构建。这个时候我们有两种方式去解决这个问题:

  1. 把html代码和样式嵌入到Java代码中,然后将Java文件解析运行,这种方式下我们需要分析的是html代码,将html代码打印出来
  2. 把Java代码嵌入到html代码和样式中,然后将该文件动态解析为Java文件运行,这种方式下我们需要分析的是Java代码,将Java代码运行。

依据实际情况来看,因为html样式代码数量远大于Java代码,我们的修改动作也较为频繁,而我们希望更方便的调整html样式而不需要面对大量的print打印语句。

所以我们选择第二种方式,由此也引出了JSP的概念:Java Server Page 简称 JSP,是由 Sun 公司倡导建立的一种动态网页技术标准,用于开发动态网页,JSP就是将传统Java代码嵌入到Html页面代码中,由Web服务器进行编译执行,生成最终的静态Html返回客户端

JSP执行流程

浏览器无法直接运行JSP文件,只有将包含JSP文件的Web项目部署到Web服务器上,才能看到JSP的显示效果,当客户端浏览器向服务器发出请求访问一个JSP页面后,服务器根据该请求加载相应的JSP 页面,并对该页面进行转换、编译和执行。

java 完整的返回报文是什么 java返回结果_Java


具体的执行过程如下:

  1. 浏览器发送一个 HTTP 请求给服务器。
  2. Web 服务器识别出这是一个对 JSP 网页的请求,并且将该请求传递给 JSP 引擎。通过使用 URL或者 .jsp 文件来完成。
  3. JSP 引擎从磁盘中载入 JSP 文件,然后将它们转化为 Servlet。这种转化只是简单地将所有模板文本改用 println() 语句,并且将所有的 JSP 元素转化成 Java 代码。
  4. JSP 引擎将 Servlet 编译成可执行类,并且将原始请求传递给 Servlet 引擎。
  5. Web 服务器的某组件将会调用 Servlet 引擎,然后载入并执行 Servlet 类。在执行过程中,Servlet 产生 HTML 格式的输出并将其内嵌于 HTTP response 中上交给 Web 服务器。
  6. Web 服务器以静态 HTML 网页的形式将 HTTP response 返回到浏览器中。
  7. 最终,Web 浏览器处理 HTTP response 中动态产生的HTML网页,就好像在处理静态网页一样

总体而言,引擎会帮我们将JSP编译为Servlet,然后执行

JSP组成元素

JSP文件中包括两类代码:JSP元素和Template(模版):

  • JSP元素:指的就是<%...... %>内的代码,这部分代码是JSP引擎直接处理的部分,这部分必须符合Java语法
  • Template(模版):指的就是<%...... %>外的代码,例如代码中HTML的内容,这些数据JSP引擎不作处理,直接返回给客户端的浏览器

所以解析处理的时候其实只需要处理JSP元素。

JSP生命周期

JSP生命周期就是从创建到销毁的整个过程,类似于servlet生命周期,区别在于JSP生命周期还包括将JSP文件编译成servlet:

  1. 编译阶段:servlet容器编译servlet源文件,生成servlet类。当浏览器请求JSP页面时,JSP引擎会首先去检查是否需要编译这个文件。如果这个文件没有被编译过,或者在上次编译后被更改过,则编译这个JSP文件。分为三个步骤:解析JSP文件;将JSP文件转为servlet;编译servlet
  2. 初始化阶段:加载与JSP对应的servlet类,创建其实例,并调用它的初始化方法jspInit
  3. 执行阶段:调用与JSP对应的servlet实例的服务方法_jspService
  4. 销毁阶段:调用与JSP对应的servlet实例的销毁方法jspDestroy,然后销毁servlet实例

整个流程图如下:

java 完整的返回报文是什么 java返回结果_Java_02

JSP语法标识

JSP的语法分为三类:指令标识、脚本标识注释标识动作标识。这里我们以一个JSP页面进行举例,贯穿接下来的练习代码

index.jsp文件

<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" import="java.util.*" %>
<%@ include file="top.jsp" %>
<!DOCTYPE html>
<html>
<head>
    <title>JSP - Hello World</title>
    <%!
        int number = 0;// 声明全局变量

        int count() {
            number++;//number 自增
            return number;
        }%>

</head>
<h1><%= "Hello World!" %>
</h1>
<br/>
<body>
<%
    String print = "我是脚本语句内容";
    List<String> strList = new ArrayList<>();
    strList.add("脚本语句1");
    strList.add("脚本语句2");
    strList.add("脚本语句3");
//  strList.add("脚本语句4");  //这是一个Java注释,用来注释Java代码,查看源码时不可见
%>
<a href="hello-servlet">我的第一个JavaWeb项目</a> <!-- 这是一个html注释,可见<%= new Date().getTime() %> -->
<br/>

<%--这是一个JSP注释,查看源码时不可见--%>
声明语句示例:这是第 <%=count()%> 次访问该页面
<br/>
表达式语句示例: <%="我是一个表达式语句"%>
<br/>
脚本语句示例: <%=print%>, 打印列表 <%=strList%>
<jsp:forward page="result.jsp"></jsp:forward>
跳转语句之后,代码不会被执行:<%="我是一个跳转后的表达式语句"%>
</body>
</html>

top.jsp文件

<%--
  Created by IntelliJ IDEA.
  User: tianmaolin
  Date: 2021/7/13
  Time: 8:15 下午
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" import="java.util.*" %>
<html>
<head>
    <title>我是include的top.jsp页面</title>

</head>
<body>
<%
  String printTop="top打印";
%>
<a href="hello-servlet">我是top的打印,<%=printTop%></a>
</body>
</html>

result.jsp文件

<%--
  Created by IntelliJ IDEA.
  User: tianmaolin
  Date: 2021/7/14
  Time: 11:32 上午
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>跳转的结果页面</title>
</head>
<body>
<a>我是跳转后的结果页面</a>
</body>
</html>

test.jsp文件

按照语法基础,我们实践三种语法流程:判断逻辑,循环逻辑:

<%--
  Created by IntelliJ IDEA.
  User: tianmaolin
  Date: 2021/7/14
  Time: 12:30 下午
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" import="java.util.*,java.io.*" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<%! int day = 3; %>

if语句输出练习:
<br/>
<% if (day == 1 || day == 7) { %>
<p>今天是周末</p>
<% } else { %>
<p>今天不是周末</p>
<% } %>


<br/>
for语句输出练习;
<br/>
<%for ( int fontSize = 1; fontSize <= 3; fontSize++){ %>
<span style="color: green; ">
    循环第 <%=fontSize%> 遍<br/>
</span><br/>
<%}%>
</body>
</html>

指令标识

指令标识主要用于设定在整个 JSP 页面范围内都有效的相关设置信息,它是被服务器解释并执行的,不会产生任何内容输出到网页中,格式如下:

<%@ 指令名 属性 1=“属性值 1” 属性 2=“属性值 2”...%>
  • 指令名:用于指定指令名称,在 JSP 中包含 page、include 和 taglib 3 个指令。
  • 属性:用于指定属性名称,不同的指令包含不同的属性。在一个指令中,可以设置多个属性,属性之间用逗号或空格隔开。
  • 属性值:用户指定属性值

上述文件中<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> 就是一个page指令。

java 完整的返回报文是什么 java返回结果_Java_03


指令标识包含:page指令、include指令以及taglib指令。

page指令

Page指令为容器提供当前页面的使用说明。一个JSP页面可以包含多个page指令,page的标准语法如下:

<%@ page attribute="value" %>

page指令的常用属性如下:

java 完整的返回报文是什么 java返回结果_JSP基础_04

例如我们想要访问list相关的方法,就需要引入java的util类:

<%@ page language="java" import="java.util.*,java.io.*" pageEncoding="UTF-8"%>

include指令

JSP可以通过include指令来包含其他文件。被包含的文件可以是JSP文件、HTML文件或文本文件。包含的文件就好像是该JSP文件的一部分,会被同时编译执行。Include指令的语法格式如下:

<%@ include file="文件相对 url 地址" %>

include 指令中的文件名实际上是一个相对的 URL 地址,例如:

java 完整的返回报文是什么 java返回结果_Java_05


注意:在应用 include 指令包含文件时,为使整个页面的层次结果不发生冲突,应将被包含页面中的 元素和 元素删除,因为包含该页面的文件中已经指定了这些标记

Taglib指令

在 JSP 文件中,可以通过 taglib 指令声明该页面中所使用的标签库,同时引用标签库,并指定标签前缀,在页面中引用标签库后,就可以通过前缀引用标签库中的标签

<%@taglib prefix=“core” uri=“http://java.sun.com/jsp/jstl/core” %>

标签库的使用后续会提到。

脚本标识

脚本标识是JSP中最主要的元素,相当于JSP中需要编译的那一部分内容,也是JSP语法中的核心内容,包含声明、表达式以及脚本段

声明

声明标识用于在 JSP 页面中定义全局的变量或方法,通常使用该标识定义整个 JSP 页面需要引用的变量或方法,在JSP文件中,必须先声明这些变量和方法然后才能使用它们,相当于Java里的成员变量和成员方法定义。语法规则如下:

<%! 声明变量或方法代码 %>

一个全局的统计访问次数的例子如下:

<%!
        int number = 0;// 声明全局变量
        int count() {  //声明全局方法
            number++;//number 自增
            return number;
        }%>
  这是第 <%=count()%> 次访问该页面

可以打印出来第几次访问页面

脚本段

Scriptlet(代码片段)脚本程序,是在 JSP 页面中嵌入的 Java 代码或脚本代码,代码片段将在页面请求的处理期间被执行,通过 Java 代码可以定义变量或流程控制语句等,相当于Java里main方法的代码逻辑,语法规则如下:

<% 表达式或者变量名 %>

脚本段语句示例:

<%
    String print = "我是脚本语句内容";
    List<String> strList = new ArrayList<>();
    strList.add("脚本语句1");
    strList.add("脚本语句2");
    strList.add("脚本语句3");
%>
脚本语句示例: <%=print%>, 打印列表 <%=strList%>

可以直接打印脚本段内的内容。

表达式

JSP 表达式用于向页面中输出信息,类似于System.out.println()括号中的内容,相当于Java里main方法里的System.out.println()语句,只不过是输出回客户端,而非服务端本地打印,语法规则如下:

<%= 表达式或者变量名 %>

以上我们无论是声明还是脚本段的输出其实都用到了表达式:

<br/>
声明语句示例:这是第 <%=count()%> 次访问该页面
<br/>
表达式语句示例: <%="我是一个表达式语句"%>
<br/>
脚本语句示例: <%=print%>, 打印列表 <%=strList%>

注释标识

注释用于对某些代码做功能性的说明,从而增加程序的可读性。JSP 程序中可以包含 3 种不同类型的注释:

JSP注释

JSP注释主要有两个作用:为代码作注释以及将某段代码注释掉。主要是给JSP页面提供注释,JSP注释在客户端浏览器无法查看到,注释内容不会被发送至浏览器甚至不会被编译,其语法规则如下:

<%-- JSP标准注释 --%>

例如我们可以这么编写:

<%--表达式展示的JSP注释--%>
声明语句示例:这是第 <%=count()%> 次访问该页面
<br/>
表达式语句示例: <%="我是一个表达式语句"%>
<br/>
脚本语句示例: <%=print%>, 打印列表 <%=strList%>
</body>

Java注释

Java注释指的是java代码,和Java代码的注释格式是一致的,同样的Java注释在客户端浏览器无法查看到,注释内容不会被发送至浏览器甚至不会被编译,语法规则如下:

<%
       //Java单行注释
       /*
           Java多行注释
       */
%>

例如我们可以这么编写:

<%
    String print = "我是脚本语句内容";
    List<String> strList = new ArrayList<>();
    strList.add("脚本语句1");
    strList.add("脚本语句2");
    strList.add("脚本语句3");
//  strList.add("脚本语句4");  //这是一个Java注释,用来注释Java代码
%>

HTML注释

HTML注释主要给JSP页面的Template元素提供注释,HTML网页注释在客户端浏览器可以看到,语法规则如下:

<!-- HTML网页注释 -->

这类在查看源码时可见,其中的脚本语句也是可执行的,示例如下:

<!-- 这是一个html注释,可见<%= new Date().getTime() %> -->

点击查看源码我们可以看到注释:

java 完整的返回报文是什么 java返回结果_JSP生命周期_06

动作标识

JSP行为标签使用XML语法结构来控制servlet引擎。它能够动态插入一个文件,重用JavaBean组件,引导用户去另一个页面,为Java插件产生相关的HTML等等。行为标签只有一种语法格式,它严格遵守XML标准。其语法格式如下:

<jsp:action_name attribute="value" />

包含很多种可以作用的动作标识:

java 完整的返回报文是什么 java返回结果_JSP生命周期_07


举例说明如下,执行到跳转动作后会跳转到结果页面:

<jsp:forward page="result.jsp"></jsp:forward>
跳转语句之后,代码不会被执行:<%="我是一个跳转后的表达式语句"%>

总结一下

这篇Blog我们大致了解了JSP的基本概念,为什么要用JSP,以及JSP的生命周期、执行流程和组成元素,但是最最重要的还是JSP的基本语法:指令、注释、脚本、动作,这几个各有用处,指令管理页面的配置和属性,脚本管理JSP元素的代码和逻辑,动作可以简化指令和脚本,用标签的形式去体现,让整个页面Java代码更少看起来更加的配置化,注释则用于对文件进行解释。其中脚本可谓核心,我们希望脚本的内容越少越好,这样JSP的视图而非逻辑作用发挥的就越好,所以后续我们还会学习一些高级语法:JSTL标签和EL表达式,这两配合起来可以大量简化写法