JSP 入门
# 90.JSP 入门
JSP,全称 Java Server Pages,可以理解为一个页面模板,其中既可以写 HTML,又可以写 Java 代码,在访问这个 JSP 的时候,服务器会将其转换为 HTML 文件返回给浏览器,用来简化书写
# 在 JSP 出现之前
一个页面,通常是包含动态内容和静态内容。例如一个页面上,有展示用户名的地方,此时用 Servlet 的话我们只能这样做:
resp.getWriter().write("<p>您好" + username + "欢迎访问</p>" );
而一个完整的 HTML 页面,内容是非常多的,我们得一个个 write:
resp.getWriter().write("<HTML>");
resp.getWriter().write("<body>");
resp.getWriter().write("<p>您好" + username + "欢迎访问</p>" );
resp.getWriter().write("</body>");
resp.getWriter().write("</HTML>");
2
3
4
5
6
7
在上古时代,通常情况是美工写好 html 静态页面后,丢给 Java 程序员。Java 程序猿在 Servlet 中的 Service 方法里,逐句复制 html 静态页面上的 html 语句到 Servlet 的中,并将需要动态展示的地方动态拼接字符串(例如上面的第 4 行)。
然后浏览器就会拿到拼接好的 HTML 文本,并展示到浏览器上,按这种方式,要想拼接数据并完整输出一个 html 页面,没个几百上千行 resp.getWriter.write()
是不可能的。所以基本上敲完两个页面两根手指就麻了。
# 动态生成
而同样是上古时期,PHP 和 ASP 就优秀得多了,不搞这繁琐的一套,它们选择在 HTML 页面中嵌入相应语言来引入动态数据,避免了手动拷贝 HTML 片段输出的尴尬局面。例如 ASP:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
@foreach(var x in Request)
{<li>@x</li>}
</body>
</html>
2
3
4
5
6
7
8
9
10
11
12
13
上述代码会自动执行,生成一系列的 li 标签,省去了我们手工拼接的麻烦!
因为仔细想来,我们的主要目的就是希望在最终输出的 html 的代码中嵌入后台数据罢了,除了把 html 语句拿出来在 Servlet 里拼接好再输出这种方式外,我们也可以直接在 html 语句中写入动态数据!这两种几乎是完全相反的设计思路!孰优孰劣,一看便知。
Java 也不甘落后,因此也发明了 JSP
# JSP 介绍
JSP 全称 Java Server Page,直译就是“运行在服务器端的页面”。上面已经介绍过,我们可以直接在 JSP 文件里写 HTML 代码,使用上把它当做 HTML 文件。而且 JSP 中 HTML/CSS/JS 等的写法和 HTML 文件中的写法是一模一样的。但它毕竟不是 HTML,而且本质差了十万八千里。因为我们还可以把 Java 代码内嵌在 JSP 页面中,很方便地把动态数据渲染成静态页面。这一点,打死 HTML 都做不到。
当有人请求 JSP 时,服务器内部会经历一次动态资源(JSP)到静态资源(HTML)的转化,服务器会自动帮我们把 JSP 中的 HTML 片段和数据拼接成静态资源响应给浏览器。也就是说 JSP 是运行在服务器端,但最终发给客户端的都已经是转换好的 HTML 静态页面(在响应体里)。
即:JSP = HTML + Java 片段(各种标签本质上还是 Java 片段)
# JSP 的本质
JSP 本质上就是一个 Servlet,访问一个 JSP,服务器内发生的事情如下:
- 服务器解析浏览器的 HTTP 请求,寻找是否有 JSP 文件,没有则返回 404
- 有该文件,则会将 JSP 文件转为一个 Servlet(一个.java 文件)
- 编译该 Servlet 会被编译为字节码,然后生成 Servlet 对象,处理请求并返回给浏览器
我们来亲自验证下。首先在 web 目录下新建 index.jsp 文件
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>首页</title>
</head>
<body>
<h1>Hello JSP!</h1>
</body>
</html>
2
3
4
5
6
7
8
9
启动 Tomcat,并访问 http://localhost: 8080/hello/index.jsp。
我们可以看到工作空间目录为:
Using CATALINA_BASE: "C:\Users\peterjxl\AppData\Local\JetBrains\IntelliJIdea2022.3\tomcat\f48a6060-bf20-4710-adb8-4ae1308d09c7"
我们打开这个目录,可以看到有 work 目录。之前我们学习 Tomcat 的时候,讲解过目录结构,work 目录是存放运行时产生的一些临时文件。我们直接进入到最里面的文件夹:
C:\Users\peterjxl\AppData\Local\JetBrains\IntelliJIdea2022.3\tomcat\f48a6060-bf20-4710-adb8-4ae1308d09c7\work\Catalina\localhost\hello\org\apache\jsp
可以看到有一个 Java 文件和一个 class 文件:
我们打开这个 Java 文件:
public final class index_jsp extends org.apache.jasper.runtime.HttpJspBase
implements org.apache.jasper.runtime.JspSourceDependent,
org.apache.jasper.runtime.JspSourceImports {
2
3
我们可以看到其继承了 HttpJspBase 类,而这个类我们可以在 Tomcat 的源码中找到:apache-tomcat-9.0.73-src\java\org\apache\jasper\runtime\HttpJspBase.java,打开,可以看到其继承了 HttpServlet。
public abstract class HttpJspBase extends HttpServlet implements HttpJspPage {
因此,我们验证了一个点:JSP 本质上是一个 Servlet。index_jsp.java 里还干了什么事情呢?既然是个 Servlet,肯定有个 service 方法,我们继续往下看,可以看到这样的代码:
public void _jspService(final javax.servlet.http.HttpServletRequest request, final javax.servlet.http.HttpServletResponse response)
//... 省略其他代码
out.write("\n");
out.write("\n");
out.write("<html>\n");
out.write(" <head>\n");
out.write(" <title>$Title$</title>\n");
out.write(" </head>\n");
out.write(" <body>\n");
out.write(" $END$\n");
out.write(" </body>\n");
out.write("</html>\n");
//... 省略其他代码
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
破案了,原理 Servlet 本质上也是拼接字符串来输出 HTML 文件的!因此使用 JSP,能极大简化我们的代码。
# JSP 的基本书写
既然 JSP 里可以写 Java 代码,要怎么写呢?不可能说我直接写个 Java 代码,Tomcat 就会知道要执行。例如我们有一个 User 对象,要展示其用户名:
<h1>user.getName()</h1>
上述代码,肯定是不会被当成 Java 代码来执行的。
要如何与 HTML 标签区分出来呢?有如下 3 种格式:
<% 代码 %>
:定义的 Java 代码,编译后在 Servlet 的 service 方法中。service 方法中可以写什么,该脚本中就可以写什么。<%! 代码 %>
:定义的 Java 代码,在 JSP 转换后会放在类的成员位置。相当于定义一个成员变量、成员方法,用的较少,因为之前讲过,最好不要定义,怕引发线程安全问题<%= 代码 %>
:相当于定义了一个输出语句,会将代码里的执行结果输出到页面上。输出语句中可以定义什么,该脚本中就可以定义什么。
我们来演示下
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>首页</title>
</head>
<body>
<h1>Hello JSP!</h1>
<% System.out.println("Hello"); %>
<%! int i = 1; %>
<%= "hello".toUpperCase() %>
</body>
</html>
2
3
4
5
6
7
8
9
10
11
12
13
14
更新 Tomcat,访问 http://localhost: 8080/hello/index.jsp。
首先可以看到 IDEA 里打印了 Hello,这是第 8 行的代码起作用了;
我们还是访问 JSP 编译后的 index_jsp.java,可以看到有我们定义的变量 i ;
public final class index_jsp extends org.apache.jasper.runtime.HttpJspBase
implements org.apache.jasper.runtime.JspSourceDependent,
org.apache.jasper.runtime.JspSourceImports {
int i = 1;
2
3
4
5
最后我们可以看到页面下面有 HELLO,这是第 12 行代码的作用