文章目录
交换会话ID:使用cookie
HttpSession
可以保存同一客户多个请求的会话状态。
HTTP协议使用的是无状态连接。浏览器与服务器建立连接,发送请求,得到响应,关闭连接。每次请求都是一个新的连接。因此,对容器而言,每次请求都源自新的客户。
那容器怎么知道客户是谁呢?
是这样的,对于客户的第一个请求,容器会生成唯一一个会话ID,并通过响应把它返回给客户。客户在以后的每个请求里都会发回这个会话ID,容器看到这个ID后,就会找到匹配的会话,并把这个会话和当前请求关联起来。
容器把会话ID作为响应的一部分返回给客户,客户把会话ID作为请求的一部分发回,那么,容器和客户是如何交换ID信息的呢?
——最常用最简单的方法,就是通过cookie。
HttpSession session = request.getSession()
,向请求要一个会话,也就是告诉容器想使用一个会话,接下来容器会负责 生成会话ID、创建新的Cookie对象、将会话ID放入Cookie对象中、将Cookie对象设置为响应的一部分 等工作;
对于后续的请求,同样HttpSession session = request.getSession()
,容器会从请求的Cookie对象中获取会话ID,然后找到匹配的会话,如果没有找到匹配的会话,则会新建一个会话,并将该会话与当前请求关联。至于会话到底是已经存在的,还是刚刚新建的,可以通过session.isNew()
来判断。如果确定只想使用已经存在的会话,可以调用getSession(false)
,这个方法要么返回null
,要么返回一个已经有的HttpSession
。
交换会话ID:使用URL重写
在服务器中调用request.getSession()
,容器就会尝试使用cookie。但是,如果客户(浏览器)没有启用cookie,它就会忽略Set-Cookie
响应首部,因此客户就不会加入会话,其后续的请求自然也不会发回带有会话ID的Cookie
请求首部,同时意味着在服务中调用getSession()
总会返回一个新的会话、session.isNew()
总返回true
。
容器会首先使用cookie来进行会话管理,但是如果cookie方法失败,比如客户没有启用cookie,容器则会转而“投奔”URL重写。当然,URL重写奏效的前提是对URL完成了编码,且URL编码只与响应有关,response.encodeURL(url)
或response.encodeRedirectURL(url)
。
会话管理
删除不必要会话
会话会占用服务器资源,怎么管理会话呢?比如,删除不必要的会话。
HttpSession session = request.getSession()
,首先我们来看下session
这个HttpSession
类实例有哪些方法。
方法名 | 作用 |
---|---|
getCreationTime() | 返回创建会话的时间 |
getLastAccessedTime() | 返回用户最后一次访问会话的时间 |
setMaxInactiveInterval() | 如果过去了指定的时间间隔,客户仍未对会话发出任何请求,会导致会话被撤销。这个方法可以用来减少服务器中的无用会话。setMaxInactiveInterval(0)或setMaxInactiveInterval(-1) 意味着会话永不超时 |
getMaxInactiveInterval() | 会话保持多久不活动仍能存活 |
invalidate() | 撤销会话 |
setAttribute(name,value) | 设置会话属性 |
getAttribute(name) | 获取会话属性 |
removeAttribute() | 删除会话属性 |
举个例子。
package com.example.web;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.util.*;
import com.example.model.*;
public class BeerSelect extends HttpServlet{
public void doPost(HttpServletRequest request,HttpServletResponse response) throws IOException,ServletException{
PrintWriter out = response.getWriter();
response.setContentType("text/html");
HttpSession session = request.getSession();
session.setAttribute("foo",10);
session.invalidate();
int foo = (int)session.getAttribute("foo");
out.println("foo is "+foo);
}
}
session.invalidate()
,撤销了会话,因此后面调用session.getAttribute("foo")
时会抛出运行时异常:java.lang.IllegalStateException
。
设置会话超时时间
虽然HttpSession
有这么多方法,但并不意味着需要我们去跟踪会话行为、撤销会话,因为容器会负责这些事情,我们只需要设置会话超时时间。
设置会话超时时间有两种方式。
- 第一种:在DD中设置会话超时时间,单位是“分钟”
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">
<servlet>
<servlet-name>Ch3 Beer</servlet-name>
<servlet-class>com.example.web.BeerSelect</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Ch3 Beer</servlet-name>
<url-pattern>/SelectBeer.do</url-pattern>
</servlet-mapping>
<session-config>
<session-timeout>15</session-timeout>
</session-config>
</web-app>
- 第二种:在程序中设置会话超时时间,单位是“秒”
public void doPost(HttpServletRequest request,HttpServletResponse response) throws IOException,ServletException{
//...
HttpSession session = request.getSession();
session.setMaxInactiveInterval(15*60);
//...
}
Cookie的其他用法
原先设计cookie是为了帮助支持会话状态,实际上,还可以使用cookie在客户和服务器之间交换一小段数据。服务器发送cookie给客户,客户在以后的每次请求里都返回cookie给服务器。
HttpServletRequest
getCookies()
服务器从客户端请求中获取cookie
HttpServletResponse
addCookie()
向响应增加一个CookieaddHeader()
向响应增加一个首部setHeader()
替换现有首部
Cookie
new Cookie(name,value)
创建一个新的Cookie对象setMaxAge()
设置cookie能在客户端存活多久getName()
getValue()
看一个例子:客户端从输入框中键入Username值,提交后发送请求到服务器,服务器中的servlet将该值保存在cookie中。当用户再次向服务器中任一servlet发送请求时,servlet都可以看到这个cookie。
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">
<servlet>
<servlet-name>CookieTester</servlet-name>
<servlet-class>com.example.CookieTest</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>CookieTester</servlet-name>
<url-pattern>/cookie-test.do</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>CookieChecker</servlet-name>
<servlet-class>com.example.CookieCheck</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>CookieChecker</servlet-name>
<url-pattern>/cookie-check.do</url-pattern>
</servlet-mapping>
</web-app>
<!-- form.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>form</title>
</head>
<body>
<form action="cookie-test.do" method="POST">
<label for="username">Username:</label>
<input type="text" id="username" name="username">
<input type="submit">
</form>
</body>
</html>
<!-- cookieresult.jsp -->
<html>
<body>
<a href="cookie-check.do">click me</a>
</body>
</html>
// CookieTest.java
package com.example;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
public class CookieTest extends HttpServlet{
public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException,ServletException{
response.setContentType("text/html");
String username = request.getParameter("username");
Cookie cookie = new Cookie("username",username);
cookie.setMaxAge(30*60);
response.addCookie(cookie);
RequestDispatcher view = request.getRequestDispatcher("cookieresult.jsp");
view.forward(request,response);
}
}
// CookieCheck.java
package com.example;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
public class CookieCheck extends HttpServlet{
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException,ServletException{
response.setContentType("text/html");
PrintWriter out = response.getWriter();
Cookie[] cookies = request.getCookies();
if(cookies!=null){
for(Cookie cookie: cookies){
if(cookie.getName().equals("username")){
String username = cookie.getValue();
out.println("username is "+ username);
break;
}
}
}
}
}
监听者们
HttpSessionListener
想知道一个Web应用中有多少个活动会话,可以使用HttpSessionListener
。
package com.example;
import javax.servlet.*;
public class SessionCounter implements HttpSessionListener{
static private int activeSessions;
public void sessionCreated(HttpSessionEvent e){
activeSessions++;
}
public void sessionDestroyed(HttpSessionEvent e){
activeSessions--;
}
public static void getActiveSessions(){
return activeSessions;
}
}
同时在部署描述文件中设置监听者。
<listener>
<listener-class>com.example.SessionCounter</listener-class>
</listener>
HttpSessionAttributeListener
如果希望在会话中增加、删除或替换属性时得到通知,可以使用HttpSessionAttributeListener
。
package com.example;
import javax.servlet.*;
public class AttributeListener implements HttpSessionAttributeListener{
public void attributeAdded(HttpSessionBindingEvent event){
String name = event.getName();
Object value = event.getValue();
}
public void attributeRemoved(HttpSessionBindingEvent event){
String name = event.getName();
Object value = event.getValue();
}
public void attributeReplaced(HttpSessionBindingEvent event){
String name = event.getName();
Object value = event.getValue();
}
}
同时在部署描述文件中配置。
<listener>
<listener-class>com.example.AttributeListener</listener-class>
</listener>
HttpSessionBindingListener
如果想在一个属性加入了会话时、属性从会话中删除时得到通知,可以使用HttpSessionBindingListener
。
package com.example;
import javax.servlet.*;
public class MyExample implements HttpSessionBindingListener{
public void valueBound(HttpSessionBindingEvent event){
}
public void valueUnbound(HttpSessionBindingEvent event){
}
}
HttpSessionListener
、HttpSessionAttributeListener
、HttpSessionActivationListener
,需要在部署描述文件中设置,容器才能发现和使用这些监听者,而HttpSessionBindingListener
不需要在部署描述文件中设置。
HttpSessionActivationListener
在一个分布式环境中,一个Web应用可能分布在多个JVM上,容器可能会为了完成负载均衡,将客户请求发送给多个JVM。这就意味着,指向ServletA的请求A在一个JVM中完成,而指向ServletA的请求B在另一个JVM中完成。
在一个分布式应用中,每个JVM上只有一个ServletContext
,每个JVM中的每个Servlet只有一个ServletConfig
,而对于一个应用的给定的会话ID,不论这个应用分布在多少个JVM中,都只有一个HttpSession
,也就是说,只有HttpSession
对象可以从一个JVM迁移到另一个JVM上。
除HttpSession
外,所有其他对象都会在另一个JVM(服务器)上复制,这就意味着,HttpSession
对象可能从一个JVM迁移到另一个JVM。
序列化对象时会调用writeObject()
,还原对象时会调用readObject()
,但会话迁移不一定会调用这些方法。所以,在会话迁移过程中,要想保存和恢复 属性的实例变量的状态,可以使用HttpSessionActivationListener
,调用其事件回调函数sessionDidActivate()
和sessionWillPassivate()
,类似writeObject()
和readObject()
。
package com.example;
import javax.servlet.*;
public class Example implements HttpSessionActivationListener{
public void sessionDidActivate(HttpSessionEvent event){
}
public void sessionWillPassivate(HttpSessionEvent event){
}
}
同时在部署描述文件中进行配置。
<listener>
<listener-class>com.example.Example</listener-class>
</listener>