JavaWeb_Cookie与Session

会话技术介绍

什么是会话?
  • 会话可简单理解为:用户开一个浏览器,点击多个超链接,访问服务器多个web资源,然后关闭浏览器,整个过程称之为一个会话。
会话过程中要解决的一些问题?
  • 每个用户在使用浏览器与服务器进行会话的过程中,不可避免各自会产生一些数据,服务器要想办法为每个用户保存这些数据。
  • 例如:多个用户点击超链接通过一个servlet各自购买了一个商品,服务器应该想办法把每一个用户购买的商品保存在各自的地方,以便于这些用户点结帐servlet时,结帐servlet可以得到用户各自购买的商品为用户结帐。
  • 提问:这些数据保存在request或servletContext中行不行?
    • 用户点结账时会产生一个全新的request对象,里面拿不到购买的商品。如果使用forward从购买servlet转发到结账的servlet,虽然只有一个request对象,但是用户的体验会很差,一购买就要结账,而用户需要的是买完了一起结账 
    • servletContext是一个全局性容器,会被所有的用户共享,一个用户将买的商品存入servletContext中,还没有结账,另外一个用户也点击购买,会将前一个用户购买的商品覆盖,前一个用户结账的时候就不是他所购买的商品了。

保存会话数据的两种技术

  • Cookie
    • Cookie是客户端技术(用户的数据存在客户端),服务器把每个用户的数据以cookie的形式写给用户各自的浏览器。当用户使用浏览器再去访问服务器中的web资源时,就会带着各自的数据去。这样,web资源处理的就是用户各自的数据了。

  • Session
    • Session是服务器端技术(用户数据存在服务器),利用这个技术,服务器在运行时可以为每一个用户的浏览器创建一个其独享的session对象,由于session为用户浏览器独享,所以用户在访问服务器的web资源时,可以把各自的数据放在各自的session中,当用户再去访问服务器中的其它web资源时,其它web资源再从用户各自的session中取出数据为用户服务。

Cookie对象

Cookie类及方法

javax.servlet.http.Cookie类用于创建一个Cookie,response接口也中定义了一个addCookie方法,它用于在其响应头中增加一个相应的Set-Cookie头字段,这样服务器创建的cookie就打给了浏览器。 同样,request接口中也定义了一个getCookies方法,它用于获取客户端提交的Cookie。Cookie类的方法: 
public Cookie(String name,String value)
setValue与getValue方法 
setMaxAge与getMaxAge方法
  • 设置Cookie的有效期。以秒为单位
  • 如果不调用setMaxAge方法设置Cookie的有效期,那么该Cookie的有效期就是浏览器进程,浏览器关闭,Cookie消失。如果调用该方法,那么Cookie会存到本地硬盘
  • 应用:用户登录状态保持多长时间
setPath与getPath方法
  • 设置cookie的有效目录,如果跟/day06,那么浏览器访问该目录下的资源时就会带着cookie去访问
  • 不调用该方法,默认的有效目录就是发送该Cookie的servlet所在目录
setDomain与getDomain方法
  • 设置域(.sina.com),意味着访问该域名时会带着Cookie过去
  • 也叫第三方Cookie,ie浏览器默认禁止该Cookie,因为可以以此攻击其他网站。实际开发中不使用,因为IE浏览器会拒收该Cookie
getName方法 

Cookie应用-显示用户上次访问时间

		response.setContentType("text/html;charset=UTF-8");
		PrintWriter out = response.getWriter();
		out.println("你好,上次访问时间是:<br/>");//浏览器换行符
		
		//获得用户的时间cookie
		Cookie[] cookies = request.getCookies();
		for(int i=0;cookies!=null&&i<cookies.length;i++){//用户第一次访问没有cookie,不会显示上次访问时间,所以需要判断
			if(cookies[i].getName().equals("lastAccessTime")){
				Long cookieValue = Long.parseLong(cookies[i].getValue());//得到用户上次访问时间
				Date date = new Date(cookieValue);//把毫秒值转成时间值
				out.print(date.toLocaleString());//以本地时间格式输出,可以使用Calendar替换
			}	
		}
		
		//给用户回送最新的访问时间
		Cookie cookie = new Cookie("lastAccessTime",System.currentTimeMillis()+"");
		cookie.setMaxAge(1*30*24*3600);//设置有效期为一个月
		cookie.setPath("/day05");//设置有效路径
		response.addCookie(cookie);//将Cookie回写给浏览器

Cookie细节

一个Cookie只能标识一种信息,它至少含有一个标识该信息的名称(NAME)和设置值(VALUE)。 
一个WEB站点可以给一个WEB浏览器发送多个Cookie,一个WEB浏览器也可以存储多个WEB站点提供的Cookie。
浏览器一般只允许存放300个Cookie,每个站点最多存放20个Cookie,每个Cookie的大小限制为4KB。
如果创建了一个cookie,并将他发送到浏览器,默认情况下它是一个会话级别的cookie(即存储在浏览器的内存中),用户退出浏览器之后即被删除。若希望浏览器将该cookie存储在磁盘上,则需要使用maxAge,并给出一个以秒为单位的时间。 将最大时效设为0则是命令浏览器删除该cookie
注意,删除cookie时,path必须一致,否则不会删除。实际开发中JavaScript也可以做到这一点。
需求:用户点击超链接清除上次访问时间。代码如下:
		response.setContentType("text/html;charset=UTF-8");
		PrintWriter out = response.getWriter();
		out.print("<a href='/day05/servlet/DeleteCookieServlet'>清除上次访问时间</a></br>");
		out.println("你好,上次访问时间是:<br/>");//浏览器换行符
		
		//获得用户的时间cookie
		Cookie[] cookies = request.getCookies();
		for(int i=0;cookies!=null&&i<cookies.length;i++){//用户第一次访问没有cookie,不会显示上次访问时间,所以需要判断
			if(cookies[i].getName().equals("lastAccessTime")){
				Long cookieValue = Long.parseLong(cookies[i].getValue());//得到用户上次访问时间
				Date date = new Date(cookieValue);//把毫秒值转成时间值
				out.print(date.toLocaleString());//以本地时间格式输出,可以使用Calendar替换
			}	
		}
		
		//给用户回送最新的访问时间
		Cookie cookie = new Cookie("lastAccessTime",System.currentTimeMillis()+"");
		cookie.setMaxAge(1*30*24*3600);//设置有效期为一个月
		cookie.setPath("/day05");//设置有效路径
		response.addCookie(cookie);
DeleteCookieServlet代码:
		//要清除指定的cookie,所以名称要和该cookie的一致。
		Cookie cookie = new Cookie("lastAccessTime",System.currentTimeMillis()+"" );
		cookie.setMaxAge(0);//删除该Cookie
		cookie.setPath("/day05");//设置有效路径
		response.addCookie(cookie);
		//删除完成之后浏览器重定向到原页面,此时会重新创建一个cookie
		response.sendRedirect("/day05/servlet/ServletDemo2");

Cookie案例-显示商品浏览历史纪录

网站首页由两部分组成。一部分显示所有商品,另一部分显示曾经浏览过的商品
用户浏览一件商品之后创建一个Cookie记录该商品,然后再浏览另一件商品时不会创建新的Cookie,而是在用户原来的Cookie后面加上新的值回写。
开发难点:列表需要限制大小,列表顺序体现最近的商品排在前面。
显示上次浏览商品的实现过程:

代表首页的Servlet类中的代码:
public class CookieDemo3 extends HttpServlet {
	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		
		//处理中文乱码。以后有过滤器技术来实现,一个过滤器解决整个网站的乱码。不需要每个Servlet都写
		response.setCharacterEncoding("utf-8");
		response.setContentType("text/html;charset=utf-8");
		PrintWriter out = response.getWriter();
		
		//1.输出网站所有商品
		out.write("本网站有如下商品:<br/>");//实际需要连接一个数据库,这里用一个类Db来模拟数据库
		Map<String, Book> map = Db.getAll();
		for(Map.Entry<String, Book> entry : map.entrySet()){//迭代商品集合
			Book book = entry.getValue();
			//输出显示书本的超链接,注意加上概述的ID。target表示在新窗口打开。断字符串:"++"
			out.print("<a href='/day05/servlet/CookieDemo4?id="+book.getId()+"' target=\"_blank\">"+book.getName()+"</a><br/>");
		}
		
		//2.显示用户曾经看过的商品
		out.print("您曾经看过如下商品:<br/>");
		Cookie[] cookies = request.getCookies();
		for(int i=0;cookies!=null&&i<cookies.length;i++){
			if(cookies[i].getName().equals("bookHistory")){
				String idString = cookies[i].getValue();//获取该cookie的值:2,3,1
				String[] ids = idString.split("\\,");//这样写可以确保逗号不论是否在正则表达式中有没有定义,都可以以该逗号分隔。","就需要查正则表达式中是否定义了该逗号
				for(String id:ids){
					Book book = (Book)Db.getAll().get(id);
					out.print(book.getName()+"<br/>");
				}
			}
		}
	}

	public void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
	}

}

//模拟数据库的类
class Db{
	//创建容器原则:如果有检索数据的需求,通通使用双列的容器
	private static Map<String, Book> map = new LinkedHashMap();//需要存入与取出的顺序一致,就使用带链表的数据结构保证有序
	//希望此类一初始化就有一系列的书,所以初始化的代码应写在静态代码块中,而且容器也是静态的才能被访问
	static{
		map.put("1", new Book("1", "JavaWeb开发", "老张", "一本好书"));
		map.put("2", new Book("2", "JDBC开发", "老张", "一本好书"));
		map.put("3", new Book("3", "Spring开发", "老黎", "一本好书"));
		map.put("4", new Book("4", "Struts开发", "老毕", "一本好书"));
		map.put("5", new Book("5", "Android开发", "老黎", "一本好书"));
	}
	//得到所有书的方法
	public static Map<String, Book> getAll(){
		return map;
	} 
}

//书类
class Book{
	private String id;
	private String name;
	private String author;
	private String description;
	//为了new Book方便,编写如下构造函数
	public Book(String id, String name, String author, String description) {
		super();
		this.id = id;
		this.name = name;
		this.author = author;
		this.description = description;
	}
	//创建无参构造函数
	public Book() {
		super();
	}
	public String getId() {
		return id;
	}
	public void setId(String id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getAuthor() {
		return author;
	}
	public void setAuthor(String author) {
		this.author = author;
	}
	public String getDescription() {
		return description;
	}
	public void setDescription(String description) {
		this.description = description;
	}
}
根据ID显示商品详细信息的Servlet代码:
public class CookieDemo4 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();
		
		//1.根据用户带过来的ID,显示商品的详细信息
		String id = request.getParameter("id");//取出带过来的数据。getAttribute是取域中存储的数据
		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,回写给浏览器。
		//构建新cookie的值,操作复杂,需要单独封装为一个方法。
		String cookieValue = buildCookie(id,request);//需要书的id和request中原有的cookie值
		Cookie cookie = new Cookie("bookHistory",cookieValue);
		cookie.setMaxAge(1*30*24*3600);//有效期设置为1个月
		cookie.setPath("/day05");
		response.addCookie(cookie);
	}

	private String buildCookie(String id, HttpServletRequest request) {
		String bookHistory=null;//定义变量记住用户带过来的Cookie的值
		Cookie[] cookies = request.getCookies();
		for(int i=0;cookies!=null&&i<cookies.length;i++){
			if(cookies[i].getName().equals("bookHistory")){
				bookHistory = cookies[i].getValue();
			}
		}
		
		//第一种情况,用户的cookie中没有值,即第一次浏览一件商品,bookHistory会一直为null
		//bookHistory=null   id:1    返回值:1
		if(bookHistory==null){
			return id;
		}
		
		LinkedList<String> list = new LinkedList<String>(Arrays.asList(bookHistory.split("\\,")));//将数组转成链表集合,方便增删改查
		/*原始代码,未优化。
		if(list.contains(id)){
			//bookHistory=2,5,1  id:1    返回值:1,2,5
			System.out.println(list.remove(id));
			list.addFirst(id);
		}else{
			//bookHistory=2,5,4  id:1    返回值:1,2,5
			if(list.size()>=3){//如果列表大小超出限制
				list.removeLast();
				list.addFirst(id);
			}else{
				//bookHistory=2,5    id:1    返回值:1,2,5
				list.addFirst(id);
			}
		}*/
	        //发现条件里都有addFirst,就将该代码提取至最后,保证必须执行
		if(list.contains(id)){
			list.remove(id);
		}else{
			if(list.size()>=3){
				list.removeLast();
  			}
		}
		list.addFirst(id);
		
		//将list集合构建成一个字符串返回
		StringBuilder sb = new StringBuilder();
		for(String newId:list){
			sb.append(newId+",");
		}
		return sb.deleteCharAt(sb.length()-1).toString();
	}

	public void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
	
	}
}

Session对象

介绍

  • 在WEB开发中,服务器可以为每个用户浏览器创建一个会话对象(session对象),注意:一个浏览器独占一个session对象(默认情况下)。因此,在需要保存用户数据时,服务器程序可以把用户数据写到用户浏览器独占的session中,当用户使用浏览器访问其它程序时,其它程序可以从用户的session中取出该用户的数据,为用户服务。
  • 一个Session只为一个会话服务。如果用户再开一个浏览器窗口,该Session就会失效。
  • Session和Cookie的主要区别在于:
    • Cookie是把用户的数据写给用户的浏览器。
    • Session技术把用户的数据写到用户独占的session中。
  • Session对象由服务器创建,开发人员可以调用request对象的getSession方法得到session对象。

Session的生命周期

  • 浏览器访问服务器,服务器第一次调用request的getSession方法时才会创建Session对象。而不是用户一进网站就会创建
  • 关闭浏览器时session对象不会被摧毁,而是30分钟无人使用服务器才会摧毁该session对象(即使浏览器不关闭)。session对象由服务器管理

配置session的摧毁时间

可以在web应用的web.xml文件中配置,单位为分钟:
	<session-config>
		<session-timeout>10</session-timeout>
	</session-config>
也可以在代码中摧毁session
		session.invalidate();

两种getSession方法的区别

  • request.getSession();
    • 得到用户的session对象,类型是HttpSession。第一次执行会创建session对象
  • request.getSession(false);
    • 不创建session,只获取session。
    • 应用:在显示购物车的servlet中使用,性能比第一种更好。例如用户将商品加入购物车调用getSeesion()方法创建了session,查看购物车时就只需调用getSession(false)获取。而且当用户没有选择任何商品就查看购物车,此时没有必要创建session。

Session的实现原理

疑问:服务器是如何实现一个session为一个用户浏览器服务的?
创建Session时服务器会将Session的ID号以cookie的形式回写给浏览器,浏览器下次访问服务器时会带着该Cookie找到对应的Session对象。
浏览器第一次访问服务器的中getSession方法时没有带ID号,所以服务器就会创建Session对象。
Session是基于Cookie的

实现多个IE浏览器共享同一session

服务器在getSession方法内部以Cookie形式回写Session的ID给浏览器时是没有写有效期的,也就是说该session是浏览器进程,用户关闭浏览器该Cookie就会消失,实际中购买的商品就消失了。
问题:如何实现多个IE浏览器共享同一session?(应用:关掉IE后,再开IE,上次购买的商品还在。或者多个浏览器窗口共享同一个session)
可以通过设置回写cookie的有效期来解决该问题。

示例代码

首页:
  <body>
  <!-- 实际开发中超链接地址不会写死 -->
    <a href="/day05/servlet/SessionDemo1">购买</a>
    <a href="/day05/servlet/SessionDemo2">结账</a>
  </body>
购买Servlet:
	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		//得到用户的session对象,类型是HttpSession。第一次执行会创建session对象,并且方法内部以cookie回写了sessionID
		HttpSession session = request.getSession();
		
		//不创建session,只获取session。
		//应用:在显示购物车的servlet中使用,性能更好。例如用户将商品加入购物车即创建了session,查看购物车时就只需要获取。当用户没有选择任何商品就查看购物车,此时没有必要创建session
		//request.getSession(false);
		
		//获取回写的session的id号
		String sessionid = session.getId();
		//创建一个新的Cookie,与回写的cookie同名
		Cookie cookie = new Cookie("JSESSIONID",sessionid);
		//设置新cookie的有效路径与回写的cookie相同
		cookie.setPath("/day05");
		//设置新cookie的有效期为30分钟,因为默认session对象也会在30分钟后摧毁
		cookie.setMaxAge(30*60);
		//回写新的cookie,覆盖掉原有的回写的cookie
		response.addCookie(cookie);
		
		session.setAttribute("name", "洗衣机");
		//session.invalidate();
	}
结账servlet:
public class SessionDemo2 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();
		String product = (String) session.getAttribute("name");
		out.write("您购买的商品是:"+product);
	}

	public void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
	}
}

禁用Cookie的解决方案-URL重写

用户浏览器将Cookie禁止了(Internet选项-->隐私-->高级-->替换自动cookie:第一方 Cookie和第三方 Cookie均勾选“阻止”),服务器回送的cookie就不会被浏览器接受
用户访问网站的回发的该网站的cookie是第一方cookie,网站回发的其他网站的cookie是第三方cookie
浏览器对localhost不阻止cookie的接受,做实验应改为127.0.0.1
解决方案为URL重写技术:用户访问网站首页时就创建session,然后将session的id号写在每个超链接后。
  • response. encodeRedirectURL(java.lang.String url) 
    • 用于对sendRedirect方法后的url地址进行重写。
  • response. encodeURL(java.lang.String url)
    • 用于对表单action和超链接的url地址进行重写 
输出网站首页的Servlet:
		response.setCharacterEncoding("utf-8");
		response.setContentType("text/html;charset=utf-8");
		PrintWriter out = response.getWriter();
		
		//首页一访问就创建或者获取一个session
		request.getSession();
		//实现URL重写,encodeURL内部自动检查浏览器是否禁用cookie,如果禁用了就在URL后加上Session的ID号。
		String url1 = response.encodeURL("/day05/servlet/SessionDemo1");
		String url2 = response.encodeURL("/day05/servlet/SessionDemo2");
		out.print("<a href='"+url1+"'>购买</a>    ");
		out.print("<a href='"+url2+"'>结账</a>");
细节:
浏览器第一次访问时服务器是不知道浏览器的Cookie是否被禁用的,所以服务器就会既产生cookie又重写url。如果浏览器的cookie没有被禁用,当浏览器再次访问服务器时(比如刷新浏览器)就会带着cookie访问,服务器的重写URL方法内部会自动进行判断,就不会重写URL。
禁用cookie之后浏览器关闭再次打开之前的商品记录会消失,该问题没有解决方案!!

共享session的细节

点击链接打开新窗口,在原窗口中新开一个选项卡会使用同一个session。只有新开一个新窗口才会使用另一个session
但是IE8默认两个浏览器窗口公用同一个session

Session案例

使用Session完成简单的购物功能

实际开发中用cookie做的多,因为考虑到服务器的压力(大型电子商务网站)。用户量不大时可用session实现
//列出网站所有商品servlet
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();
		
		//如果浏览器禁用了cookie,那么web应用中所有的URL都需要重写
		request.getSession();//获取用户的session
		
		out.print("本网站有如下商品:<br/>");
		Map<String, Book> map = Db.getAll();
		for(Map.Entry<String, Book> entry : map.entrySet()){
			Book book = entry.getValue();
			//重写URL
			String url = response.encodeURL("/day05/servlet/BuyServlet?id="+book.getId());
			out.write(book.getName()+"<a href='"+url+"' target='_blank'>购买</a><br/>");
		}
	}
	public void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
	}
}

//模拟数据库的类
class Db{
	//创建容器原则:如果有检索数据的需求,通通使用双列的容器
	private static Map<String, Book> map = new LinkedHashMap();//需要存入与取出的顺序一致,就使用带链表的数据结构保证有序
	//希望此类一初始化就有一系列的书,所以初始化的代码应写在静态代码块中,而且容器也是静态的才能被访问
	static{
		map.put("1", new Book("1", "JavaWeb开发", "老张", "一本好书"));
		map.put("2", new Book("2", "JDBC开发", "老张", "一本好书"));
		map.put("3", new Book("3", "Spring开发", "老黎", "一本好书"));
		map.put("4", new Book("4", "Struts开发", "老毕", "一本好书"));
		map.put("5", new Book("5", "Android开发", "老黎", "一本好书"));
	}
	//得到所有书的方法
	public static Map<String, Book> getAll(){
		return map;
	} 
}

//书类,必须实现Serializable接口,servlet监听器会讲到
class Book implements Serializable{
	private String id;
	private String name;
	private String author;
	private String description;
	//为了new Book方便,编写如下构造函数
	public Book(String id, String name, String author, String description) {
		super();
		this.id = id;
		this.name = name;
		this.author = author;
		this.description = description;
	}
	//创建无参构造函数
	public Book() {
		super();
	}
	public String getId() {
		return id;
	}
	public void setId(String id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getAuthor() {
		return author;
	}
	public void setAuthor(String author) {
		this.author = author;
	}
	public String getDescription() {
		return description;
	}
	public void setDescription(String description) {
		this.description = description;
	}
}
购买商品Servlet:
//购买servlet
public class BuyServlet extends HttpServlet {
	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		String id = request.getParameter("id");
		Book book =(Book)Db.getAll().get(id);
		
		//保存用户的购买数据到Session
		HttpSession session = request.getSession(false);
		//手工以cookie发sessionid,确保浏览器关闭后记录还存在
		String sessionid = session.getId();
		Cookie cookie = new Cookie("JSESSIONID",sessionid);
		cookie.setMaxAge(30*60);
		cookie.setPath("/day05");
		response.addCookie(cookie);
		
		//通常把用户的购买数据存储到一个集合中,再把集合存储在session中,而不是直接把数据存储在session中
		//从session中得到用户用于保存所有书的集合(用户的购物车。实际开发中会专门写一个Cart类来保存用户购买的商品)
		List list = (List) session.getAttribute("list");
		if(list==null){//如果用户还没有选过商品,那么集合为空,就需要创建集合并添加至session
			list = new ArrayList();
			//list.add(book);//此句不需要,执行在下一句
			session.setAttribute("list", list);
		}
		//如果list存在了,就直接添加
		list.add(book);
		
		//用户点购买跳转到购物车显示页面,这里使用重定向技术,如果使用转发,点刷新会再次执行购买操作。
		//request.getRequestDispatcher("/servlet/ListCartServlet").forward(request, response);
		//使用重定向,是给浏览器使用,所以url应加上web应用名称,并且url不要写死,因为应用发布后会更改名称
		//对于重定向技术,重写URL需要使用以下方法
		String url = response.encodeRedirectURL(request.getContextPath()+"/servlet/ListCartServlet");
		response.sendRedirect(url);
	}
	public void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
	}
}
显示用户购买的商品Servlet:
//显示用户购买的商品
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();
		
		//获取session中存储商品的list集合,注意加false,确保没有商品时不会创建session对象
		HttpSession session = request.getSession(false);
		if(session==null){
			out.write("您没有购买任何商品!");
			return;
		}
		out.write("您购买了如下商品:<br/>");
		List<Book> list = (List) session.getAttribute("list");
		for(Book book:list){
			out.write(book.getName()+"<br/>");
		}
	}
	
	public void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
	}
}

使用Session完成用户登陆

用户登陆成功的状态需要保存在session中,这样其他的servlet就能知道用户登录状态了。
首页代码:
  <body>
   	<!-- 一个EI表达式,取出session域中的user对象 -->
  	欢迎您:${user.username }  <a href="/day05/login.html">登陆</a>  <a href="/day05/servlet/LogoutServlet">退出登陆</a>
  	<br/>
  <!-- 实际开发中超链接地址不会写死 -->
    <a href="/day05/servlet/SessionDemo1">购买</a>
    <a href="/day05/servlet/SessionDemo2">结账</a>
  </body>
登陆页代码:
  <body>
  	<!-- 注意表单一定要是post方式,get方式用户名和密码会显示 -->
    <form action="/day05/servlet/LoginServlet" method="post">
    	用户名:<input type="text" name="username"><br/>
    	密码:<input type="password" name="password"><br/>
    	<input type="submit" value="提交">
    </form>
  </body>
处理登录Servlet
public class LoginServlet 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();
		
		//获取表单提交的用户名和密码
		String username = request.getParameter("username");
		String password = request.getParameter("password");
		
		//查询数据库
		List<User> list = DB.getAll();
		for(User user:list){
			if(user.getUsername().equals(username)&&user.getPassword().equals(password)){
				request.getSession().setAttribute("user", user);//用户登陆成功,就在session中存入登陆标记,就是把用户对象存入
				response.sendRedirect("/day05/index.jsp");//重定向用户到首页
				return;
			}
		}
		out.write("用户名或密码不对!");//实际开发是跳转到jsp显示的
	}

	public void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		doGet(request,response);
	}
}

//用一个类来模拟数据库
class DB{
	public static List<User> list = new ArrayList<User>();
	//初始化数据库中的用户
	static{
		list.add(new User("aaa","123"));
		list.add(new User("bbb","123"));
		list.add(new User("ccc","123"));
	}
	//得到所有用户方法
	public static List<User> getAll(){
		return list;
	}
}
用户类,必须是public的,否则会出现异常:Property 'username' not readable on type java.lang.String。类没有使用public修饰,造成该类的属性username无法读取而报错
public class User{
	private String username;
	private String password;
	public User() {
		super();
	}
	public User(String username, String password) {
		super();
		this.username = username;
		this.password = password;
	}
	public String getUsername() {
		return username;
	}
	public void setUsername(String username) {
		this.username = username;
	}
	public String getPassword() {
		return password;
	}
	public void setPassword(String password) {
		this.password = password;
	}
}
完成用户注销servlet
public class LogoutServlet extends HttpServlet {
	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		HttpSession session = request.getSession(false);
		if(session==null){//用户本来没登陆,点击注销,还是跳回首页
			response.sendRedirect("/day05/index.jsp");
			return;
		}
		
		session.removeAttribute("user");//移除登陆标记
		response.sendRedirect("/day05/index.jsp");//还是跳回首页,这里不要跳回登陆页面
	}
	public void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
	}
}

利用Session防止表单重复提交

问题
用户在服务器延迟响应的时间内重复点击表单提交按钮,会多次向服务器发送相同请求。
表单页面:
  <body>
    <form action="/day05/servlet/DoFormServlet" method="post">
    	用户名:<input type="text" name="username"><br>
    	<input type="submit" value="提交">
    </form>
  </body>
处理表单提交servlet:
		String username = request.getParameter("username");
		try {
			Thread.sleep(1000);//模拟服务器延迟响应
		} catch (InterruptedException e) {}
		System.out.println("向数据库中注册用户~~~");//模拟数据库注册用户
方案一:客户端JavaScript代码阻止表单重复提交
    <script type="text/javascript">	
    	//让后续提交无效
    	var iscommitted = false;//定义一个全局变量记录表单是否提交过,如果提交过则为true
    	function dosubmit(){
    		if(!iscommitted){//如果该表单没有提交过
    			iscommitted = true;
    			return true;//让该表单提交
    		}else{
    			alert("不能重复提交");
    			return false;//让表单不提交
    		}
    	}
    	/* 
    	//让提交按钮灰掉
    	function dosubmit(){
    		var input = document.getElementById("submit");//得到submit按钮
    		//document.getElementById("submit").disabled = true;
    		input.disabled = 'disabled';//将该按钮灰掉
    		return true;//提交表单
    	} */
    </script>
  </head>
  
  <body>
    <form action="/day05/servlet/DoFormServlet" method="post" οnsubmit="return dosubmit()">
    	用户名:<input type="text" name="username"><br>
    	<input id="submit" type="submit" value="提交">
    </form>
  </body>
该方式不能彻底防止表单重复提交,比如少数客户端用户可能删除javascript代码然后使用自己的页面提交表单,或者点击刷新按钮或点后退按钮再次提交表单。
但是实际开发中还是要使用该方式,可以使大部分用户的体验好一点
方案二:服务器端session防表单重复提交
要在服务器端防止表单重复提交,那么该表单页面由servlet程序生成,servlet为每次产生的表单页面分配一个唯一的随机标识号,并在FORM表单的一个 隐藏字段中设置这个标识号,同时在当前用户的Session域中保存这个标识号。 
当用户提交FORM表单时,负责处理表单提交的serlvet得到表单提交的标识号,并与session中存储的标识号比较,如果相同则处理表单提交,处理完后清除当前用户的Session域中存储的标识号。
在下列情况下,服务器程序将拒绝用户提交的表单请求:
  • 存储Session域中的表单标识号与表单提交的标识号不同
  • 当前用户的Session中不存在表单标识号
  • 用户提交的表单数据中没有标识号字段
知识:base64编码算法
  • 任何数据经过base64算法编码之后的结果都是明文字符,避免了乱码的出现
  • 任意的二进制数据经过该算法编码后会把每3个字节变成4个字节
  • 3个字节有24比特数据,所以4个字节中每个字节接收6比特数据
  • 4个字节中每个字节中有效位只有后6位,最高2位都是0,所以最大值为00111111(63),最小为00000000(0),有64个数字
  • 按照该数字查询一个base64自定义码表,表中的字符都是键盘上的可读字符(明文字符)
  • 网络上传输数据通常都会使用base64算法编码之后再传,选用的开始和结束符号都是base64码表中没有的,因为传输的数据是任意的,可能会和开始结束符相同(比如电影传送一半就结束了),采用base64编码可以避免该问题。
知识:md5算法
不是对数据进行加密,而是得到数据的摘要
应用场景1:保存用户名和密码
  • 如果只是将用户名和密码保存在数据库中,那么就会将其暴露给数据库管理员
  • 通常会使用MD5算法得到密码的摘要,存入数据库的就是该摘要,即密码的md5码
  • 反向破解md5码需要40亿年,但是密码通常有长度限制,可以暴力破解(对比存储的md5码和被检测密码的md5码)
  • 实际中会将密码和一个随机数一起进行md5算法的处理,增加暴力破解的成本
应用场景2:校验数据的完整性
  • 不同的数据有不同的指纹,通常下载资源链接后会有下载资源的md5码,用于和用户下载的数据生成的md5码进行比对,如果一致则下载的数据是完整的,并且在下载过程中没有被种木码
  • 确认光盘是否损坏:将光盘的CRC校验码和生成的MD5码进行比对,一致则代表光盘没有一个磁道损坏
产生标识号的Servlet(客户端首先访问)
package cn.itcast.form;

import java.io.IOException;
import java.io.PrintWriter;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Random;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import sun.misc.BASE64Encoder;

import com.sun.mail.util.BASE64EncoderStream;
//输出表单的Servlet
public class FormServlet extends HttpServlet {
	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		//产生随机数(表单号)
		TokenProcessor tp = TokenProcessor.getInstance();
		String token = tp.generateToken();
		
		//使用session存储产生的表单token,如果使用request的话用户提交表单会产生新的请求,token会消失
		request.getSession().setAttribute("token", token);
		
		//使用jsp页面向用户输出表单
		request.getRequestDispatcher("/form.jsp").forward(request, response);
		
	}

	public void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		doGet(request, response);
	}

}

//编写工具类生成表单标识号:TokenProcessor,即随机数发生器
class TokenProcessor{ //token:令牌
	/*
	 * 需要保证令牌发生器生成的令牌是唯一的,所以通常将令牌发生器设计为单例,否则多个对象创建的随机数重复率高
	 * 1.把构造方法私有
	 * 2.自己创造一个类对象
	 * 3.对外暴露一个方法允许获取创建的对象
	 */
	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()+"";
		//通过得到随机数的数据摘要(数据指纹),不论原始数据多大,它们的长度固定一致,为128位,16个字节
		//获取数据摘要对象,指定算法为md5
		try {//内部处理异常,不给调用者带来麻烦
			MessageDigest md = MessageDigest.getInstance("md5");
			//接受一个输入,对数据进行摘要运算
			byte[] md5 = md.digest(token.getBytes());
			//将字节数组变为字符串返回
			//return new String(md5);//不能直接返回,因为会默认查GB2312码表,产生乱码
			//对字节数组进行base64编码,防止乱码产生
			BASE64Encoder encoder = new BASE64Encoder();//BASE64Encoder API没有被sun公司正式发布,所以不能再API文档中查看,只能再eclipse中查看方法
			return encoder.encode(md5);
		} catch (NoSuchAlgorithmException e) {
			throw new RuntimeException(e);
		}
	}
}
表单jsp页面
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>

<html>
  <head>  
    <title>My JSP 'form.jsp' starting page</title>
  </head>
  
  <body>
     <form action="/day05/servlet/DoFormServlet" method="post">
    	<input 	type="hidden" name="token" value="${token}">
    	用户名:<input type="text" name="username"><br>
    	<input id="submit" type="submit" value="提交">
    </form>
  </body>
</html>
处理表单提交的serlvet
package cn.itcast.form;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
//处理表单提交请求
public class DoFormServlet extends HttpServlet {
	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {

		/*		String username = request.getParameter("username");
		try {
			Thread.sleep(1000);//模拟服务器延迟响应
		} catch (InterruptedException e) {}
		System.out.println("向数据库中注册用户~~~");//模拟数据库注册用户
		 */		
		boolean b = isTokenValid(request);
		//如果表单号无效,阻止表单提交
		if(!b){
			System.out.println("请不要重复提交");
			return;
		}
		//如果表单号有效,则先将表单号置为无效,再处理提交
		request.getSession().removeAttribute("token");
		System.out.println("向数据库中注册用户~~~");//模拟数据库注册用户
	}

	//判断表单号是否有效
	private boolean isTokenValid(HttpServletRequest request) {
		String client_token = request.getParameter("token");//得到客户机带过来的表单号
		//如果客户机没有带表单号过来(比如遇见坏的客户自行建立表单提交)
		if(client_token==null){
			return false;
		}
		//如果客户机带来了表单号,就判断服务器里有没有该表单号
		String server_token = (String) request.getSession().getAttribute("token");
		//如果服务器端没有,表单就重复提交了
		if(server_token==null){
			return false;
		}
		
		if(!client_token.equals(server_token)){
			return false;
		}
		return true;
	}

	public void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		doGet(request,response);
	}
}

利用session校验图片认证码

一次性验证码的主要目的就是为了限制人们利用工具软件来暴力猜测密码。 
服务器程序接收到表单数据后,首先判断用户是否填写了正确的验证码,只有该验证码与服务器端保存的验证码匹配时,服务器程序才开始正常的表单处理流程。 
密码猜测工具要逐一尝试每个密码的前题条件是先输入正确的验证码,而验证码是一次性有效的,这样基本上就阻断了密码猜测工具的自动地处理过程。 

用户注册页面
<!DOCTYPE html>
<html>
  <head>
    <title>Register.html</title>
    <meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
    <meta http-equiv="description" content="this is my page">
    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
   	<script type="text/javascript">
		function changeImage(img){
			img.src = img.src+"?"+new Date().getTime();
		}
	</script>
  </head>
  <body>
    <form action="/day05/servlet/RegisterServlet" method="post">
    	用户名:<input type="text" name="username"><br/>
    	密码:<input type="password" name="password"><br/>
    	认证码:<input type="text" name="checkcode">
    	<img src="/day05/servlet/ImageServlet" οnclick="changeImage(this)" alt="换一张" style="cursor:hand"><br/>
    	<input type = "submit" value="注册">
    </form>
  </body>
</html>
生成注册码图片Servlet
package cn.itcast.checkcode;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Random;
import javax.imageio.ImageIO;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class ImageServlet extends HttpServlet {

	public static final int WIDTH = 120;
	public static final int HEIGHT = 35;	

	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		BufferedImage image = new BufferedImage(WIDTH,HEIGHT,BufferedImage.TYPE_INT_RGB);
		Graphics g = image.getGraphics();
		setBackGround(g);
		setBorder(g);
		drawRandomLine(g);
		String random = drawRandomNum((Graphics2D)g);//得到生成的随机字符串
		//将随机字符串存入session用于服务器校验
		request.getSession().setAttribute("checkcode", random);
		response.setDateHeader("expires", -1);
		response.setHeader("Cache-Control","no-cache");
		response.setHeader("Pragma","no-cache");
		response.setContentType("image/jpeg");
		ImageIO.write(image, "jpg", response.getOutputStream());
	}

	private void setBackGround(Graphics g) {
		g.setColor(Color.WHITE);
		g.fillRect(0, 0, WIDTH, HEIGHT);
	}
	
	private void setBorder(Graphics g) {
		g.setColor(Color.BLUE);
		g.drawRect(1, 1, WIDTH-2, HEIGHT-2);
	}
	
	private void drawRandomLine(Graphics g) {
		g.setColor(Color.GREEN);
		for (int i=0; i<5; i++) {
			int x1 = new Random().nextInt(WIDTH);
			int y1 = new Random().nextInt(HEIGHT);
			int x2 = new Random().nextInt(WIDTH);
			int y2 = new Random().nextInt(HEIGHT);
			g.drawLine(x1, y1, x2, y2);;
		}
	}

	//返回生成的随机字符串,用于服务器存储
	private String drawRandomNum(Graphics2D g) {
		g.setColor(Color.RED);
		g.setFont(new Font("宋体",Font.BOLD,20));
		String base = "\u7684\u4e00\u662f\u4e86\u6211\u4e0d\u4eba\u5728\u4ed6\u6709\u8fd9\u4e2a\u4e0a\u4eec\u6765\u5230\u65f6\u5927\u5730\u4e3a\u5b50\u4e2d\u4f60\u8bf4\u751f\u56fd\u5e74\u7740\u5c31\u90a3\u548c\u8981\u5979\u51fa\u4e5f\u5f97\u91cc\u540e\u81ea\u4ee5\u4f1a\u5bb6\u53ef\u4e0b\u800c\u8fc7\u5929\u53bb\u80fd\u5bf9\u5c0f\u591a\u7136\u4e8e\u5fc3\u5b66\u4e48\u4e4b\u90fd\u597d\u770b\u8d77\u53d1\u5f53\u6ca1\u6210\u53ea\u5982\u4e8b\u628a\u8fd8\u7528\u7b2c\u6837\u9053\u60f3\u4f5c\u79cd\u5f00\u7f8e\u603b\u4ece\u65e0\u60c5\u5df1\u9762\u6700\u5973\u4f46\u73b0\u524d\u4e9b\u6240\u540c\u65e5\u624b\u53c8\u884c\u610f\u52a8\u65b9\u671f\u5b83\u5934\u7ecf\u957f\u513f\u56de\u4f4d\u5206\u7231\u8001\u56e0\u5f88\u7ed9\u540d\u6cd5\u95f4\u65af\u77e5\u4e16\u4ec0\u4e24\u6b21\u4f7f\u8eab\u8005\u88ab\u9ad8\u5df2\u4eb2\u5176\u8fdb\u6b64\u8bdd\u5e38\u4e0e\u6d3b\u6b63\u611f\u89c1\u660e\u95ee\u529b\u7406\u5c14\u70b9\u6587\u51e0\u5b9a\u672c\u516c\u7279\u505a\u5916\u5b69\u76f8\u897f\u679c\u8d70\u5c06\u6708\u5341\u5b9e\u5411\u58f0\u8f66\u5168\u4fe1\u91cd\u4e09\u673a\u5de5\u7269\u6c14\u6bcf\u5e76\u522b\u771f\u6253\u592a\u65b0\u6bd4\u624d\u4fbf\u592b\u518d\u4e66\u90e8\u6c34\u50cf\u773c\u7b49\u4f53\u5374\u52a0\u7535\u4e3b\u754c\u95e8\u5229\u6d77\u53d7\u542c\u8868\u5fb7\u5c11\u514b\u4ee3\u5458\u8bb8\u7a1c\u5148\u53e3\u7531\u6b7b\u5b89\u5199\u6027\u9a6c\u5149\u767d\u6216\u4f4f\u96be\u671b\u6559\u547d\u82b1\u7ed3\u4e50\u8272\u66f4\u62c9\u4e1c\u795e\u8bb0\u5904\u8ba9\u6bcd\u7236\u5e94\u76f4\u5b57\u573a\u5e73\u62a5\u53cb\u5173\u653e\u81f3\u5f20\u8ba4\u63a5\u544a\u5165\u7b11\u5185\u82f1\u519b\u5019\u6c11\u5c81\u5f80\u4f55\u5ea6\u5c71\u89c9\u8def\u5e26\u4e07\u7537\u8fb9\u98ce\u89e3\u53eb\u4efb\u91d1\u5feb\u539f\u5403\u5988\u53d8\u901a\u5e08\u7acb\u8c61\u6570\u56db\u5931\u6ee1\u6218\u8fdc\u683c\u58eb\u97f3\u8f7b\u76ee\u6761\u5462\u75c5\u59cb\u8fbe\u6df1\u5b8c\u4eca\u63d0\u6c42\u6e05\u738b\u5316\u7a7a\u4e1a\u601d\u5207\u600e\u975e\u627e\u7247\u7f57\u94b1\u7d36\u5417\u8bed\u5143\u559c\u66fe\u79bb\u98de\u79d1\u8a00\u5e72\u6d41\u6b22\u7ea6\u5404\u5373\u6307\u5408\u53cd\u9898\u5fc5\u8be5\u8bba\u4ea4\u7ec8\u6797\u8bf7\u533b\u665a\u5236\u7403\u51b3\u7aa2\u4f20\u753b\u4fdd\u8bfb\u8fd0\u53ca\u5219\u623f\u65e9\u9662\u91cf\u82e6\u706b\u5e03\u54c1\u8fd1\u5750\u4ea7\u7b54\u661f\u7cbe\u89c6\u4e94\u8fde\u53f8\u5df4\u5947\u7ba1\u7c7b\u672a\u670b\u4e14\u5a5a\u53f0\u591c\u9752\u5317\u961f\u4e45\u4e4e\u8d8a\u89c2\u843d\u5c3d\u5f62\u5f71\u7ea2\u7238\u767e\u4ee4\u5468\u5427\u8bc6\u6b65\u5e0c\u4e9a\u672f\u7559\u5e02\u534a\u70ed\u9001\u5174\u9020\u8c08\u5bb9\u6781\u968f\u6f14\u6536\u9996\u6839\u8bb2\u6574\u5f0f\u53d6\u7167\u529e\u5f3a\u77f3\u53e4\u534e\u8ae3\u62ff\u8ba1\u60a8\u88c5\u4f3c\u8db3\u53cc\u59bb\u5c3c\u8f6c\u8bc9\u7c73\u79f0\u4e3d\u5ba2\u5357\u9886\u8282\u8863\u7ad9\u9ed1\u523b\u7edf\u65ad\u798f\u57ce\u6545\u5386\u60ca\u8138\u9009\u5305\u7d27\u4e89\u53e6\u5efa\u7ef4\u7edd\u6811\u7cfb\u4f24\u793a\u613f\u6301\u5343\u53f2\u8c01\u51c6\u8054\u5987\u7eaa\u57fa\u4e70\u5fd7\u9759\u963f\u8bd7\u72ec\u590d\u75db\u6d88\u793e\u7b97\u4e49\u7adf\u786e\u9152\u9700\u5355\u6cbb\u5361\u5e78\u5170\u5ff5\u4e3e\u4ec5\u949f\u6015\u5171\u6bdb\u53e5\u606f\u529f\u5b98\u5f85\u7a76\u8ddf\u7a7f\u5ba4\u6613\u6e38\u7a0b\u53f7\u5c45\u8003\u7a81\u76ae\u54ea\u8d39\u5012\u4ef7\u56fe\u5177\u521a\u8111\u6c38\u6b4c\u54cd\u5546\u793c\u7ec6\u4e13\u9ec4\u5757\u811a\u5473\u7075\u6539\u636e\u822c\u7834\u5f15\u98df\u4ecd\u5b58\u4f17\u6ce8\u7b14\u751a\u67d0\u6c89\u8840\u5907\u4e60\u6821\u9ed8\u52a1\u571f\u5fae\u5a18\u987b\u8bd5\u6000\u6599\u8c03\u5e7f\u8716\u82cf\u663e\u8d5b\u67e5\u5bc6\u8bae\u5e95\u5217\u5bcc\u68a6\u9519\u5ea7\u53c2\u516b\u9664\u8dd1\u4eae\u5047\u5370\u8bbe\u7ebf\u6e29\u867d\u6389\u4eac\u521d\u517b\u9999\u505c\u9645\u81f4\u9633\u7eb8\u674e\u7eb3\u9a8c\u52a9\u6fc0\u591f\u4e25\u8bc1\u5e1d\u996d\u5fd8\u8da3\u652f\u6625\u96c6\u4e08\u6728\u7814\u73ed\u666e\u5bfc\u987f\u7761\u5c55\u8df3\u83b7\u827a\u516d\u6ce2\u5bdf\u7fa4\u7687\u6bb5\u6025\u5ead\u521b\u533a\u5965\u5668\u8c22\u5f1f\u5e97\u5426\u5bb3\u8349\u6392\u80cc\u6b62\u7ec4\u5dde\u671d\u5c01\u775b\u677f\u89d2\u51b5\u66f2\u9986\u80b2\u5fd9\u8d28\u6cb3\u7eed\u54e5\u547c\u82e5\u63a8\u5883\u9047\u96e8\u6807\u59d0\u5145\u56f4\u6848\u4f26\u62a4\u51b7\u8b66\u8d1d\u8457\u96ea\u7d22\u5267\u554a\u8239\u9669\u70df\u4f9d\u6597\u503c\u5e2e\u6c49\u6162\u4f5b\u80af\u95fb\u5531\u6c99\u5c40\u4f2f\u65cf\u4f4e\u73a9\u8d44\u5c4b\u51fb\u901f\u987e\u6cea\u6d32\u56e2\u5723\u65c1\u5802\u5175\u4e03\u9732\u56ed\u725b\u54ed\u65c5\u8857\u52b3\u578b\u70c8\u59d1\u9648\u83ab\u9c7c\u5f02\u62b1\u5b9d\u6743\u9c81\u7b80\u6001\u7ea7\u7968\u602a\u5bfb\u6740\u5f8b\u80dc\u4efd\u6c7d\u53f3\u6d0b\u8303\u5e8a\u821e\u79d8\u5348\u767b\u697c\u8d35\u5438\u8d23\u4f8b\u8ffd\u8f83\u804c\u5c5e\u6e10\u5de6\u5f55\u4e1d\u7259\u515a\u7ee7\u6258\u8d76\u7ae0\u667a\u51b2\u53f6\u80e1\u5409\u5356\u575a\u559d\u8089\u9057\u6551\u4fee\u677e\u4e34\u85cf\u62c5\u620f\u5584\u536b\u836f\u60b2\u6562\u9760\u4f0a\u6751\u6234\u8bcd\u68ee\u8033\u5dee\u77ed\u7956\u4e91\u89c4\u7a97\u6563\u8ff7\u6cb9\u65e7\u9002\u4e61\u67b6\u6069\u6295\u5f39\u94c1\u535a\u96f7\u5e9c\u538b\u8d85\u8d1f\u52d2\u6742\u9192\u6d17\u91c7\u6beb\u5634\u6bd5\u4e5d\u51b0\u65e2\u72b6\u4e71\u666f\u5e2d\u73cd\u7ae5\u9876\u6d3e\u7d20\u8131\u519c\u7591\u7ec3\u91ce\u6309\u72af\u62cd\u5f81\u574f\u9aa8\u4f59\u627f\u7f6e\u81d3\u5f69\u706f\u5de8\u7434\u514d\u73af\u59c6\u6697\u6362\u6280\u7ffb\u675f\u589e\u5fcd\u9910\u6d1b\u585e\u7f3a\u5fc6\u5224\u6b27\u5c42\u4ed8\u9635\u739b\u6279\u5c9b\u9879\u72d7\u4f11\u61c2\u6b66\u9769\u826f\u6076\u604b\u59d4\u62e5\u5a1c\u5999\u63a2\u5440\u8425\u9000\u6447\u5f04\u684c\u719f\u8bfa\u5ba3\u94f6\u52bf\u5956\u5bab\u5ffd\u5957\u5eb7\u4f9b\u4f18\u8bfe\u9e1f\u558a\u964d\u590f\u56f0\u5218\u7f6a\u4ea1\u978b\u5065\u6a21\u8d25\u4f34\u5b88\u6325\u9c9c\u8d22\u5b64\u67aa\u7981\u6050\u4f19\u6770\u8ff9\u59b9\u85f8\u904d\u76d6\u526f\u5766\u724c\u6c5f\u987a\u79cb\u8428\u83dc\u5212\u6388\u5f52\u6d6a\u542c\u51e1\u9884\u5976\u96c4\u5347\u7883\u7f16\u5178\u888b\u83b1\u542b\u76db\u6d4e\u8499\u68cb\u7aef\u817f\u62db\u91ca\u4ecb\u70e7\u8bef";
		//定义容器,存储生成的字符
		StringBuilder sb = new StringBuilder();
		int x = 5;
		for(int i=0;i<4;i++){
			//写入字之前,设置好旋转
			int degree = new Random().nextInt()%30;
			String ch = base.charAt(new Random().nextInt(base.length()))+"";
			sb.append(ch);
			g.rotate(degree*Math.PI/180, x, 20); 
			g.drawString(ch, x,20);
			g.rotate(-degree*Math.PI/180, x, 20);
			x+=30;
		}
		return sb.toString();
	}

	public void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		doGet(request,response);
	}
}
处理注册请求servlet
package cn.itcast.checkcode;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class RegisterServlet extends HttpServlet {

	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		
		//设置以什么码表打开表单中的参数,否则会出现乱码,参考客户机是以哪个码表打开页面的(还要注意浏览器的码表设置)
		request.setCharacterEncoding("UTF-8");
		
		//处理注册请求之前,校验认证码是否有效
		String c_checkcode = request.getParameter("checkcode");//得到客户机输入的认证码
		System.out.println(c_checkcode);
		String s_checkcode = (String) request.getSession().getAttribute("checkcode");//得到服务器存储的认证码
		System.out.println(s_checkcode);
		if(c_checkcode!=null&&s_checkcode!=null&&c_checkcode.equals(s_checkcode)){
			System.out.println("处理注册请求");
		}else{
			System.out.println("认证码错误");
		}
	
	}

	public void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		doGet(request,response);
	}
}

Servlet的三个域对象的总结

产生的数据显示完了就没有用了,就用 Request域对象作为数据的容器
产生的数据除了显示,等一会儿还要用,就用 Session域对象作为数据的容器
  • 比如图片校验示例,校验码除了显示外,还要校验用
  • 用户登录示例中把用户对象存储在session中,因为登录的状态等一会儿还要用
产生的数据不仅等一会儿要用,还要提供给别人用,就用 ServletContext域对象作为数据的容器
  • 例如聊天室,用户产生的数据除了显示给用户自己,还要给比人看

综合案例

应用Session+Cookie技术完成用户自动登陆功能

如果处理自动登录的代码写在生成首页的servlet中,那么用户只有访问网站首页才会实现自动登录,访问其他级别的页面则不会自动登录
难道需要在每一个页面中都写自动登录代码吗?
需要将改代码写在 servlet过滤器中,那么客户端所有的请求都会经过过滤器过滤
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值