第5章JSP技术 5.1JSP运行与生命周期 在前面的章节中已经介绍了利用Servlet进行Web应用开发的方法。Servlet能够非常方便地处理来自客户端的请求,进而使用session、Cookie等机制进行相关业务的操作。但对于客户而言,最终还需要通过由HTML元素组成的页面展示其响应结果。Servlet对于HTML元素的输出过于低效,为了能更快捷地进行页面的显示,JSP技术应运而生。 一个典型的JSP页面由声明、表达式、程序段、注释、指令、动作以及静态HTML标签等部分组成。JSP底层实现仍然是利用Servlet。与Servlet一样,JSP文件也存在生命周期的概念。JSP的生命周期如图51所示,包括编译、初始化、执行及销毁4个阶段。 图51JSP的生命周期 JSP文件在生命周期内需要执行如下步骤。 (1) 编译阶段: JSP在运行时和Servlet类似,当客户端通过HTTP请求对一个JSP页面进行访问时,通过Connector监听到请求,转发给Engine,然后通过URL匹配到主机Host,找到对应Context下的JSP文件,此时需要查找是否已经存在JSP对应的Servlet文件,如果该Servlet文件存在,那么执行即可; 如果不存在,就说明是第一次运行该JSP文件,就应将JSP文件转换为Servlet文件,并进行编译。 (2) 初始化阶段: 加载与JSP对应的Servlet类,创建其实例,并调用它的jspInit()方法。 (3) 执行阶段: 主要调用jspService(request,response)方法。该方法等价于Servlet中的service()方法,其功能是负责接收用户请求,并对其进行处理,然后将响应返回给客户。 (4) 销毁阶段: 当JSP页面从容器中移除时,执行jspDestroy()方法,等价于Servlet的destroy()方法。 5.2JSP基础语法 5.2.1变量声明与表达式 JSP嵌入的Java语句必须包含在<% %>标签里面。Java代码可以嵌入JSP文件的任意地方。可以使用JSP表达式进行字符串变量的输出,在进行表达式输出前,需要对变量进行声明以及初始化。变量的声明语法格式如下: <%!变量声明语句;%> 例如: <%! int i = 0; %>或者<% Date e = new Date(); %>。 注意,变量声明语句前面的感叹号!表示该变量的声明范围为整个页面,因此不管使用该变量的语句是在其前面或者后面,都是可以的。如果去掉该感叹号,就不能在变量声明之前使用该变量,否则报错。 表达式的语法格式为<% =变量/表达式%>,用于将声明的变量以字符串的形式输出到客户端。 注意,表达式不能以分号;结尾,否则报错。 下面通过例51来演示JSP中的变量声明以及表达式的使用方法。 视频讲解 【例51】JSP中的变量声明以及表达式的使用方法。 (1) 新建一个Web项目Chapt_05,在WebContent目录下新建一个JSP文件,命名为express.jsp,编写代码如下: <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> Insert title here <%=name +","%> <%! String name="小明"; String msg="你好!今天的日期是:"; %> <%=msg +(new java.text. SimpleDateFormat ("yyyy-MM-dd HH:mm:ss")).format(new java.util.Date())%> (2) 右击express.jsp文件,选择Run As→Run on Server,选择Tomcat 9,部署并启动该项目。然后打开浏览器,输入http://localhost:8080/Chapt_05/expression.jsp进行访问。express.jsp页面输出变量和表达式的结果如图52所示。 图52express.jsp页面输出变量和表达式的结果 可以看到name变量的使用语句在声明之前,但是由于声明的时候使用了页面全局变量的设置,因此语句也是可以执行的。 表达式虽然对变量输出很方便,但是表达式不能出现多条语句,并且只能用于字符串类型的变量以及表达式语句的输出。 5.2.2程序段 表达式一般只用于简单的字符串输出,如果想实现较为复杂的功能,表达式显然是不够的。实际上,可以在JSP文件中插入多行甚至多段Java代码,只需要将这些代码包含在<% %>标签里面即可。在3.6节中曾使用Servlet生成了一个10以内的加法表,下面通过例52来演示将Java程序段嵌入到JSP页面中,完成相同的加法表。 视频讲解 【例52】JSP页面中程序段的使用。 在WebContent下新建一个JSP文件,取名为additiontable.jsp,代码如下: <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> JSP页面显示加法表格 <% for(int i=1;i<=9;i++) {%> <%for(int j=1;j<=i;j++){%> <% }%> <% }%>
<%=i-j+1 %>+<%=j %>=<%=i+1 %>
运行该JSP页面,效果如图324所示。 可以看到,在JSP页面中可以灵活地将Java程序段、表达式以及HTML标签混合使用,Tomcat针对JSP页面中的代码进行如下处理。 (1) <% %>标签里的代码将被认为是Java语句而被Tomcat服务器进行编译执行。 (2) 没有被<% %>标签包含的代码被认为是HTML以及文本元素,直接交由浏览器进行解释执行。 在JSP页面编写Java代码时,如果要插入非Java代码,需要先使用%>标签结束Java代码的编写。然后在输入完HTML标记或者文本代码后,再继续使用<% %>标签编写Java代码。采用JSP的方式进行表格的输出,从页面结构来说更加直观,只用关注动态的部分即可,静态不变的标签和样式部分不用像Servlet中逐句地通过代码进行输出。 5.2.3JSP注释 在JSP中可以使用两种类型的注释,一种是HTML风格的注释,一种是JSP类型的注释。 (1) HTML风格的注释: 写法与HTML静态页面中的一样,即的形式。注释中可以加入JSP表达式,编译并执行以动态生产注释内容。客户端可以接收HTML风格注释的内容。 (2) JSP类型的注释: 其又可以分为Java代码注释和JSP语法注释。 ① Java代码注释: 单行使用//注释内容,多行采用/*注释内容*/的形式。 ② JSP语法注释: 使用<% 注释内容%>的形式。 JSP类型的注释不会发送到客户端,在客户端不会看到该类型注释的内容。 下面通过例53来演示JSP中两种注释方式的使用方法。 视频讲解 【例53】JSP中注释的使用方法。 新建一个JSP文件,取名为comment.jsp,在标签体内插入代码如下: <%int a=1; %> <%--这是一个JSP风格的注释,不会发送到客户端 --%> <% // int b=2; //上面为JSP中代码的单行注释 /* 下面为JSP中代码的多行注释 for(int i=0;i<9;i++){ out.println(i); } */ %> <%--上面的JSP 代码注释同样不会发送到客户端 --%> 访问comment.jsp页面,此时页面显示为空白内容,右击“查看页面源代码”菜单,弹出JSP页面中不同风格的注释显示,如图53所示。 可以发现,HTML风格的注释被发送到客户端,并且其中插入的JSP表达式也能正确解析并显示。而JSP风格的注释以及JSP代码注释都没有发送到客户端。 图53JSP页面中不同风格的注释显示 5.3JSP指令与动作 5.3.1JSP指令 JSP指令的作用是定义页面如何编译,指令中不包含逻辑控制,也不会在页面中产生任何可见的输出。其语法格式如下: <% @ 指令类别 属性1="属性值1" 属性2="属性值2"… 属性n="属性值n" %> 常见的JSP指令及其功能如表51所示。 表51常见的JSP指令及其功能 指令功能 page用于导入需要的类、指定页面编码方式、输出内容类型、处理错误页面等属性 include用于引入其他文件 taglib用于引入标签库 本节主要介绍page以及include指令,taglib指令将在第8章介绍EL与JSTL中进行讲解。 1. page指令 page指令主要有以下作用。 (1) 导入包。在express.jsp中,实例化Date以及SimpleDateFormat对象时,使用了该对象的全类名,显然这样写比较麻烦。如果想引用JDK中的类或者自定义类,可以使用page指令的import属性来引入,语法格式如下: <% @ page import ="包名1.类名1","包名2.类名2",…,"包名n.类名n"%> 也可以每引入一个类,使用一条import指令。例如,在express.jsp中的开头使用以下语句。 <% @ page import ="java.util.Date"> <% @ page import =" java.text.SimpleDateFormat"> 通过import指令引入包中的类以后,在实例化时就可以直接使用类名。如果想引入包中所有的类,和在普通Java类中引入时一样,可以使用通配符*。 (2) 设定字符集。在page指令中可以使用pageEncoding属性来指定页面的编码字符集。 注意,此处的pageEncoding的值是指定JSP文件在编译成Servlet源代码时的编码方式。为了保证页面显示不同语言文字的兼容性,建议统一使用UTF8编码方式。因此,可以利用page指令进行如下设置。 <% @ page pageEncoding="UTF-8"> (3) 指定MIME类型和字符编码。使用ContentType和charset指定页面输出的类型以及编码字符集,代码如下: <%@ page ContentType="text/html; charset=UTF-8" %> 表示该页面的输出为HTML格式的文本类型,使用UTF8进行编码。 (4) 设置错误信息提示页面。当后台程序发生错误时,服务器会直接将错误信息输出到页面,此时用户看到了错误信息,影响了使用体验,因此这种方式不太符合程序对鲁棒性的要求。因此比较合适的做法是,当程序出现错误时,在内部跳转到一个指定页面并显示该页面中的提示信息给用户查看。在JSP页面中可以使用errorPage属性指定发生错误时跳转的页面。提示错误信息的页面使用isErrorPage=true来指定。下面通过例54来演示page指令设置错误提示页面的操作步骤。 视频讲解 【例54】page指令设置错误提示页面。 (1) 新建两个JSP文件,分别取名为error.jsp和isError.jsp。在error.jsp页面的标签体内编写代码如下: <% int [] num={1,2,3,4,5}; for(int i=0;i<6;i++){%> <%=num[i] %> <% } %> 上述代码通过for循环语句输出数组中的数字,但此时变量i的最大数值超出了数组长度而造成了越界。 注意,此时的编译能够通过,但访问该JSP页面且在页面运行时,错误直接将出错信息显示出来,如图54所示。 图54访问JSP页面且在页面运行时,错误直接将出错信息显示出来 (2) 在error.jsp中,通过<%@ page errorPage="isError.jsp"%>指令将isError.jsp设置为发生错误时进行提示的页面。 (3) 在isError.jsp页面中,通过page指令设置isErrorPage属性为true,并在标签内进行信息提示,代码如下: <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" isErrorPage="true" %> Insert title here 页面出现错误,请返回 (4) 此时再次运行error.jsp页面,发现页面不再直接输出错误信息,而是通过isError.jsp页面显示提示信息,如图55所示。 图55通过isError.jsp页面显示提示信息 2. include指令 include指令的作用是引入外部文件,可以是JSP、HTML、Java程序以及其他脚本程序。在实际开发中,很多页面会有很多部分是类似的,比如位于页面顶部的导航栏,以及页面底部的显示一些基本网站信息的部分。为了复用这些信息,可以将这两部分抽离出来,形成单独的文件,然后在页面上使用include指令引入头部和尾部文件。其语法格式如下所示。 <%@ include file="文件名" %> 下面通过例55来演示include指令的使用方法。 视频讲解 【例55】include指令的使用方法。 (1) 新建3个JSP文件,分别取名为header.jsp、footer.jsp和includefile.jsp。其中header.jsp的代码如下: <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <% String message="欢迎您"; %> <%=message %>
footer.jsp的代码如下: <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>

Copyright XXXX, All Rights Reserved 联系电话:4000000000

在includefile.jsp的标签内添加代码如下: <%@ include file="header.jsp" %> 这是主体部分 <%@ include file="footer.jsp" %> (2) 访问includefile.jsp页面,include指令引入外部文件并显示,如图56所示。 图56include指令引入外部文件并显示 注意,外部引入的文件里面如果定义了变量,就不要和主页面中定义的变量名一致; 否则会出现错误。因为include指令相当于把被引入文件直接插入本页面,然后整个页面代码再进行编译,相当于重复定义变量。因此,要避免这种情况的出现。 视频讲解 5.3.2JSP动作 JSP动作是使用XML标记语法来控制JSP页面的行为。语法如下: 或者使用如下形式: …… JSP动作的语法及其功能如表52所示。 表52JSP动作的语法及其功能 语法描述 jsp:include 类似于include指令,用于引入文件 jsp:forward 请求转发到另外的页面 jsp:useBean 寻找一个JavaBean jsp:setProperty 设置JavaBean属性 jsp:getProperty 获取JavaBean属性 jsp:plugin 根据浏览器类型为Java插件生成OBJECT或EMBED标记 本节主要介绍include以及forward两个动作,关于JavaBean的指令将在第6章介绍,此外jsp:element、jsp:attribute、jsp:body、jsp:text等动作使用较少,感兴趣的读者可以自行查阅资料。 1. include动作 include动作的基本语法如下: include动作也是用于外部文件的被引入,与include指令的区别如下所述。 (1) include动作会将被包含文件先编译,然后将输出包含到主页面中,因此不会因为相同变量的定义导致报错的问题。 (2) include动作会自动检查被包含页面的内容的变化,并实时更新。include指令则不会实时检测。 2. forward动作 forward动作用于实现页面的跳转,语法如下所示。 注意,跳转页面必须是服务器内部资源,不能是外部链接。该动作等价于Servlet的request对象的请求转发。执行该动作后,当前页面将不再执行,而直接跳到指定的页面。例如,新建一个JSP文件,命名为forward.jsp,在body标签体内添加代码如下: 运行forward.jsp,页面直接跳转到additiontable.jsp页面并显示加法表格。 5.4JSP内置对象 所谓内置对象,是指JSP页面生成后,通过Web容器来实现和管理,自动载入不需要实例化就可以直接使用的对象。通过内置对象的使用,可以提高开发效率。事实上,由于JSP是对Servlet进行封装,所以JSP的内置对象和Servlet中使用的对象很相似。 JSP共有以下9个内置对象。 (1) out对象: 负责对客户端的输出。其等同于在Servlet中通过response.getWriter()方法获取的客户端输出对象,主要的方法和Servlet中使用的类似。 (2) request对象: 负责得到客户端的请求信息。其等同于Servlet中相关方法(一般为doGet或者doPost)中的request参数,主要的方法和Servlet中使用的类似。 (3) response对象: 负责向客户端返回响应。其等同于Servlet中service()方法中的response参数,主要的方法和Servlet中使用的类似。 (4) session对象: 负责保存客户端在一次会话过程中的信息。其等同于Servlet中使用request.getSession()方法获取的HttpSession对象,主要的方法和Servlet中使用的类似。 (5) application对象: 表示整个应用的环境信息。其等同于Servlet中使用request.getServletContext()方法获取的ServletContext对象,主要的方法和Servlet中使用的类似。 (6) exception对象: 表示页面上发生的异常,该对象对应java.lang.Exception接口,可以获得页面的异常信息。可以在isError.jsp中添加如下代码,获取该页面中出现的错误信息以及信息的详细描述。 <%=exception.getMessage() %>
<%=exception.toString() %>
(7) page对象: 该对象的对应类型是java.lang.Object,表示JSP页面本身,可以使用Object对象的方法。在页面中可以用this替代,一般较少使用该对象。 (8) pageContext对象: 该对象的对应类型是javax.servlet.jsp.PageContext,表示JSP的上下文。通过该对象,可以获取除自身外的其他8个对象,也可以直接访问绑定在page、request、session以及application对象上的Java对象。例如,假设request对象通过setAttribute()方法设置了一个username对象,则可以使用以下语句直接获取该username对象。 <%=pageContext.request.username %> (9) config对象: 该对象的对应类型是javax.servlet.jsp.ServletConfig,表示JSP初始化时所需要的参数以及服务器的相关信息,等同于Servlet中使用request.getServletConfig()方法获取的ServletConfig对象。这在实际开发中使用较少。 上述9大对象中,使用较多的是前面5个,其用法在Servlet中已经做过介绍。此外,在JSP中如果想处理Cookie,与Servlet中的一样,可通过response对象向客户端写入Cookie对象,利用request对象读取Cookie对象的数组。 5.5JSP与Servlet共同开发 在前面的章节中已经讨论过JSP与Servlet之间的关系以及各自的优势,下面通过例56来演示JSP与Servlet是如何共同进行开发的。 5.5.1需求分析 视频讲解 【例56】JSP与Servelt共同开发。 该例子主要实现如下的功能。 (1) 用户在登录页面输入用户名和密码,如果用户名和密码相等,就进入欢迎页面。在欢迎页面显示有用户名和一个安全退出超链接。在不关闭浏览器的情况下,页面进行回退后,若再次访问欢迎页面,则仍能显示用户信息。 (2) 如果不是单击安全退出超链接退出,而是直接关闭浏览器,那么当再次访问登录页面时,用户名和密码信息就会自动输入,不用用户手动填写,单击登录按钮即可直接登录。 (3) 如果是单击安全退出超链接退出,那么在返回到登录页面时,需要重新填写用户的登录信息。 (4) 若在没有登录成功的情况下直接访问欢迎页面,则直接跳转到登录页面。 5.5.2实现思路 该例子需要结合session和Cookie机制,大致的思路如下所述。 (1) 在登录页面需要首先判断是否存在键名为userinfo的Cookie对象,如果存在,就将对应的键值字符串以“|”符号进行分离。将分离后的两个子字符串分别赋给表单中用户名和密码的值; 如果不存在Cookie对象,就按照正常登录处理。 (2) 单击登录后提交给Servlet进行处理,该Servlet在处理完用户名和密码的校验后,新建一个键名为userinfo的Cookie对象,键值设置为username|password,其中username和password为用户提交的用户名和密码的字符串信息。然后向客户端写入该Cookie,以及向HttpSession对象中存储用户名信息,并跳转到欢迎页面。 (3) 欢迎页面需要判断session对象是否存在,并获取username的值。 (4) 安全退出由负责退出的Servlet处理,该Servlet需要将Httpsession对象销毁,并且删除Cookie信息。 5.5.3代码实现 下面按照上述思路进行代码的编写。 (1) 首先新建一个JSP页面,取名为login.jsp,在标签内编写代码如下: <% String username=""; String password=""; Cookie[] cookies=request.getCookies(); if(cookies!=null) { String userinfo=null; for(int i=0;i
用户名:>
密   码: >
在获取到userinfo的Cookie后,以“|”为分隔符,分别取出Cookie中存储的username和password的值,通过JSP表达式赋给填写用户名文本框和密码框元素的value值,从而达到自动填入的功能。 (2) 新建一个Servlet,取名为LoginServlet,通过注解@WebServlet("/LoginServlet")设置其映射路径为/LoginServlet,在doGet()方法中编写代码如下: request.setCharacterEncoding("UTF-8"); response.setCharacterEncoding("UTF-8"); response.setHeader("Content-type","text/plain;charset=UTF-8") ; String username=request.getParameter("username"); String password=request.getParameter("password"); if(username!=null&&password!=null) {//表单提交内容不为空 if(username.equals(password)) {//验证用户名密码 HttpSession session=request.getSession(); //创建一个session对象 session.setAttribute("username", username); //将用户名存储在session中 //创建一个键名为userinfo的Cookie对象,键值为username|password Cookie userinfocookie=new Cookie("userinfo",username+"|"+password); userinfocookie.setMaxAge(60*5); //Cookie有效值为5分钟 response.addCookie(userinfocookie); //将用户名的Cookie发送到客户端 response.sendRedirect("welcome.jsp"); //跳转到欢迎页面 }else { response.getWriter().append("用户名密码错误,请重新登录, 5秒后回到登录页面……"); response.setHeader("Refresh", "5;URL=login.jsp"); } }else {//防止未经表单提交,直接访问该Servlet response.getWriter().append("禁止直接访问,5秒后回到登录页面……"); response.setHeader("Refresh", "5;URL=login.jsp"); } 在LoginServlet中,对用户名和密码进行校验后,创建HttpSession对象存储用户名,并创建Cookie对象,其值是username和password使用“|”连接而成。 (3) 创建一个JSP文件,取名为welcome.jsp,在body标签体内编写代码如下: <% //以下三条语句是为了使页面不使用缓存 response.setHeader("Pragma","No-Cache"); response.setHeader("Cache-Control","No-Cache"); response.setDateHeader("Expires", 0); if (session != null) {// 如果session存在,说明不是直接访问 // 从session中读取用户名 String username = (String) session.getAttribute("username"); if (username != null)// 进一步判断会话中是否存在用户名 {out.print("欢迎您," + username+" 安全退出"); } else { out.print("你还没有登录,5秒后跳转到登录页面……"); response.setHeader("Refresh", "5;URL=login.jsp"); } } else {// 如果session不存在,说明是直接访问该页面 out.print("禁止直接访问,5秒后跳转到登录页面……"); response.setHeader("Refresh", "5;URL=login.jsp"); } %> 该页面通过内置session对象获取用户名信息。另外开头的三条语句是为了防止页面使用缓存和显示过期的信息。 (4) 新建一个Servlet,取名为LogoutServlet,通过注解@WebServlet("/LogoutServlet")设置其映射路径为/LogoutServlet,在doGet()方法中编写代码如下: response.setCharacterEncoding("UTF-8"); response.setHeader("Content-type","text/plain;charset=UTF-8") ; //判断当前请求是否存在对象 HttpSession session = request.getSession(false); if(session!=null) {//如果会话存在,去除会话中的登录状态 session.invalidate(); //销毁整个会话 Cookie usernameinfo=new Cookie("userinfo",""); usernameinfo.setMaxAge(0); //设置Cookie有效时间为0,即立即删除 //发送到客户端覆盖之前Cookie的设置 response.addCookie(usernameinfo); response.getWriter().append("注销成功,5秒后跳转到登录页面……"); response.setHeader("Refresh", "5;URL=login.jsp"); }else {//会话不存在,说明是直接访问该页面 response.getWriter().append("你还未登录,无须注销, 5秒后跳转到登录页面……"); response.setHeader("Refresh", "5;URL=login.jsp"); } 在页面中单击安全退出超链接后,除了销毁会话外,还应该去除之前存储在客户端的Cookie,达到安全退出的目的。 以上就是该例子的代码实现,读者可以在编写完毕后运行相关页面,验证是否实现相关功能。 在本例中,Servlet更多的是进行业务逻辑的处理,即用户的登录、退出以及针对HttpSession和Cookie的设置。而JSP页面更多的是根据Servlet处理后的结果,进行相关信息的显示。这也正是二者各自的优势所在,后续章节的讲解和示例基本也是遵循这样的分工。