会话及其会话技术

定义:服务器跟踪用户信息的技术称为会话技术

会话概述

当两个用户需要进行结账的时候,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必须以键值对的形式存在,其中属性可以有多个,但是这些属性之间必须用分号和空格号分开

java 配置会话保持 java会话技术_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表是空格

java 配置会话保持 java会话技术_java 配置会话保持_02

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手机信息取出进行结算。

java 配置会话保持 java会话技术_java 配置会话保持_03


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>

购物车案例

java 配置会话保持 java会话技术_java 配置会话保持_04


购物车的实现流程图

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>

java 配置会话保持 java会话技术_cookie_05

在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);

结果如下:

java 配置会话保持 java会话技术_cookie_06

注意:在进行浏览器的cookie禁用后,需要重启,否则无法在网页的源代码中看到分号后的jsessionid的内容。