第3章〓Servlet会话跟踪 本章思维导图 本章目标  掌握会话跟踪的相关技术。  理解Cookie的原理。  掌握Cookie的读写方法。  理解Session的原理。  理解Session的生命周期。  熟练掌握Session的使用方法。  掌握ServletConfig的使用方法。  掌握ServletContext的使用方法。 3.1会话跟踪技术简介◆ Internet通信协议可以分为两大类: 有状态协议(stateful)和无状态协议(stateless),两者最大的差别在于客户端与服务器之间维持联机上的不同。 HTTP即是一种无状态协议。HTTP采用“连接—请求—应答—关闭连接”模式。当客户端发出请求时,服务器才会建立连接,一旦客户端的请求结束,服务器便会中断连接,不会一直与客户端保持联机的状态。当下一次请求发起时,服务器会把这个请求看成一个新的连接,与之前的请求无关。 对于交互式的Web应用,保持状态是非常重要的。一个有状态协议可以用来帮助在多个请求和响应之间实现复杂的业务逻辑。例如: 在购物网站中,服务器会为每个用户分配一个购物车,购物车会一直伴随该用户的整个购物过程并且互不混淆,此种情况下,就需要为客户端和服务器之间的交互存储状态。本章所要讲述的会话跟踪技术可以解决这些问题。 会话跟踪技术是一种在客户端与服务器间保持HTTP状态的解决方案。从开发角度考虑,就是使上一次请求所传递的数据能够维持状态到下一次请求,并可辨认出是否为同一客户端所发送出来的。会话跟踪技术的解决方案主要分为以下几种。  Cookie技术。  Session技术。  URL重写技术。  隐藏表单域技术。 视频讲解 3.2Cookie技术◆ Cookie技术是一种在客户端保持会话跟踪的解决方案。Cookie是指某些网站为了辨别用户身份而储存在用户终端上的文本信息(通常经过加密)。Cookie在用户第一次访问服务器时,由服务器通过响应头的方式发送给客户端浏览器; 当用户再次向服务器发送请求时会附带上这些文本信息。图31为服务器对第一次客户端请求所响应的含有“SetCookie”响应头的报文信息,图32为客户端再次请求时附带的含有“Cookie”请求头的报文信息。 图31第一次请求时服务器响应的Cookie报头 图32再次请求时附带的Cookie报头 服务器在接收到来自客户端浏览器的请求时,通过Cookie能够分析请求头的内容而得到客户端特有的信息,从而动态生成与该客户端相对应的内容。例如,在很多登录界面中可以看到“记住我”这样的选项,如果勾选则下次再访问该网站时就会自动记住用户名和密码。另外,一些网站可以根据用户的使用喜好不同浏览器设置稍有不同,进行个性化的风格设置、广告投放等,这些功能都可以通过存储在客户端的Cookie实现。 注意 在使用Cookie时,要保证浏览器接受Cookie。对IE浏览器来说,设置接受Cookie的方法: 选择浏览器的右上角“工具”菜单→“Internet选项”→“隐私”→“高级”→“接受”选项。 Cookie可以通过jakarta.servlet.http.Cookie类的构造方法来创建,其示例代码如下所示。 【示例】Cookie对象的创建 Cookie unameCookie = new Cookie("username","zhaokeling"); 其中,Cookie的构造方法通常需要两个参数。  第一个String类型的参数用于指定Cookie的属性名。  第二个String类型的参数用于指定属性值。 创建完成的Cookie对象可以使用HttpServletResponse对象的addCookie()方法,通过增加“SetCookie”响应头的方式(不是替换原有的)将其响应给客户端浏览器,存储在客户端机器上,示例代码如下所示。生成的Cookie仅在当前浏览器有效,不能跨浏览器。 【示例】服务器向客户端响应Cookie response.addCookie(unameCookie); 其中,addCookie()方法中的参数为一个Cookie对象。存储在客户端的Cookie,通过HttpServletRequest对象的getCookies()方法获取,该方法返回所访问网站的所有Cookie的对象数组,遍历该数组可以获得各个Cookie对象,示例代码如下所示。 【示例】获取并遍历客户端Cookie Cookie[] cookies = request.getCookie(); if(cookies != null) for(Cookie c : cookies){ out.println("属性名:" + c.getName()); out.println("属性值" + c.getValue()); } 在默认情况下,Cookie只能被创建它的应用获取。Cookie的setPath()方法可以重新指定其访问路径,例如将其设置为可被某个应用下的某个路径共享,或被同一服务器内的所有应用共享,如下述示例所示。 【示例】设置Cookie在某个应用下的访问路径 unameCookie.setPath("/ch03/jsp/"); 【示例】设置Cookie在服务器中所有应用下的访问路径 unameCookie.setPath("/"); Cookie有一定的存活时间,不会在客户端一直保存。默认情况下,Cookie保存在浏览器内存中,在浏览器关闭时失效,这种Cookie也称为临时Cookie(或会话Cookie)。若要让其长久地保存在磁盘上,可以通过Cookie对象的setMaxAge()方法设置其存活时间(以秒为单位),时间若为正整数,表示其存活的秒数; 若为负数,表示其为临时Cookie; 若为0,表示通知浏览器删除相应的Cookie。保存在磁盘上的Cookie也称为持久Cookie。下述示例描述存活时间为1周的持久Cookie。 【示例】设置Cookie的存活时间 unameCookie.setMaxAge(7*24*60*60); //参数以秒为基本单位 下述代码演示使用Cookie保存用户名和密码,当用户再次登录时在相应的文本栏显示上次登录时输入的信息。 首先,编写用于接收用户输入的HTML表单文件,在该例子中,没有使用HTML文件而是用一个Servlet来完成此功能,这是因为需要通过Servlet去读取客户端的Cookie,而HTML文件无法完成此功能。 【案例31】LoginServlet.java @WebServlet("/LoginServlet") public class LoginServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throwsServletException, IOException { String cookieName = "userName"; String cookiePwd = "pwd"; // 获得所有Cookie Cookie[] cookies = request.getCookies(); String userName = ""; String pwd = ""; String isChecked = ""; // 如果Cookie数组不为null,说明曾经设置过 // 也就是曾经登录过,那么取出上次登录的用户名和密码 if (cookies != null) { // 如果曾经设置过Cookie,checkbox状态应该是checked isChecked = "checked"; for (inti = 0; i < cookies.length; i++) { // 取出登录名 if (cookies[i].getName().equals(cookieName)) { userName = cookies[i].getValue(); } // 取出密码 if (cookies[i].getName().equals(cookiePwd)) { pwd = cookies[i].getValue(); } } } response.setContentType("text/html;charset=GBK"); PrintWriter out = response.getWriter(); out.println("\n"); out.println("登录\n"); out.println("\n"); out.println("
\n"); out.println("
\n"); out.println("姓名:
\n"); out.println("密码:
\n"); out.println("保存用户名和密码\n"); out.println("
\n"); out.println("\n"); out.println("
\n"); out.println("
\n"); out.println("\n"); out.println("\n"); } public void doPost(HttpServletRequest request, HttpServletResponse response) throwsServletException, IOException { doGet(request, response); } } 上述代码先使用request.getCookies()获取客户端Cookie数组; 再遍历该数组,找到对应的Cookie,取出用户名和密码; 最后将信息显示在相应的表单控件中。 然后,编写CookieTest.java程序创建Cookie并保存到客户端。 【案例32】CookieTest.java @WebServlet("/CookieTest") public class CookieTest extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throwsServletException, IOException { Cookie userCookie = new Cookie("userName", request .getParameter("UserName")); Cookie pwdCookie = new Cookie("pwd", request.getParameter("Pwd")); if (request.getParameter("SaveCookie") != null &&request.getParameter("SaveCookie").equals("Yes")) { userCookie.setMaxAge(7 * 24 * 60 * 60); pwdCookie.setMaxAge(7 * 24 * 60 * 60); } else { //删除客户端对应的Cookie userCookie.setMaxAge(0); pwdCookie.setMaxAge(0); } response.addCookie(userCookie); response.addCookie(pwdCookie); PrintWriter out = response.getWriter(); out.println("Welcome," + request.getParameter("UserName")); } public void doPost(HttpServletRequest request, HttpServletResponse response) throwsServletException, IOException { doGet(request, response); } } 上述代码创建了两个Cookie对象,分别用来储存表单中传递过来的用户名和密码,然后根据客户端的“SaveCookie”元素的值,决定是否向客户端发送Cookie,或者删除以前存储的Cookie。 启动Tomcat服务器,在浏览器中访问http://localhost:8080/ch03/LoginServlet,第一次请求的访问登录页面,如图33所示。 图33第一次请求的响应报文 输入姓名和密码,选中保存复选框,单击提交按钮,显示结果如图34所示。 图34第二次请求的请求报文 当再次登录时,用户名和密码已显示,如图35所示。 图35第二次请求的响应报文 上述实例效果只限于使用同一浏览器且允许Cookie下访问,这是由Cookie本身的局限性决定的。Cookie的缺点主要集中在其安全性和隐私保护上,主要包括以下几点。  Cookie可能被禁用,当用户非常注重个人隐私保护时,很可能会禁用浏览器的Cookie功能。  Cookie是与浏览器相关的,这意味着即使访问的是同一个页面,不同浏览器之间所保存的Cookie也是不能互相访问的。  Cookie可能被删除,因为每个Cookie都是硬盘上的一个文件,因此很有可能被用户删除。  Cookie的大小和个数受限,单个Cookie保存的数据不能超过4KB,很多浏览器都限制一个站点最多保存20个Cookie。  Cookie安全性不够高,所有的Cookie都是以纯文本的形式记录于文件中,因此如果要保存用户名和密码等信息,最好事先经过加密处理。 视频讲解 3.3Session技术◆ Session技术是指使用HttpSession对象实现会话跟踪的技术,是一种在服务器端保持会话跟踪的解决方案。HttpSession对象是jakarta.servlet.http.HttpSession接口的实例,也称为会话对象,该对象用来保存单个用户访问时的一些信息,是服务器在无状态的HTTP下用来识别和维护具体某个用户的主要方式。 3.3.1Session创建 HttpSession对象会在用户第一次访问服务器时由容器创建(注意只有访问JSP、Servlet等程序时才会创建,只访问HTML、IMAGE等静态资源并不会创建),当用户调用其失效方法(invalidate()方法)或超过其最大不活动时间时会失效。在此期间,用户与服务器之间的多次请求都属于同一个会话。 服务器在创建会话对象时,会为其分配一个唯一的会话标识——SessionId,在用户随后的请求中,服务器通过读取SessionId属性值来识别不同的用户,从而实现对每个用户的会话跟踪。 HttpServletRequest接口提供了获取HttpSession对象的方法,如表31所示。 表31获取HttpSession对象的方法 方法描述 getSession()获取与客户端请求关联的当前的有效的Session,若没有Session关联则新建一个 getSession(boolean create)获取与客户端请求关联的当前的有效的Session,若没有Session关联,当参数为真时,Session被新建,为假时,返回空值 获取一个会话对象的示例代码如下所示。 【示例】获取会话对象 HttpSession session = request.getSession(); HttpSession session = request.getSession(true); HttpSession接口提供了存取会话域属性和管理会话生命周期的方法,如表32所示。 表32HttpSession接口常用方法 方法描述 void setAttribute(String key,Object value)以key/value的形式将对象保存在HttpSession对象中 Object getAttribute(String key)通过key获取对象值 void removeAttribute(String key)从HttpSession对象中删除指定名称key所对应的对象 void invalidate()设置HttpSession对象失效 void setMaxInactiveInterval(int interval)设定HttpSession对象的最大不活动时间(以秒为单位),若超过这个时间,HttpSession对象将会失效 int getMaxInactiveInterval()获取HttpSession对象的有效最大不活动时间(以秒为单位) String getId()获取HttpSession对象标识SessionID long getCreationTime()获取HttpSession对象产生的时间,单位是毫秒 long getLastAccessedTime()获取用户最后通过这个HttpSession对象送出请求的时间 其中,存取会话域属性数据的方法示例如下所示。 【示例】存取会话域属性 //存储会话域属性"username",值为"zkl" session.setAttribute("username","zkl"); //通过属性名"username"从会话域中获取属性值 String uname = (String)session.getAttribute("username"); //通过属性名将属性从会话域中移除 session.removeAttribute("username"); HttpSession接口用于管理会话生命周期的方法示例如下所示。 【示例】获取会话的最大不活动时间 int time = session.getMaxInactiveInterval(); //单位为秒 会话的最大不活动时间是指,会话超过此时间不进行任何操作则会话自动失效的时间。HttpSession对象的最大不活动时间与容器配置有关,对于Tomcat容器,默认时间为1800s。实际开发中,可以根据业务需求,通过web.xml重新设置该时间,设置方式如下所示。 【示例】在web.xml中设置会话最大不活动时间 10 其中设置时间的单位为分钟。 除了此种方式外,还可以通过会话对象的setMaxInactiveInterval()方法进行设置,示例如下。 【示例】使用代码设置会话最大不活动时间 session.setMaxInactiveInterval(600); //单位为秒 会话对象除了在超过最大不活动时间自动失效外,也可以通过调用invalidate()方法让其立即失效,示例代码如下所示。 【示例】设置会话立即失效 session.invalidate(); 服务器在执行会话失效代码后,会清除会话对象及其所有会话域属性,同时响应客户端浏览器Session。在实际应用中,此方法多用来实现系统的“安全退出”,使客户端和服务器彻底结束此次会话,清除所有与会话相关的信息,防止会话劫持等黑客攻击。 3.3.2Session生命周期 Session生命周期经过以下几个过程。 (1) 客户端向服务器第一次发送请求时,request中并无SessionID。 (2) 此时服务器会创建一个Session对象,并分配一个SessionID。Serssion对象保存在服务器端,此时为新建状态,调用session.isNew()返回true。 (3) 当服务器端处理完毕后,会将SessionID通过response对象传回到客户端,浏览器负责保存到当前进程中。 (4) 当客户端再次发送请求时,会同时将SessionID发送给服务器。 (5) 服务器根据传递过来的SessionID将这次请求(request)与保存在服务器端的Session对象联系起来。此时Session已不处于新建状态,调用session.isNew()返回false。 (6) 循环执行上面的过程(3)~(5),直到Session超时或销毁。 Session的生命周期和访问范围如图36所示。每个客户(如Client1)可以访问多个Servlet,但是一个客户的多个请求将共享一个Session,同一Web应用下的所有Servlet共享一个ServletContext,即Servlet上下文。有关ServletContext在本章后续章节将详细介绍。 图36Session的生命周期和访问范围 3.3.3Session应用 下述代码演示使用Session实现一个购物车。其中,案例33中的bookChoose.jsp页面用于让用户选择需要放入购物车的书籍,案例34中的ShoppingCarServlet.java用于将书籍存入购物车,案例35中的ShoppingListServlet.java用于从购物车中取出书籍进行显示。 【案例33】bookChoose.jsp <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> 书籍选购

请选择您要购买的书籍:

JavaSE应用与开发

JavaWeb应用与开发

JavaEE应用与开发

启动Tomcat服务器,在浏览器中访问“http://localhost:8080/ch03/bookChoose.jsp”,运行结果如图37所示。 图37bookChoose.jsp运行结果 实现购物车功能的ShoppingCarServlet代码如下所示。 【案例34】ShoppingCarServlet.java @WebServlet("/ShoppingCarServlet") public class ShoppingCarServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); response.setContentType("text/html;charset=UTF-8"); PrintWriter out = response.getWriter(); // 获取会话对象 HttpSession session = request.getSession(); // 从会话域中获取shoppingCar属性对象(即:购物车) // 对象定义为Map类型,key为书名,value为购买数量 Map car = (Map) session .getAttribute("shoppingCar"); // 若会话域中无shoppingCar属性对象,则实例化一个 if (car == null) { car = new HashMap(); } // 获取用户选择的书籍 String[] books = request.getParameterValues("book"); if (books != null && books.length > 0) { for (String bookName : books) { // 判断此书籍是否已在购物车中 if (car.get(bookName) != null) { int num = car.get(bookName); car.put(bookName, num + 1); } else { car.put(bookName, 1); } } } // 将更新后的购物车存储在会话域中 session.setAttribute("shoppingCar", car); response.sendRedirect("ShoppingListServlet"); } } 在ShoppingListServlet中,从会话域中取出购物车,对其存储的货物进行遍历显示,代码如下所示。 【案例35】ShoppingListServlet.java @WebServlet("/ShoppingListServlet") public class ShoppingListServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doPost(request, response); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html;charset=UTF-8"); PrintWriter out = response.getWriter(); HttpSession session = request.getSession(); Map car = (Map) session .getAttribute("shoppingCar"); if (car != null && car.size() > 0) { out.println("

您购买的书籍有:

"); // 遍历显示购物车中的书籍名称和选择次数 for (StringbookName : car.keySet()) { out.println("

" + bookName + " , " + car.get(bookName) + "本

"); } } else { out.println("

您还未购买任何书籍!

"); } out.println("

继续购买

"); } } 用户提交书籍选择表单后的运行结果如图38所示。 图38表单提交后的运行结果 单击“继续购买”链接,返回到bookChoose.jsp图书页面可以继续购买,结果如图39所示。 图39继续购买后的运行结果 视频讲解 3.4URL重写技术◆ URL重写是指服务器程序对接收的URL请求重新写成网站可以处理的另一个URL的过程。URL重写技术是实现动态网站会话跟踪的重要保障。在实际应用中,当不能确定客户端浏览器是否支持Cookie的情况下,使用URL重写技术可以对请求的URL地址追加会话标识,从而实现用户的会话跟踪功能。 例如,对于如下格式的请求地址: http://localhost:8080/ch03/EncodeURLServlet 经过URL重写后,地址格式变为: http://localhost:8080/ch03/EncodeURLServlet;jsessionid=24666BB458B4E0A68068CC49A97FC4A9 其中“jsessionid”即为追加的会话标识,服务器可以通过它来识别跟踪某个用户的访问。 URL重写通过HttpServletResponse的encodeURL()方法和encodeRedirectURL()方法实现,其中encodeRedirectURL()方法主要对使用sendRedirect()方法的URL进行重写。URL重写方法根据请求信息中是否包含“SetCookie”请求头来决定是否进行URL重写,若包含该请求头,会将URL原样输出; 若不包含,则会将会话标识重写到URL中。 URL重写的示例代码如下所示。 【示例】encodeURL()方法的使用 out.print("链接请求"> 【示例】encodeRedirectURL()方法的使用 response.sendRedirect(response.encodeRedirectURL("EncodeURLServlet")); 下述代码演示在浏览器Cookie禁用后,普通请求和重定向请求的URL重写方法,以及重写后会话标识“jsessionid”的跟踪情况。 图310演示对IE浏览器Cookie的禁用设置。 图310IE浏览器Cookie的禁用设置 【案例36】UrlRewritingServlet.java @WebServlet("/UrlRewritingServlet") public class UrlRewritingServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html;charset=UTF-8"); PrintWriter out = response.getWriter(); // 获取会话对象 HttpSession session = request.getSession(); // 对CommonServlet和UseRedirectServlet两个请求地址进行URL重写 String link1 = response.encodeURL("CommonServlet"); String link2 = response.encodeURL("UseRedirectServlet"); // 使用超链接形式对URL重写地址进行请求 out.println("对一个普通Servlet的请求"); out.println("对一个含有重定向代码的Servlet的请求"); } } 启动服务器,在IE中访问“http://localhost:8080/ch03/UrlRewritingServlet”,运行结果和页面源码如图311所示。 图311UrlRewritingServlet.java运行结果和页面源码 从运行结果可以看出,两个Servlet请求地址经URL重写后,都被附加了jsessionid标识。下述代码演示第一个超链接CommonSevlet对会话标识的获取。 【案例37】CommonServlet.java @WebServlet("/CommonServlet") public class CommonServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { PrintWriter out = response.getWriter(); // 获取经URL重写传递来的会话标识值 String sessionId = request.getSession().getId(); out.println(sessionId); } } 单击第一个超链接,运行结果如图312所示。 图312CommonServlet.java运行结果 下述代码演示第二个超链接UseRedirectServlet对重定向URL的重写方法。 【案例38】UseRedirectServlet.java @WebServlet("/UseRedirectServlet") public class UseRedirectServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 对重定向的URL进行重写 String encodeURL = response.encodeRedirectURL("CommonServlet"); // 进行重定向 response.sendRedirect(encodeURL); } } 单击第二个超链接,可以发现运行结果与图312完全相同。 由此实例可以看出,在客户端浏览器完全禁用了Cookie后,通过在请求地址后附加会话标识的URL重写技术仍可实现会话的跟踪。但使用此种方式,有以下几个方面需要注意。  如果应用需要使用URL重写,那么必须对应用的所有请求(包括所有的超链接、表单的action属性值和重定向地址)都进行重写,从而将jsessionid维持下来。  浏览器对URL地址长度有限制,所以在对含有查询参数的GET请求进行URL重写时,需要注意其总长度。  由于静态页面不能进行会话标识的传递,因此所有的URL地址都必须为动态请求地址。 3.5隐藏表单域技术◆ 利用Form表单的隐藏表单域技术,可以在完全脱离浏览器对Cookie的使用限制以及在用户无法从页面显示看到隐藏标识的情况下,将标识随请求一起传送给服务器处理,从而实现会话的跟踪。 设置隐藏表单域的示例代码如下所示。 【示例】在Form表单中定义隐藏域
在服务器端通过HttpServletRequest对象获取隐藏域的值,示例代码如下所示。 【示例】隐藏域的获取 String flag = request.getParameter("userID"); 由于使用隐藏表单域技术进行会话跟踪的基本前提是只能通过Form表单来传递标识信息,因此此技术在实际应用中并不常用。 3.6ServletConfig接口◆ jakarta.servlet.ServletConfig接口的定义为: public abstract interface jakarta.servlet.ServletConfig 容器在初始化一个Servlet时,将为该Servlet创建一个唯一的ServletConfig对象,并将这个ServletConfig对象通过init(ServletConfig config)方法传递并保存在此Servlet对象中。 ServletConfig接口的主要方法如表33所示。 表33ServletConfig接口的主要方法 方法方 法 描 述 getInitParameter(String param)根据给定的初始化参数名称,返回参数值,若参数不存在,返回null getInitParameterNames()返回一个Enumeration对象,里面包含了所有的初始化参数名称 getServletContext()返回当前ServletContext()对象 getServletName()返回当前Servlet的名字,即@WebServlet的name属性值。如果没有配置这个属性,则返回Servlet类的全限定名 使用ServletConfig接口中的方法主要可以访问两项内容: Servlet初始化参数和ServletContext对象。前者通常由容器从Servlet的配置属性中读取(如initParams或所指定的参数); 后者为Servlet提供有关容器的信息。 在实际应用中经常会遇到一些随需求不断变更的信息,例如数据库的链接地址、账号、密码等,若将这些信息硬编码到Servlet类中,则信息的每次修改都将使Servlet重新编译,这将大大降低系统的可维护性。这时可以采用Servlet的初始参数配置来解决这类问题。 下述示例演示通过web.xml文件配置初始化参数和使用ServletConfig对象获取初始化参数。 【示例】Servlet初始化参数在web.xml文件中的配置 HelloServlet com.zkl.ch03.servlet.HelloServlet url jdbc:oracle:thin:@localhost:1521:orcl user zkl password 123456 在上述代码中,配置Servlet时使用元素设定初始化参数信息,该元素有两个子元素: 子元素设置初始化参数名,子元素设置初始化参数值。 【示例】Servlet初始化参数的获取 public class HelloServlet extends HttpServlet { public void init(ServletConfig config) throws ServletException { String url = config.getInitParameter("url"); String user = config.getInitParameter("user"); String password = config.getInitParameter("password"); try { Connection conn = DriverManager.getConnection(url, user, password); } catch (SQLException e) { e.printStackTrace(); } } ... 通过上述示例可以看出,在项目开发和应用过程中若要对数据库连接信息进行变更,只需修改web.xml中的Servlet配置属性即可,而不需要修改代码和重新编译代码。 3.7ServletContext接口◆ jakarta.servlet.ServletContext接口的定义为: public abstract interface jakarta.servlet.ServletContext ServletContext也称为Servlet上下文,代表当前Servlet运行环境,是Servlet与Servlet容器之间直接通信的接口。Servlet容器在启动一个Web应用时,会为该应用创建一个唯一的ServletContext对象供该应用中的所有Servlet对象共享,Servlet对象可以通过ServletContext对象来访问容器中的各种资源。 获得ServletContext对象可以通过以下两种方式。 (1) 通过ServletConfig接口的getServletContext()方法获得ServletContext对象。 (2) 通过GenericServlet抽象类的getServletContext()方法获得ServletContext对象,实质上该方法也是调用了ServletConfig接口的getServletContext()方法。 ServletContext接口提供了以下几种类型的方法。  获取应用范围的初始化参数的方法。  存取应用域属性的方法。  获取当前Web应用信息的方法。  获取当前容器信息和向容器输出日志的方法。  获取服务器文件资源的方法。 下述各小节将依次对其进行详细介绍。 视频讲解 3.7.1获取应用范围的初始化参数 在Web应用开发中可以通过web.xml配置应用范围的初始化参数,容器在应用程序加载时会读取这些配置参数并存入ServletContext对象中。ServletContext接口提供了这些初始化参数的获取方法,如表34所示。 表34ServletContext接口获取应用范围的初始化参数的方法 方法方 法 描 述 getInitParameter(String name) 返回Web应用范围内指定的初始化参数值。在web.xml中使用元素表示应用范围内的初始化参数 getInitParameterNames() 返回一个包含所有初始化参数名称的Enumeration对象 下述代码演示Web应用范围的初始化参数的配置及获取。 首先,在web.xml配置文件中配置Web应用范围的初始化参数,该参数通过元素来指定,代码如下所示。 【案例39】web.xml ch03 webSite www.baidu.com adminEmail zkl@qq.com index.html index.jsp ... 然后,通过使用ServletContext对象获取初始化参数的值,其代码如下所示。 【案例310】ContextInitParamServlet.java @WebServlet("/ContextInitParamServlet") public class ContextInitParamServlet extends HttpServlet { private static final long serialVersionUID = 1L; public ContextInitParamServlet() { super(); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 设置响应到客户端的MIME类型及编码方式 response.setContentType("text/html;charset=UTF-8"); // 使用ServletContext对象获取所有初始化参数 Enumeration paramNames = super.getServletContext() .getInitParameterNames(); // 使用ServletContext对象获取某个初始化参数 String webSite = super.getServletContext().getInitParameter("webSite"); StringadminEmail = super.getServletContext().getInitParameter( "adminEmail"); // 获取输出流 PrintWriter out = response.getWriter(); // 输出响应结果 out.print("

当前Web应用的所有初始化参数:"); while (paramNames.hasMoreElements()) { String name = paramNames.nextElement(); out.print(name + " "); } out.println("

webSite参数的值:" + webSite); out.println("

adminEmail参数的值:" + adminEmail + "

"); } } 启动服务器,在IE中访问“http://localhost:8080/cha03/ContextInitParamServlet”,运行结果如图313所示。 图313ContextInitParamServlet运行结果 视频讲解 3.7.2存取应用域属性 ServletContext对象可以理解为容器内的一个共享空间,可以存放具有应用级别作用域的数据,Web应用中的各个组件都可以共享这些数据。这些共享数据以key/value的形式存放在ServletContext对象中,并以key作为其属性名被访问。具体的应用域属性的存取方法如表35所示。 表35ServletContext对象应用域属性的存取方法 方法方 法 描 述 setAttribute(String name,Object object) 把一个对象和一个属性名绑定并存放到ServletContext中,参数name指定属性名,参数Object表示共享数据 getAttribute(String name)根据参数给定的属性名,返回一个Object类型的对象 getAttributeNames()返回一个Enumeration对象,该对象包含了所有存放在ServletContext中的属性名 removeAttribute(String name)根据参数指定的属性名,从ServletContext对象中删除匹配的属性 注意 应用域具有以下两层含义: 一是表示由Web应用的生命周期构成的时间段; 二是表示在Web应用范围内的可访问性。 下述代码通过一个网站访问计数的例子演示应用域属性的存取方法。 【案例311】ContextAttributeServlet.java @WebServlet("/ContextAttributeServlet") public class ContextAttributeServlet extends HttpServlet { private static final long serialVersionUID = 1L; public ContextAttributeServlet() { super(); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //设置响应到客户端的文本类型 response.setContentType("text/html;charset=UTF-8"); //获取ServletContext对象 ServletContext context = super.getServletContext(); //从ServletContext对象获取count属性存放的计数值 Integer count = (Integer) context.getAttribute("count"); if (count == null) { count = 1; } else { count = count + 1; } //将更新后的数值存放到ServletContext对象的count属性中 context.setAttribute("count", count); //获取输出流 PrintWriter out = response.getWriter(); //输出计数信息 out.println("

本网站目前访问人数是: " + count + "

"); } } 再新建一个Servlet命名为ContextAttributeOtherServlet,代码内容与ContextAttributeServlet完全相同,启动服务器,在IE中先后访问“http://localhost:8080/ch03/ContextAttributeServlet”与“http://localhost:8080/ch03/ContextAttributeOtherServlet”,运行结果如图314所示。 图314ContextAttributeServlet与ContextAttributeOtherServlet运行结果 由上述代码可以看出,对于存放在ServletContext对象中的属性count,不同的Servlet都可以通过ServletContext对象对其进行访问和修改,并且一方的修改会影响另一方获取的数据值,因此在多线程访问情况下,需要注意数据的同步问题。 视频讲解 3.7.3获取当前应用信息 ServletContext对象还包含有关Web应用的信息,例如: 当前Web应用的根路径、应用的名称、应用组件间的转发,以及容器下其他Web应用的ServletContext对象等。具体信息的获取如表36所示。 表36ServletContext接口访问当前应用信息的方法 方法方 法 描 述 getContextPath()返回当前Web应用的根路径 getServletContextName()返回Web应用的名称,即元素中元素的值 getRequestDispatcher(String path)返回一个用于向其他Web组件转发请求的RequestDispatcher对象 getContext(String uripath)根据参数指定的URL返回当前Servlet容器中其他Web应用的ServletContext()对象,URL必须是以“/”开头的绝对路径 下述代码演示获取当前应用信息的方法的使用。 【案例312】ContextAppInfoServlet.java @WebServlet("/ContextAppInfoServlet") public class ContextAppInfoServlet extends HttpServlet { private static final long serialVersionUID = 1L; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 设置响应到客户端的文本类型为HTML response.setContentType("text/html;charset=UTF-8"); // 获取当前ServletContext对象 ServletContext context = super.getServletContext(); // 获取当前Web应用的上下文根路径 String contextPath = context.getContextPath(); // 获取当前Web应用的名称 String contextName = context.getServletContextName(); // 获取输出流 PrintWriter out = response.getWriter(); out.println("

当前Web应用的上下文根路径是:" + contextPath + "

"); out.println("

当前Web应用的名称是:" + contextName + "

"); } } 启动服务器,在IE中访问“http://localhost:8080/ch03/ContextAppInfoServlet”,运行结果如图315所示。 图315ContextAppInfoServlet.java运行结果 注意 Tomcat服务器默认不能跨应用访问,因此若要使用当前应用的ServletContext对象的getContext(String uripath)方法访问同一容器下的其他应用,需要将"%TOMCAT_HOME%/conf/context.xml"文件中的“”的属性"crossContext"设为"true",例如: “”。 3.7.4获取容器信息 ServletContext接口还提供了获取有关容器信息和向容器输出日志的方法,如表37所示。 表37ServletContext接口获取容器信息和向容器输出日志的方法 方法方 法 描 述 getServerInfo()返回Web容器的名字和版本 getMajorVersion()返回Web容器支持的Servlet API的主版本号 getMinorVersion()返回Web容器支持的Servlet API的次版本号 log(String msg)用于记录一般的日志 log(String message,Throwable throw)用于记录异常的堆栈日志 ServletContext接口中常用方法的具体使用如下述代码所示。 【案例313】ContextLogInfoServlet.java @WebServlet("/ContextLogInfoServlet") public class ContextLogInfoServlet extends HttpServlet { private static final long serialVersionUID = 1L; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 设置响应到客户端MIME类型和字符编码方式 response.setContentType("text/html;charset=UTF-8"); // 获取ServletContext对象 ServletContext context = super.getServletContext(); // 获取Web容器的名字和版本 String serverInfo = context.getServerInfo(); // 获取Web容器支持的Servlet API的主版本号 int majorVersion = context.getMajorVersion(); // 获取Web容器支持的Servlet API的次版本号 int minoVersion = context.getMinorVersion(); // 记录一般的日志 context.log("自定义日志信息"); // 记录异常的堆栈日志 context.log("自定义错误日志信息",new Exception("异常堆栈信息")); // 获取输出流 PrintWriter out = response.getWriter(); out.println("

Web容器的名字和版本为:" + serverInfo +"

"); out.println("

Web容器支持的Servlet API的主版本号为:" + majorVersion +"

"); out.println("

Web容器支持的Servlet API的次版本号为:" + minoVersion +"

"); } } 启动服务器,在浏览器中访问http://localhost:8080/ch03/ContextLogInfoServlet,运行结果如图316所示。 图316ContextLogInfoServlet.java运行结果 ContextLogInfoServlet中记录的日志信息在Tomcat服务器控制台显示效果如图317所示。 图317日志信息在Tomcat服务器控制台显示效果 3.7.5获取服务器文件资源 使用ServletContext接口可以直接访问Web应用中的静态内容文件,例如HTML、GIF、Properties文件等,同时还可以获取文件资源的MIME类型以及其在服务器中的真实存放路径,具体方法如表38所示。 表38ServletContext接口访问服务器端文件系统资源的方法 方法方 法 描 述 getResourceAsStream(String path)返回一个读取参数指定的文件的输入流,参数路径必须以"/"开头 getResource(String path)返回由path指定的资源路径对应的一个URL对象,参数路径必须以"/"开头 getRealPath(String path)根据参数指定的虚拟路径,返回文件系统中的一个真实的路径 getMimeType(String file)返回参数指定的文件的MIME类型 下述代码演示使用ServletContext接口访问当前ch03应用中images目录下的mypic.jpg文件。 【案例314】ContextFileResourceServlet.java @WebServlet("/ContextFileResourceServlet") public class ContextFileResourceServlet extends HttpServlet { private static final long serialVersionUID = 1L; public ContextFileResourceServlet() { super(); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 设置响应到客户端MIME类型和字符编码方式 response.setContentType("text/html;charset=UTF-8"); // 获取ServletContext对象 ServletContext context = super.getServletContext(); // 获取用于读取指定静态文件的输入流 InputStream is = context.getResourceAsStream("/images/mypic.jpg"); // 获取一个映射到指定静态文件路径的URL URL url = context.getResource("/images/mypic.jpg"); // 从URL对象中获取文件的输入流 InputStream in = url.openStream(); // 比较使用上述两种方法获取同一文件输入流的大小 boolean isEqual = is.available() == in.available(); // 根据指定的文件虚拟路径获取真实路径 String fileRealPath = context.getRealPath("/images/mypic.jpg"); // 获取指定文件的MIME类型 String mimeType = context.getMimeType("/images/mypic.jpg"); // 获取输出流 PrintWriter out = response.getWriter(); out.println("

两种方式获取同一文件输入流的大小是否相等:" + isEqual + "

"); out.println("

虚拟路径"/images/mypic.jpg"的真实路径为:" + fileRealPath + "

"); out.println("

mypic.jpg的MIME类型为:" + mimeType + "

"); out.close(); } } 启动服务器,在浏览器中访问http://localhost:8080/ch03/ContextFileResourceServlet,运行结果如图318所示。 图318ContextFileResourceServlet运行结果 本章总结  Cookie是保存在客户端的小段文本。  通过请求可以获得Cookie,通过响应可以写入Cookie。  Session是浏览器与服务器之间的一次通话,它包含浏览器与服务器之间的多次请求、响应过程。  Session可以在用户访问一个Web站点的多个页面时共享信息。  在Servlet中通过request.getSession()获取当前Session对象。  关闭浏览器、调用Session的invalidate()方法或者等待Session超时都可以使Session失效。  HttpSession使用getAttribute()和setAttribute()方法读写数据。  ServletContext是运行Servlet的容器。  在Servlet中可以通过getServletContext()方法获取ServletContext实例。  ServletContext使用getAttribute()和setAttribute()方法读写数据。 本章习题 1. 下列关于Cookie的说法正确的是。(多选) A. Cookie保存在客户端B. Cookie可以被服务器端程序修改 C. Cookie中可以保存任意长度的文本D. 浏览器可以关闭Cookie功能 2. 写入和读取Cookie的代码分别是。 A. request.addCookies()和response.getCookies() B. response.addCookie()和request.getCookie() C. response.addCookies()和request.getCookies() D. response.addCookie()和request.getCookies() 3. HttpServletRequest的方法可以得到会话。(多选) A. getSession()B. getSession(boolean) C. getRequestSession()D. getHttpSession() 4. 下列选项可以关闭会话的是。(多选) A. 调用HttpSession的close()方法 B. 调用HttpSession的invalidate()方法 C. 等待HttpSession超时 D. 调用HttpServletRequest的getSession(false)方法 5. 在HttpSession中写入和读取数据的方法是。 A. setParameter()和getParameter()B. setAttribute()和getAttribute() C. addAttribute()和getAttribute()D. set()和get() 6. 关于HttpSession的getAttribute()方法和setAttribute()方法,说法正确的是。(多选) A. getAttribute()方法返回类型是String B. getAttribute()方法返回类型是Object C. setAttribute()方法保存数据时如果名字重复会抛出异常 D. setAttribute()方法保存数据时如果名字重复会覆盖以前的数据 7. 设置session的有效时间(也叫超时时间)的方法是。 A. setMaxinactivelnterval(int interval) B. getAttributeName() C. setAttrlbuteName(Strlng name,java.lang.Object value) D. getLastAccessedTime() 8. 使HttpSession失效的三种方式是、、。 9. 测试在其他浏览器下session的生命周期,如Firefox、chrome等。