在JavaWeb基础(五)中,我们分享了.Servlet规范、Servlet生命周期、Servlet请求流程、Servlet初始化参数、Servlet继承体系结构和设计起因。
今天我主要来分享下Cookie和Session, Cookie和Session用起来其实很简单, 主要使用来处理多个request之间的数据共享问题。先说下我这篇博客会分享的内容.
Servlet3.0的注解、Http协议存的问题、Cookie技术、Session技术、总结下用场景之前的编码中,我们每次添加一个Servlet。都需要到WEB-INF下配置web.xml.我们需要注册Servlet并关联资源名.
而从J2EE6规范起, 我们即可以使用注解的方式来配置这些信息.这个版本的Servlet即Servlet3.0, 对应着J2EE6的规范和Tomcat7.*.
每个新技术的引入都是有肯定应使用场景, Servlet注解的引入主要是为了处理配置xml的繁琐和臃肿.由于随着Sevlet的添加, web.xml里的配置会爆炸性的增长, 这时候对于修改和维护该配置文件效率往往会非常低.
该属性表示元数据的完整性.即xml是使用来形容Servlet的,我们也可以把XML看成是形容Servlet的一种元数据.
假如我们公告其为true。表示xml形容信息是完整的,那么这时候tomcat就不会再去解析Servlet的注解信息.假如我们公告其为false。表示xml形容信息是不完整的,那么这时候tomcat机会再去解析Servlet的注解信息, 这时候我们用注解替代web.xml才会生效.我们之前分享的中,提到最多的配置是资源名称映射到Servlet配置和Servlet初始化参数配置.我们就来看下如何用注解来配置.首先这两个配置对应的是@WebServlet注解和@WebInitParam注解.以下是其源代码, 我们只列出部分常见属性.
@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface WebServlet { String name() default ""; String[] value() default {}; String[] urlPatterns() default {}; int loadOnStartup() default -1; WebInitParam[] initParams() default {};}@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface WebInitParam { String name(); String value();}@WebServlet有name、value、urlPatterns、loadOnStartup、initParams五个常使用属性.
资源名要关联的Servlet.如下代码,用Servlet注解
package com.sweetcs.web.servlet.servlet3_0;import java.io.IOException;import java.util.Arrays;import javax.jws.soap.InitParam;import javax.servlet.ServletException;import javax.servlet.annotation.WebInitParam;import javax.servlet.annotation.WebServlet;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;@WebServlet(value={"/annotation_servlet"}, initParams={ @WebInitParam(name = "enocding", value ="UTF-8") })public class AnnotationServlet extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 用反射读取配置初始化参数 Class<AnnotationServlet> clazz = null; try { clazz = (Class<AnnotationServlet>) Class.forName("com.sweetcs.web.servlet.servlet3_0.AnnotationServlet"); WebServlet annOfWebServlet = clazz.getAnnotation(WebServlet.class); System.out.println(Arrays.toString(annOfWebServlet.value())); System.out.println(Arrays.toString(annOfWebServlet.urlPatterns())); System.out.println(Arrays.toString(annOfWebServlet.initParams())); } catch (ClassNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } }}启动浏览器, 输入http://127.0.0.1:8080/annotation_servlet. 运行输出如下.注解能在运行时成功读取到,其中value和urlpattern属性作使用是一样,但是假如我们只配置其中一个,其只能读取到其中一个的值.
XML和注解区别
选择
一般在企业级开发中, 我们当然要尽量的选择其优点, 所以我们在xml中做通使用配置。个别的Servlet配置才使用注解。
从打开浏览器,到关闭浏览器过程中的操作可以称为一次会话.我们把一次会话也称为Session。而一次会话中我们可以发送屡次的请求Request.
http协议是一种无状态连接的协议.导致了服务端无法让多个请求共享数据.
说白话就是
服务端不知道上一次是哪个用户端请求了自己。一次会话中可以发送屡次请求, 但是服务器却不知道这屡次请求是来自同一个用户端。问题:这也就导致了服务端无法让多个请求共享数据.为理解决这个问题就引入了参数传递机制、Cookie技术和Session技术
用户端例子
学过手机端开发的同学都知道, 对于同一个使用户, 我们需要在多个页面之前传递数据.而由于Http协议的
无状态连接问题导致我们无法在多个页面之前传递数据.这只是在用户端之前的一个例子, 并不精确。
服务端例子
在服务端开发中,涉及到网络通信,所以可以了解为
多个请求无法标识,导致了服务端无法有效的利使用之前的数据在多个请求前实现共享.这也就是导致了你发次请求过来,下次我就不知道是你了,假如有很多页面需要做权限控制,那么每次页面一跳转,因为Http协议的健忘性.你又得重新登入做权限验证
为理解决服务端无法识别请求。我们可以在请求中自己附加参数,使用来标记请求,这是最原始的处理方案。如下代码用参数传递来处理数据共享问题.
登入界面
登入界面
LoginServlet
@WebServlet(urlPatterns={"/login"})public class LoginServlet extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("UTF-8"); resp.setContentType("text/html;charset=UTF-8"); String username = req.getParameter("username"); String password = req.getParameter("password"); System.out.println("username = " + username +"password = " + password); IUserDAO userDAO = new UserDAOImpl(); User user = userDAO.loginReturnUserOrNull(username, password); PrintWriter printWriter = resp.getWriter(); if (null == user) { printWriter.write("login failed"); }else { resp.sendRedirect("/get?username=" + username); } } }GetServlet
负责显示邮件箱
@WebServlet(urlPatterns={"/get"})public class GetServlet extends HttpServlet{ @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("UTF-8"); resp.setContentType("text/html;charset=UTF-8"); PrintWriter pw = resp.getWriter(); String username = req.getParameter("username"); pw.write("使用户:" + username + "<br />"); for (int i = 0; i < 6; i++) { pw.write("<a href="+"'/content?username="+ username +"'>第(" + i +")封邮件</a> <br />"); } }}邮箱界面, 显示邮件
邮箱界面
ContentServlet
负责显示邮件内容
@WebServlet("/content")public class ContentServlet extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("UTF-8"); resp.setContentType("text/html;charset=UTF-8"); String username = req.getParameter("username"); PrintWriter pw = resp.getWriter(); pw.write(username + " 大哥你好!有空聚聚"); }}邮件内容
可以看到这种方式,是通过URL携带参数进行数据共享.但是这种方式是不安全的, 而且很繁琐的, 假如要共享使用户信息需要在每个连接后携带共享数据.
Cookie是一种用户端技术。
- 1.程序把每个使用户的数据以Cookie响应给使用户的浏览器.
- 2.使用户浏览器接收到Cookie后就会将其保存到本地。
- 3.当使用户用浏览器取访问服务器资源中,就会携带各自的Cookie数据, 这样服务器即可以通过Cookie里知道该请求是哪些使用户发送的.
@Test public void testCreateAndReadCookie() { Cookie cookie = new Cookie("name", "Sweetcs"); String name = cookie.getName(); String value = cookie.getValue(); System.out.println(cookie); System.out.println("name="+name +" value= " +value); }@WebServlet(urlPatterns={"/cookie/login"})public class LoginServlet extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("UTF-8"); resp.setContentType("text/html;charset=UTF-8"); String username = req.getParameter("username"); String password = req.getParameter("password"); System.out.println("username = " + username +"password = " + password); IUserDAO userDAO = new UserDAOImpl(); User user = userDAO.loginReturnUserOrNull(username, password); PrintWriter printWriter = resp.getWriter(); // 1.创立Cookie Cookie cookie = new Cookie(username, password); // 2.setPath设置的共享范围是在同一个web服务器之下. cookie.setPath("/"); // 该设置表示该浏览器请求的web应使用只需是该服务器下都会传递该Cookie给服务器 // 3.设置共享Cookie的域名,通常应使用在多个二级域名之间传递数据// cookie.setDomain(""); // 一般使用于设置二级域名,那多个跨域web app能共享Cookie. // 4. 设置Cookie存活时间为3分钟。假如设置为0表示让浏览器删除Cookie,设置为负数, 表示Cookie只在本次会话中有效. cookie.setMaxAge(60 * 3); if (null == user) { printWriter.write("login failed"); }else { // 2.将Cookie加入响应头,响应给浏览器,用户端接收到会进行存储 resp.addCookie(cookie); resp.sendRedirect("/cookie/get"); } }}登入后的Http报文
可以看到如上Http报文,由于我们在LoginServlet的响应给浏览器Cookie,其响应头中有一个字段Set-Cookie:xxx.浏览器接受到后,判断有这个字段就将Cookie进行解析保存.
@WebServlet(urlPatterns={"/cookie/get"})public class GetServlet extends HttpServlet{ @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("UTF-8"); resp.setContentType("text/html;charset=UTF-8"); // 1.用户端接受到Cookie后,当再次发送请求,会携带Cookie数据. PrintWriter pw = resp.getWriter(); Cookie[] cookies = req.getCookies(); String username = null; for (Cookie cookie : cookies) { String key = cookie.getName(); String value = cookie.getValue(); if ("username".equals(key)) { username = value; break; } } pw.write("使用户:" + username + "<br />"); for (int i = 0; i < 6; i++) { pw.write("<a href='/cookie/content'>第(" + i +")封邮件</a> <br />"); } }}查看邮件内容页面对应的Http报文.可以看到当我们跳转到该页面,浏览器会携带Cookie数据.
在一次会话范围内有效的Cookie,我们称为会话Cookie.setMaxAge(seconds)seconds == 0: 删除Cookie.(让浏览器删除Cookie)seconds < 0: 会话Cookie.(持久化Cookie转换成会话Cookie)seconds > 0: 存储指定的秒数.(让浏览器持久化Cookiecookie主要分为会话Cookie和持久化Cookie.持久化Cookie可以通过setMaxAge(-1)转换成会话Cookie.
Cookie不够安全.一个浏览器的其余使用户可以查看其余人的CookieCookie不能直接支持中文.需要先将中文进行编码才能保存中文.// 用URLEncode和URLDecoder对中文进行编码 @Test public void testCookieStoreChinese() { Cookie cookie = null; try { cookie = new Cookie("name", URLEncoder.encode("苏轼", "UTF-8")); System.out.println("encode value = " + cookie.getValue()); System.out.println("decode value = " + URLDecoder.decode(cookie.getValue(), "UTF-8")); } catch (UnsupportedEncodingException e) { // TODO Auto-generated catch block e.printStackTrace(); } }运行输出
每个Cookie只能存储一个数据.需要多个数据, 需要多个Cookie站点对Cookie大小有限制, 服务器和浏览器对Cookie个数也有限制。站点对Cookie大小限制在4KB内.服务器在一个用户端最多保存20个,浏览器最多可以保存300个。设计上有问题。由于Cookie存储在浏览器端,一旦丢失就不能恢复, 不能再次用。假如是存储在服务器上就没这个问题。Cookie cookie = new Cookie(Key, Value);response对象.addCookie(cookie);cookie.setMaxAge(0)// 方式一cookie.setValue(newValue);// 方式二cookie = new Cookie(oldKey, newValue);response对象.addCookie(cookie);cookie.getValue()cookie = new Cookie(key, value)response.addCookie(cookie))path和domain(cookie.setPath("\")同服务器跨app。 cookie.setDomain(".baidu.com")不同服务器, 不同app跨域)setMaxAge(seconds)).过期后浏览器会自动删除Cookie[] cookies = req.getCookies())Session是服务端共享数据的技术.其作使用和Cookie相似, 使用于存储数据, 只是其是存储于服务端, 而不是用户端.其主要是为理解决Cookie的缺陷
服务器创立一个Session对象, 作为存储共享数据的地方。并将session的地址作为Cookie响应给浏览器.(其key为jsessionid, value为session的地址),浏览器用Cookie技术存储下该jssesionid,并在下一次访问的时候, 将该Cookie携带给服务器, 服务器通过该session的地址(jsessionid)可以获取到其对应的session, 进行数据的共享。
// 1.创立Session.假如session存在则返回,假如不存在则创立一个.session本质上是一个Map结构 HttpSession session = req.getSession(true); // 2.创立Session.假如session存在则返回,假如不存在返回null HttpSession session = req.getSession(false); session.setAttribute("username", username); // 存的是对象类型 String username = (String)session.getAttribute("username"); // 取出来的时候需要强转// 1.删除Session中指定属性session.removeAttribute("username");// 2.销毁整个session对象session.invalidate();sessioin对象.setMaxInactiveInterval(秒). // 用户端和服务端没有交互的最长时间,超过这个时间,session自动销毁 session.setMaxInactiveInterval(60 * 10);LoginServlet
@WebServlet(urlPatterns={"/session/login"})public class LoginServlet extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("UTF-8"); resp.setContentType("text/html;charset=UTF-8"); String username = req.getParameter("username"); String password = req.getParameter("password"); System.out.println("username = " + username +" password = " + password); IUserDAO userDAO = new UserDAOImpl(); User user = userDAO.loginReturnUserOrNull(username, password); PrintWriter printWriter = resp.getWriter(); // 1.创立Session.假如session存在则返回,假如不存在则创立一个.session本质上是一个Map结构 HttpSession session = req.getSession(true); // 2.向Session中增加共享数据 session.setAttribute("USERNAME_IN_SESSION", username); // 3.配置假如没有请求过来,session保存多久. session.setMaxInactiveInterval(60 * 10); if (null == user) { printWriter.write("login failed"); }else { resp.sendRedirect("/session/get"); } }}GetServlet
@WebServlet(urlPatterns={"/session/get"})public class GetServlet extends HttpServlet{ @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("UTF-8"); resp.setContentType("text/html;charset=UTF-8"); // 1.用户端接受到Cookie后,当再次发送请求,会携带Cookie数据. PrintWriter pw = resp.getWriter(); HttpSession session = req.getSession(true); String username = (String)session.getAttribute("USERNAME_IN_SESSION"); pw.write("使用户:" + username + "<br />"); for (int i = 0; i < 6; i++) { pw.write("<a href='/session/content'>第(" + i +")封邮件</a> <br />"); } }}ContentServlet
@WebServlet("/session/content")public class ContentServlet extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("UTF-8"); resp.setContentType("text/html;charset=UTF-8"); HttpSession session = req.getSession(true); String username = (String)session.getAttribute("USERNAME_IN_SESSION"); PrintWriter pw = resp.getWriter(); pw.write(username + " 大哥你好!有空聚聚"); }}LoginServlet对应的HTTP响应报文(由登入界面跳转到邮件列表)
可以看到用Session的后,会响应一个Cookie.该Cookie的key是JSESSIONID,value是Session对象在服务端的地址
ContentServlet对应的HTTP请求报文(由邮件列表跳转到邮件内容的请求过程)
可以看到,此时的请求会携带Cookie,Cookie中存储有JSESSIONID的值
XXX_IN_SESSION多个数据存储于Session, 一般我们把存储的数据封装成一个对象.再进行存储.开发中一般浏览器我们都是不会禁使用Session和Cookie的。假如禁使用了Cookie.此时就十分麻烦了.这就使得我们没法利使用Cookie技术,在请求之前传递JESSIONID.从而session机制也就失效了.为理解决这个问题,我们可以使用最传统的方式,将JESSIONID拼接在每个URL之后。但这样做又十分麻烦。J2EE为我们提供了一个十分便捷的接口, 只需传入要资源的路径,就会自动拼接出带JESSIONID的参数地址。
该接口会自动判断能否需要在资源路径后拼接JESSIONID
String url = response.encodeURL("/session/list"); System.out.println(url);