上文中提到,Tomcat会处理JSP和Servlet两种类型的文件,那么我们先来看看这两种类型文件是什么,有什么作用,然后再从Tomcat的架构角度去看请求是怎么被处理的,本篇文章重点探讨JSP的整体运行机制。
JSP概念简介
前文我们说过有静态和动态两种网站模式,其实我们大多数的网站都是动态网站,所以不能依赖单纯的Html去构建。这个时候我们有两种方式去解决这个问题:
- 把html代码和样式嵌入到Java代码中,然后将Java文件解析运行,这种方式下我们需要分析的是html代码,将html代码打印出来
- 把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 页面,并对该页面进行转换、编译和执行。
具体的执行过程如下:
- 浏览器发送一个 HTTP 请求给服务器。
- Web 服务器识别出这是一个对 JSP 网页的请求,并且将该请求传递给 JSP 引擎。通过使用 URL或者 .jsp 文件来完成。
- JSP 引擎从磁盘中载入 JSP 文件,然后将它们转化为 Servlet。这种转化只是简单地将所有模板文本改用 println() 语句,并且将所有的 JSP 元素转化成 Java 代码。
- JSP 引擎将 Servlet 编译成可执行类,并且将原始请求传递给 Servlet 引擎。
- Web 服务器的某组件将会调用 Servlet 引擎,然后载入并执行 Servlet 类。在执行过程中,Servlet 产生 HTML 格式的输出并将其内嵌于 HTTP response 中上交给 Web 服务器。
- Web 服务器以静态 HTML 网页的形式将 HTTP response 返回到浏览器中。
- 最终,Web 浏览器处理 HTTP response 中动态产生的HTML网页,就好像在处理静态网页一样
总体而言,引擎会帮我们将JSP编译为Servlet,然后执行
JSP组成元素
JSP文件中包括两类代码:JSP元素和Template(模版):
- JSP元素:指的就是
<%...... %>
内的代码,这部分代码是JSP引擎直接处理的部分,这部分必须符合Java语法 - Template(模版):指的就是
<%...... %>
外的代码,例如代码中HTML的内容,这些数据JSP引擎不作处理,直接返回给客户端的浏览器
所以解析处理的时候其实只需要处理JSP元素。
JSP生命周期
JSP生命周期就是从创建到销毁的整个过程,类似于servlet生命周期,区别在于JSP生命周期还包括将JSP文件编译成servlet:
- 编译阶段:servlet容器编译servlet源文件,生成servlet类。当浏览器请求JSP页面时,JSP引擎会首先去检查是否需要编译这个文件。如果这个文件没有被编译过,或者在上次编译后被更改过,则编译这个JSP文件。分为三个步骤:解析JSP文件;将JSP文件转为servlet;编译servlet
- 初始化阶段:加载与JSP对应的servlet类,创建其实例,并调用它的初始化方法jspInit
- 执行阶段:调用与JSP对应的servlet实例的服务方法_jspService
- 销毁阶段:调用与JSP对应的servlet实例的销毁方法jspDestroy,然后销毁servlet实例
整个流程图如下:
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指令。
指令标识包含:page指令、include指令以及taglib指令。
page指令
Page指令为容器提供当前页面的使用说明。一个JSP页面可以包含多个page指令,page的标准语法如下:
<%@ page attribute="value" %>
page指令的常用属性如下:
例如我们想要访问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 地址,例如:
注意:在应用 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() %> -->
点击查看源码我们可以看到注释:
动作标识
JSP行为标签使用XML语法结构来控制servlet引擎。它能够动态插入一个文件,重用JavaBean组件,引导用户去另一个页面,为Java插件产生相关的HTML等等。行为标签只有一种语法格式,它严格遵守XML标准。其语法格式如下:
<jsp:action_name attribute="value" />
包含很多种可以作用的动作标识:
举例说明如下,执行到跳转动作后会跳转到结果页面:
<jsp:forward page="result.jsp"></jsp:forward> 跳转语句之后,代码不会被执行:<%="我是一个跳转后的表达式语句"%>
总结一下
这篇Blog我们大致了解了JSP的基本概念,为什么要用JSP,以及JSP的生命周期、执行流程和组成元素,但是最最重要的还是JSP的基本语法:指令、注释、脚本、动作,这几个各有用处,指令管理页面的配置和属性,脚本管理JSP元素的代码和逻辑,动作可以简化指令和脚本,用标签的形式去体现,让整个页面Java代码更少看起来更加的配置化,注释则用于对文件进行解释。其中脚本可谓核心,我们希望脚本的内容越少越好,这样JSP的视图而非逻辑作用发挥的就越好,所以后续我们还会学习一些高级语法:JSTL标签和EL表达式,这两配合起来可以大量简化写法