一、会话
1、什么是会话?
会话可简单理解为:用户开一个浏览器,点击多个超链接,访问服务器多个web资源,然后关闭浏览器,整个过程称之为一个会话.类似打电话一样.
2、会话过程中要解决的一些问题?
每个用户在使用浏览器与服务器进行会话的过程中,不可避免各自会产生一些数据,服务器要想办法为每个用户保存这些数据.
例如:多个用户点击超链接通过一个servlet各自购买了一个商品,服务器应该想办法把每一个用户购买的商品保存在各自的地方,以便于这些用户点结帐servlet时,结帐servlet可以得到用户各自购买的商品为用户结帐.
提问:这些数据保存在request或servletContext中行不行?
二、保存会话数据的两种技术
1、Cookie
Cookie是客户端技术,服务器把每个用户的数据以cookie的形式写给用户各自的浏览器.当用户使用浏览器再去访问服务器中的web资源时,就会带着各自的数据去.这样,web资源处理的就是用户各自的数据了.
2、Session
Session是服务器端技术,利用这个技术,服务器在运行时可以为每一个用户的浏览器创建一个其独享的session对象,由于session为用户浏览器独享,所以用户在访问服务器的web资源时,可以把各自的数据放在各自的session中,当用户再去访问服务器中的其它web资源时,其它web资源再从用户各自的session中取出数据为用户服务.
三、Cookie
1、Cookie API
javax.servlet.http.Cookie类用于创建一个Cookie,response接口也中定义了一个addCookie方法,它用于在其响应头中增加一个相应的Set-Cookie头字段. 同样,request接口中也定义了一个getCookies方法,它用于获取客户端提交的Cookie.Cookie类的方法:
Cookie(String name,String value) //唯一的构造方法,cookie中只能存字符串
setValue与getValue方法
setMaxAge与getMaxAge方法 //设置cookie的有效时间,秒为单位
setPath与getPath方法 //设置cookie的有效路径,在该路径下访问会带cookie
setDomain与getDomain方法 //第三方cooie,不使用
getName方法
2、Cookie应用
1>显示用户上次访问时间
public class ServletTest extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//注意:resp写的时候是先写给自己,所以相当于可以组装后再输出
//先读取到当前cookie
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
out.print("上次访问时间:");
Cookie cookies[]=request.getCookies();
for(int i=0;cookies!=null && i<cookies.length;i++){
if(cookies[i].getName().equals("lastAccessTime")){
long cookieValue=Long.parseLong(cookies[i].getValue());
Date date=new Date(cookieValue);
out.print(date.toLocaleString());
}
}
//cookie中只能存字符串
//再将最新的时间返回给客户端
Cookie cookie=new Cookie("lastAccessTime",System.currentTimeMillis()+"");
cookie.setMaxAge(3600);// 以秒为单位
cookie.setPath("/CNMServlet");//默认是个cookie只能由创建它的web应用获得
response.addCookie(cookie);
}
}
2>通过setMaxAge置为0来删除当前Cookie
public class CookieTest extends HttpServlet{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
//cookie的value可以随意设置
Cookie cookie=new Cookie("lastAccessTime",System.currentTimeMillis()+"");
//setMaxAge如果不设置,那么保存时间和Session效果一样,浏览器进程关闭时消失,也就是会话级别.
cookie.setMaxAge(0);// 以秒为单位
cookie.setPath("/CNMServlet");//路径也要一样才能删除
resp.addCookie(cookie);
}
}
3、Cookie细节
1.一个Cookie只能标识一种信息,它至少含有一个标识该信息的名称(NAME)和设置值(VALUE).
2.一个WEB站点可以给一个WEB浏览器发送多个Cookie,一个WEB浏览器也可以存储多个WEB站点提供的Cookie.
3.浏览器一般只允许存放300个Cookie,每个站点最多存放20个Cookie,每个Cookie的大小限制为4KB.
4.如果创建了一个cookie,并将他发送到浏览器,默认情况下它是一个会话级别的cookie(即存储在浏览器的内存中),用户退出浏览器之后即被删除.若希望浏览器将该cookie存储在磁盘上,则需要使用maxAge,并给出一个以秒为单位的时间.将最大时效设为0则是命令浏览器删除该cookie.只限于同一种浏览器,各种浏览器管理各自的cookie.
5.注意,删除cookie时,path必须一致,否则不会删除.
4、cookie的经典案例:电商网站显示用户上次浏览过的商品
//首页显示最近浏览的商品
public class CookieDemo3 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
//输出所有商品
out.write("本站有如下商品:<br/>");
Map<String,Book>map=Db.getAll();
for(Map.Entry<String, Book> entry:map.entrySet()){
Book book=entry.getValue();
out.print("<a target=\"_blank\" href='/day07/servlet/cookieDemo4?id="
+book.getId()+"'>"+book.getName()+"</a><br/>");
}
//显示用户看过的商品
out.print("<br/>你曾经看过的商品<br/>");
Cookie cookies[]=request.getCookies();
for(int i=0;cookies!=null && i<cookies.length;i++){
if(cookies[i].getName().equals("bookHistory")){
//全部+//转义,这样不用判断符号在正则中是否已经转义
String ids[]=cookies[i].getValue().split("\\,");//2,3,1
for(String id:ids){
Book book=(Book) Db.getAll().get(id);//这里进行了检索
out.print("<a target=\"_blank\" href='/day07/servlet/cookieDemo4?id="
+book.getId()+"'>"+book.getName()+"</a><br/>");
}
}
}
}
}
//模拟数据库,因为有序且要检索,所以采用LinkedHashMap
class Db{
private static Map<String,Book> map=new LinkedHashMap<String,Book>();
static {
map.put("1", new Book("1","JavaWeb开发","老k","一本好书"));
map.put("2", new Book("2","jdbc开发","老张","一本好书"));
map.put("3", new Book("3","spring开发","老li","一本好书"));
map.put("4", new Book("4","struts开发","老张","一本好书"));
map.put("5", new Book("5","android开发","老bi","一本好书"));
}
public static Map getAll(){
return map;
}
}
class Book{
private String id;
private String name;
private String author;
private String description;
}
//商品详情页,在这里添加商品到Cookie
public class CookieDemo4 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//根据用户带过来的id,显示相应的详细信息
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
String id=request.getParameter("id");
Book book=(Book)Db.getAll().get(id);
out.write(book.getId()+"<br/>");
out.write(book.getName()+"<br/>");
out.write(book.getAuthor()+"<br/>");
out.write(book.getDescription()+"<br/>");
//2.构建cookie,回写给浏览器;
String cookieValue=buildCookie(id,request);
Cookie cookie=new Cookie("bookHistory",cookieValue);
cookie.setMaxAge(1*30*24*3600);//1 个月
cookie.setPath("/day07");
response.addCookie(cookie);
}
private String buildCookie(String id, HttpServletRequest request) {
//可能出现的4种情况
//bookHistory =null 1 1
//bookHistory=2,5,1 1 1,2,5
//bookHistory=2,5,4 1 1,2,5
//bookHistroy=2,5 1 1,2,5 // 假如列表最多3个
String bookHistroy=null;
Cookie cookies[]=request.getCookies();
for(int i=0;cookies!=null && i<cookies.length;i++){
if(cookies[i].getName().equals("bookHistory")){
bookHistroy=cookies[i].getValue();
}
}
if(bookHistroy==null)
return id;
//if(bookHistroy.contains(id))不能这样 21,23 也包括1
List<String> list=Arrays.asList(bookHistroy.split("\\,"));
LinkedList <String>linkedlist=new LinkedList<String>(list);
if(list.contains(id)){
linkedlist.remove(id);
linkedlist.addFirst(id);
}else{
if(list.size()>=3){
linkedlist.removeLast();
linkedlist.addFirst(id);
}else
linkedlist.addFirst(id);
}
StringBuffer sb=new StringBuffer();
for(String bid : linkedlist){
sb.append(bid+",");
}
return sb.deleteCharAt(sb.length()-1).toString();
}
}
ps:Cookie可以使用在最近浏览商品、购物车可以存在Cookie中、最近登录时间、多长时间自动登录、等.
四、Session
1、在WEB开发中,服务器可以为每个用户浏览器创建一个会话对象(session对象),注意:一个浏览器独占一个session对象(默认情况下).因此,在需要保存用户数据时,服务器程序可以把用户数据写到用户浏览器独占的session中,当用户使用浏览器访问其它程序时,其它程序可以从用户的session中取出该用户的数据,为用户服务.
2、Session和Cookie的主要区别在于:Cookie是把用户的数据写给用户的浏览器.Session技术把用户的数据写到用户独占的session中.
3、Session对象由服务器创建,开发人员可以调用request对象的getSession方法得到session对象.
4、最简单session实现一个购物
//用户买了一个洗衣机
public class SessionDemo1 extends HttpServlet{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp){
//如果有session则拿到,没有则创建
HttpSession session=req.getSession();
session.setAttribute("name", "洗衣机");
//session.setMaxInactiveInterval(3600);//设置session的有效时间,还可以在web.xml中设置 //session.invalidate();//手动摧毁session
} }
//再来一个请求完成结账
public class SessionDemo2 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=utf-8");
//加false,只会获取不会创建,性能更好,在用户直接访问的情况下不会创建session
HttpSession session=request.getSession(false);
//HttpSession session=request.getSession();
String name=(String) session.getAttribute("name");
PrintWriter out = response.getWriter();
if(name!=null)
out.write(name);
else
out.write("no buy");
}
}
session实现原理:用户首先访问getSession方法时,创建一个session,这时服务器会通过Cookie的形式回写给客户端一个sessionId,客户端再访问服务器时,会带着这个Cookie过来,服务器根据这个ID可以找到客户端的session.这种情况下session默认的时间也就是和cookie一样,会话级别,如果想在浏览器关闭以后再次打开还能找到之前的Session,可以自己回写Cookie,设置Cookie的MaxAge,精测这种方式也只能在同种浏览器才能实现,如果在不同浏览器之间,即使设置了MaxAge也无法找到之前的Session.
5、实现多个浏览器共享同一session,也就是关闭后再打开,Session还能找到.
public class SessionDemo1 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
HttpSession session=request.getSession();
session.setAttribute("name", "aaa");
String id=session.getId();
Cookie cookie=new Cookie("JSESSIONID",id);//必须和浏览器返回的那么一样
cookie.setPath("/CNMServlet/");//这个也必须和浏览器返回的一样
cookie.setMaxAge(30*60);//30 minutes
response.addCookie(cookie);
}
}
6、浏览器禁用Cookie后的session处理.解决方案:URL重写.将sessionId通过URL的方式传递,略.
response. encodeRedirectURL(java.lang.String url) 用于对sendRedirect方法后的url地址进行重写.
response. encodeURL(java.lang.String url) 用于对表单action和超链接的url地址进行重写.
五、session案例
1、实现一个购物车,实际使用cookie较多.
//首页,列出所有书 public class ListBookServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setCharacterEncoding("UTF-8"); response.setContentType("text/html;charset=UTF-8"); PrintWriter out = response.getWriter(); out.print("本站有如下商品<br/>"); Map<String, Book> map = Db.getAll(); for (Map.Entry<String, Book> entry : map.entrySet()) { Book book = entry.getValue(); String str = "<a target=\"_blank\" href='" + request.getContextPath() + "/servlet/buyServlet?id=" + book.getId() + "'>" + book.getName() + "购买" + "</a><br/>"; out.print(str); } System.out.println("a" + request.getContextPath() + "b"); } } class Db { private static Map<String, Book> map = new LinkedHashMap<String, Book>(); static { map.put("1", new Book("1", "JavaWeb开发", "老k", "一本好书")); map.put("2", new Book("2", "jdbc开发", "老张", "一本好书")); map.put("3", new Book("3", "spring开发", "老li", "一本好书")); map.put("4", new Book("4", "struts开发", "老张", "一本好书")); map.put("5", new Book("5", "android开发", "老bi", "一本好书")); } public static Map getAll() { return map; } } class Book { private String id; private String name; private String author; private String description; }
public class BuyServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("UTF-8");
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
String id=request.getParameter("id");
Book book=(Book)Db.getAll().get(id);
HttpSession session=request.getSession();
//用session中得到用户购买的商品集合
List <Book> list=(List)session.getAttribute("list");
if(list==null){
list=new LinkedList<Book>();
session.setAttribute("list", list);
}
list.add(book);
session.setAttribute("list",list);
response.sendRedirect(request.getContextPath()+"/servlet/listCartServlet");
}
}
public class ListCartServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
HttpSession session=request.getSession(false);
if(session==null){
out.write("您没有购买任何商品");return;
}
List<Book> list=(List) session.getAttribute("list");
out.write("你购买了如下商品");
for(Book book:list){
out.write(book.getName()+"<br/>");
}
}
}
2、session案例一次性校验码
一次性验证码的主要目的就是为了限制人们利用工具软件来暴力猜测密码.
服务器程序接收到表单数据后,首先判断用户是否填写了正确的验证码,只有该验证码与服务器端保存的验证码匹配时,服务器程序才开始正常的表单处理流程.密码猜测工具要逐一尝试每个密码的前题条件是先输入正确的验证码,而验证码是一次性有效的,这样基本上就阻断了密码猜测工具的自动地处理过程.
3、session案例防止表单重复提交(struts2底层实现原理)
1>js处理方式:
//访问提交表单页面时,服务器自动写入隐藏域token
public class FormServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//产生随机数(表单号)
TokenProcessor tp=TokenProcessor.getInstance();
String token=tp.generateToken();
request.getSession().setAttribute("token", token);
request.getRequestDispatcher("/form.jsp").forward(request, response);
}
}
//随机数生成器
class TokenProcessor{//令牌 单例
private TokenProcessor(){}
private static final TokenProcessor instance=new TokenProcessor();
public static TokenProcessor getInstance(){
return instance;
}
public String generateToken(){
//这个拿到的数据可能长度不一致
String token=System.currentTimeMillis()+new Random().nextInt()+"";
try {
//采用Mmd5算法获取数据摘要,也就是数据指纹,任何数据的指纹长度一样
MessageDigest md=MessageDigest.getInstance("md5");
byte []md5=md.digest(token.getBytes());
//这个地方必定要查码表,且肯定是乱码
//return new String(md5);
//base64编码,转换为明文字符串,键盘可见的
BASE64Encoder encoder=new BASE64Encoder();
return encoder.encode(md5);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
}
public class DoFormServlet extends HttpServlet {
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
String r_token = request.getParameter("token");
HttpSession session = request.getSession(false);
if (r_token != null && session != null && r_token.equalsIgnoreCase(
(String) session.getAttribute("token"))) {
request.getSession().removeAttribute("token");
System.out.println("向数据库写用户名");
} else {
System.out.println("重复提交");
}
}
}
六、三个域对象,独家总结
Request | 显示完就不用了 |
session | 显示完等下还要用,用户登录,验证码,防表单重复提交 |
servletContext | 显示完等下还要用,还要给别人用,如聊天室 |