- JSP原理深度解析、JSP的基础语法
4.JSP
在 Servlet 当中编写 HTML/CSS/JavaScript 等前端代码。存在什么问题?
- java程序中编写前端代码,编写难度大、麻烦。
- 在java程序中,前端代码都是在 “” 中,在java看来,就是字符串,无法利用 IDE 的报错功能对前端代码进行错误排查。
- java程序中编写前端代码,显然程序的耦合度非常高。
- java程序中编写前端代码,代码非常不美观。
- java程序中编写前端代码,维护成本太高(非常难于维护)。
- 修改小小的一个前端代码,只要有改动,就需要重新编译 java 代码,生成新的 class 文件,打一个新的 war 包,重新发布。
- java程序中编写前端代码,编写难度大、麻烦。
我们需要使用 JSP 技术来解决在 Servlet 当中编写 HTML/CSS/JavaScript 等前端代码的问题。
4.1 ★JSP原理深度解析
我的第一个 JSP 程序:
在 WEB-INF 目录之外创建一个
index.jsp
文件。
将上面的项目部署之后,启动服务器,打开浏览器,访问以下地址:http://localhost:8080/jsp/index.jsp,展现在大家面前的
index.jsp
文件的显示效果。实际上访问以上的这个:
index.jsp
,底层执行的是:index_jsp.class
这个 java 程序。这个
index.jsp
会被 tomcat 翻译生成index_jsp.java
文件,然后 tomcat 服务器又会将index_jsp.java
编译生成index_jsp.class
文件。
访问
index.jsp
,实际上执行的是index_jsp.class
中的方法。
JSP底层原理
JSP是什么?
- JSP 是 java 程序(JSP 本质还是一个 Servlet)。
- JSP是:JavaServer Pages 的缩写(基于 Java 语言实现的服务器端的页面)。
- Servlet 是 JavaEE 的 13 个子规范之一,JSP 也是 JavaEE 的 13 个子规范之一。
- JSP 是一套规范,所有的 web容器/web服务器 都是遵循这套规范的,都是按照这套规范进行的“翻译”。
- 每一个 web容器/web服务器 都会内置一个 JSP 翻译引擎。
JSP 实际上就是一个 Servlet。
访问
index.jsp
的时候,会被 tomcat 自动翻译生成index_jsp.java
,然后会自动编译生成index_jsp.class
文件,那么index_jsp
最终其实就是一个类。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84package org.apache.jsp;
import jakarta.servlet.*;
import jakarta.servlet.http.*;
import jakarta.servlet.jsp.*;
public final class index_jsp extends org.apache.jasper.runtime.HttpJspBase
implements org.apache.jasper.runtime.JspSourceDependent,
org.apache.jasper.runtime.JspSourceImports,
org.apache.jasper.runtime.JspSourceDirectives {
......
public void _jspInit() {
}
public void _jspDestroy() {
}
public void _jspService(final jakarta.servlet.http.HttpServletRequest request, final jakarta.servlet.http.HttpServletResponse response)
throws java.io.IOException, jakarta.servlet.ServletException {
if (!jakarta.servlet.DispatcherType.ERROR.equals(request.getDispatcherType())) {
final java.lang.String _jspx_method = request.getMethod();
if ("OPTIONS".equals(_jspx_method)) {
response.setHeader("Allow","GET, HEAD, POST, OPTIONS");
return;
}
if (!"GET".equals(_jspx_method) && !"POST".equals(_jspx_method) && !"HEAD".equals(_jspx_method)) {
response.setHeader("Allow","GET, HEAD, POST, OPTIONS");
response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, "JSP 只允许 GET、POST 或 HEAD。Jasper 还允许 OPTIONS");
return;
}
}
final jakarta.servlet.jsp.PageContext pageContext;
jakarta.servlet.http.HttpSession session = null;
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;
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>$Title$</title>\n");
out.write(" </head>\n");
out.write(" <body>\n");
out.write(" $END$\n");
out.write(" </body>\n");
out.write("</html>\n");
} catch (java.lang.Throwable t) {
if (!(t instanceof jakarta.servlet.jsp.SkipPageException)){
out = _jspx_out;
if (out != null && out.getBufferSize() != 0)
try {
if (response.isCommitted()) {
out.flush();
} else {
out.clearBuffer();
}
} catch (java.io.IOException e) {}
if (_jspx_page_context != null) _jspx_page_context.handlePageException(t);
else throw new ServletException(t);
}
} finally {
_jspxFactory.releasePageContext(_jspx_page_context);
}
}
}index_jsp
类继承HttpJspBase
,而HttpJspBase
类继承的是HttpServlet
。所以index_jsp
类就是一个Servlet
类。jsp 的生命周期和 Servlet 的生命周期完全相同,完全就是一个东西,没有任何区别。
jsp 和 Servlet 一样,都是单例的(假单例)。
jsp 文件第一次访问的时候是比较慢的,为什么?
- 为什么大部分的运维人员在给客户演示项目的时候,要提前先把所有的 jsp 文件先访问一遍。
- 第一次比较麻烦:
- 要把 jsp 文件翻译生成 java 源文件;
- java 源文件要编译生成 class 字节码文件;
- 然后通过 class 去创建 servlet 对象;
- 然后调用 servlet 对象的 init 方法;
- 最后调用 servlet 对象的 service 方法。
- 第二次就比较快了,为什么?
- 因为第二次直接调用单例 servlet 对象的 service 方法即可。
- 第一次比较麻烦:
- 为什么大部分的运维人员在给客户演示项目的时候,要提前先把所有的 jsp 文件先访问一遍。
对 JSP 进行错误调试的时候,还是要直接打开 JSP 文件对应的 java 文件,检查 java 代码。
开发 JSP 的最高境界:
- 眼前是 JSP 代码,但是脑袋中呈现的是 java 代码。
JSP 既然本质上是一个 Servlet,那么 JSP 和 Servlet 到底有什么区别呢?
- 职责不同:
- Servlet 的职责是什么:收集数据(Servlet 的强项是逻辑处理,业务处理,然后链接数据库,获取/收集数据)。
- JSP 的职责是什么:展示数据(JSP的强项是做数据的展示)。
- 职责不同:
4.2 JSP的基础语法
4.2.1 JSP中直接编写文字
- 在 JSP 文件中直接编写文字,都会自动被翻译到哪里?
- 会被翻译到 servlet 类的 service 方法的 out.write(“翻译到这里”),直接翻译到双引号里,被 java 程序当做普通字符串打印输出到浏览器。
- 在 JSP 中编写的 HTML CSS JS 代码,这些代码对于对应于 JSP 文件的 java 文件来说,只是一个普通的字符串。但是一旦 java 文件把这个普通的字符串 out.write 到浏览器,浏览器就会对 HTML CSS JS 进行解释执行,展现一个效果。
4.2.2 JSP的page指令
- JSP 的 page 指令(写在文件内容开始位置):
- 通过 page 指令来设置响应的内容类型,在内容类型的最后面添加:
charset=UTF-8
<%@page contentType=“text/html;charset=UTF-8”%>,表示响应的内容类型是text/html,采用的字符集是UTF-8
- 通过 page 指令来导包:
<%@page import="java.util.List,java.util.ArrayList"%>
- 通过 page 指令来设置响应的内容类型,在内容类型的最后面添加:
4.2.3 JSP中编写Java程序
<% %>
语法:
<% java语句; %>
- 这被称为脚本块。
- 在这个符号
<% %>
当中编写的代码被视为 java 程序,被翻译到 Servlet 类的 service 方法内部。
例如如下的 JSP 文件
1
2
3
4
5<%@ page contentType="text/html;charset=UTF-8" language="java" %>
System.out.println("This is java program...");
<%
System.out.println("This is java program...");
%>在被翻译为 java 文件后,在
_jspService
方法体中,被翻译成了这个样子:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22public void _jspService(final jakarta.servlet.http.HttpServletRequest request, final jakarta.servlet.http.HttpServletResponse response)
throws java.io.IOException, jakarta.servlet.ServletException {
...
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("\r\n");
out.write("System.out.println(\"This is java program...\");\r\n");
System.out.println("This is java program...");
...
}上面的
System.out.println("This is java program...");
被翻译为out.write("System.out.println(\"This is java program...\");\r\n");
。下面的
1
2
3<%
System.out.println("This is java program...");
%>被翻译为
System.out.println("This is java program...");
。
这里你要细心点,你要思考,在
<% %>
这个符号里面写 java 代码的时候,你要时时刻刻的记住你正在 “方法体” 当中写代码,方法体中可以写什么,不可以写什么,你心里是否明白呢?- service 方法当中不能写静态代码块,不能定义方法,不能定义成员变量……
在同一个 JSP 当中
<% %>
这个符号可以出现多个。在 service 方法当中编写的代码是有顺序的,方法体当中的代码要遵循自上而下的顺序依次逐行执行。
<%! %>
- 可以进行成员变量、静态代码块的声明。
- 在这个符号当中编写的 java 程序会自动翻译到 service 方法之外。
- 这个语法很少用,为什么?不建议使用,因为在 service 方法外面写静态变量和实例变量,都会存在线程安全问题,因为 JSP 就是 servlet,servlet 是单例的,多线程并发的环境下,这个静态变量和实例变量一旦有修改操作,必然会存在线程安全问题。
<%= %>
怎么向浏览器上输出一个 java 变量?
1
2
3
4<%
String name = "jack";
out.write("name" + " = " + name);
%>上面的代码在 JSP 对应的 java 文件中会被翻译为:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21public void _jspService(final jakarta.servlet.http.HttpServletRequest request, final jakarta.servlet.http.HttpServletResponse response)
throws java.io.IOException, jakarta.servlet.ServletException {
...
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;
String name = "jack";
out.write("name" + " = " + name);
...
}- 注意:以上代码中的 out 是 JSP 的九大内置对象之一。可以直接拿来用。当然,必须只能在 service 方法内部使用。
- 九大内置对象只能在 service 方法中使用。
- 注意:以上代码中的 out 是 JSP 的九大内置对象之一。可以直接拿来用。当然,必须只能在 service 方法内部使用。
如果向浏览器上输出的内容中没有“java代码”,例如输出的字符串是一个固定的字符串,则可以直接在 JSP 中编写,不需要写到
<% %>
这里。如果输出的内容中含有“java代码”,也可以使用以下语法格式:
<%= %>
。- 注意:在
=
的后面编写要输出的内容。
<%= %>
这个符号会被翻译到哪里?最终翻译成什么?- 翻译成了 java 代码:
out.print();
- 翻译到 service 方法当中了。
例如:
1
<%=100 + 200%>
被翻译为
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19public void _jspService(final jakarta.servlet.http.HttpServletRequest request, final jakarta.servlet.http.HttpServletResponse response)
throws java.io.IOException, jakarta.servlet.ServletException {
...
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.print(100 + 200);
...
}- 什么时候使用
<%= %>
输出呢?- 输出的内容中含有 java 的变量,输出的内容是一个动态的内容,不是一个死的字符串。如果输出的是一个固定的字符串,直接在 JSP 文件中编写即可。
- 注意:在
4.2.4 JSP中的注释<%– –%>
- 语法:
<%--JSP的专业注释,不会被翻译到java源代码当中。--%>
- 另外,
<!--这种注释属于HTML的注释,这个注释信息仍然会被翻译到java源代码当中,不建议使用。-->
- 另外,
4.2.5 JSP基础语法小结
JSP的基础语法总结:
JSP 中
直接编写普通字符串
:翻译到 service 方法的out.write("这里")
。<% %>
:翻译到 service 方法体内部,里面是一条一条的 java 语句。<%! %>
:翻译到 service 方法之外。<%= %>
:翻译到 service 方法体内部,翻译为:out.print();
。<%-- --%>
:JSP 中的专业注释,不会被翻译到 java 源代码当中。<%@page contentType="text/html;charset=UTF-8"%>
:page 指令,通过 contentType 属性用来设置响应的内容类型。<%@page import="java.util.List,java.util.ArrayList"%>
:page 指令来导包。
思考
如果我只用 JSP 这一个技术,能不能开发 web 应用?
- 当然可以使用 JSP 来完成所有的功能。因为 JSP 就是 Servlet,在 JSP 的
<% %>
里面写的代码就是在 service 方法当中的,所以在<% %>
当中完全可以编写 JDBC 代码,连接数据库,查询数据,也可以在这个方法当中编写业务逻辑代码,处理业务,都是可以的,所以使用单独的 JSP 开发 web 应用完全没问题。 - 虽然 JSP 一个技术就可以完成 web 应用,但是不建议,还是建议采用 servlet + jsp 的方式进行开发,这样才能将各自的优点发挥出来。JSP 就是做数据展示,Servlet 就是做数据的收集,JSP 中编写的 Java 代码越少越好,一定要职责分明。
- 当然可以使用 JSP 来完成所有的功能。因为 JSP 就是 Servlet,在 JSP 的
JSP 文件的扩展名必须是
xxx.jsp
吗?JSP 文件的扩展名是可以配置的,不是固定的。
在
CATALINA_HOME/conf/web.xml
这个文件当中配置 JSP 文件的扩展名。1
2
3
4
5<servlet-mapping>
<servlet-name>jsp</servlet-name>
<url-pattern>*.jsp</url-pattern>
<url-pattern>*.jspx</url-pattern>
</servlet-mapping>xxx.jsp
文件对于小猫咪来说,只是一个普通的文本文件,web 容器会将xxx.jsp
文件最终翻译生成 java 程序,最终调用的是 java 对象相关的方法,真正执行的时候,和 JSP 文件就没有关系了。小窍门:JSP 如果看不懂,建议把 JSP 翻译成 java 代码,就能看懂了。
包名
bean
是什么意思?javabean(java 的 logo 是一杯冒着热气的咖啡,javabean 被翻译为:咖啡豆)。
java 是一杯咖啡,咖啡又是由一粒一粒的咖啡豆研磨而成。
- 整个 java 程序中有很多 bean 的存在,由很多 bean 组成。
什么是 javabean?实际上 javabean 你可以理解为符合某种规范的 java 类,比如:
- 有“无参数构造方法”
- 属性私有化
- 对外提供公开的
set
和get
方法 - 实现
java.io.Serializable
接口 - 重写
toString
- 重写
hashCode+equals
- …
javabean 其实就是 java 中的实体类,负责数据的封装。
javabean 符合 javabean 规范,具有更强的通用性。