JavaWeb_08_JSP


前面数据库CRUD操作的例子中可以体会到,一些页面是动态显示的,所以为了动态显示,就在Servlet中形成页面将其返回到浏览器上渲染成页面。但是这样做耦合度比较高,因为前端的页面很可能为了提升用户体验而频繁发生改变,这样Servlet程序就需要修改,重新编译,重新部署,前后端的耦合度太高。那么能不能将页面展示部分的程序独立出来呢?或者动态的部分作为参数传入该独立的程序中。

和JDBC类似,对于驱动、URL、用户名和密码等等,这些应该写在配置文件中(不需要编译),程序只需要读取即可,当需要连接其他数据库时,只需要修改驱动和URL即可(修改配置文件),对核心程序不会造成太大的改动。

因此,JSP技术就诞生了,Java Sever Pages,基于Java语言实现的服务器页面,这样就不再通过核心Servlet代码来实现前端的页面,解耦合。JSP是JavaEE规范的一种,JSP文件通常存放在WEB-INF里面,保护JSP,不能通过URL路径直接访问到;但是也可以放在该目录外面。另外jsp文件的后缀是.jsp,这种后缀也是可以通过Tomcat安装目录下的conf/web.xml文件修改。

1. 第一个JSP

为了方便访问,新建Module,在项目里面,WEB-INF外面,创建一个最简单的JSP空页面。部署项目并启动服务器,在浏览器访问该页面。虽然前端页面是空白的,但是在06文章中提到,Tomcat安装目录下的work文件夹存放的是JSP文件翻译后的java文件以及编译后的class文件注意,如果是用IDEA创建的项目,就需要在IDEA的目录下去找,不是在Tomcat安装目录下去找。)。也就是说JSP文件,本质上也是某种功能的Java程序,只不过这个功能为了更加解耦合,独立出来。

JSP文件的内容如下所示(可以看到自动生成的jsp和网页的框架一样):

jsp_03.png (709×240) (gitee.io)

jsp_02.png (545×203) (gitee.io)

jsp_01.png (814×122) (gitee.io)

打开翻译后的java文件,发现该文件是一个类文件,该类继承了org.apache.jasper.runtime.HttpJspBase类,其中_jspService方法中的部分代码如下所示:

1
2
3
public final class index_jsp extends org.apache.jasper.runtime.HttpJspBase
implements org.apache.jasper.runtime.JspSourceDependent,
org.apache.jasper.runtime.JspSourceImports{}

查看Tomcat实现的该类的源码,该类是一个抽象类,并且该类继承了HttpServlet,即意味着jsp本质上也是一个Servlet,该类的部分代码如下所示,可以看到,index_jsp类作为servlet实现类,Tomcat会创建对象并调用service()方法,而该方法必定是HttpJspBase的service()方法,然后该方法再调用自己index_jsp实现的_jspService()方法:

1
2
3
4
5
6
7
public abstract class HttpJspBase extends HttpServlet implements HttpJspPage{
public final void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
_jspService(request, response);
}
}

1.1 JSP的执行原理

经过上面的分析,可以看到,浏览器上访问的路径虽然是以.jsp结尾,访问的是某个JSP文件,其实底层执行的是JSP文件对应的Java程序。

Tomcat服务器负责将.jsp文件翻译生成.java文件,并将.java源文件编译生成.class字节码文件。访问.jsp文件,底层仍然执行的是.class文件中的程序。Tomcat服务器内置了一个JSP翻译引擎,专门负责翻译JSP文件,编译.java源文件。

index.jsp会被翻译生成index_jsp.java文件,编译生成index_jsp.class文件。index_jsp这个类继承了HttpJspBase,而HttpJspBase又继承了HttpServlet,所以jsp就是Servlet,只不过职责不同,jsp的强项是做页面展示这在一定程度上解耦合,也解释了为什么IDEA生成web项目默认创建一个jsp文件,就是作为首页展示的。

翻译后的index_jsp.java文件中的index_jsp类中的service方法部分代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void _jspService(final javax.servlet.http.HttpServletRequest request, final javax.servlet.http.HttpServletResponse response)
throws java.io.IOException, javax.servlet.ServletException {
...
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");
...
}

可以看到,实际上就是将页面的内容以及相关的代码独立成一个功能类,达到解耦合。即jsp文件中的普通内容(如html、css、js等等)都是被当做普通字符串来处理的,在翻译之后的java文件中,以out输出流的形式将其输出到浏览器中渲染。

1.2 JSP第一次访问比较慢

  • 启动JSP翻译引擎
  • 需要一个翻译的过程
  • 需要一个编译的过程
  • 需要Servlet对象的创建过程
  • init方法的调用
  • service方法的调用……(后续访问只需要调用该方法即可)

JSP本质上就是Servlet,所以它也是单实例多线程环境下运行的一个Servlet对象。所以一般情况下,项目部署后,自己先提前访问一遍所有的jsp,方便提升用户体验。

那么Tomcat是怎么知道JSP文件需要重新翻译的呢?在访问jsp的时候,会记录一下该文件的最后修改时间(lastModified()方法),然后对比上一次访问该jsp时该文件的修改时间,如果二者不一致,则重新翻译并编译。

2. 小脚本

2.1 注释

因为JSP是作为页面展示用的,所以里面的内容主体上是前端的内容。这里的注释也就分为两种,一种是JSP注释,即不会被翻译引擎翻译到Java文件中(推荐)。

1
<%-- 这是JSP中的注释 --%>

另一种注释就是前端内容(html、css、js)的注释了,但是它仍然会被翻译到Java文件中,只不过在浏览器端被当做注释不被展示。

2.2 普通字符串翻译

JSP文件中所编写的所有的html、css、js都会被当做普通字符串原封不动地翻译到Servlet的service方法中的out.write("翻译到这里")部分,即输出到浏览器上。这些内容不需要特殊标签符号限制。

2.3 JSP的小脚本scriplet

小脚本即指的是一段Java程序,因为作为动态页面,所以需要程序来控制,这就是JSP中的脚本,小脚本可以有多个,之间存在顺序关系。脚本格式如下所示:

1
2
3
4
5
6
<%
Java语句;
Java语句;
Java语句;
......
%>

小脚本中的内容会被翻译到Servlet的service方法中,所以必须要编写Java语句,以分号结尾。

所谓的JSP规范,就是SUN公司制定好的一些翻译规则,按照翻译规则进行翻译,生成对应的Java源程序。不同的Web服务器,翻译的结果是完全相同的,因为这些服务器在翻译的时候,都遵守了JSP翻译规范。

JSP简单案例如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<%@ page contentType="text/html;charset=UTF-8" language="java" %>

<%
System.out.println("这是JSP中的小脚本");
int i = 10;
%>
<html>
<head>
<%
System.out.println("i的值是:" + i);
%>
<title>小脚本测试</title>
</head>
<body>
Hello 小脚本!
</body>
</html>

3. 声明语法

前面的小脚本是将程序翻译到service方法中,那么怎么将其他内容,比如声明成员变量、静态变量等等翻译到service方法之外、Servlet类中呢?这就用到声明语法了。注意,声明块中的Java程序会被JSP翻译引擎翻译到service方法之外,声明块中不能直接编写Java语句,除非是变量的声明。语法格式如下:

1
2
3
4
5
6
7
8
<%!
实例变量;
静态变量;
方法;
静态语句块;
构造函数;
......
%>

案例如下所示:

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
<%--
下面的是声明块,翻译到service方法外面,类里面
--%>
<%!
public void doSome(){
System.out.println("doSome方法执行");
System.out.println("i + 10 = " + (i+10));
}
%>

<%--
下面的是小脚本
--%>
<%
System.out.println("service 方法执行");
System.out.println("成员变量i的值是:" + i);
doSome();
%>

<%--
下面的是声明块,翻译到service方法外面,类里面
--%>
<%!
int i = 0;
static{
System.out.println("静态语句块执行");
}
%>

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>$Title$</title>
</head>
<body>
$END$
</body>
</html>

4. JSP的内置对象

这里所说的内置对象指的是可以直接在JSP小脚本中拿来使用的引用,即一些成员变量局部变量或者传入的实参等等,即只能在service方法中直接使用的对象(当然也可以在service方法中传参的形式去调用其他方法,这样其他方法就可以使用了)。JSP中有九大内置对象(可以在JSP翻译之后的Java源码中看到):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public void _jspService(final javax.servlet.http.HttpServletRequest request, final javax.servlet.http.HttpServletResponse response)
throws java.io.IOException, javax.servlet.ServletException {
final javax.servlet.jsp.PageContext pageContext;
javax.servlet.http.HttpSession session = null;
final javax.servlet.ServletContext application;
final javax.servlet.ServletConfig config;
javax.servlet.jsp.JspWriter out = null;
final java.lang.Object page = this;

try{
application = pageContext.getServletContext();
config = pageContext.getServletConfig();
session = pageContext.getSession();
out = pageContext.getOut();
} catch(){
...
} finally {
...
}
}
对象名称 完整类名(接口名)
pageContext javax.servlet.jsp.PageContext(页面域)
request javax.servlet.http.HttpServletRequest(请求域/请求对象)
session javax.servlet.http.HttpSession(会话域)
application javax.servlet.ServletContext(应用域)
out javax.servlet.jsp.JspWriter(标准输出流)
response javax.servlet.http.HttpServletResponse(响应对象)
config javax.servlet.ServletConfig(Servlet配置信息对象)
exception java.lang.Throwable(异常引用,isErrorPage=”true”使用)
page java.lang.Object(page=this,很少用)

注意,除了应用域、会话域和请求域,JSP又提供了一个页面域,即pageContext对象,只在一个JSP页面中有效,覆盖范围最小。request和session两个域使用较多。

测试案例如下所示:

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
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>测试内置对象</title>
</head>
<body>
测试内置对象
</body>
</html>

<br>
<%
out.print("pageContext = " + pageContext);
%>
<br>

<%
out.print("request = " + request);
%>
<br>

<%
out.print("session = " + session);
%>
<br>

<%
out.print("application = " + application);
%>
<br>

<%
out.print("out = " + out);
%>
<br>

<%
out.print("response = " + response);
%>
<br>

<%
out.print("config = " + config);
%>
<br>

<%
out.print("page = " + page);
%>
<br>

jsp_04.png (843×403) (gitee.io)

4.1 pageContext对象

该对象只能在同一个JSP页面中共享数据,不能跨JSP页面。可以用转发机制来进行测试一下。用的较少,常用方法也就是setAttribute和getAttribute等等。可以看一下翻译之后的源码,该对象的主要作用就是初始化其余的内置对象,另外获取JSTL标签库写入的内容。

5. 表达式

上面说的是语句,但是有时候我们需要的是表达式,不是语句。比如要在页面上动态显示:登录成功,欢迎XX回来。这种该怎么做呢?目前为止有两种方法:

  • 一种是在小脚本中以输出流的形式,动态打印。(即前面servlet中的方法)
  • 一种是将静态内容以纯文本的形式打印出来,将动态内容以输出流的形式打印出来。
1
2
3
4
5
6
7
8
9
10
11
<%
String username="jack";
%>

<%-- 第一种方式 --%>
<%
out.print("登录成功,欢迎"+ username +"回来");
%>

<%-- 第二种方式 --%>
登录成功,欢迎<% out.print(username); %>回来

除了之外,还有一种更为简单的方法,那就是表达式语法。如下所示:

1
2
3
<%=
Java表达式
%>

上面的例子如下所示:

1
登录成功,欢迎<%= username %>回来

查看翻译后的Java程序,可以看到,二者没什么区别,即翻译引擎会自动将表达式语句转换为out.print(Java表达式);语句,即表达式语句法具有输出到浏览器上的功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 对应第二种方法
out.write("登录成功,欢迎");
out.print(username);
out.write("回来\n");
out.write("\n");
out.write("<br>\n");
out.write("\n");

// 对应第三种方法
out.write("登录成功,欢迎");
out.print( username );
out.write('回');
out.write('来');

5.1 表达式案例:动态输出<h1>到<h6>标题字

采用Servlet的方法如下所示:

1
2
3
4
5
<%
for(int i=1 ; i<=6 ; i++){
out.print("<h" + i + ">" + "标题字" + "</h" + i + ">");
}
%>

那么既然表达式语法由自动输出的功能,可以将上面的out.print()语句省略即可,将字符串双引号去掉即可,注意,表达式语法自带输出功能,所以和前面的h标记中间要没有空格,否则不会将其作为一个完整标签来看待

1
2
3
4
5
6
7
8
<%
for(int i=1 ; i<=6 ; i++){
%>

<h<%=i%>>标题字</h<%=i%>>

<% }
%>

这样,可以用JSP页面代替html页面,就可以动态获取项目名,然后将其拼接在form表单的action路径中了。

6. 指令

JSP中的指令是指导JSP的翻译引擎如何翻译JSP代码。共三个指令:

  • page(页面指令)
  • include (包含指令)
  • taglib(标签库指令)

指令的使用语法格式如下所示,可以有多个指令,同一个指令的多个属性可以分开写:

1
2
<%@指令名 属性名=属性值 属性名=属性值[ 属性名=属性值...]%>
...

6.1 page指令

page指令的属性有很多个,可以在IDE中,光标选中标签,然后点击ctrl+p,查看参数提示。常用属性如下所示:

属性名 描述
contentType 设置JSP的响应内容类型
pageEncoding 设置JSP响应时的字符编码方式
import 组织导入
session 设置当前JSP页面中是否可以直接使用session内置对象
errorPage 错误页面
isErrorPage 是否是错误页面
isELIgnored 是否忽略EL表达式(见后续文章讲解)

6.1.1 contentType

该属性设置JSP的响应内容类型,即告诉浏览器本内容可以以哪种文本格式来看待渲染,对应response.setContentType(属性值);,也可以附带字符编码方式,如:

1
<%@ page contentType="text/html;charset=UTF-8" %>

翻译之后的Java代码是response.setContentType("text/html;charset=UTF-8")

6.1.2 pageEncoding

该属性设置JSP响应时的字符编码方式,即以哪种格式编码的,所以浏览器在获取到数据时要以对应的编码方式来解码,解码之后再按照上面的响应内容类型来对待数据。也是对应response.setContentType(属性值)中的编码方式charset。如:

1
<%@ page pageEncoding="UTF-8" %>

翻译之后的Java代码是response.setContentType("charset=UTF-8")。因为contentType属性设置的内容可以包含charset,所以一般这个不用写,直接在contentType属性中写即可。

6.1.3 import

前面提到了JSP翻译后,可以在service方法中,也可以翻译到servlet类中,那么怎么导入外部类呢?这就需要用到import属性。如下所示:

1
<%page import="java.util.Date"%>

翻译之后的Java代码是import java.util.Date;

6.1.4 session

session属性设置本JSP是否可以直接使用session内置对象。用法如下所示:

1
2
<%page session="true"%>
<%page session="false"%>

通过前面的翻译源码可以看到,默认是没有session属性的(默认属性值为true),但是仍然创建了session对象,即session = pageContext.getSession();也就是说,如果没有session,会自己创建session。

只有当session属性值为false的时候,才不会定义session局部变量(可以自己测试一下,查看翻译之后的源码),**注意,这里的不定义指的是根本不会创建这个变量,而不是getSession(false)**。

注意,session的内置对象,是没有session则自己创建session。那么如果没有session就不创建session,只获取当前已有的session或者null该怎么办呢?此时我们可以禁用内置对象session,自己创建session = pageContext.getSession(false)。

6.1.5 errorPage

我们可能会遇到这种情况,服务器端程序出现异常错误,这时候前端页面就会报错,那么这种情况对于程序员来说比较常见,但是对于客户来说就比较麻烦,他们没见过这种情况。所以一般来说,为了避免影响客户体验,如果程序出现错误,就跳转到指定页面,用errorPage属性指明跳转到的页面。语法如下所示:

1
<%@page errorPage="asdf.jsp"%>

通过对比两个jsp文件翻译之后的Java代码,发现errorPage属性对应下面的变化:

1
2
3
4
pageContext = _jspxFactory.getPageContext(this, request, response, null, true, 8192, true);

// 变化为
pageContext = _jspxFactory.getPageContext(this, request, response, "asdf.jsp", true, 8192, true);

6.1.6 isErrorPage

上个指令虽然在前端省去了异常堆栈信息,但是注意后台却没有异常堆栈信息,这样就会导致程序员无法排错,此时我们需要使用内置对象exception,手动将异常堆栈信息打印到指定的位置(日志),注意,是在跳转后的错误页面进行设置首先设置isErrorPage属性为true,这样才能使用内置对象exception。默认不写表示为false

1
2
3
4
5
6
7
8
9
10
11
12
13
<%@ page contentType="text/html;charset=UTF-8" isErrorPage="true" %>
<html>
<head>
<title>Title</title>
</head>
<body>
error page
</body>
</html>

<%
exception.printStackTrace();
%>

6.2 include指令

在网页中有一些主体框架,例如:网页头、网页脚,这些都是固定不变的,我们可以将网页头、网页脚这些固定不变的内容单独抽取出来编写到某个JSP文件中,在需要的页面使用include指令将该JSP文件包含进来。该指令只有file一个属性,用于将其他文件内容引入到本文件中。

优点是代码量少了,便于维护,修改一个文件即可作用于所有的页面。在一个JSP文件中可以使用多个include指令。

注意,前面提到过,JSP文件会被翻译成.java文件,那么这个include指令是怎么工作的呢?是在翻译前将两个jsp文件整合到一起再翻译成一个java文件,还是分别翻译,然后再将Java文件整合到一起呢?

查看翻译后的文件发现,只有一个java文件,那么include指令是在编译期包含,先将文件整合,然后再翻译成一个java文件。在翻译器包含或者编译期包含,这被称为静态联编但是这样,两个文件不能包含重名的变量,会造成冲突。

案例如下所示:

1
2
3
4
5
6
7
8
9
10
<%-- index.jsp文件 --%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>$Title$</title>
</head>
<body>
<%@include file="/index2.jsp"%>
</body>
</html>
1
2
3
<%-- index2.jsp文件 --%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
这是index2.jsp页面。

注意,因为是将JSP文件整合到一个文件中(service方法中的局部变量或者servlet中的成员变量),最终翻译到一个service方法中或者同一个servlet类中,所以两个文件定义的变量是可以共用的。

6.3 taglib指令

见后续标签库部分。

7. 动作

JSP的动作指的是转发等操作(并不包含重定向),语法格式如下所示:

1
<jsp:动作名 属性名=属性值 属性名=属性值...></jsp:动作名jsp:动作名>

JSP的动作如下所示:

jsp_05.png (624×321) (gitee.io)

主要的动作有forward、include等等。

7.1 include

前面提到过,include指令可以静态联编,将JSP文件整合到一起再翻译成Java文件。而include动作则可以动态联编,分别翻译成Java程序,在调用包括的文件即可。

jsp_06.png (796×142) (gitee.io)

可以看到在主JSP文件翻译后的Java程序中多了下面的这句话:

1
org.apache.jasper.runtime.JspRuntimeLibrary.include(request, response, "index2.jsp", out, false);

即运行时将二者联合起来,实际上是在运行阶段调用,动态联编。允许两个文件包括重名的变量。

所以,如果两个文件不共享变量,但是有重名变量,这就需要动态联编;如果想共享变量,只能采用静态联编。

7.2 forward

转发动作和Java中的转发一样,没什么区别。转发动作语法如下所示:

1
<jsp:forward page="index2.jsp"></jsp:forward>

8. JSP总结

可以看到,目前JSP的初衷就是为了读取传过来的封装好的数据进行动态页面展示,可以自动翻译前端代码,也可以利用特殊语法嵌入后端代码。但是不得不说JSP规定了很多种语法,导致仍然出现了大量代码,前后端的分离并不是很理想。所以JSP后期改进,采用EL表达式+JSTL标签库来简化JSP中的Java代码,从而在一定程度上形成前后端解耦合

9. EL表达式

上面总结中提到,JSP主要目的就是读取封装好的数据,然后将其输出到页面中,这部分代码比较繁琐,包括类型转换等等。那么可不可以将这些步骤简化呢?实际上是可以的,EL(Expression Language)表达式就是负责这部分,将这部分代码再封装。

EL表达式是一个由Java开发的工具包,专门用于从域对象读取数据并写入到响应体中,该工具包在Tomcat安装目录下的lib目录中(el-api.jar)。

简单语法如下所示:

1
2
3
${域对象别名.关键字}

<%-- 这里的域对象指的就是四个域,关键字就是Attribute中的key部分,返回的内容就是其value部分。 --%>

域对象的别名如下所示:

域对象 EL表达式别名
application applicationScope
session sessionScope
request requestScope
pageContext pageScope

9.1 简单使用

在域中存储基本数据类型,简单获取如下所示:

1
2
3
4
5
6
7
8
9
10
11
<%
pageContext.setAttribute("pageContext", "this is pageContext");
request.setAttribute("request", 123);
session.setAttribute("session", 3.1415926);
application.setAttribute("application", "this is application");
%>

pageContext: ${pageScope.pageContext}<br>
request: ${requestScope.request}<br>
session: ${sessionScope.session}<br>
application: ${applicationScope.application}<br>

jsp_07.png (512×339) (gitee.io)

查看翻译后的Java文件,翻译成的Java语句如下所示:

1
out.write((java.lang.String) org.apache.jasper.runtime.PageContextImpl.proprietaryEvaluate("${pageScope.pageContext}", java.lang.String.class, (javax.servlet.jsp.PageContext)_jspx_page_context, null));

9.2 读取引用数据类型的属性数据

用法和上面类似:${域对象别名.key.属性名}注意,EL表达式利用反射机制,通过调用当前属性的get方法来读取对应的属性值的,所以引用数据类型要有get、set等方法。测试案例如下所示:

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
package el;

import java.util.Objects;

public class Student {
private String name;
private double height;
private double weight;

public Student(){}

public Student(String name, double height, double weight){
this.name = name;
this.height = height;
this.weight = weight;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public double getHeight() {
return height;
}

public void setHeight(double height) {
this.height = height;
}

public double getWeight() {
return weight;
}

public void setWeight(double weight) {
this.weight = weight;
}

@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", height=" + height +
", weight=" + weight +
'}';
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return Double.compare(student.height, height) == 0 &&
Double.compare(student.weight, weight) == 0 &&
Objects.equals(name, student.name);
}

@Override
public int hashCode() {
return Objects.hash(name, height, weight);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="el.Student" %>
<html>
<head>
<title>EL表达式测试</title>
</head>
<body>
<%


pageContext.setAttribute("pageContext", new Student("张三", 1.76, 70));
request.setAttribute("request", new Student("李思", 1.65, 50));
session.setAttribute("session", 3.1415926);
application.setAttribute("application", "this is application");
%>

pageContext: ${pageScope.pageContext.name}; ${pageScope.pageContext.height}; ${pageScope.pageContext.weight}<br>
request: ${requestScope.request}<br>
session: ${sessionScope.session}<br>
application: ${applicationScope.application}<br>

</body>
</html>

9.3 简化版

可以更简单点,不写域对象别名,直接写key。但是这样做的原理是什么呢?他怎么知道是哪个域对象的呢?它是根据作用域的范围来选择的,首先对pageContext对象进行查找,如果找到关键字则输出,没有则对request对象进行查找,然后对session查找,最后对application对象查找。都没有找到则返回null。所以它首选是根据域对象的作用范围由小到大进行查找,然后小范围对象优先,所以如果采用简化版,key要尽量不同。

缺点:会增加检索时间,效率降低;另外key不同相同,否则会“覆盖”。所以,适合专门读取pageContext对象中的内容

9.4 EL表达式中的运算

EL表达式支持算术运算(+-*/)、关系运算(>, >=, <, <=, ==, !=)和逻辑运算等运算(&&, ||, !)。注意,EL表达式没有if…else条件语句,所以在涉及到关系运算时可以采用条件表达式。简单案例如下所示:

1
2
3
4
5
6
7
8
<%
request.setAttribute("age", 23);
%>

el表达式:<br>
算术运算:${requestScope.age + 10}<br>
关系运算:${requestScope.age > 18 ? "欢迎光临" : "谢绝入内"}<br>
逻辑运算:${(requestScope.age > 18) && true}<br>

jsp_08.png (507×322) (gitee.io)

9.5 EL表达式其他内置对象

  1. param

    该对象表示request.getParameter(),语法格式${param.请求参数名},等同于request.getParameter("请求参数名")。即读取请求对象中的参数内容。

  2. paramValues

    该对象相当于request.getParameterValues(),语法格式${paramValues.请求参数名},相当于获取类似复选框的这种一对多的属性的值,返回一个数组。注意,EL表达式不支持循环,所以对于数组这种就需要后续的JSTL标签库了。

  3. initParam

    该对象相当于application.getInitParameter(),读取应用域对象的一些全局配置信息(web.xml)。

简单案例测试如下所示:

1
2
3
用户名:${param.username}<br>
密码:${param.password}<br>
爱好:${paramValues.interests[0]};${paramValues.interests[1]};${paramValues.interests[2]}<br>

jsp_09.png (1229×298) (gitee.io)

9.6 EL表达式缺点

  • 只能读取域对象数据,不能像域对象中写入数据和修改数据;
  • 不支持控制语句(if、for、while)等等。

而JSP页面中应当尽量少出现Java代码,所以这就需要另一个工具JSTL标签库。

10. JSTL标签库

JSTL的全称是:JSP Standard Tag Lib,JSP的标准标签刻库。因为JSP中最好不要出现Java代码,所以出现了EL表达式简化Java代码,但是EL表达式存在一定的缺点,无法将所有的Java代码都转换成EL表达式,所以SUN公司又扩展了一些功能,将Java代码转换成标签形式。其实就是为了从表面看起来前后端分离,但是本质上仍然是Java代码,相当于EL表达式的扩展。

JSTL主要由以下四个模块组成:

  • 核心标签:Java在JSP上的基本功能的封装(如if、while等等)
  • SQL标签:JDBC在JSP上的使用
  • xml标签:DOM4J在JSP上的使用(专门用于读取xml文件)
  • Format标签:JSP文件格式转换(如日期转换成字符串)

其中,因为JSP现在越来越少用,所以主要是其核心标签用的比较多,主要讲解一下核心标签的用法。

10.1 配置JSTL

和JDBC.jar包一样,这个标签库属于扩展的功能,不属于Java SE,所以首先需要导入已经实现的标签转换Jar包:jstl.jarstandard.jar,这两个jar包可以在Tomcat自带的examples项目中找到(这里的jar包,需要在external libraries中导入,不是项目的web-inf/lib下,不知道二者的区别是什么上)。导入项目所需的jar包后,需要在jsp文件中引入JSTL中core包依赖约束,即上面所述的jsp的taglib指令。如下所示,其中uri是固定的,prefix指的是所用标签的一个前缀,用于区分不同人所写的标签,相当于python中的import xxx as后的别名,下面的c指的就是core的别名,后续用core中的标签,可以先写一个前缀c用于表示core中的标签。

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

10.2 set标签

set标签用于在四个域对象中存储内容,(从域对象中取内容已经在EL表达式中讲解了),语法如下所示:

1
<c:set scpoe="session" var="key", value="value"></c:set>

该句代替的是:

1
session.setAttritube("key", "value");

其中scope指的是四个域对象别名的别名,这里的别名是将上述别名后面的Scpoe去掉。var指的就是键值对中的key,value指的是键值对中的value。

10.3 if标签

if标签用于替代Java中的if语句。语法如下所示:

1
2
3
<c:if test="通过EL表达式进行判断">
条件满足后执行该语句
</c:if>

10.4 choose标签

choose标签用于代替Java中的switch语句。语法如下所示:

1
2
3
4
5
<c:choose>
<c:when test="通过EL表达式进行判断">条件满足后执行该语句</c:when>
<c:when test="通过EL表达式进行判断">条件满足后执行该语句</c:when>
<c:otherwise>以上条件都不满足执行该语句</c:otherwise>
</c:choose>

10.5 forEach标签

该标签用于替代Java中的循环,语法如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 第一种用法
// var变量会自动保存在当前页面域对象pageContext中
// step不写默认为1
<seleect>
<c:forEach var="i" begin="1" end="5" step="1">
<option>第${pageScope.i}页</option>
</c:forEach>
</seleect>

// 第二种用法
// 遍历items指向的对象,类似增强for循环,stu代表每次遍历的对象子对象。
<c:forEach items="通过EL表达式获得域对象中的集合" var="stu">
${stu.name}
</c:forEach>

10.6 案例

案例如下所示:

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
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="core" %>
<html>
<head>
<title>$Title$</title>
</head>
<body>
<core:set scope="page" var="name" value="zhangSan"></core:set>
<core:set scope="request" var="name" value="liSi"></core:set>
<core:set scope="session" var="name" value="wagnWu"></core:set>
<core:set scope="application" var="name" value="zhaoLiu"></core:set>
pageScope中的name: ${pageScope.name}<br>
requestScope中的name: ${requestScope.name}<br>
sessionScope中的name: ${sessionScope.name}<br>
applicationScope中的name: ${applicationScope.name}<br>

<core:set scope="session" var="age" value="15"></core:set>
<core:if test="${sessionScope.age > 18}">
年龄满足,欢迎进入!
</core:if>

<core:if test="${sessionScope.age <= 18}">
年龄未满18周岁,禁止进入!
</core:if>
<br>

该学生的成绩等级是:
<core:set scope="session" var="scope" value="95"></core:set>
<core:choose>
<core:when test="${sessionScope.scope >= 90}">A</core:when>
<core:when test="${sessionScope.scope >= 80}">B</core:when>
<core:when test="${sessionScope.scope >= 70}">C</core:when>
<core:when test="${sessionScope.scope >= 60}">D</core:when>
<core:otherwise>E</core:otherwise>
</core:choose>
<br>
下拉列表
<select>
<%
for(int i=1; i<=5; i++){
%>
<option>第<%=i%>页</option>
<%
}
%>
</select>
<br>

下拉列表(JSTL)
<%-- 自动将变量写入页面域中 --%>
<select>
<core:forEach var="i" begin="1" end="5" step="1">
<option>第${pageScope.i}页</option>
</core:forEach>
</select>
</body>
</html>

jsp_10.png (496×439) (gitee.io)

11. 备注

参考B站《动力节点》。


文章作者: 浮云
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 浮云 !
  目录