在JavaWeb的Servlet篇,我们介绍了Servlet是如何作为一个中间件来帮助Tomcat服务器处理来自用户客户端请求的。
当我们最后讲到服务器返回资源时提到了在两种输出流(字节流/字符流)选择一种后,再直接输出内容。但是如果需要返回完整的一个页面,就需要将HTML的内容逐行返回,这就会使整个过程变得非常繁琐,同时极大地增加了开发成本和维护成本。
JSP, Java Server Pages,我们在这篇blog中要介绍的,就是JSP如何代替Servlet更为高效的回传HTML页面的数据
因为脚本语言的学习较为基础,没有特别多逻辑性的需要理解的地方,因此本篇主要作为学习笔记。
JSP是一个Servlet程序
当JSP文件被客户端访问时,JSP就会被翻译成为.java文件再被编译成.class文件来使用
那我们访问翻译之后的.java源码可以发现,翻译之后的类继承了org.apache.jasper.runtime.HttpJspBase这个类继承了HttpServlet,因此我们可以说JSP就是一个Servlet程序
同时我们可以看到源码当中就像我们之前说的那样逐行的打印了HTML源码
try {
response.setContentType("text/html;charset=UTF-8");
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("\n");
out.write("\n");
out.write("<html>\n");
out.write("<head>\n");
out.write(" <title>Hello JSP</title>\n");
out.write("</head>\n");
out.write("<body>\n");
out.write(" Hi, I am learning JSP!\n");
out.write("</body>\n");
out.write("</html>\n");
}
JSP指令
接下来我们看看实际的JSP代码,我个人感觉其实和PHP是一个道理,就是前后端不分离,HTML以及处理的脚本语言都放在一起,同时脚本语言会通过特殊的格式单独拎出来方便翻译
page指令
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Hello JSP</title>
</head>
<body>
Hi, I am learning JSP!
</body>
</html>
<%@ page ... %>
就是我们第一个要了解的JSP脚本语句,主要用来修改JSP页面中的一些重要的属性,或者行为,从而影响之后翻译的.java以及.class文件
属性 | Description |
---|---|
language | 表示jsp翻译之后是什么语言。暂时只支持java |
contentType | 表示服务器响应中的数据类型是什么(response.setContentType()) |
pageEncoding | 表示当前jsp页面文件本身的字符集 |
import | 需要导入的package(和java源码中一样) |
errorPage | 设置当jsp页面运行出错时,自动跳转去的错误页面路径(一般以斜杠打头,http://ip:port/%E5%B7%A5%E7%A8%8B%E8%B7%AF%E5%BE%84) |
isErrorPage | 设置当前jsp页面是否为错误信息页面(默认为false)。如果是true的话会额外开辟一个存储异常信息的对象 |
session | 设置访问当前jsp页面,是否会创建HttpSession对象(默认为true) |
extends属性 | 设置jsp翻译出来的java类默认继承类(默认为HttpServlet) |
out输出流属性: | 如果没有正确设置会出现 BufferOverflow的错误 |
autoFlush | 设置为 out 输出流缓冲区满了之后,是否自动刷新缓冲区(默认为true) |
buffer属性 | 设置 out 缓冲区的大小(默认为8kb) |
include指令
这里的include和PHP中的include比较像,主要是通过include包含指令来组合页面,避免复写一些公共网页模块(比如导航栏,页脚)
<%@ include ... %> 包含其他文件(静态包含)
静态包含
file属性置顶需要包含的jsp页面的路径,一般以/斜杠打头(表示http://ip:port/%E5%B7%A5%E7%A8%8B%E8%B7%AF%E5%BE%84) 映射到工程的web目录下
<%@ include file="" %> 包含其他文件(静态包含)
静态包含的特点:
- 静态包含不会将包含的jsp页面翻译翻译成为.java文件
- 而是直接把被包含的jsp页面的代码转换成为html内容直接拷贝到包含的位置执行输出(out.write())
动态包含
page属性是指定要包含的jsp的页面路径
<jsp:include page="" %></jsp:include>
动态包含的特点:
- 动态包含会将包含的jsp页面主动翻译成为.java文件
- 在本jsp被翻译的.java文件中使用JspRuntimeLibrary.include(request, response, “/include/footer.jsp”, out, false)的形式来动态包含jsp文件
- 动态包含还可以传递参数
<jsp:include page="/index.jsp">
<jsp:param name="username" value="admin"/>
<jsp:param name="password" value="root"/>
</jsp:include>
一般使用静态包含
taglib指令
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> 引入标签库的定义
JSP常用脚本
JSP 声明脚本
格式为: <%! 声明 java 代码 %>
主要用来对一下内容进行声明:
- 声明类属性
- 声明static静态代码块
- 声明方法
- 声明内部类
表达式脚本
格式为:<%= 表达式%>
表达式脚本是用来在JSP页面上来输出以下数据数据
- 整形
- 浮点型
- 字符串
- 对象
使用的特点:
- 所有的表达式脚本都会被翻译到
_jspService()
方法中 - 因此_jspService()方法中的对象都可以直接使用
- 表达式脚本都会被翻译成out.print()输出到页面上
- 表达式脚本中的表达式不能以
;
结尾
代码脚本
格式:<% java语句 %>
作用:可以在jsp页面中,编写我们自己需要的功能(写的是java语句)
- if语句
- for循环语句
- 翻译后java文件中
_jspService
方法内的代码都可以写
特点:
- 代码脚本翻译之后的崽
_jspService()
方法中 - 因此
jspService()
方法中的对象都可以直接使用 - 还可以由多个代码脚本块组成完成一个完整的java语句
- 代码脚本还可以和表达式脚本一起组合使用,在jsp页面上输出内容
<table border="1" cellspacing="0"><%
for (int i = 0; i < 5; i++) {
%>
<tr>
<td>第<%= i%>行</td>
</tr>
<%
}
%></table>
JSP中的三种注释
-
html 注释
<!-- html 注释 -->
html注释会被翻译到java源代码中。在_jspService方法里,以out.writer输出到客户端(还是作为html注释)
-
java注释
// 单行java注释 /* 多行java注释 */
java注释会被翻译到java源代码中还是作为java注释
-
jsp注释
<%-- jsp注释 --%>
jsp注释可以真正注释掉任意代码
JSP中的九大内置对象
jsp中的内置对象,指的是Tomcat在翻译jsp页面成为Servlet源代码后,内部提供的九大对象
final jakarta.servlet.jsp.PageContext pageContext;
jakarta.servlet.http.HttpSession session = null;
java.lang.Throwable exception = org.apache.jasper.runtime.JspRuntimeLibrary.getThrowable(request);
final jakarta.servlet.ServletContext application;
final jakarta.servlet.ServletConfig config;
jakarta.servlet.jsp.JspWriter out = null;
final java.lang.Object page = this;
jakarta.servlet.jsp.JspWriter _jspx_out = null;
jakarta.servlet.jsp.PageContext _jspx_page_context = null;
内置对象 | 描述 |
---|---|
request | 请求对象 |
response | 响应对象 |
pageContext | jsp的上下文对象 |
session | 会话对象 |
application | ServletContext对象 |
config | ServletConfig对象 |
out | jsp输出流对象 |
page | 指向当前jsp的对象 |
exception | 异常对象(只有当page指令表示当前页面为错误信息页面时才会生成) |
JSP四大域对象
域对象 | 类 | 描述 |
---|---|---|
pageContext | PageContextImpl | 当前jsp页面范围内有效(换一个jsp页面就无法访问) |
request | HttpServletRequest | 一次请求内有效(发出一个新的请求就无法访问) |
session | HttpSession | 一个会话范围内有效(打开浏览器访问服务器,直到关闭浏览器)(关闭浏览器窗口之后就会失效) |
application | ServletContext | 整个web工程范围内都有效(只要web工程不停止,数据都还在,可以参考我们在Servlet中介绍的ServletContext)(重新部署工程后就会失效) |
域对象是可以像Map一样可以存储数据的对象。
四个域对象功能一样,但是他们对数据的存取以及有效范围(时间,空间)不同
四个域对象在使用时的优先顺序是按照他们的从小到大的作用范围所排序的,主要的原因是为了优化内存:因为作用范围小的对象在不用是就可以提早释放,减轻内存负担,而范围大的对象就需要存储很多地方都要用到的数据。
pageContext > request > session > application
out与response的区别
由于jsp翻译之后,底层源代码都是使用out来进行输出,所以一般情况下我们也是用out来进行输出,避免打乱页面输出内容的顺序。
同时对于out中的两种输出方法:
out.write() 输出字符串没有问题(整型就有问题了) out.print() 输出任意数据都没有问题(都转换成为字符串后调用的write输出)
因此我们一般都用 out.print()
来进行输出
JSP的常用标签
JSP请求转发
JSP简化了Servlet的请求转发两步走的流程(先获取Dispatcher再forward),而是直接使用 jsp:forward
标签来进行转发
<jsp:forward page="/index.jsp"></jsp:forward>