会话及其会话技术
定义:服务器跟踪用户信息的技术称为会话技术
会话概述
当两个用户需要进行结账的时候,web服务器需要对用户甲和用户乙的信息进行保存,但是对于HttpServlet和HTTPServletResponse对象和ServletContext对象都可以对信息进行保存,但是两个对象都不可行。原因如下:
1、HTTPServletRequest对象对于每次的Http请求,web服务器都会创建一个HTTPServletRequest对象,该对象只能保存本次请求所传递的数据。由于购买和结账是两个不同的请求,因此,在发送结账请求时,之前购买的数据都会丢失。
2、使用ServletContext对象保存数据,但是对于web服务器共享的是同一个ServletContext对象,所以对于用户在发送结账请求时,由于无法区分哪些商品是哪个用户买的,而会将网站的用户购买的商品进行结算,所以显然是不可行的。
Cookie对象
定义:Cookie是一种会话技术,它用于将会话过程中的数据保存到用户的浏览器中,从而使浏览器和服务器可以更好的进行数据交互。
什么是Cookie?
在现实生活中,当顾客在购物时,商城经常会赠送顾客一张会员卡,卡上记录用户的个人信息(姓名、手机号等)、消费额度和积分额度等。顾客一旦接受了会员卡,以后每次光临该商场时,都可以使用这张会员卡,商场也将根据会员卡上的消费记录计算会员的优惠额度和累加积分。在Web应用中,Cookie的功能类似于这张会员卡,当用户通过浏览器访问Web服务器时,服务器会给客户端发送一一些信息, 这些信息都保存在Cookie 中。这样,当该浏览器再次访问服务器时,都会在请求头中将Cookie 发送给服务器,方便服务器对浏览器做出正确的响应。
服务器向客户端发送Cookie时,会在Http响应头字段中增加Set-Cookie响应头字段,其中遵循一定的格式:
Set-Cookie: user=itcast; Path=/;
//user代表Cookie的名称
//itcast代表user的值即Cookie的值
//Path代表Cookie的属性
//注意Cookie必须以键值对的形式存在,其中属性可以有多个,但是这些属性之间必须用分号和空格号分开
Cookie在浏览器和服务器之间的传输过程
Cookie API
为了对封装Cookie信息,在Servlet API中提供了一个javax.servlet.http.Cookie类,该类包含了生成Cookie信息和提取Cookie信息各个属性方法。Cookie的构造方法和常用方法具体如下。
1、构造方法
Cookie仅有一个构造方法
public Cookie (java.lang.String name ,java.lang.String value)
注意Cookie一旦创建,它的名称就不可以进行改变,Cookie的值可以为任何值,创建后允许更改。
2、Cookie类的常用方法
Cookie的常用方法
方法声明 | 功能描述 |
String getName() | 用于返回Cookie的名称 |
void setValue(String newValue) | 用于为Cookie设置一个新的值 |
String getValue() | 用于返回Cookie的值 |
void setMaxAge(int expiry) | 用于设置Cookie在浏览器客户机上保持有效的秒数 |
int getMaxAge() | 用于返回Cookie在浏览器客户机上保持有效的秒数 |
void setPath(String url) | 用于设置Cookie项的有效目录路径 |
String getPath() | 用于返回该Cookie项的有效目录路径 |
void setDomain(String pattern) | 用于设置Cookie项的有效域 |
String getDomain() | 用于返回Cookie项的有效域 |
void setVersion(int v) | 用于设置Cookie项采用的协议版本 |
int getVersion() | 用于返回Cookie项采用的协议版本 |
void setComment(String purpose) | 用于设置该Cookie项的注解部分 |
String getComment() | 用于返回该Cookie项的注解部分 |
void setSecure(boolean flag) | 用于设置该Cookie项是否只能使用安全的协议传送 |
boolean getSecure() | 用于返回该Cookie项是否只能使用安全的协议传送 |
Cookie的方法部分理解
setMaxAge(int expiry)和getMaxAge()方法
如果设置的值为负整数时,浏览器会将Cookie信息保存在浏览器中,当浏览器关闭时,Cookie会被删除。
如果是0时,则表示通知浏览器立即删除这个Cookie的信息。默认情况下,MaxAge的值为-1
setPath(String uri)和getPath()方法
如果创建的时候并没有对Cookie的Path进行设置,那么该Cookie只对当前访问路径所属的目录及其子目录有效。如果要让某个Cookie对站点的所有目录下的访问路径都有效,应调用Cookie对象的setpath(String uri)方法将其Path的属性设置为"/"
setDomain(String pattern)和getDomain()方法
domain属性是用来指定浏览器访问域。 例如,传智播客的域为"itcast.cn"。那么,当设置domain属性时,其值必须以"."开头。如domain=.itcast.cn。默认情况下,domain属性的值为当前主机名,浏览器在访问当前主机下的资源时,都会将Cookie信息回送服务器。并且注意domain属性不区分大小写。
Cookie应用例子–显示用户上次访问的时间
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class DisplayTime extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//指定服务器的解码方式
resp.setContentType("text/html;charset=utf-8");
//存储时间
String lastAccessTime = "lzy";
//获取所有的Cookie对象
Cookie[] cookies = req.getCookies();
//进行遍历
for (int i=0; cookies != null && i< cookies.length; i++){
if ("lastAccess".equals(cookies[i].getName())){
//如果Cookie的名称为lastAccessTime Access是访问
System.out.println("cookies[i]="+cookies[i]);
lastAccessTime = cookies[i].getValue();
break;
}
}
//判断是否存在名称为lassAccess的Cookie
if(lastAccessTime == "lzy" ){
//表示不存在Cookie
resp.getWriter().println("您是首次访问本站!");
}else{
//表示存在Cookie
resp.getWriter().println("您上次的访问时间:"+lastAccessTime);
}
System.out.println("lastAccessTime="+lastAccessTime);
//为每次访问添加Cookie值,即访问的时间
//创建Cookie将当前的时间发送给客户端
String currentTime = new SimpleDateFormat("yyyy-MM-ddhh:mm:ss").format(new Date());//不可空格
System.out.println("currentTime="+currentTime);
Cookie cookie = new Cookie("lastAccess",currentTime);
System.out.println("cookie="+cookie);
System.out.println(cookie.getMaxAge());
resp.addCookie(cookie);
}
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req,resp);
}
}
每次访问都是显示首次访问本站,刚开始以为是浏览器禁止了第三方Cookie。
debug后发现cookie中不能有无效字符32==对应Ascii表是空格
Session对象
Cookie对象可以实现在多次请求下实现数据的共享。但是,如果传递的信息比较多,使用Cookie技术显然会增大服务器端程序处理的难度,这时,便可以使用Session技术。Session是一种将会话数据保存到服务器的技术。对于每一个请求对象都会产生一个session对象,并且会产生一个与之对应的jsessionid,在进行返回的时候将jsessionid作为cookie进行返回。
什么是Session?
Session就好比在医院看病时,医院发给病人的就医卡和医院为每个病人保留病例档案的过程。当浏览器访问web服务器时,Servlet容器就会创建创建一个Session对象和ID属性,其中,Session对象就相当于病例档案,ID就相当于就诊卡号。当客户端后续访问服务器时,只要将标识号传递给服务器,服务器便可以判断出该请求是哪个客户端发送的,从而选择与之对应的Session对象及其服务。
需要注意的是由于客户端需要接收、记录、和回送Session对象的ID,因此,通常情况下,Session是借助Cookie技术来传递ID属性的。
以网站购物为例
在图中,用户甲和乙都调用buyServlet将商品添加到购物车中,调用payServlet进行商品结算。由于甲和乙购买商品的过程类似,在此,以用户甲为例进行详细说明。当用户甲访问购物网站时,服务器为甲创建了一个Session对象( 相当于购物车)。当甲将Nokia手机添加到购物车时,Nokia手机的信息便存放到了Session 对象中。同时,服务器将Session对象的ID属性以Cookie (Set- -Cookie: JSESSIONID= 111)的形式返回给甲的浏览器。当甲完成购物进行结账时,需要向服务器发送结账请求,这时,浏览器自动在请求消息头中将Cookie (Cookie:JSESSIONID=111)信息回送给服务器,服务器根据ID属性找到为用户甲所创建的Session对象,并将Sesion对象中所存放的Nokia手机信息取出进行结算。
Session保存用户信息的过程
HttpSession API
HttpServletRequest定义了用于获取Session对象的getSession()方法,该方法有两种重载形式。
public HttpSession getSession(boolean create)
public HttpSession getSession()
两个方法都是用于返回当前HttpSession对象。不同的是,第一个方法根据传递的参数来判断是否创建新的HttpSession对象,如果true则在相关HttpSession对象不存在时创建并返回新的HttpSession对象,否则不创建HttpSession对象,而是返回null。
第二个方法相当于第一个方法为true的情况,
但是需要注意的是getSession()方法可能会产生发送会话标识号的Cookie头字段,因此,必须在发送任何响应之前调用getSession()方法。
HttpSession接口中常用的方法
方法声明 | 功能描述 |
String getId() | 用于返回与当前HttpSession对象关联的会话标识号 |
long getCreationTime() | 返回Session创建的时间。该时间是距离1970年1月1日00:00:00之间的时间差的毫秒表示形式 |
long getLastAccessedTime() | 返回客户端最后一次与Session相关请求的时间。同上时间差 |
void setMaxInactiveInterval(int intterval) | 用于设置当前HttpSession对象可空闲的以秒为单位的最长时间,也就是修改当前会话的默认超时间间隔 |
boolean isNew() | 判断当前HttpSession对象是否是新创建的 |
void invalidate() | 用于强制使Session对象无效 |
ServletContext getServlet() | 用于返回当前HttpSession对象所属于的web应用程序对象 |
void setAttribute(String name,Object value) | 用于将一个对象与一个名称关联后储存到当前的HttpSession对象中 |
String getAttribute() | 用于从当前HttpSession对象中返回指定的名称的属性对象 |
void removeAttribute(String name) | 用于从当前HttpSession对象中删除指定名称的属性 |
Session超时管理
当客户端第一次访问某个能开启会话功能的资源时,web服务器会创建一个HttpSession对象。但是对于服务器端却不知道浏览器是否已经关闭,所以通过设置Session对象在web服务器中采用“超时限制”的办法判断客户端是否还在继续访问。如果一段时间内,客户端一直未请求,则会将HttpSession对象变成垃圾,等待垃圾回收器将其从内存中彻底清除。
可以在web.xml对会话的有效时间进行设置。其默认值由Servlet容器定义,在tomcat安装目录的\conf\web.xml中,其信息如下:
<session-config>
//默认为30分钟,如果为负数或者为0,则代表永不超时
//同时,除了等待其会话超时之外,还可以通过invalidate()方法进行强制使会话失效。
<session-timeout>30</session-timeout>
</session-config>
购物车案例
购物车的实现流程图
1.创建封装图书信息类
Book.java
package gouwuche;
import java.io.Serializable;
public class Book implements Serializable {
private static final long serivalizableUID = 1L;
private String id;
private String name;
public Book() {
}
public Book(String id, String name) {
this.id = id;
this.name = name;
}
public String getId() {
return id;
}
public void setId() {
this.id = id;
}
public String getName() {
return name;
}
public void getName(String name) {
this.name = name;
}
}
2.创建数据库模拟类
用于模拟保存所有图书的数据库
BookDB.java
package gouwuche;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
public class BookDB {
private static Map<String, Book> books = new LinkedHashMap<String, Book>();
//模拟数据库
static {
books.put("1",new Book("1","javaWeb开发"));
books.put("2",new Book("2","jdbc开发"));
books.put("3",new Book("3","java基础"));
books.put("4",new Book("4","struct开发"));
books.put("5",new Book("5","spring开发"));
}
//获取所有图书
public static Collection<Book> getAll() {
return books.values();
}
//根据指定的图书进行图书的名称的返回
public Book getBook(String id){
return books.get(id);
}
}
3.创建servlet–进行请求的处理
(1)创建ListBookServlet的Servlet类,该servlet进行显示所有可购买图书的列表通过点击“购买”链接便可以将指定的图书添加到购物车上。
ListBookServlet.java
package gouwuche.servletBuy;
import gouwuche.Book;
import gouwuche.BookDB;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Collection;
public class ListBookServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html;charset=utf-8");
PrintWriter out = resp.getWriter();
Collection<Book> books = BookDB.getAll();
out.write("本站提供的图书有:<br />");
for (Book book: books) {
String url = "/servletTest_war_exploded/gouwuche/servletBuy/PurchaseServlet?id="+ book.getId() ;
out.write(book.getName() + "<a href='"+ url + "'>点击购买</a><br />'");
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doPost(req, resp);
}
}
(2)、创建PurchaseServlet的Servlet类
PurchaseServlet.java
package gouwuche.servletBuy;
import gouwuche.Book;
import gouwuche.BookDB;
import javax.servlet.ServletException;
import javax.servlet.http.*;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class PurchaseServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//获取用户的购买的商品
String id = req.getParameter("id");
if (id == null) {
//如果id为null,重定向到ListBookServlet页面
String url = "/gouwuche/servletBuy/ListBookServlet";
resp.sendRedirect(url);
return;
}
//当是已经登录时
Book book = BookDB.getId(id);
//创建获得用户的Session对象
HttpSession session = req.getSession();
List<Book> cart = (List<Book>) session.getAttribute("cart");
if (cart == null ) {
//首次购买,为用户创建一个购物车(List模拟购物车)
cart = new ArrayList<Book>();
//将购物车存入Session对象
session.setAttribute("cart",cart);
}
//将商品放入购物车
cart.add(book);
//创建Cookie存放Session的标识号
Cookie cookie = new Cookie("JSESSION",session.getId());
//设置cookie在浏览器的缓存时间
cookie.setMaxAge(60 * 30);
//在gouwuche请求下都可以使用这个Cookie
cookie.setPath("/gouwuche");
resp.addCookie(cookie);
//重定向到购物车页面
String url = "/servletTest_war_exploded/gouwuche/servletBuy/CartServlet";
resp.sendRedirect(url);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doPost(req, resp);
}
}
(3)、创建CartServlet购物车-用于展示用户已经购买的图书列表
CartServlet.java
package gouwuche.servletBuy;
import gouwuche.Book;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;
public class CartServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html;charset=utf-8");
PrintWriter out = resp.getWriter();
//变量cart引用用户的购物车
List<Book> cart = null;
//使用一个变量标记用户是否购买商品-purFlag
boolean purFlag = true;
//获取用户的session
//false的意思是如果不存在session对象则session对象为空
HttpSession session = req.getSession(false);
if (session == null) {
purFlag = false;
} else {
//获取用户的购物车
//因为现在是在服务器上,并且购物车的数据可能过大,并且session存的数据大
cart = (List)session.getAttribute("cart");
if (cart == null) {
//如果购物车weikong
purFlag = false;
}
}
/*
如果purFlag为null的话表明用户并没有购买图书,重定向到购买页面
*/
if (!purFlag ) {
out.write("您没有购买任何图书<br />");
} else {
//否则显示用户购买的图书的信息
out.write("您购买的图书有:<br />");
double price = 0;
for (Book book: cart) {
out.write(book.getName() + "<br />");
}
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doPost(req, resp);
}
}
(4)、servlet映射
<!--============================购物车=========================================-->
<servlet>
<servlet-name>ListBookServlet</servlet-name>
<servlet-class>gouwuche.servletBuy.ListBookServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ListBookServlet</servlet-name>
<url-pattern>/gouwuche/servletBuy/ListBookServlet</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>PurchaseServlet</servlet-name>
<servlet-class>gouwuche.servletBuy.PurchaseServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>PurchaseServlet</servlet-name>
<url-pattern>/gouwuche/servletBuy/PurchaseServlet</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>CartServlet</servlet-name>
<servlet-class>gouwuche.servletBuy.CartServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>CartServlet</servlet-name>
<url-pattern>/gouwuche/servletBuy/CartServlet</url-pattern>
</servlet-mapping>
在List集合中允许出现重复的元素,所有元素以线性方式进行存储,可以通过索引来访问集合中指定的元素。List集合的元素的存储顺序和取出顺序一致。
利用URL进行重写session跟踪
原因:浏览器可以对cookie进行禁用。所以当进行请求的时候,无法在请求中带入cookie对象,从而无法对web服务器中的session保存的购买商品进行查找。从而造成商品的存储的丢失。
从而考虑到浏览器可能不支持cookie的情况,servlet规范中引入了URL重写机制来保存用户的会话信息。
所谓url重写,指的是将session的会话标识号以参数的形式附加在超链接的URL地址后面。对于Tomcat服务器来说,就是将session关键字作为参数名以及会话标识号的值作为参数值附加到URL地址的后面。
在HttpServletResponse接口中,定义了两个用于完成URL重写的方法。
1、encodeURL(String url):用于对超链接和form表单的action属性中设置的URL进行重写。
2、encodeRedirectURL(String url):用于对传递给HttpServletResponse.sendRedirect方法的URL进行重写。
对于上面的购物车代码进行修改
1、ListBookServlet的代码进行修改。对for循环里面的代码进行修改为
String url = "/servletTest_war_exploded/gouwuche/servletBuy/PurchaseServlet?id="+ book.getId();
HttpSession s = req.getSession();
System.out.println("session="+s.getId());
String newurl = resp.encodeRedirectURL(url);
System.out.println(newurl);
out.write(book.getName() + "<a href='"+ newurl + "'>点击购买</a><br />'");
2、对PurchaseServlet的代码修改
// resp.sendRedirect(url);
// 禁用cookie的重定向
String newurl = resp.encodeRedirectURL(url);
resp.sendRedirect(newurl);
结果如下:
注意:在进行浏览器的cookie禁用后,需要重启,否则无法在网页的源代码中看到分号后的jsessionid的内容。