总结:
以下几个案例难点在于:
a、什么时候需要创建session,什么时候只是读取(涉及getsession方法的参数),还需要了解一下getsession方法是如何实现的,为什么好像没有访问cookies就得到了session。
b、什么时候要向浏览器发送cookies,使用哪个Servlet向浏览器发送cookies。
(注:由于案例比较简单,更多是起示范作用,所以很少引入html页面和表单,很多时候是直接通过在浏览器地址栏输入URL来直接访问Servlet)。
案例1:(cookie案例)——显示用户上次访问时间
该案例要实现的是当用户访问某些web应用时,显示出上次的访问时间。
该案例只用到一个LastAccessServlet,并且不需要多余的页面(例如表单),结果也是直接通过字符流打印出来显示在浏览器上。通过localhost/chapter06/LastAccessServlet来进行访问。(其中chapter06该案例对应的应用的名称,而/LastAccessServlet会跳转到LastAccessServlet这个类)
接下来,request会获取cookies中name为lastAccessTime的cookie,首先request请求先获取cookies。这时候会有cookies为空和非空两种可能性。只有cookies非空我们才对它进行遍历,如果能找到name为LastAccessTime的cookie,那么我们就获取它的值,如果遍历完成还没找到,或者本身cookies就为空,这两种情况我们都归纳为lastAccessTime== null,在该条件下在浏览器返回“首次访问本站”的信息。否则就将LastAccessTime的值显示出来。
// LastAccessServlet类doGet方法中的部分代码,request代表HttpServletRequest请求。
String lastAccessTime = null;
Cookie[] cookies = request.getCookies();
for (int i=0;cookies != null && i<cookies.length;i++){
if("lastAccess".equals(cookies[i].getName())){
lastAccessTime = cookies[i].getValue();
break;
}
}
if(lastAccessTime == null){...//返回首次访问本站信息
}else{
...//返回上次访问时间
}
这一切结束后,再将lastAccess赋值为当前时间,然后通过HttpServletResponse对象将其添加到cookie中。
String currenttime = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date());
response.addCookie(cookie);
案例2(session案例)——实现购物车
这里的购物车只是一个比较简单的模型,没有涉及数据库的内容。用一个BookDB.java来模拟保存所有图书(定义为Book类)的数据库,说的直白一些,就是在BookDB里面定义了一个Map集合(之所以在这里使用集合而不使用数组,一是因为可以增删,长度不固定,二是这里保存的是键值对。键(id)在后续中用于实现查询功能。而在 purchaseServlet中,由于只需要遍历而不需要查询,所以使用的是ArrayList),键(id)的查询功能包括两个:一是根据指定的id来获得一个图书对象。这个功能本质上是通过Map集合的get(Object key)方法来实现,返回Object类型。二是获取所有的图书对象集合,这个功能通过Map集合的values()方法实现,返回Collection<V> 类型。这个集合里面的一个元素由序号和Book组成。
public class BookDB {
private static Map<String, Book> books = new LinkedHashMap<String, Book>();
static {
books.put("1", new Book("1", "javaweb开发"));
......
books.put("5", new Book("5", "spring开发"));
}
// 获得所有的图书
public static Collection<Book> getAll() {
return books.values();
}
// 根据指定的id获得图书
public static Book getBook(String id) {
return books.get(id);
}
}
不同的对象就好像象棋中不同的棋子(例如车马象),有不同的“走法”(方法)。我们当然都希望他们能够横竖甚至对角线都能走,但是做不到。 每个对象都有它们自己特定的方法这里除了request和response对象以外,还需要引入ListBookServlet、PurchaseServlet和CartServlet。
需要的Servlet的多少是根据我们的业务需求来决定的。

第一个需求就是访问某个Servlet(这里是
ListBookServlet)列出我们的所有“可购买书单”。上面说过,列出所有书单的功能Map的values()方法已经能够实现(甚至直接BookDB类就可以触发方法,而不需要实例化),而response对象能将其遍历的结果通过输出流显示到浏览器上。而ListBookServlet除了实现这两个基本的功能,还在输出结果时,在遍历的每个条目的后面添加一个购买链接(显示为“点击购买”字样)。一旦我们点击了链接,就会跳转到PurchaseServlet,同时还会带上一个id参数(这部分代码如下所示)。到这里,第一个Servlet(ListsBookServlet)的功能就结束了。
for (Book book : books) {
String url = "/chapter06/PurchaseServlet?id=" + book.getId();
out.write(book.getName() + "<a href='" + url
+ "'>点击购买</a><br>");
}
接着我们的PurchaseServlet出场了,他面对的是一个新的请求,这个请求是从ListBookServlet跳转而来,后面带有一个id参数。如果没有这个id,表示可能直接由浏览器输入localhost:/chapter06/PurchaseServlet发起的请求,这时候就需要重定向回到 ListBookServlet,这么做是确保我们的PurchaseServlet面对的请求带有id这个参数。
当存在id参数时,表明已经有用户点击购买。这时候通过getSession()方法(手动)获取或者创建一个HttpSession对象,这个session用于存放我们的信息。
request.getSession()方法等同于request.getSession(True),它意味着能够在Httpsession对象不存在时创建新的session对象。
注:这时候如果没有session服务器是需要创建session的。因为我们需要session来存放这个购买请求。所以我们使用的是getSession(True)而不是getSession(False)方法。
至于为什么来自客户端的request调用getSession方法就可以访问到服务端的session信息。我的理解是request(包装后的 HttpServlet对象)先查询自己的cookies,根据cookies中的session关联id获取到存放于服务器中的session对象。
session的关联号存放于cookies中,而cookies存放于浏览器端,在发起特定请求后可以理解为cookies信息被封装到 request对象中,所以通过request.getSession()方法可以获取cookies中的session关联号,再据此找到对应的session。
当我们在浏览器地址栏中直接访问cartServlet,想读取已购买数目信息,我们的服务器就需要读取session,所以在cartServlet之前,也即是PurchaseServlet这里就应该发送cookies到客户端了。
而在案例3中,我们会直接访问login.html,并且这个请求同样需要读取对应的 session,所以我们应该在login.html之前,也就是indexServlet这里,将我们的cookies发回给浏览器。只有这样,当我们直接访问时,才能够通过getSession()方法来找到对应的session对象。
session有两个比较常用的方法,一个是getAttribute(),用于返回session中指定名称的属性对象。另外一个是getID,用于返回与当前HttpSession对象关联的会话标识号。
在purchaseServlet中只需要一个ArrayList(而不用 Map)来存储cart(购物车)的值就可以了。
List<Book> cart = (List) session.getAttribute("cart");
if (cart == null) {
// 首次购买,为用户创建一个购物车(List集合模拟购物车)
cart = new ArrayList<Book>();
// 将购物城存入Session对象
session.setAttribute("cart", cart);
}
// 将商品放入购物车
cart.add(book);
然后创建cookie用于存放session的标识号。并且这时候其实purchaseServlet没有向用户显示任何的信息,只是进行了一些后台的操作,然后继续跳转到CartServlet。
CartServlet先获得用户的session.
HttpSession session = req.getSession(false);
这里使用 false表明如果当前没有session存在,那么session = null,而不会继续新建一个session对象。 最终是想要读取session中名为“cart”的ArrayList集合。并通过字符流将book对象的名字打印出来。
那么为什么不将CartServlet和purchaseServlet结合在一起呢。这里需要搞清楚这个案例的意义,在这个案例中,用户直接访问/ListBookServlet进行购买(当我们点击购买链接以后就在浏览器上显示我们已经购买了这本书),而直接访问cartServlet用于查看已经购买的书。想要重新购买新的书时,需要再次访问/ListBookServlet来进行购买。如果将PurchaseServlet和CartServlet结合在一起。就没有办法制作出不带session的访问请求了。而现在,在请求跳转到CartServlet前,已经将带有session标识号的cookie发送到客户端了,这时候无论是从哪里再跳转到Servlet,都可以访问到session了。在应用中跳转过去的可以,直接从浏览器地址栏访问的也可以。但是手动删除浏览器的cookies,那么再直接访问就访问不到session了。
案例3(session案例)——实现用户登录

回顾文章开头提出的两个难点:
a、什么时候创建session,什么时候只是读取(getsession方法)
b、什么时候要向浏览器发送cookies。
可以这样理解,如果访问时需要这些session信息,例如通过浏览器访问Login.html时要想成功登录,就需要读取session信息,那么就需要在这之前提前发送cookies。所以在这个案例中,我们是在indexServlet中发送cookies信息。而在前面的案例中,我们希望直接访问cartServlet来读取我们当前购买了什么书,那么在这个cartServlet之前就必须要将cookies传送完毕。
其实从流程图就可以看出来,loginservlet和logoutservlet都是会连接到我们的indexservlet的。loginservlet的作用是将表单的内容创建成为一个user对象保存到session中。而logoutservlet则是将我们的user对象从session中进行删除。然后indexservlet先从服务器中读取我们的session,如果有就登录成功,没有就跳转到登录页面。并且只有当存在对象的时候,才会将我们的session使用 getid的方法保存到cookie中。
能不能在loginServlet中发送cookies?
loginservlet还能通过“注销”按钮跳转到logoutservlet,当我们登录跳转到loginservlet,如果用户信息验证通过就发送cookies的话,这时候再注销,session里面的对象就没有了,但是cookies却已经发出去了。那么我们再次访问时,cookies里面的内容就对不上了。这样的逻辑容易混乱。
能不能实现用户自动登录功能?
这里只是实现了用户登录功能,并没有实现用户自动登录功能。原因在于该案例中用户想要登录时都必须经过Login.html,然后再经过LoginServlet验证。也就是说,虽然我们的indexServlet已经将cookies发送到浏览器端。但是用户想登录还是要老老实实通过Login.html,如果能够将任何一个请求都绕过Login.html,直接跳转到LoginServlet进行验证,如果通过的话不就能实现自动登录了吗?Filter就可以用来实现这样的功能。
在Filter实现用户自动登录的案例中,可以看到,AutoLoginFilter中同样有LoginServlet中一样的用于验证用户信息的代码(其实就相当于这里提出的跳转到LoginServlet的思路)。另外,Filter案例中没有IndexServlet,所以发送cookie的代码在loginServlet和LogoutServlet中都要有。