【Servlet教科书】Servlet之状态管理总结(Cookie、Session、ServletContext、Request大总结)
文章目录
- @[toc]
- 一、状态管理介绍
- 二、状态管理之Cookie应用
- 三、认识域对象
- 四、状态管理之Sesstion应用
- 4.1 什么是Session
- 4.2 Session的工作原理
- 4.3 如何获取Cookie
- 4.4 关于Google怎么查看Cookie
- 4.5 使用Session绑定对象
- 4.6 删除Session对象
- 4.7 Session超时
- 4.8 关于浏览器禁用Cookie的解决方案【了解】
- 五、Request作为域对象
- 六、ServletContext对象
文章目录
- @[toc]
- 一、状态管理介绍
- 二、状态管理之Cookie应用
- 三、认识域对象
- 四、状态管理之Sesstion应用
- 4.1 什么是Session
- 4.2 Session的工作原理
- 4.3 如何获取Cookie
- 4.4 关于Google怎么查看Cookie
- 4.5 使用Session绑定对象
- 4.6 删除Session对象
- 4.7 Session超时
- 4.8 关于浏览器禁用Cookie的解决方案【了解】
- 五、Request作为域对象
- 六、ServletContext对象
一、状态管理介绍
1.1 什么是状态管理
WEB应用中的会话是指一个客户端浏览器与WEB服务器之间连续发生的一系列请求和响应过程。
WEB应用的会话状态是指WEB服务器与浏览器在会话过程中产生的状态信息,借助会话状态,WEB服务器能够把属于同一会话中的一系列的请求和响应过程关联起来并达成数据共享
1.2 为什么需要状态管理
HTTP协议是无状态的,不能保存每次提交的信息,即当服务器返回与请求相对应的应答之后,这次事务的所有信息就丢掉了。 如果用户发来一个新的请求,服务器无法知道它是否与上次的请求有联系。 对于那些需要多次提交数据才能完成的Web操作,比如登录来说,就成问题了。通俗一点的例子就是,我们登录了在Github页面登录了,关闭页面之后,再次打开页面是不是发现你的Github还是保持你的账号登录的状态呢?这就是状态管理的作用!
1.3 状态管理的两种常见模式
客户端状态管理技术:将状态保存在客户端。代表性的是Cookie技术。
服务器状态管理技术:将状态保存在服务器端。代表性的是Session技术(服务器传递SessionID时需要使用Cookie的方式)。
二、状态管理之Cookie应用
2.1 什么是Cookie
Cookie是在浏览器访问WEB服务器的某个资源时,由WEB服务器在HTTP响应消息头中附带传送给浏览器的一小段数据,WEB服务器传送给各个客户端浏览器的数据是可以各不相同的。
一旦WEB浏览器保存了某个Cookie,那么它在以后每次访问该WEB服务器时,都应在HTTP请求头中将这个Cookie回传给WEB服务器。
WEB服务器通过在HTTP响应消息中增加Set-Cookie响应头字段将Cookie信息发送给浏览器,浏览器则通过在HTTP请求消息中增加Cookie请求头字段将Cookie回传给WEB服务器。
一个Cookie只能标识一种信息,它至少含有一个标识该信息的名称(NAME)和设置值(VALUE)。
一个WEB站点可以给一个WEB浏览器发送多个Cookie,一个WEB浏览器也可以存储多个WEB站点提供的Cookie。
浏览器一般只允许存放300个Cookie,每个站点最多存放20个Cookie,每个Cookie的大小限制为4KB。但是要知道现在的技术可以传输数据的方式有很多,而且浏览器也可以对Cookie存放的最大数量做扩容机制,所以改限制不是一成不变的!
2.2 创建Cookie对象
/**
* 创建一个Cookie对象
* 默认生命周期为:浏览器关闭
*/
Cookie cookie = new Cookie("username", "ziph");
/**
* 更新Cookie的生命周期:负数->浏览器内存里 0->失效 正数->过期时间
* </p>
* 过期时间以秒为单位,如下表示:
* 浏览器请求后Cookie存活60秒,60秒过后就会过期
*/
cookie.setMaxAge(60);
/**
* 设置Cookie的共享范围: 默认同一项目下 /->当前服务器下
*/
cookie.setPath("/");
/**
* 将Cookie对象响应给浏览器
*/
response.addCookie(cookie);
2.2.1 关于Cookie生命周期的设置
设置Cookie生命周期时间核心代码:cookie.setMaxAge(); (设置生命周期为60秒)
- 设置时间值说明:
- 正数:有效期(单位:秒)
- 0:失效
- 负数:内存存储
/**
* 更新Cookie的生命周期:负数->浏览器内存里 0->失效 正数->过期时间
* </p>
* 过期时间以秒为单位,如下表示:
* 浏览器请求后Cookie存活60秒,60秒过后就会过期
*/
cookie.setMaxAge(60);
2.2.2 关于Cookie共享范围的设置
cookie 一般都是由于用户访问页面而被创建的,可是并不是只有在创建 cookie 的页面才可以访问这个cookie。在默认情况下,出于安全方面的考虑,只有与创建 cookie 的页面处于同一个目录或在创建cookie页面的子目录下的网页才可以访问。那么此时如果希望其父级或者整个网页都能够使用cookie,就需要进行路径的设置。
关于Cookie的共享范围,默认是同一项目下。而设置"/"可以在同意服务器下共享
/**
* 设置Cookie的共享范围: 默认同一项目下 /->当前服务器下
*/
cookie.setPath("/");
2.2.3 遍历查询Cookie
/**
* 1.获取所有Cookie
* 2.非空判断
* 3.遍历Cookies数组
* 4.检查遍历Cookie并获取name值是否为username
* 5.检查遍历Cookie并获取value值是否为ziph
* 6.两步检查都没有问题,则打印提示信息
*/
Cookie[] cookies = request.getCookies();
if (cookies.length != 0) {
for (Cookie cookie : cookies) {
if (cookie.getName().equals("username")) {
if (cookie.getValue().equals("ziph")) {
System.out.println("您已登录!不用重新登录!");
}
}
}
}
2.3 Cookie的编码与解码问题
中文和英文字符不同,中文属于Unicode字符,在内存中占用4个字符,而英文属于ASCII字符,内存中只占2个字节。Cookie中使用Unicode字符时需要对Unicode字符进行编码,否则会出现乱码。编码可以使用java.net.URLEncoder类的encode(String str,String encoding)方法,解码使用java.net.URLDecoder类的decode(String str,String encoding)方法
/**
* Cookie的编码
* URLEncoder.encode("username", "utf-8")
*/
Cookie cookie = new Cookie(URLEncoder.encode("username", "utf-8"), URLEncoder.encode("ziph", "utf-8"));
/**
* Cookie的解码
* URLDecoder.decode(cookie.getName(), "utf-8")
*/
if (cookies.length != 0) {
for (Cookie cookie : cookies) {
if (URLDecoder.decode(cookie.getName(), "utf-8").equals("username")) {
if (URLDecoder.decode(cookie.getValue(), "utf-8").equals("ziph")) {
System.out.println("您已登录!不用重新登录!");
}
}
}
}
2.4 发送Cookie的条件
浏览器在发送请求之前,首先会根据请求url中的域名在cookie列表中找所有与当前域名一样的cookie,然后再根据指定的路径进行匹配,如果当前请求在域匹配的基础上还与路径匹配那么就会将所有匹配的cookie发送给服务器。
2.5 设置Cookie路径
通过Cookie的setPath方法设置路径
2.5 Cookie的优缺点
2.5.1 Cookie的优点
可配置到期规则: Cookie 可以在浏览器会话结束时到期,或者可以在客户端计算机上无限期存在,这取决于客户端的到期规则,不需要任何服务器资源,Cookie 存储在客户端并在发送后由服务器读取。
简单性: Cookie 是一种基于文本的轻量结构,包含简单的键值对。
数据持久性: 虽然客户端计算机上 Cookie 的持续时间取决于客户端上的 Cookie 过期处理和用户干预,Cookie 通常是客户端上持续时间最长的数据保留形式
2.5.2 Cookie的缺点
大小受到限制: 大多数浏览器对 Cookie 的大小有 4096 字节的限制,尽管在当今新的浏览器和客户端设备版本中,支持 8192 字节的 Cookie 大小已愈发常见。
用户配置为禁用: 有些用户禁用了浏览器或客户端设备接收 Cookie 的能力,因此限制了这一功能。
潜在的安全风险: Cookie 可能会被篡改。用户可能会操纵其计算机上的 Cookie,这意味着会对安全性造成潜在风险或者导致依赖于Cookie 的应用程序失败。
三、认识域对象
3.1 什么是域对象
可以存储数据的对象,从而实现从对象中存储和取出
3.2 常见的域对象有什么?
- Session (Session域)
- Request (Request域)
- ServletContext (ServletContext域)
四、状态管理之Sesstion应用
4.1 什么是Session
Session用于跟踪客户端的状态。Session指的是在一段时间内,单个客户与Web服务器的一连串相关的交互过程。
在一个Session中,客户可能会多次请求访问同一个网页,也有可能请求访问各种不同的服务器资源。
4.2 Session的工作原理
session被用于表示一个持续的连接状态,在网站访问中一般指代客户端浏览器的进程从开启到结束的过程。session其实就是网站分析的访问(visits)度量,表示一个访问的过程。
session的常见实现形式是cookie(session cookie),即未设置过期时间的cookie,这个cookie的默认生命周期为浏览器会话期间,只要关闭浏览器窗口,cookie就消失了。实现机制是当用户发起一个请求的时候,服务器会检查该请求中是否包含sessionid,如果未包含,则Tomcat会创造一个名为JSESSIONID的输出 cookie返回给浏览器(只放入内存,并不存在硬盘中),并将其以HashTable(键值对)的形式写到服务器的内存里面;当已经包含sessionid时,服务端会检查找到与该session相匹配的信息,如果存在则直接使用该sessionid,若不存在则重新生成新的 session。这里需要注意的是session始终是由服务端创建的,并非浏览器自己生成的。
4.3 如何获取Cookie
/**
* 获取Session对象中的id(唯一标记)
* A827D88EE704FE070A912BE908E64954
*/
HttpSession session = request.getSession();
System.out.println("SessionID为:"+session.getId());
/**
* 获取Session对象的空闲时间(单位:秒)
* 默认Session失效时间为1800(30分钟)
* 1800
*/
System.out.println("此Session对象"+session.getMaxInactiveInterval()+"秒后失效!");
/**
* 获取Session对象的创建时间(单位:毫秒)
*/
System.out.println("Session的创建时间:" + session.getCreationTime());
/**
* 获取Session对象的最后一次访问时间(单位:毫秒)
*/
System.out.println("Session的最后一次访问时间:"+session.getLastAccessedTime());
如下是我获取的Session的内容:
SessionID为:C8F34002131D14631E15BA7DF5C47C49
此Session对象1800秒后失效!
Session的创建时间:1587201492030
Session的最后一次访问时间:1587201557924
SessionID为:C8F34002131D14631E15BA7DF5C47C49
此Session对象1800秒后失效!
Session的创建时间:1587201492030
Session的最后一次访问时间:1587201620359注意:如下图片找到了打开Google的SessionID,并确定它仍然生效!(如何打开Google查询Cookie,下一章节,我介绍给大家!)
4.4 关于Google怎么查看Cookie
查看步骤: 以前步骤可以查看Google的Cookie(我已用图解的方法展示给大家了!)
网址栏直接跳转访问查看Cookie: chrome://settings/content/cookies
4.5 使用Session绑定对象
使用HttpSession的setAttribute(属性名,Object)方法实现绑定对象
//第一个Servlet代码
HttpSession session = request.getSession();
System.out.println("SessionID为:" + session.getId());
/**
* 使用Session来绑定对象
*/
Test test = new Test();
test.name = "Ziph";
test.age = 18;
session.setAttribute("name", test);
//第一个Servlet代码中的测试类
/**
* 使用Session来绑定对象的测试类
*/
class Test {
String name;
int age;
@Override
public String toString() {
return "Test{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
//第二个Servlet代码
HttpSession session = request.getSession();
System.out.println("SessionID为:" + session.getId());
Object name = session.getAttribute("name");
System.out.println(name);
SessionID为:804218657264AD72C75196B4C0121C38
SessionID为:804218657264AD72C75196B4C0121C38
Test{name=‘Ziph’, age=18}
4.6 删除Session对象
使用HttpSession的invalidate方法
//第一个Servlet代码
HttpSession session = request.getSession();
System.out.println("SessionID为:" + session.getId());
/**
* 删除Session对象
*/
session.invalidate();
//第二个Servlet代码
HttpSession session = request.getSession();
System.out.println("SessionID为:" + session.getId());
控制台获取SessionID如下: (注意:所获取的SessionID不一样了,证明第一个Session对象被删除了!)
SessionID为:42F4DC652DD812772DA8B591A5CA4C2E
SessionID为:7C7BBEC30188C4D1744D0A41EE9D9AEB
4.7 Session超时
4.7.1 什么是Session超时
HttpSession的最后一次访问时间和当前时间的差距大于了指定的最大空闲时间,这时服务器就会销毁Session对象。默认的空闲时间为30分钟。(生命周期结束)
4.7.2 如何修改Session的缺省时间限制
- 使用HttpSession的session.setMaxInactiveInterval(60);设置,(单位:秒)
- 修改xml配置信息(单位:分钟)
注意:两者设置不冲突,xml配置是在服务器初始化的时候配置起到了作用,而如果再在代码中设置时间限制的话,该时间限制则覆盖xml配置的时间限制。
<session-config>
<session-timeout>10</session-timeout>
</session-config>
4.7.3 Session失效的几种情况
- 超过了设置的超时时间
- 主动调用了删除Session的方法invalidate()
- 服务器主动或异常关闭
注意:浏览器关闭并不会让Session失效!(在4.7.4中特别讲解了这句话!)
4.7.4 解释浏览器关闭不会让Session失效
如果有小伙伴试过的话,这个注意也许会对你们产生一种困惑!
当我们在测试的时候,获取并查看当前SessionID。关闭浏览器后,重新发起请求,则看到再一次获取的SessionID变了!到看到发生的这一幕的时候,你们是不是会质疑这个结论呢?怎么浏览器关闭并不会让Session失效呢?它不是已经变了吗?那就让我来回答这个问题,我感觉这个问题还是有必要去说一下的!
我们第一次在浏览器发起请求,获取一个SessionID,我们在这里用①表示。这时候关闭浏览器,重新打开浏览器重新发起请求,在获取当前SessionID,我们在这里获取的是②。大家有没有发现①并不是和我们的②是同一个对象呢?Sesssion对象是存储再服务器中的,第一次发起请求获得①,关闭后重新发起请求获得②,这时候就有聪明的小伙伴发现了我两次加重标记了重新发起请求这几个字,你是不是找出其中的端倪了呢?(找不出也没事,我来给你答案!)
答案: 浏览器关闭并不会让Session失效,这里所指的Session对象是服务器端的Session对象没有失效!你想我们第一次发起请求获得一个Session对象,而当我们关闭浏览器重新发起请求时,这就是一个新的请求(Request),它会创建一个新的Session对象,所以我们看到的表面现象是改变的。而我们看不到的服务器内的Session对象是没有发生改变的!
4.8 关于浏览器禁用Cookie的解决方案【了解】
4.8.1 怎么主动禁用浏览器Cookie
在我们查看浏览器Cookie的页面里,会有禁用Cookie的按钮,我们可以自行添加禁用某个站点的Cookie!
4.8.2 浏览器禁用Cookie的后果
如果浏览器禁用Cookie,session还能用吗?
答:不能,但有其他的解决方案
服务器在默认情况下,会使用Cookie的方式将sessionID发送给浏览器,如果用户禁止Cookie,则sessionID不会被浏览器保存。此时,服务器可以使用如URL重写这样的方式来发送sessionID。
4.8.3 使用Session区分每个用户的方式
- 使用Cookie
- 作为隐藏域嵌入HTML表单中,附加在主体的URL中,通常作为指向其他应用程序页面的链接,即URL重写。
4.8.3 什么是URL重写
浏览器在访问服务器上的某个地址时,不再使用原来的那个地址,而是使用经过改写的地址(即:在原来的地址后面加上了sessionID)
4.8.4 如何实现URL重写
如果是链接地址和表单提交,使用response.encodeURL(String url)生成重写后的URL
如果是重定向,使用response.encodeRedirectURL(String url)生成重写的URL
PrintWriter writer = response.getWriter();
HttpSession session = request.getSession();
/**
* 重定向方式(地址不能为空!)
*/
String s1 = response.encodeRedirectURL("/项目名称/index.html");
response.sendRedirect(s1);
/**
* 链接地址方式(地址可以为空!)
* 注意:下面的是打印手写a超链接标签并附跳转链接
*/
String s2 = response.encodeURL("/项目名称/index.html");
writer.println("<a href='"+ s2 +"'>" + "跳转" + "</a>");
五、Request作为域对象
5.1 Request域对象的生命周期
Request的作用范围是一次访问的过程,生命周期随着访问开始而开始,随着服务器的响应完毕而结束。所以更多的数据会添加到这个域对象
5.2 再次理解重定向和转发的区别
- 转发会携带这第一次的ServletRequest的信息传递到下一个文件,而重定向是重新开启了一次访问(发起请求),所以并不会携带
- 转发上面的地址栏不会发生改变就访问了别的内容,重定向会发生改变
- 转发只能存在于服务器内部,重定向可以访问任何其他网页
- 因为转发只创建了一次客户端和服务器的链接,所以相对而言转发会比较节省网络资源
5.3 Request域对象作用在重定向和转发中的区别
鉴于重定向是重新开启访问,这个访问即是重新发起了一次请求。再来看我们Request域对象的生命周期是一次访问的过程。所以我们的数据存在Request域对象中,是不能应用再重定向中的。假如我们应用在重定向中,取出来的即是null。(域对象生命周期已经结束!)
鉴于转发是服务器内部操作,我们用的时候即是当前的这一次请求。再想到Request域对象的声明周期的时候,正是符合转发操作。所以我们应用在转发中,可以正常存取数据。
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
/**
* Reuqest域对象应用于转发
*/
String message = "Ziph is Very Good!";
request.setAttribute("message", message);
request.getRequestDispatcher("/test5").forward(request, response);
/**
* Request域对象应用于重定向(不能正常存取)
* request.getContextPath():获取根路径
* 根路径拼接Servlet中的urlPatterns即是需要重定向过去的路径
*/
response.sendRedirect(request.getContextPath() + "/test5");
}
六、ServletContext对象
6.1 什么是ServletContext对象
ServletContext对象泛指Servlet上下文,代表当前整个应用程序。(jsp中application)
- 当WEB服务器启动时,会为每一个WEB应用程序(webapps下的每个目录就是一个应用程序)创建一块共享的存储区域
- ServletContext也叫做“公共区域”,也就是同一个WEB应用程序中,所有的Servlet和JSP都可以共享同一个区域
- ServletContext在WEB服务器启动时创建,服务器关闭时销毁
6.2 如何获取ServletContext对象
- ServletConfig提供了getServletContext()方法。
- ServletContext servletContext1 = this.getServletConfig().getServletContext();
- GenericServlet提供了getServletContext()方法。(推荐)
- ServletContext servletContext2 = this.getServletContext();
- HttpServletRequest提供了getServletContext()方法。(推荐)
- ServletContext servletContext3 = request.getServletContext();
- HttpSession提供了getServletContext()方法。
- ServletContext servletContext4 = request.getSession().getServletContext();
- 注意的点,我写在了代码的注释中!
/**
* 通过ServletConfig获取
* 注意:ServletConfig是生命在GenericServlet类中
*/
ServletContext servletContext1 = this.getServletConfig().getServletContext();
/**
* 通过GenericServlet获取(推荐使用)
* 注意:GenericServlet是HttpServlet的父类
*/
ServletContext servletContext2 = this.getServletContext();
/**
* 通过HttpServletRequest对象获取(推荐使用)
* 注意:ServletRequest是HTTPServlet的父类
*/
ServletContext servletContext3 = request.getServletContext();
/**
* 通过HttpSession对象获取
*/
ServletContext servletContext4 = request.getSession().getServletContext();
/**
* 打印四个对象并查看是否获取的是同一对象(答案在下方)
* <p>
* aaa@qq.com
* aaa@qq.com
* aaa@qq.com
* aaa@qq.com
*/
System.out.println(servletContext1);
System.out.println(servletContext2);
System.out.println(servletContext3);
System.out.println(servletContext4);
6.3 Servlet上下文的作用及特点
6.3.1 Servlet上下文的作用
- 获取当前项目的发起路径
- request.getServletContext().getRealPath("/");
- 获取容器的附加信息
- 获取Servlet信息
- request.getServletContext().getServerInfo()
- 获取上下文路径
- request.getServletContext().getContextPath()
- 获取请求路径
- request.getContextPath()
- 全局容器
- 将数据存储在ServletContext域中
- request.getServletContext().setAttribute(“username”, “Ziph”);
- 获取ServletContext域中的数据
- request.getServletContext().getAttribute(“username”)
- 把ServletContext域中的数据移除掉
- request.getServletContext().removeAttribute(“username”);
- 代码如下:
//第一个Servlet代码
/**
* 获取ServletContext对象
*/
ServletContext servletContext = request.getServletContext();
/**
* 获取当前项目的发布路径(这是我IDEA中的war包存放路径)
* D:\Code\web\testrequest\out\artifacts\reqweb_war_exploded\
*/
System.out.println(servletContext.getRealPath("/"));
/**
* 获取容器的附加信息
* 1.获取Servlet信息 : Apache Tomcat/8.5.45
* 2.获取上下文路径 : /reqweb
* 3.获取请求路径 : /reqweb
*/
System.out.println(servletContext.getServerInfo());
System.out.println(servletContext.getContextPath());
System.out.println(request.getContextPath());
/**
* 1.将数据存储到ServletContext域中
* 2.从域中获取数据并查看
* 3.从域中移除该数据并查看
*/
servletContext.setAttribute("username", "ziph");
/**
* 移除ServletContext域中的数据
*/
servletContext.removeAttribute("username");
//第二个Servlet代码
/**
* 获取ServletContext域中的数据
*/
System.out.println(request.getServletContext().getAttribute("username"));
6.3.2 Servlet上下文的特点
唯一性: 一个应用对应一个servlet上下文。
一直存在: 只要服务器不关闭或者应用不卸载,Servlet上下文就一直存在。
6.3.3 xml文件配置ServletContext参数
xml配置文件信息: (可以写多对参数)
<context-param>
<param-name>username</param-name>
<param-value>Ziph</param-value>
</context-param>
<context-param>
<param-name>password</param-name>
<param-value>123456</param-value>
</context-param>
Java代码:
/**
* 获取Servlet上下文参数
*/
String appname=application.getInitParameter("username");
String appversion=application.getInitParameter("password");
注意:配置文件是随着项目初始化而初始化的,我们是不可以人工干预该Servlet上下文参数的,这里指定的不可以干预是不可以移除该参数!
6.4 使用ServletContext域来完成站点访问次数案例
- 获取一个ServletContext对象
- 利用对象获取域中的一个count(计数器)
- 使用if判空,为空初始化count为次数1,并将访问次数存储到域中的count
- 如果不为空,那就开始计数,并将访问次数更新到域中的count
- 显示访问次数(响应给浏览器或控制台打印)
package com.mylifes1110.java.contextservlet;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet(name = "VisitsServlet", value = "/visits")
public class VisitsServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8");
ServletContext servletContext = request.getServletContext();
Integer count = (Integer) servletContext.getAttribute("count");
if (count == null) {
count = 1;
servletContext.setAttribute("count", count);
} else {
count++;
servletContext.setAttribute("count", count);
}
/**
* 浏览器显示访问站点次数
* 控制台显示访问站点次数
*/
response.getWriter().println("该站点访问次数为:" + count);
System.out.println("该站点访问次数为:" + count);
}
}
下一篇教科书系列【Servlet之Filter过滤器】,期待你的到来!谢谢大家!我将持续分享…