2.6.3 exception对象

exception对象是Throwable的实例,代表JSP脚本中产生的错误和异常,是JSP页面异常机制的一部分。
JSP脚本中无须处理异常,即使该异常是checked异常。事实上,JSP脚本包含的所有可能出现的异常都可交给错误处理页面处理。
看如图2.21所示的异常处理结构,这是典型的异常捕捉处理块。在JSP页面中,普通的JSP脚本只执行第一个部分——代码处理段,而异常处理页面负责第二个部分——异常处理段。在异常处理段中,可以看到有个异常对象,该对象就是内置对象exception
exception对象仅在异常处理页面中才有效,通过前面的异常处理结构,读者可以非常清晰地看出这点

_jspService方法中已经有异常处理代码

打开普通JSP页面所生成的Servlet类,将可以发现如下代码片段

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
public void _jspService(final javax.servlet.http.HttpServletRequest request,
final javax.servlet.http.HttpServletResponse response)
throws java.io.IOException, javax.servlet.ServletException {
......
try {
response.setContentType("text/html; charset=UTF-8");
......
out.write("<!DOCTYPE html>\r\n");
out.write("<html>\r\n");
......
out.write("</html>");
} catch (java.lang.Throwable t) {
if (!(t instanceof javax.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);
}
}

从上面代码的粗体字代码中可以看出,JSP脚本和静态HTML部分都将转换成_jspService方法里的执行性代码这就是JSP脚本无须处理异常的原因:因为这些脚本已经处于try块中。一旦try块捕捉到JSP脚本的异常,并且_jspx_page_context不为null,就会由该对象来处理该异常。
_jspx_page_context对异常的处理也非常简单:如果该页面的page指令指定了errorPage属性,则将请求forwarderrorPage属性指定的页面,否则使用系统页面来输出异常信息。

由于只有JSP脚本、输出表达式才会对应于_jspService方法里的代码,所以这两个部分的代码无须处理checked异常。但JSP的声明部分依然需要处理checked异常,JSP的异常处理机制对JSP声明不起作用
JSP的异常处理机制中,一个异常处理页面可以处理多个JSP页面脚本部分的异常。异常处理页面通过page指令的errorPage属性确定。

throwEx.jsp

下面的页面再次测试了JSP脚本的异常机制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- 通过errorPage属性指定异常处理页面 -->
<%@ page contentType="text/html; charset=UTF-8" language="java" errorPage="error.jsp" %>
<!DOCTYPE html>
<html>
<head>
<title> JSP脚本的异常机制 </title>
</head>
<body>
<%
int a = 6;
int c = a / 0;
%>
</body>
</html>

以上页面的粗体字代码将抛出一个ArithmeticEception,则JSP异常机制将会转发到error.jsp页面.

error.jsp

error.jsp页面代码如下。

<%@ page contentType="text/html; charset=UTF-8" language="java" isErrorPage="true" %>



     异常处理页面 


异常类型是:<%=exception.getClass()%>
异常信息是:<%=exception.getMessage()%>

以上页面page指令的isErrorPage属性被设为true,则可以通过exception对象来访间上一个页面所出现的异常。

测试

在浏览器中请求throwEx.jsp页面,将看到如图2.22所示的界面

_jspService方法

打开error.jsp页面生成的Servlet类,在_jspService()方法中发现如下代码片段

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;
    java.lang.Throwable exception = org.apache.jasper.runtime.JspRuntimeLibrary.getThrowable(request);
    if (exception != null) {
        response.setStatus(javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
    }
    ......
}

从以上代码片段的粗体字代码中可以看出,当JSP页面page指令的isErrorPagetrue时,该页面就会提供exception内置对象。

在异常处理页面才能访问exception内置对象

应将异常处理页面中page指令的isErrorPage属性设置为true。只有当isErrorPage属性设置为true时才可访问exception内置对象.

2.6.2 config对象

config对象代表当前JSP配置信息,但JSP页面通常无须配置,因此也就不存在配置信息,所以JSP页面比较少用该对象。但在Servlet中则用处相对较大,因为Servlet需要在web.xml文件中进行配置,可以指定配置参数。关于Servlet的使用将在2.7节介绍。

configTest.jsp

看如下JSP页面代码,该JSP代码使用了config的一个方法getServletName()

1
2
3
4
5
6
7
8
9
10
11
12
13
<%@ page contentType="text/html; charset=GBK" language="java" errorPage="" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>测试config内置对象</title>
<meta name="website" content="http://www.crazyit.org" />
</head>
<body>
<!-- 直接输出config的getServletName的值 -->
<%=config.getServletName()%>
</body>
</html>

上面的代码:

1
<%=config.getServletName()%>

输出了configgetServletName()方法的返回值,所有的JSP页面都有相同的名字:jsp,所以粗体字代码输出为jsp
实际上,也可以在web.xml文件中配置JSP(只是比较少用),这样就可以为JSP页面指定配置信息,并可为JSP页面另外设置一个URL
config对象是ServletConfig的实例,该接口用于获取配置参数的方法是getInitParameter(String paranName)。下面的代码示范了如何在页面中使用config获取JSP配置参数

configTest2.jsp

1
2
3
4
5
6
7
8
9
10
11
12
13
<%@ page contentType="text/html; charset=GBK" language="java" errorPage="" %>
<!DOCTYPE html>
<html>
<head>
<title>测试config内置对象</title>
</head>
<body>
<!-- 输出该JSP名为name的配置参数 -->
name配置参数的值:<%=config.getInitParameter("name")%><br/>
<!-- 输出该JSP名为age的配置参数 -->
age配置参数的值:<%=config.getInitParameter("age")%>
</body>
</html>

上面的代码中两行粗体字代码输出了configgetlnitParameter()方法返回值,它们分别获取nameage两个配置参数的值。
配置JSP也是在web.xml文件中进行的,JSP被当成Servlet配置,为Servlet配置参数使用init-param元素,该元素可以接受param-nameparam-value两个子元素,分别指定参数名和参数值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<servlet>
<!-- 指定Servlet名字 -->
<servlet-name>config</servlet-name>
<!-- 指定将哪个JSP页面配置成Servlet -->
<jsp-file>/configTest2.jsp</jsp-file>
<!-- 配置名为name的参数,值为川普 -->
<init-param>
<param-name>name</param-name>
<param-value>川普</param-value>
</init-param>
<!-- 配置名为age的参数,值为30 -->
<init-param>
<param-name>age</param-name>
<param-value>30</param-value>
</init-param>
</servlet>
<servlet-mapping>
<!-- 指定将config Servlet配置到/config URL-->
<servlet-name>config</servlet-name>
<url-pattern>/config</url-pattern>
</servlet-mapping>

上面的配置片段中该Servlet(其实是JSP)配置了两个参数:mameage
上面的配置片段把configTest2.jsp页面配置成名为configServlet,并将该Servlet映射到/config处,这就允许通过/config来访问该页面。在浏览器中访间/config看到如图2.19所示的界面。

从图2.19中可以看出,通过config可以访问到web.xm文件中的配置参数。实际上,也可以直接访问configTest2.jsp页面,在浏览器中访问该页面将看到如图2.20所示的界面。

对比图2.19和2.20不难看出,如果希望JSP页面可以获取web.xml配置文件中的配置信息,则必须通过为该JSP配置的路径来访问该页面,因为只有这样访问JSP页面才会让配置参数起作用

2.6.1 application对象

在介绍application对象之前,先简单介绍一些web服务器的实现原理。虽然绝大部分读者都不需要、甚至不曾想过自己开发Web服务器,但了解一些web服务器的运行原理,对于更好地掌握JSP知识将有很大的帮助
虽然常把基于Web应用称为B/S(Browser/Server)架构的应用,但其实Web应用一样是C/S(Client/Server)结构的应用,只是这种应用的服务器是Web服务器,而客户端是浏览器。

浏览器功能

现在抛开Web应用直接看Web服务器和浏览器,对于大部分浏览器而言,它通常负责完成三件事情.

  • 向远程服务器发送请求
  • 读取远程服务器返回的字符串数据。
  • 负责根据字符串数据渲染出一个丰富多彩的页面。

实际上,浏览器是一个非常复杂的网络通信程序,它除了可以向服务器发送请求、读取网络数据之外,最大的技术难点在于将HTML文本渲染成页面,建立HTML页面的DOM模型,支持JavaScript脚本程序等。
通常浏览器有InternetExplorer,FireFoxChromeOperaSafari等,至于其他如MyE、傲游等浏览器可能只是对它们进行了简单的包装。

Web服务器功能

Web服务器则负责接收客户端请求,每当接收到客户端连接请求之后,web服务器应该使用单独的线程为该客户端提供服务:接收请求数据、送回响应数据。
图2.16显示了Web服务器的运行机制。

如图2.16所示的应用架构总是先由客户端发送请求,服务器接收到请求,然后后送回响应的数据,所以也将这种架构称做“请求响应”架构。根

Web服务器处理请求步骤

据如图2.16所示的机制进行归纳,对于每次客户端请求而言,Web服务器大致需要完成如下几个步骤。

  1. 启动单独的线程。
  2. 使用I/O流读取用户请求的二进制流数据。
  3. 从请求数据中解析参数
  4. 处理用户请求。
  5. 生成响应数据。
  6. 使用I/O流向客户端发送请求数据。

最新版的Tomcat已经不需要对每个用户请求都启用单独的线程、使用普通I/O读取用户请求的数据,最新的Tomcat使用的是异步I/O,具有更高的性能
在上面6个步骤中,第1、2和6步是通用的,可以由Web服务器来完成,但第3、4和5步则存在差异:因为不同请求里包含的请求参数不同,处理用户请求的方式也不同,所生成的响应自然也不同。那么Web服务器到底如何执行第3、4和5步呢?

Servlet的_jspService方法

实际上,web服务器会调用Servlet_jspService()方法来完成第3、4和5步。编写JSP页面时,页面里的静态内容、JSP脚本都会转换成_jspService方法的执行代码,这些执行代码负责完成解析参数、处理请求、生成响应等业务功能,而web服务器则负责完成多线程、网络通信等底层功能。
Web服务器在执行了第3步解析到用户的请求参数之后,将需要通过这些请求参数来创建HttpServletRequestHttpServletResponse等对象,作为调用_jspService()方法的参数,实际上一个Web服务器必须为Servlet API中绝大部分接口提供实现类。

JSP和Servlet如何交换数据

从上面介绍可以看出,Web应用里的JSP页面、Servlet等程序都将由Web服务器来调用,JSPServlet之间通常不会相互调用,这就产生了一个问题:JSPServlet之间如何交换数据?

通过application,session,request,page交换数据

为了解决这个问题,几乎所有Web服务器(包括JavaASPPHPRuby等)都会提供4个类似Map的结构,分别是:
applicationsessionrequestpage,并允许JSPServlet将数据放入这4个类似Map的结构中,并允许从这4个Map结构中取出数据。这4个Map结构的区别是范围不同:

  • application:对于整个Web应用有效,一旦JSPServlet将数据放入application中,该数据将可以被该应用下其他所有的JSPServlet访问
  • session:仅对一次会话有效,一旦JSPServlet将数据放入session中,该数据将可以被本次会话的其他所有的JSPServlet访问。
  • request:仅对本次请求有效,一旦JSPServlet将数据放入request中,该数据将可以被该次请求的其他JSPServlet访问.
  • page:仅对当前页面有效,一旦JSPServlet将数据放入page中,该数据只可以被当前页面的JSP脚本、声明部分访问。

类似银行

就像现实生活中有两个人,他们的钱需要相互交换,但他们两个人又不能相互接触那么只能让A把钱存入银行,而B从银行去取钱。因此可以把applicationsessionrequestpage理解为类似银行的角色。
把数据放入applicationsessionrequestpage之后,就相当于扩大了该数据的作用范围,所以认为applicationsessionrequestpage中的数据分别处于applicationsessionrequestpage范围之内。

application,session,request,pageContext内置对象

JSP中的applicationsessionrequestpageContext4个内置对象分别用于操作applicationsessionrequestpage范围中的数据

application对象代表Web应用本身,因此使用application来操作Web应用相关数据。application对象通常有如下两个作用:

  • 在整个Web应用的多个JSPServlet之间共享数据。
  • 访问Web应用的配置参数。

1. 让多个JSP、Servlet共享数据

setAttribute getAttribute方法

application通过setAttribute(String attrName, Object value)方法将一个值设置成applicationattrName属性,该属性的值对整个Web应用有效,因此该Web应用的每个JsP页面或Servlet都可以访问该属性访问属性的方法为getAttribute(String attrName)

put-application.jsp

看下面的页面,该页面仅仅声明了一个整型变量,每次刷新该页面时,该变量值加1,然后将该变量的值放入application内。下面是页面的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<%@ page contentType="text/html; charset=GBK" language="java" errorPage="" %>
<!DOCTYPE html>
<html>
<head>
<title>application测试</title>
</head>
<body>
<!-- JSP声明 -->
<%!
int i;
%>
<!-- 将i值自加后放入application的变量内 -->
<%
application.setAttribute("counter",String.valueOf(++i));
%>
<!-- 输出i值 -->
<%=i%>
</body>
</html>

以上页面在每次刷新该页面时,变量i都先自加,并被设置为applicationcounter属性的值,即每次application中的counter属性值都会加1。

get-application.jsp

再看下面的JSP页面,该页面可以直接访问到applicationcounter属性值。

1
2
3
4
5
6
7
8
9
10
11
<%@ page contentType="text/html; charset=GBK" language="java" errorPage="" %>
<!DOCTYPE html>
<html>
<head>
<title>application测试</title>
</head>
<body>
<!-- 直接输出application 变量值 -->
<%=application.getAttribute("counter")%>
</body>
</html>

get-application.jsp页面中的代码:

1
<%=application.getAttribute("counter")%>

直接输出applicationcounter属性值,虽然这个页面和put-application.Jsp没有任何关系,但它一样可以访问到application的属性,因为application的属性对于整个Web应用的JSPServlet都是共享的。

测试1

在浏览器的地址栏中访问第一个put-application.jsp页面,经多次刷新后,看到如图2.17所示的页面。
访问get-application.Jsp页面,也可看到类似于图2.17所示的效果,因为get-application.Jsp页面可以访问applicationcounter属性值。

application不仅可以用于两个JSP页面之间共享数据,还可以用于ServletJSP之间共享数据。可以把application理解成一个Map对象,任何JSPServlet都可以把某个变量放入application中保存,并为之指定一个属性名;而该应用里的其他JSPServlet就可以根据该属性名来得到这个变量

GetApplication.java

下面的Servlet代码示范了如何在Servlet中访问application里的变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package lee;

import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;

import java.io.*;

@WebServlet(name = "get-application", urlPatterns = { "/get-application" })
public class GetApplication extends HttpServlet {
public void service(HttpServletRequest request, HttpServletResponse response) throws IOException {
response.setContentType("text/html;charset=GBK");
PrintWriter out = response.getWriter();
out.println("<html><head><title>");
out.println("测试application");
out.println("</title></head><body>");
ServletContext sc = getServletConfig().getServletContext();
out.print("application中当前的counter值为:");
out.println(sc.getAttribute("counter"));
out.println("</body></html>");
}
}

由于在Servlet中并没有application内置对象,所以上面程序中的代码:

1
ServletContext sc = getServletConfig().getServletContext();

显式获取了该Web应用的ServletContext实例,每个Web应用只有一个ServletContext实例,在JSP页面中可通过application内置对象访问该实例,而Servlet中则必须通过代码获取。
程序代码:

1
out.println(sc.getAttribute("counter"));

访问、输出了application中的counter变量。
Servlet类同样需要编译成class文件才可使用,实际上该Servlet还使用了@WebServlet注解进行部署,关于Servlet的用法请参看2.7节。编译Servlet时可能由于没有添加环境出现异常,只要将Tomcat8.5lib路径下的jsp-api.jarservlet-api.jar两个文件添加到CLASSPATH环境变量中即可。

测试2

Servlet部署在Web应用中,在浏览器中访问Servlet(http://localhost:8080/jspObject/get-application),出现如图2.18所示的页面。

最后要指出的是:虽然使用application(即Servletcontext实例)可以方便多个JsPServlet共享数据,但不要仅为了JSPServlet共享数据就将数据放入application中!由于application代表整个Web应用,所以通常只应该把Web应用的状态数据放入application里。

2. 获得Web应用配置参数

application还有一个重要用处:可用于获得web应用的配置参数。看如下JSP页面,该页面访问数据库,但访问数据库所使用的驱动、URL、用户名及密码都在web.xml中给出

getWebParam.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
<%@ page contentType="text/html; charset=GBK" language="java" errorPage="" %>
<%@ page import="java.sql.*" %>
<!DOCTYPE html>
<html>
<head>
<title>application测试</title>
</head>
<body>
<%
//从配置参数中获取驱动
String driver = application.getInitParameter("driver");
//从配置参数中获取数据库url
String url = application.getInitParameter("url");
//从配置参数中获取用户名
String user = application.getInitParameter("user");
//从配置参数中获取密码
String pass = application.getInitParameter("pass");
//注册驱动
Class.forName(driver);
//获取数据库连接
Connection conn = DriverManager.getConnection(url,user,pass);
//创建Statement对象
Statement stmt = conn.createStatement();
//执行查询
ResultSet rs = stmt.executeQuery("select * from news_inf");
%>
<table bgcolor="#9999dd" border="1" width="480">
<%
//遍历结果集
while(rs.next())
{
%>
<tr>
<td><%=rs.getString(1)%></td>
<td><%=rs.getString(2)%></td>
</tr>
<%
}
%>
<table>
</body>
</html>

获取Web应用配置参数 getInitParameter方法

上面的程序中使用applicationgetInitParameter(String paramName)来获取Web应用的配置参数,这些配置参数应该在web.xml文件中使用context-param元素配置.

context-param元素

每个<context-param>元素配置一个参数,该元素下有如下两个子元素

  • param-name:配置web参数名。
  • param-value:配置Web参数值。

web.xml文件中使用<context-param>元素配置的参数对整个Web应用有效,所以也被称为web应用的配置参数。与整个web应用有关的数据,应该通过application对象来操作。

web.xml

为了给Web应用配置参数,应在web.xml文件中增加如下片段

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
<?xml version="1.0" encoding="GBK"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
......
<!-- 配置第一个参数:driver -->
<context-param>
<param-name>driver</param-name>
<param-value>com.mysql.jdbc.Driver</param-value>
</context-param>
<!-- 配置第二个参数:url -->
<context-param>
<param-name>url</param-name>
<param-value>jdbc:mysql://localhost:3306/javaee</param-value>
</context-param>
<!-- 配置第三个参数:user -->
<context-param>
<param-name>user</param-name>
<param-value>root</param-value>
</context-param>
<!-- 配置第四个参数:pass -->
<context-param>
<param-name>pass</param-name>
<param-value>32147</param-value>
</context-param>
......
</web-app>

在浏览器中浏览getWebParam.jsp页面时,可看到数据厍连接、数据査询完全成功。可见,使用application可以访问Web应用的配置参数。
通过这种方式,可以将一些配置信息放在web.xml文件中配置,避免使用硬编码方式写在代码中,从而更好地提高程序的移植性.

2.6 JSP脚本中的9个内置对象

JSP脚本中包含9个内置对象,这9个内置对象都是Servlet API接口的实例,只是JSP规范对它们进行了默认初始化(由JSP页面对应Servlet_jspService方法来创建这些实例)。也就是说,它们已经是对象,可以直接使用。9个内置对象依次如下

application

javax.servlet.ServletContext的实例,该实例代表JSP所属的Web应用本身,可用于JSP页面,或者在Servlet之间交换信息。
常用的方法有:

  • getAttribute(String attName)
  • setAttribute(String attName, String att Value)
  • getInitParameter(String paramName)

config

javax.servlet.ServletConfig的实例,该实例代表该JSP的配置信息。
常用的方法有:
getInitParameter(String paramName),getInitParameterNames()等方法。

事实上,JSP页面通常无须配置,也就不存在配置信息。因此,该对象更多地在Servlet中有效。

exception

java.lang.Throwable的实例,该实例代表其他页面中的异常和错误。只有当页面是错误处理页面,即编译指令pageisErrorPage属性为true时,该对象才可以使用。
常用的方法有:getMessage()printStackTrace()等。

out

javax.servlet.jsp.JspWriter的实例,该实例代表JSP页面的输出流,用于输出内容,形成HTML页面。

page

代表该页面本身,通常没有太大用处。也就是Servlet中的this,其类型就是生成的Servlet类,能用page的地方就可用this

pageContext

javax.servlet.jsp.PageContext的实例,该对象代表该JSP页面上下文,使用该对象可以访问页面中的共享数据。
常用的方法有:getServletContext()getServletConfig()等。

request

javax.servlet.http.HttpServletRequest的实例,该对象封装了一次请求,客户端的请求参数都被封装在该对象里。这是一个常用的对象,获取客户端请求参数必须使用该对象。
常用的方法有:

  • getParameter(String paramName)
  • getParameterValues(String paramName)
  • setAttribute(String attrName, Object attrValue)
  • getAttribute(String attrName)
  • setCharacterEncoding(String env)

response

javax.servlet.http.HttpServletResponse的实例,代表服务器对客户端的响应。通常很少使用该对象直接响应,而是使用out对象,除非需要生成非字符响应。而response对象常用于重定向。
常用的方法有:

  • getOutputStream()
  • sendRedirect(java.lang.String location)

session

javax.servlet.http.HttpSession的实例,该对象代表一次会话。当客户端浏览器与站点建立连接时,会话开始;当客户端关闭浏览器时,会话结束。
常用的方法有:

  • getAttribute(String attrName)
  • setAttribute(String attrName, Object attrValue)

_jspService方法源码

进入Tomcat\work\Catalina\localhost\directive\org\apache\jsp路径下,打开任意一个JSP页面对应生成的Servlet类文件,看到如下代码片段:

......
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;
    javax.servlet.jsp.JspWriter _jspx_out = null;
    javax.servlet.jsp.PageContext _jspx_page_context = null;

    try {
        response.setContentType("text/html; charset=UTF-8");
        pageContext = _jspxFactory.getPageContext(this, request, response, "", true, 8192, true);
        _jspx_page_context = pageContext;
        application = pageContext.getServletContext();
        config = pageContext.getServletConfig();
        session = pageContext.getSession();
        out = pageContext.getOut();
        _jspx_out = out;
    ......
}
......

几乎所有的JSP页面编译后Servlet类都有如上所示的结构,上面Servlet类的粗体字代码表明:requestresponse两个对象是_jspService方法的形参,当Tomcat调用该方法时会初始化这两个对象。
pagepageContextapplicationconfigsessionout都是_jspService方法的局部变量,由该方法完成初始化。

JSP内置对象_jspService方法的形参或局部变量

通过上面的代码不难发现JSP内置对象的实质:它们要么是_jspService方法的形参,要么是_jspService方法的局部变量,所以可以直接在JSP脚本(脚本将对应于ServletjspService方法部分)中调用这些对象,无须创建它们。

JSP内置对象智能用于JSP小脚本,JSP输出表达式,不能用于JSP声明

由于JSP内置对象都是在_jspService方法中完成初始化的,因此只能在JSP小脚本、JSP输岀表达式中使用这些内置对象。千万不要在JSP声明中使用它们!否则,系统将提示找不到这些变量。当编写JSP页面时,一定不要仅停留在JSP页面本身来看问题,这样可能导致许多误解,导致无法理解JSP的运行方式。很多书籍上随意介绍这些对象,也是形成误解的原因之一

只有异常处理页面对应的Servlet中才会初始化exception对象

细心的读者可能已经发现了:上面的代码中并没有exception内置对象,这与前面介绍的正好相符只有当页面的page指令的isErrorPage属性为true时,才可使用exception对象。也就是说,只有异常处理页面对应的Servlet中才会初始化exception对象

2.5.4 plugin指令

plugin指令主要用于下载服务器端的JavaBeanApplet到客户端执行。由于程序在客户端执行,因此客户端必须安装虚拟机。
实际由于现在很少使用Applet,而且就算要使用Applet,也完全可以使用支持AppletHTML标签,所以jsp:Plugin标签的使用场景并不多。因此为了节省篇幅起见,本书不再详细介绍plugin指令的用法。

2.5.5 param指令

param指令用于设置参数值,这个指令本身不能单独使用,因为单独的param指令没有实际意义。param指令可以与以下三个指令结合使用:

  • jsp:include

  • jspforward

  • jsp:plugin

  • 当与include指令结合使用时,param指令用于将参数值传入被导入的页面;

  • 当与forward指令结合使用时,paran指令用于将参数值传入被转向的页面;

  • 当与plugin指令结合使用时,则用于将参数传入页面中的JavaBean实例或Applet实例。

param指令的语法格式如下:

1
<jsp:param name="paramName" value="paramValue"/>

关于param的具体使用,请参考前面的示例

2.5.3 useBean、 setProperty、 getPropelty指令

这三个指令都是与JavaBean相关的指令,其中

  • useBean指令用于在JSP页面中初始化一个JAVA实例;
  • setProperty指令用于为JavaBean实例的属性设置值;
  • getProperty指令用于输出JavaBean实例的属性

如果多个JSP页面中需要重复使用某段代码,则可以把这段代码定义成Java类的方法,然后让多个JSP页面调用该方法即可,这样可以达到较好的代码复用。

useBean指令

useBean的语法格式如下:

1
<jsp:useBean id="beanInstanceName" class="className" scope="page | request | session | application" />

其中,

  • id属性是JavaBean的实例名,
  • class属性确定JavaBean的实现类。
  • scope属性用于指定JavaBean实例的作用范围,scope属性有以下4个值:
    • page:该Javabean实例仅在该页面有效
    • request:该Javabean实例在本次请求有效。
    • session:该JavaBean实例在本次session内有效。
    • application:该JavaBean实例在本应用内一直有效。

setProperty指令

setProperty指令的语法格式如下:

1
<jsp:setProperty name="beanName" property="propertyName" value="value"/>

其中,

  • name属性确定需要设定JavaBean的实例名;
  • property属性确定需要设置的属性名;
  • value属性则确定需要设置的属性值

getProperty指令

getProperty的语法格式如下:

1
<jsp:getProperty name="beanName" property="propertyName"/>

其中,

  • name属性确定需要输出的JavaBean的实例名;
  • property属性确定需要输出的属性名。

beanTest.jsp

下面的JSP页面示范了如何使用这三个动作指令来操作JavaBean.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<%@ page contentType="text/html; charset=GBK" language="java" errorPage="" %>
<!DOCTYPE html>
<html>
<head>
<title> Java Bean测试 </title>
</head>
<body>
<!-- 创建lee.Person的实例,该实例的实例名为p1 -->
<jsp:useBean id="p1" class="lee.Person" scope="page"/>
<!-- 设置p1的name属性值 -->
<jsp:setProperty name="p1" property="name" value="Trump"/>
<!-- 设置p1的age属性值 -->
<jsp:setProperty name="p1" property="age" value="23"/>

<!-- 输出p1的name属性值 -->
<jsp:getProperty name="p1" property="name"/><br/>
<!-- 输出p1的age属性值 -->
<jsp:getProperty name="p1" property="age"/>
</body>
</html>

setProperty和getProperty指令通过调用JavaBean的setter和getter方法来操作javaBean属性

上页面中示范了使用useBeansetPropertygetProperty来操作JavaBean的方法。
对于上面的JSP页面中的setPropertygetProperty标签而言,它们都要求根据属性名来操作JavaBean的属性。
实际上setPropertygetProperty要求的属性名,与Java类中定义的属性有一定的差别,例如setPropertygetProperty需要使用name属性,但JavaBean中是否真正定义了name属性并不重要,重要的是在JavaBean中提供了setName()getName()方法即可。
事实上,当页面使用setPropertygetProperty标签时,系统底层就是调用setName()getName()方法来操作Person实例的属性的。

Person.java

下面是Person类的源代码。

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

public class Person {
private String name;
private int age;

// 无参数的构造器
public Person() {
}

// 初始化全部成员变量的构造器
public Person(String name, int age) {
this.name = name;
this.age = age;
}

// name的setter和getter方法
public void setName(String name) {
this.name = name;
}

public String getName() {
return this.name;
}

// age的setter和getter方法
public void setAge(int age) {
this.age = age;
}

public int getAge() {
return this.age;
}
}

上面的Person.java只是源文件,此处将该文件放在Web应用的WEB-INF\src\路径下,实际上**Java源文件对Web应用不起作用**,所以此处会使用Ant来编译它,并将编译得到的二进制文件放入WEB-INF\classes\路径下。而且,为Web应用提供了新的class文件后,必须重启该Web应用,让它可以重新加载这些新的class文件。
该页面的执行效果如图2.15所示。

beanTest2.jsp

对于上面三个标签完全可以不使用,将beanTest.jsp修改成如下代码,其内部的执行是完全一样的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<%@ page contentType="text/html; charset=UTF-8" language="java" errorPage="" %>
<%@ page import="lee.Person" %>
<!DOCTYPE html>
<html>
<head>
<title> Java Bean测试 </title>
</head>
<body>
<%
// 创建对象(javaBean)
Person p1=new Person();
// 设置属性
p1.setName("Trump");
p1.setAge(23);
// 讲bean放到当前页面
pageContext.setAttribute("p1", p1);
%>
<!-- 使用输出表达式输出 -->
<%=p1.getName()%><br>
<%=p1.getAge()%>
</body>
</html>

手动创建的javaBean需要手动指定要放入的scope

使用useBean标签时,除在页面脚本中创建了JavaBean实例之外,该标签还会将该JavaBean实例放入指定scope中.
所以在脚本中创建号javaBean后,通常还需要在脚本中将该JavaBean放入指定scope中,如下面的代码片段所示:

1
2
3
4
5
6
7
8
//将p1放入page的生存范围中
pageContext.setAttribute("p1", p1);
//将p1放入 request的生存范围中
request.setAttribute("p1", p1);
//将p1放入 session的生存范围中
session.setAttribute("p1", p1);
//将p1放入 application的生存范围中
application.setAttribute("p1", p1);

关于pagerequestsessionapplication四个生存范围请参看下一节介绍

2.5.2 include指令

include指令是一个动态include指令,也用于包含某个页面,它不会导入被include页面的编译指令,仅仅将被导入页面的body内容插入本页面。
下面是include动作指令的语法格式:

1
<jsp:include page="{relative URL|<%=expression%>}" flush="true" />

或者

1
2
3
<jsp:include page="{relative URL|<%=expression%>}" flush="true" >
<jsp:param name="parameterName" value="patametervalue "/>
</jsp:include>

fush属性用于指定输出缓存是否转移到被导入文件中。如果指定为true,则包含在被导入文件中;如果指定为false,则包含在原文件中。对于JSP1.1以前版本,只能设置为false
对于第二种语法格式,则可在被导入页面中加入额外的请求参数

jsp-include.jsp

下面的页面使用了动态导入语法来导入指定JSP页面。

1
2
3
4
5
6
7
8
9
10
11
<%@ page contentType="text/html; charset=GBK" language="java" errorPage="" %>
<!DOCTYPE html>
<html>
<head>
<title> jsp-include测试 </title>
</head>
<body>
<!-- 使用动态include指令导入页面 -->
<jsp:include page="scriptlet.jsp" />
</body>
</html>

以上页面中代:

1
<jsp:include page="scriptlet.jsp" />

码使用了动态导入语法来导入了criptlet.Jsp。表面上看,该页面的执行效果与使用静态include导入的页面并没有什么不同。但查看jsp-include.Jsp页面生成Servlet的源代码,可以看到如下片段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public void _jspService(final javax.servlet.http.HttpServletRequest request,
final javax.servlet.http.HttpServletResponse response)
throws java.io.IOException, javax.servlet.ServletException {
......
out.write("\r\n");
out.write("<!DOCTYPE html>\r\n");
out.write("<html>\r\n");
out.write("<head>\r\n");
out.write("\t<title> jsp-include测试 </title>\r\n");
out.write("</head>\r\n");
out.write("<body>\r\n");
out.write("<!-- 使用动态include指令导入页面 -->\r\n");
org.apache.jasper.runtime.JspRuntimeLibrary.include(request, response, "scriptlet.jsp", out, false);
out.write("\r\n");
out.write("</body>\r\n");
out.write("</html>");

以上代码片段:

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

显示了动态导入的关键:动态导入只是使用一个include方法来插入目标页面的内容,而不是将目标页面完全融入本页面中

静态导入和动态导入的区别

归纳起来,静态导入和动态导入有如下三点区别

  1. 静态导入是将被导入页面的代码完全融入,两个页面融合成一个整体Servlet;而动态导入则在Servlet中使用include方法来引入被导入页面的内容
  2. 静态导入时被导入页面的编译指令会起作用;而动态导入时被导入页面的编译指令则失去作用,只是插入被导入页面的body内容。
  3. 动态包含还可以增加额外的参数。

除此之外,执行include动态指令时,还可增加额外的请求参数,如下面JSP页面所示

jsp-include2.jsp

1
2
3
4
5
6
7
8
9
10
11
12
<%@ page contentType="text/html; charset=GBK" language="java" errorPage="" %>
<!DOCTYPE html>
<html>
<head>
<title> jsp-include测试 </title>
</head>
<body>
<jsp:include page="forward-result.jsp" >
<jsp:param name="age" value="32"/>
</jsp:include>
</body>
</html>

在上面的JSP页面中的粗体字代码同样使用<jsp:include>指令包含页面,而且在jsp:include>指令中还使用param指令传入参数,该参数可以在forward-result.jsp页面中使用request对象获取。
forward-result.jsp前面已经给出,此处不再赘述。页面执行的效果如图2.14所示。

forward动作指令和include动作指令的异同

实际上,forward动作指令和include动作指令十分相似(它们的语法就很相似),它们都采用方法来引入目标页面,通过查看JSP页面所生成Servlet代码可以得出:forward指令使用jspxPageContextforward(()方法来引入目标页面,而include指令则使用通过JspRuntimeLibraryinclude()方法来引入目标页面。区别在于:执行forward时,被forward的页面将完全代替原有页面;而执行include时,被include的页面只是插入原有页面。简而言之,forward拿目标页面代替原有页面,而include则拿目标页面插入原有页面。

2.5 JSP的7个动作指令

动作指令编译指令不同,编译指令是通知Servlet引擎的处理消息,而动作指令只是运行时的动作。

  • 编译指令在将JSP编译成Servlet时起作用;
  • 处理指令通常可替换成JSP小脚本,它只是JSP小脚本的标准化写法

JSP动作指令

JSP动作指令主要有如下7个。

  1. jsp:forward:执行页面转向,将请求的处理转发到下一个页面
  2. jsp:param:用于传递参数,必须与其他支持参数的标签一起使用
  3. jsp:include:用于动态包含一个JSP页面
  4. jsp:plugin:用于下载JavaBeanApplet到客户端执行。
  5. jsp:useBean:创建一个Javabean的实例
  6. jsp:setProperty:设置JavaBean实例的属性值
  7. jsp:getProperty:输出Javabean实例的属性值。

下面依次讲解这些动作指令。

2.5.1 forward指令

forward指令用于将页面响应转发到另外的页面。既可转发到静态的HTML页面,也可转发到动态的JSP页面,或者转发到容器中的Servlet
JSPforward指令的格式如下。

JSP1.0语法

对于JSP1.0,使用如下语法:

1
<jsp:forward page={"relativeURL" | "<%= expression %>"} />

JSP1.1语法

对于JSP1.1以上规范,可使用如下语法:

1
2
3
<jsp:forward page="{relativeURL|<%=expression%>}">
{jsp:param..../>}
</jsp:forward>

第二种语法用于在转发时增加额外的请求参数。增加的请求参数的值可以通过HttpservletrEquest类的getParameter方法获取。

jsp-forward.jsp

下面示例页面使用了forward动作指令来转发用户请求。

1
2
3
4
5
6
7
8
9
10
11
12
13
<%@ page contentType="text/html; charset=UTF-8" language="java" errorPage="" %>
<!DOCTYPE html>
<html>
<head>
<title> forward的原始页 </title>
</head>
<body>
<h3>forward的原始页</h3>
<jsp:forward page="forward-result.jsp">
<jsp:param name="age" value="29"/>
</jsp:forward>
</body>
</html>

这个JSP页面非常简单,它包含了简单的title信息,页面中也包含了简单的文本内容,页面的forward动作指令:

1
2
3
<jsp:forward page="forward-result.jsp">
<jsp:param name="age" value="29"/>
</jsp:forward>

forward-result.jsp

将客户端请求转发到forward-result.jsp页面,并且在转发请求时增加了一个请求参数:参数名为age,参数值为29。
forward-result.jsp页面中,使用request内置对象(request内置对象是HttpServletRequest的实例,关于request的详细信息参看下一节)来获取增加的请求参数值。

1
2
3
4
5
6
7
8
9
10
11
<%@ page contentType="text/html; charset=UTF-8" language="java" errorPage="" %>
<!DOCTYPE html>
<html>
<head>
<title>forward结果页</title>
</head>
<body>
<!-- 使用request内置对象获取age参数的值 -->
<%=request.getParameter("age")%>
</body>
</html>

forward-result.jsp页面中代码设置了title信息,并输出了age请求参数的值,在浏览器中访问jsp-forward.jsp页面的执行效果如图2.12所示

执行forward时不会丢失请求参数

从图2.12中可以看出,执行forward指令时,用户请求的地址依然没有发生改变,但页面内容却完全变成被forward目标页的内容

执行forward指令转发请求时,客户端的请求参数不会丢失

form.jsp

看下面表单提交页面的例子,该页面没有任何动态的内容,只是一个静态的表单页,作用是将请求参数提交到jsp-forward.jsp页面。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<%@ page contentType="text/html; charset=GBK" language="java" errorPage="" %>
<!DOCTYPE html>
<html>
<head>
<title> 提交 </title>
</head>
<body>
<!-- 表单提交页面 -->
<form id="login" method="post" action="jsp-forward.jsp">
<input type="text" name="username">
<input type="submit" value="login">
</form>
</body>
</html>

修改forward-result.jsp页,增加输出表单参数的代码,也就是在forward-result.jsp页面改成如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
<%@ page contentType="text/html; charset=UTF-8" language="java" errorPage="" %>
<!DOCTYPE html>
<html>
<head>
<title>forward结果页</title>
</head>
<body>
<!-- 使用request内置对象获取age参数的值 -->
<%=request.getParameter("age")%>
<!-- 输出username请求参数的值 -->
<%=request.getParameter("username")%>
</body>
</html>

在表单提交页面中的文本框中输入任意字符串后提交该表单,即可看到如图2.13所示的执行效果

从图2.13中可看到,forward-result.jsp页面中不仅可以输出forward指令增加的请求参数,还可以看到表单里username表单域对应的请求参数,这表明执行forward时不会丢失请求参数

2.4.2 include指令

使用include指令,可以将一个外部文件嵌入到当前JSP文件中,同时解析这个页面中的JSP语句(如果有的话)。这是个静态的include语句,它会把目标页面的其他编译指令也包含进来,但动态include则不会。
include既可以包含静态的文本,也可以包含动态的JSP页面。静态的include编译指令会将被包含的页面加入本页面,融合成一个页面,因此被包含页面甚至不需要是一个完整的页面。

include编译指令语法

include编译指令的语法如下:

1
<%@include file="relativeURLSpec" %>

如果被嵌入的文件经常需要改变,建议使用<jsp:include>操作指令,因为它是动态的include语句。
下面的页面是使用静态导入的示例代码。

staticInclude.jsp

1
2
3
4
5
6
7
8
9
10
<%@ page contentType="text/html; charset=UTF-8" language="java" errorPage="" %>
<!DOCTYPE html>
<head>
<title> 静态include测试 </title>
</head>
<body>
<!-- 使用include编译指定导入页面 -->
<%@include file="scriptlet.jsp"%>
</body>
</html>

以上页面中使用静态导入的语法:

1
<%@include file="scriptlet.jsp"%>

scriptlet.jsp页面导入本页,该页面的执行效果与scriptlet.jsp的执行效果相同。

scriptlet.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
<%@ page contentType="text/html; charset=UTF-8" language="java" errorPage="" %>
<!DOCTYPE html>
<html>
<head>
<title> 小脚本测试 </title>
</head>
<body>
<table bgcolor="#9999dd" border="1" width="300px">
<!-- JSP小脚本,这些脚本会对HTML的标签产生作用 -->
<%
for(int i = 0 ; i < 10 ; i++)
{
%>
<!-- 上面的循环将控制<tr>标签循环 -->
<tr>
<td>循环值:</td>
<td><%=i%></td>
</tr>
<%
}
%>
<table>
</body>
</html>

测试

访问staticInclude.jsp,然后查看Tomcat生成的staticInclude_jsp.java(E:\apache-tomcat-8.5.35\work\Catalina\localhost\directive\org\apache\jsp\staticInclude_jsp.java),从staticInclude.jsp编译后的源代码可看到,staticlnclude.jsp页面已经完全将scriptlet.jsp的代码融入到本页面中。

staticInclude_jsp.java

下面是staticInclude_jsp.java文件的片段

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
public void _jspService(final javax.servlet.http.HttpServletRequest request,
final javax.servlet.http.HttpServletResponse response)
throws java.io.IOException, javax.servlet.ServletException {
......
out.write("\r\n");
out.write("<!DOCTYPE html>\r\n");
out.write("<head>\r\n");
out.write("\t<title> 静态include测试 </title>\r\n");
out.write("</head>\r\n");
out.write("<body>\r\n");
out.write("<!-- 使用include编译指定导入页面 -->\r\n");
out.write("\r\n");
out.write("<!DOCTYPE html>\r\n");
out.write("<html>\r\n");
out.write("<head>\r\n");
out.write("\t<title> 小脚本测试 </title>\r\n");
out.write("</head>\r\n");
out.write("<body>\r\n");
out.write("<table bgcolor=\"#9999dd\" border=\"1\" width=\"300px\">\r\n");
out.write("<!-- JSP小脚本,这些脚本会对HTML的标签产生作用 -->\r\n");

for (int i = 0; i < 10; i++) {

out.write("\r\n");
out.write("\t<!-- 上面的循环将控制<tr>标签循环 -->\r\n");
out.write("\t<tr>\r\n");
out.write("\t\t<td>循环值:</td>\r\n");
out.write("\t\t<td>");
out.print(i);
out.write("</td>\r\n");
out.write("\t</tr>\r\n");

}

out.write("\r\n");
out.write("<table>\r\n");
out.write("</body>\r\n");
out.write("</html>");
out.write("\r\n");
out.write("</body>\r\n");
out.write("</html>");
......

上面这些页面代码并不是由staticInclude.jsp页面所生成的,而是由scriptlet.jsp页面生成的。也就是说,scriptlet.Jsp页面的内容被完全融入staticInclude.jsp页面所生成的Servlet,这就是静态包含意义:页面在编译时将被包含页面的代码全都导入了

需要指岀的是,静态包含还会将被包含页面的编译指令也包含进来,如果两个页面的编译指令冲突,那么页面就会出错

2.4 JSP的3个编译指令

JSP的编译指令是通知JSP引擎的消息,它不直接生成输出。编译指令都有默认值,因此开发人员无须为每个指令设置值。

JSP常见编译指令

常见的编译指令有如下三个:

  1. page:该指令是针对当前页面的指令。
  2. include:用于指定包含另一个页面。
  3. taglib:用于定义和访问自定义标签。

编译指令语法格式

使用编译指令的语法格式如下:

1
<%@ 编译指令名 属性名="属性值" ... %>

下面主要介绍pageinclude指令,关于taglib指令,将在自定义标签库处详细讲解。

2.4.1 page指令

page指令通常位于JSP页面的顶端,一个JSP页面可以使用多条page指令。

page指令语法格式

page指令的语法格式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
<%@ page
[ language="java" ]
[ extends="package.class" ]
[ import="{package.class | package.*}, ..." ]
[ session="true | false" ]
[ buffer="none | 8kb | sizekb" ]
[ autoFlush="true | false" ]
[ isThreadSafe="true | false" ]
[ info="text" ]
[ errorPage="relativeURL" ]
[ contentType="mimeType [ ;charset=characterSet ]" | "text/html ; charset=ISO-8859-1" ]
[ isErrorPage="true | false" ]
%>

page指令属性

下面依次介绍page指令各属性的意义。

page指令属性 描述
language 声明当前JSP页面使用的脚本语言的种类,因为页面是JSP页面,该属性的值通常都是java,该属性的默认值也是java,所以通常无须设置。
extends 指定JSP页面编译所产生的Java类所继承的父类,或所实现的接口。
import 用来导入包。下面几个包是默认自动导入的,不需要显式导入。默认导入的包有:java.lang.*javax.servlet.*javax.servletjsp.*javax.servlet.http.*
session 设定这个JSP页面是否需要HttpSession
buffer 指定输出缓冲区的大小。输出缓冲区的JSP内部对象:out用于缓存JSP页面对客户浏览器的输出,默认值为8KB,可以设置为none,也可以设置为其他的值,单位为KB
autoFlush 当输岀缓冲区即将溢出时,是否需要强制输出缓冲区的内容。设置为true时为正常输出;如果设置为false,则会在buffer溢出时产生一个异常。
info 设置该JSP程序的信息,也可以看做其说明,可以通过Servlet.getServletInfo(()方法获取该值。如果在JSP页面中,可直接调用getServletInfo()方法获取该值,因为JSP页面的实质就是Servlet
errorPage 指定错误处理页面。如果本页面产生了异常或者错误,而该JSP页面没有对应的处理代码,则会自动调用该属性所指定的JSP页面
isErrorPage 设置本JSP页面是否为错误处理程序。如果该页面本身已是错误处理页面,则通常无须指定errorPage属性。
contentType 用于设定生成网页的文件格式和编码字符集,即MIME类型和页面字符集类型,默认的MIME类型是text/html
pageEncoding 指定生成网页的编码字符集。

因为JSP内建了异常机制支持,所以JSP可以不处理异常,即使是checked异常

从2.3节中执行数据库操作的JSP页面中可以看出,在connDb.jsp页面的头部,使用了两条page指令:

1
2
<%@ page contentType="text/html; charset=GBK" language="java" errorPage="" %>
<%@ page import="java.sql.*" %>

其中第二条指令用于导入本页面中使用的类,如果没有通过page指令的import属性导入这些类,则需在脚本中使用全限定类名即必须带包名。可见,此处的**import属性类似于java程序中的import关键字的作用**。
如果删除第二条page指令,则执行出现错误。

info属性 getServletInfo方法

jspInfo.jsp

看下面的JSP页面,该页面使用page指令的info属性指定了JSP页面的描述信息,又使用getServletInfo()方法输出该描述信息

1
2
3
4
5
6
7
8
9
10
11
12
13
<%@ page contentType="text/html; charset=GBK" language="java" errorPage="" %>
<!-- 指定info信息 -->
<%@ page info="this is a jsp"%>
<!DOCTYPE html>
<html>
<head>
<title> 测试page指令的info属性 </title>
</head>
<body>
<!-- 输出info信息 -->
<%= getServletInfo() %>
</body>
</html>

以上页面的第代码:

1
<%@ page info="this is a jsp"%>

设置了info属性,用于指定该JSP页面的描述信息;代码:

1
<%= getServletInfo() %>

使用了getServletInfo()方法来访问该描述信息。

在浏览器中执行该页面,将看到如图2.8所示的效果

errorPage属性

errorPage属性的实质是JSP的异常处理机制,JSP脚本不要求强制处理异常,即使该异常是checked异常

  • 如果JSP页面在运行中抛出未处理的异常,系统将自动跳转到errorPage属性指定的页面;
  • 如果errorPage没有指定错误页面,系统则直接把异常信息呈现给客户端浏览器——这是所有的开发者都不愿意见到的场景。

errorTest.jsp

看下面的JSP页面,该页面设置了page指令的errorPage属性,该属性指定了当本页面发生异常时的异常处理页面。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<%@ page contentType="text/html; charset=gbk" language="java" errorPage="error.jsp" %>
<!DOCTYPE html>
<html>
<head>
<title> new document </title>
</head>
<body>
<%
// 下面代码将出现运行时异常
int a = 6;
int b = 0;
// 除零异常
int c = a / b;
%>
</body>
</html>

以上页面的代码:

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

指定errorTest.jsp页面的错误处理页面是error.jsp

error.jsp

下面是error.jsp页面,该页面本身是错误处理页面,因此将isErrorPage设置成true

1
2
3
4
5
6
7
8
9
10
11
<%@ page contentType="text/html; charset=GBK" language="java" isErrorPage="true" %>
<!DOCTYPE html>
<html>
<head>
<title> 错误提示页面 </title>
</head>
<body>
<!-- 提醒客户端系统出现异常 -->
系统出现异常<br/>
</body>
</html>

上面页面的代码:

1
<%@ page ...... isErrorPage="true" %>

指定error.jsp页面是一个错误处理页面。在浏览器中浏览errorTest.jsp页面的效果如图29所示

如果将前一个页面中page指令的errorPage属性删除,再次通过浏览器浏览该页面,执行效果如图2.11所示。
可见,使用errorPage属性控制异常处理的效果在表现形式上要好得多。

关于JSP异常,本章在介绍exception内置对象时还会有更进一步的解释。