# Session
session在Web开发环境下的语义又有了新的扩展,它的含义是指一类用来在客户端与服务器端之间保持状态的解决方案。有时候Session也用来指这种解决方案的存储结构。
### Session机制
session机制采用的是在服务器端保持 HTTP 状态信息的方案 。
除了使用Cookie,Web应用程序中还经常使用Session来记录客户端状态。Session是服务器端使用的一种记录客户端状态的机制,使用上比Cookie简单一些,相应的也增加了服务器的存储压力。
Session是另一种记录客户状态的机制,不同的是Cookie保存在客户端浏览器中,而Session保存在服务器上。客户端浏览器访问服务器的时候,服务器把客户端信息以某种形式记录在服务器上。这就是Session。客户端浏览器再次访问时只需要从该Session中查找该客户的状态就可以了。
如果说Cookie机制是通过检查客户身上的“通行证”来确定客户身份的话,那么Session机制就是通过检查服务器上的“客户明细表”来确认客户身份。Session相当于程序在服务器上建立的一份客户档案,客户来访的时候只需要查询客户档案表就可以了。
当程序需要为某个客户端的请求创建一个session时,服务器首先检查这个客户端的请求里是否包含了一个session标识(即sessionId),如果已经包含一个sessionId则说明以前已经为此客户创建过session,服务器就按照session id把这个session检索出来使用(如果检索不到,可能会新建一个,这种情况可能出现在服务端已经删除了该用户对应的session对象,但用户人为地在请求的URL后面附加上一个JSESSION的参数)。如果客户请求不包含sessionId,则为此客户创建一个session并且生成一个与此session相关联的sessionId,这个session id将在本次响应中返回给客户端保存。
***
session机制演示图
![](https://box.kancloud.cn/e20fff0016efd6e01c4132f838763a15_836x533.png)
***
代码演示
~~~
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<%= session.getId() %>
</body>
</html>
~~~
在开发者工具中查看,第一发出时请求响应头当中有Set-Cooie
![](https://box.kancloud.cn/c0c46eed1fdd2c9437e997779dac5394_460x123.png)
再次请求时(刷新),响应头当中将没有Set-Cookie,而请求头当中将有Cookie发回给服务器,
![](https://box.kancloud.cn/5572088fbe3a9af110db90d0ad448d4f_457x213.png)
两个Cookie完全一样,以后的每次请求都会找到那个session对象,完成跟踪
### 浏览器禁用Cookie
浏览器禁用Cookie后,每次请求客户端都会得到一个新的Cookie,因为请求中将不再包含Cookie,服务器的session对象会再为这个请求新创建一个JESSIONID通过Cookie返回给了客户端浏览器
***
### 保存session id的几种方式
保存session id的方式可以采用cookie,这样在交互过程中浏览器可以自动的按照规则把这个标识发送给服务器。
由于cookie可以被人为的禁用,必须有其它的机制以便在cookie被禁用时仍然能够把session id传递回服务器,经常采用的一种技术叫做URL重写,就是把session id附加在URL路径的后面,附加的方式也有两种,一种是作为URL路径的附加信息,另一种是作为查询字符串附加在URL后面。网络在整个交互过程中始终保持状态,就必须在每个客户端可能请求的路径后面都包含这个session id。
***
### Session cookie
session通过SessionID来区分不同的客户, session是以cookie或URL重写为基础的,默认使用cookie来实现,系统会创造一个名为JSESSIONID的输出cookie,这称之为session cookie,以区别persistent cookies(也就是我们通常所说的cookie),session cookie是存储于浏览器内存中的,并不是写到硬盘上的,通常看不到JSESSIONID,但是当把浏览器的cookie禁止后,web服务器会采用URL重写的方式传递Sessionid,这时地址栏看到
session cookie针对某一次会话而言,会话结束session cookie也就随着消失了,而persistent cookie只是存在于客户端硬盘上的一段文本。
关闭浏览器,只会是浏览器端内存里的session cookie消失,但不会使保存在服务器端的session对象消失,同样也不会使已经保存到硬盘上的持久化cookie消失。
#### 持久化session cookie
~~~
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<%= session.getId() %>
<%
Cookie cookie = new Cookie("JESSIONID",session.getId());
cookie.setMaxAge(20);
%>
</body>
</html>
~~~
***
### HttpSession的生命周期
①什么时候创建HttpSession对象
**误区:浏览器访问服务端的任意一个JSP或Servlet,服务器就立即创建一个HttpSession对象**
1.page指令中session属性设为false,无法使用session对象
~~~
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8" session="false"%>
~~~
2.直接访问session.jsp无法获取到session对象。先访问另一个a.jsp,然后在访问session.jsp,session对象会获取到,但并不是session.jsp创建的session对象
~~~
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8" session="false"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<!-- 如果有一个与当前页面有关的session就返回,否则返回null -->
<%= request.getSession(false) %>
</body>
</html>
~~~
看上面两段代码得出结论:
A.若当前的JSP(或Servlet)是客户端访问的第一个资源,且JSP的page指令的session属性为false,则服务器就不会为JSP创建一个HttpSession对象;
若当前JSP不是客户端访问的第一个资源。且其他页面已经创建了一个HttpSession对象,则当前JSP页面会返回上一个会话的HttpSession对象,而不会创建一个新的HttpSession对象
B.session="false"表示当前JSP页面禁用session隐含变量,但可以使用其他显式的HttpSession对象
C.对于Servlet而言,若Servlet是客户端访问的第一个WEB应用资源,则只有调用了request.getSession()或request.getSession(true)才会创建HttpSession对象
②什么时候销毁HttpSession对象
**误区:关闭浏览器就销毁了session对象**
A.调用invalidate()方法,该方法使HttpSession时效
~~~
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<%
session.invalidate();
%>
<%= session.getId() %>
</body>
</html>
~~~
B.服务器卸载了当前WEB应用
C.超出session过期时间
~~~
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<%
session.invalidate();
// 设置session的过期时间,以秒为单位
session.setMaxInactiveInterval(5);
%>
<!-- 获取session的过期时间,默认1800秒,即30分钟 -->
<%= session.getMaxInactiveInterval() %>
<%= session.getId() %>
</body>
</html>
~~~
在conf目录下的web.xml文件中也可以修改session的过期时间(全局的,所有的session的过期时间,若想单独设置还是像上面那样单独设置)
~~~
<session-config>
<session-timeout>30</session-timeout>
</session-config>
~~~
***
课堂实例--1
session完成重新登录
login.jsp
~~~
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
SessionId:<%= session.getId() %>
<br><br>
isNew:<%= session.isNew() %>
<br><br>
maxInactiveInternal:<%= session.getMaxInactiveInterval() %>
<br><br>
createTime:<%= session.getCreationTime() %>
<br><br>
lastAccessTime:<%= session.getLastAccessedTime() %>
<br><br>
<%
Object username = session.getAttribute("username");
if(username == null){
username = "";
}
%>
<form action="hello.jsp" method="post">
username:<input type="text" name="username" value="<%= username %>">
<input type="submit" value="Submit">
</form>
</body>
</html>
~~~
hello.jsp
~~~
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
SessionId:<%= session.getId() %>
<br><br>
isNew:<%= session.isNew() %>
<br><br>
maxInactiveInternal:<%= session.getMaxInactiveInterval() %>
<br><br>
createTime:<%= session.getCreationTime() %>
<br><br>
lastAccessTime:<%= session.getLastAccessedTime() %>
<br><br>
Hello:<%= request.getParameter("username") %>
<br><br>
<%
session.setAttribute("username", request.getParameter("username"));
%>
<a href="login.jsp">重新登录</a>
<a href="logout.jsp">注销</a>
</body>
</html>
~~~
logout.jsp
~~~
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
SessionId:<%= session.getId() %>
<br><br>
isNew:<%= session.isNew() %>
<br><br>
maxInactiveInternal:<%= session.getMaxInactiveInterval() %>
<br><br>
createTime:<%= session.getCreationTime() %>
<br><br>
lastAccessTime:<%= session.getLastAccessedTime() %>
<br><br>
Bye:<%= session.getAttribute("username") %>
<br><br>
<%
session.invalidate();
%>
<a href="login.jsp">重新登录</a>
</body>
</html>
~~~
***
课堂实例--2
完成用户登录
index.jsp
~~~
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<form action="session.jsp" method="post">
username:<input type="text" name="username"> <br> <br>
password:<input type="password" name="password">
<input type="submit" value="Submit">
</form>
</body>
</html>
~~~
session.jsp
~~~
<%@page import="com.neusoft.mvcapp.dao.Person"%>
<%@page import="java.text.SimpleDateFormat"%>
<%@page import="java.util.Date"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<% // 日期格式化器
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
Person[] persons = {
// 基础数据,保存三个人的信息
new Person("Tomcat", "password1", 34, dateFormat.parse("1982-01-01")),
new Person("HelloKitty", "hellokitty", 23, dateFormat.parse("1984-02-21")),
new Person("Garfield", "garfield_pass", 23, dateFormat.parse("1994-09-12")),
};
// 要显示的消息
String message = "";
for (Person person : persons) {
// 遍历基础数据,验证账号、密码
// 如果用户名正确且密码正确
if (person.getName().equalsIgnoreCase(request.getParameter("username"))
&& person.getPassword().equals(request.getParameter("password")))
{
// 登录成功,设置将用户的信息以及登录时间保存到Session
session.setAttribute("person", person); // 保存登录的Person
session.setAttribute("loginTime", new Date()); // 保存登录的时间
response.sendRedirect(request.getContextPath() + "/20160123/welcome.jsp");
return;
}
}
// 登录失败
message = "用户名密码不匹配,登录失败。";
%>
<%= message %>
</body>
</html>
~~~
welcome.jsp
~~~
<%@page import="java.util.Date"%>
<%@page import="com.neusoft.mvcapp.dao.Person"%>
<%@page import="java.text.SimpleDateFormat"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<%
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); // 日期格式化器
Person person = (Person) session.getAttribute("person"); // 获取登录的person
Date loginTime = (Date) session.getAttribute("loginTime"); // 获取登录时间
if (person != null) {
%>
<table border="2" cellpadding="10" cellspacing="0">
<tr>
<td>您的姓名:</td>
<td><%=person.getName()%></td>
</tr>
<tr>
<td>登录时间:</td>
<td><%=loginTime%></td>
</tr>
<tr>
<td>您的年龄:</td>
<td><%=person.getAge()%></td>
</tr>
<tr>
<td>您的生日:</td>
<td><%=dateFormat.format(person.getBirthday())%></td>
</tr>
</table>
<%
}
%>
</body>
</html>
~~~
***
### 利用URL重写实现Session跟踪
如果浏览器禁用了Cookie,如何跟踪session
login.jsp
~~~
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
SessionId:<%= session.getId() %>
<br><br>
isNew:<%= session.isNew() %>
<br><br>
maxInactiveInternal:<%= session.getMaxInactiveInterval() %>
<br><br>
createTime:<%= session.getCreationTime() %>
<br><br>
lastAccessTime:<%= session.getLastAccessedTime() %>
<br><br>
<%
Object username = session.getAttribute("username");
if(username == null){
username = "";
}
%>
<form action="<%=response.encodeURL("hello.jsp") %>" method="post">
username:<input type="text" name="username" value="<%= username %>">
<input type="submit" value="Submit">
</form>
</body>
</html>
~~~
hello.jsp
~~~
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
SessionId:<%= session.getId() %>
<br><br>
isNew:<%= session.isNew() %>
<br><br>
maxInactiveInternal:<%= session.getMaxInactiveInterval() %>
<br><br>
createTime:<%= session.getCreationTime() %>
<br><br>
lastAccessTime:<%= session.getLastAccessedTime() %>
<br><br>
Hello:<%= request.getParameter("username") %>
<br><br>
<%
session.setAttribute("username", request.getParameter("username"));
%>
<a href="<%=response.encodeURL("login.jsp") %>">重新登录</a>
<a href="<%=response.encodeURL("logout.jsp") %>">注销</a>
</body>
</html>
~~~
此时查看网页源代码可以发现
![](https://box.kancloud.cn/d9353673c598d1072cac99b734650e3d_527x60.png)
HttpServletResponse接口中定义了两个用于完成URL重写方法:
encodeURL方法
encodeRedirectURL方法
***
课堂实例--2
Session的典型案例:简易版购物车
***
课堂实例--3
Session的典型案例:一次性验证码
使用如下现成的一个类,直接复制即可
ValidateColorServlet
~~~
package com.neusoft.javaweb.checkcode;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Random;
import javax.imageio.ImageIO;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/validateColorServlet")
public class ValidateColorServlet extends HttpServlet {
//设置验证图片的宽度, 高度, 验证码的个数
private int width = 152;
private int height = 40;
private int codeCount = 4;
//验证码字体的高度
private int fontHeight = 4;
//验证码中的单个字符基线. 即:验证码中的单个字符位于验证码图形左上角的 (codeX, codeY) 位置处
private int codeX = 0;
private int codeY = 0;
//验证码由哪些字符组成
char [] codeSequence = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz23456789".toCharArray();
//初始化验证码图形属性
public void init(){
fontHeight = height - 2;
codeX = width / (codeCount + 2);
codeY = height - 4;
}
public void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//定义一个类型为 BufferedImage.TYPE_INT_BGR 类型的图像缓存
BufferedImage buffImg = null;
buffImg = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);
//在 buffImg 中创建一个 Graphics2D 图像
Graphics2D graphics = null;
graphics = buffImg.createGraphics();
//设置一个颜色, 使 Graphics2D 对象的后续图形使用这个颜色
graphics.setColor(Color.WHITE);
//填充一个指定的矩形: x - 要填充矩形的 x 坐标; y - 要填充矩形的 y 坐标; width - 要填充矩形的宽度; height - 要填充矩形的高度
graphics.fillRect(0, 0, width, height);
//创建一个 Font 对象: name - 字体名称; style - Font 的样式常量; size - Font 的点大小
Font font = null;
font = new Font("", Font.BOLD, fontHeight);
//使 Graphics2D 对象的后续图形使用此字体
graphics.setFont(font);
graphics.setColor(Color.BLACK);
//绘制指定矩形的边框, 绘制出的矩形将比构件宽一个也高一个像素
graphics.drawRect(0, 0, width - 1, height - 1);
//随机产生 15 条干扰线, 使图像中的认证码不易被其它程序探测到
Random random = null;
random = new Random();
graphics.setColor(Color.GREEN);
for(int i = 0; i < 30; i++){
int x = random.nextInt(width);
int y = random.nextInt(height);
int x1 = random.nextInt(20);
int y1 = random.nextInt(20);
graphics.drawLine(x, y, x + x1, y + y1);
}
//创建 randomCode 对象, 用于保存随机产生的验证码, 以便用户登录后进行验证
StringBuffer randomCode;
randomCode = new StringBuffer();
for(int i = 0; i < codeCount; i++){
//得到随机产生的验证码数字
String strRand = null;
strRand = String.valueOf(codeSequence[random.nextInt(36)]);
randomCode.append(strRand);
//用随机产生的颜色将验证码绘制到图像中
graphics.setColor(Color.BLUE);
graphics.drawString(strRand, (i + 1)* codeX, codeY);
}
request.getSession().setAttribute("CHECK_CODE", randomCode.toString());
//禁止图像缓存
response.setHeader("Pragma", "no-cache");
response.setHeader("Cache-Control", "no-cache");
response.setDateHeader("Expires", 0);
//将图像输出到输出流中
ServletOutputStream sos = null;
sos = response.getOutputStream();
ImageIO.write(buffImg, "jpeg", sos);
sos.close();
}
}
~~~
check.jsp
~~~
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<form action="checkCodeServlet" method="post">
username:<input type="text" name="username">
checkCode:<input type="text" name="check">
<img src="${pageContext.request.contextPath }/validateColorServlet">
<input type="submit" value="Submit">
</form>
</body>
</html>
~~~
CheckCodeServlet
~~~
package com.neusoft.javaweb.checkcode;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Servlet implementation class CheckCodeServlet
*/
@WebServlet("/checkCodeServlet")
public class CheckCodeServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String strCheckCode = request.getParameter("check");
String checkCode = (String)request.getSession().getAttribute("CHECK_CODE");
if(strCheckCode.equalsIgnoreCase(checkCode)) {
response.sendRedirect("hello.jsp");
}else {
response.setContentType("text/html");
response.setCharacterEncoding("utf-8");
PrintWriter pr = response.getWriter();
pr.println("验证码错误");
}
}
}
~~~
***
### 相对路径和绝对路径
开发时建议编写绝对路径,相对路径可能会出现问题
a.jsp
~~~
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>A Page</h1>
<a href="path/b.jsp">To B Page</a>
</body>
</html>
~~~
b.jsp
~~~
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>B Page</h1>
<a href="c.jsp">To C Page</a>
</body>
</html>
~~~
c.jsp
~~~
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>C Page</h1>
<a href="../a.jsp">To A Page</a>
</body>
</html>
~~~
运行a.jsp,能够正确完成跳转
但有些时候我们不是直接申请一个JSP资源,而是通过Servlet然后进行请求转发
a.jsp
~~~
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>A Page</h1>
<a href="testsServlet">To B Page</a>
</body>
</html>
~~~
TestsServlet
~~~
package com.neusoft.shopping;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Servlet implementation class TestsServlet
*/
@WebServlet("/testsServlet")
public class TestsServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
List<String> cities = Arrays.asList("北京","阜新","阜新");
request.setAttribute("cities", cities);
request.getRequestDispatcher("/path/b.jsp").forward(request, response);
}
}
~~~
b.jsp
~~~
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>B Page</h1>
cities:<%= request.getAttribute("cities") %>
<br>
<a href="c.jsp">To C Page</a>
</body>
</html>
~~~
再运行a.jsp,b.jsp无法正确完成跳转,因为a.jsp发送请求到Servlet,Servlet的路径是WEB应用的根目录,而b.jsp中的超链接相对路径就是相对Servlet的相对路径,自然找不到c.jsp,所以开发时一定要使用绝对路径
JavaWEB开发中的“/”
①当前WEB应用的根路径:http://localhost:8080/ykbbs/
--请求转发时
--web.xml文件中映射Servlet访问路径
②WBN站点的根目录:http://localhost:8080/
--超链接
--表单中的action
如何记忆:
若/需要交给Servlet容器来处理,则/就代表WEB应用根目录(在其前面加上getContextPath即可-->request,applicaion都可以)
若/需要交给浏览器处理,则/就代表WEB站点目录
***
### 避免表单的重复提交
①表单重复提交的情况
(1)在表单提交到一个Servlet,而Servlet又通过请求转发的方式响应一个JSP或HTML页面,此时地址栏还保留着Servlet的哪个路径,在响应页面点击“刷新”;
(2)在响应页面没有到达时重复点击“提交按钮”
(3)点击“返回”,在点击“提交”
②不算表单重复提交的情况
点击“返回”,“刷新”原表单页面,再“提交”
token.jsp
~~~
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<form action="<%= request.getContextPath() %>/tokenServlet" method="post">
Name:<input type="text" name="name">
<input type="submit" value="Submit">
</form>
</body>
</html>
~~~
TokenServlet
~~~
package com.neusoft.mvcapp.servlet;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Servlet implementation class TokenServlet
*/
@WebServlet("/tokenServlet")
public class TokenServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
String name = request.getParameter("name");
System.out.println("name:"+name);
request.getRequestDispatcher("/token/success.jsp").forward(request, response);
}
}
~~~
success.jsp
~~~
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>Success Page</h1>
</body>
</html>
~~~
有延迟的情况下,表单重复提交会比较的严重,会加重服务器的负担
③如何避免表单重复提交
在表单中做一个标记,提交到Servlet时,检查标记是否存在且是否和预定义的标记一直,若一致,则受理请求,并销毁标记,若不一致或没有标记,则响应“重复提交”。
token.jsp
~~~
<%@page import="java.util.Date"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<%
String tokenValue = new Date().getTime()+"";
session.setAttribute("token",tokenValue);
%>
<form action="<%= request.getContextPath() %>/tokenServlet" method="post">
<!-- 仅用一个隐藏域,不能实现标记功能 -->
<input type="hidden" value="<%= tokenValue %>" name="token">
Name:<input type="text" name="name">
<input type="submit" value="Submit">
</form>
</body>
</html>
~~~
tokenServlet
~~~
package com.neusoft.mvcapp.servlet;
import java.io.IOException;
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 javax.servlet.http.HttpSession;
/**
* Servlet implementation class TokenServlet
*/
@WebServlet("/tokenServlet")
public class TokenServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
HttpSession session = request.getSession();
Object token = session.getAttribute("token");
String tokenValue = request.getParameter("token");
if(token != null && token.equals(tokenValue)) {
session.removeAttribute("token");
}else {
response.sendRedirect(request.getContextPath()+"/token/tokened.jsp");
return;
}
String name = request.getParameter("name");
System.out.println("name:"+name);
// request.getRequestDispatcher("/token/success.jsp").forward(request, response);
response.sendRedirect(request.getContextPath()+"/token/success.jsp");
}
}
~~~
success.jsp
~~~
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>Success Page</h1>
</body>
</html>
~~~
tokened.jsp
~~~
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>Sorry,已经提交过了</h1>
</body>
</html>
~~~
***
### Session小结:
(1)简介
Session是另一种记录客户状态的机制,不同的是Cookie保存在客户端浏览器中,而Session保存在服务器上。客户端浏览器访问服务器的时候,服务器把客户端信息以某种形式记录在服务器上。这就是Session。
Session由服务端生成,保存在服务器的内存、缓存、硬盘或数据库中。
(2)工作原理
① 创建Session
当用户访问到一个服务器,如果服务器启用Session,服务器就要为该用户创建一个SESSION,在创建这个SESSION的时候,服务器首先检查这个用户发来的请求里是否包含了一个SESSION ID,如果包含了一个SESSION ID则说明之前该用户已经登陆过并为此用户创建过SESSION,那服务器就按照这个SESSION ID把这个SESSION在服务器的内存中查找出来(如果查找不到,就有可能为他新创建一个),如果客户端请求里不包含有SESSION ID,则为该客户端创建一个SESSION并生成一个与此SESSION相关的SESSION ID。这个SESSION ID是唯一的、不重复的、不容易找到规律的字符串,这个SESSION ID将被在本次响应中返回到客户端保存,而保存这个SESSION ID的正是COOKIE,这样在交互过程中浏览器可以自动的按照规则把这个标识发送给服务器。
② 使用Session
我们知道在IE中,我们可以在工具的Internet选项中把Cookie禁止,那么会不会出现把客户端的Cookie禁止了,那么可以使用一种技术叫做URL重写,**就是把Session id直接附加在URL路径的后面**
(3)作用
Session的根本作用就是在服务端存储用户和服务器会话的一些信息。典型的应用有:
1、判断用户是否登录。
2、购物车功能。
(4)生命周期
Session在用户第一次访问服务器的时候自动创建(特殊情况,session属性为false,以及是否是第一个访问资源)。需要注意只有访问JSP、Servlet等程序时才会创建Session,只访问HTML等静态资源并不会创建Session。如果尚未生成Session,也可以使用request.getSession(true)强制生成Session。
Session生成后,只要用户继续访问,服务器就会更新Session的最后访问时间,并维护该Session。用户每访问服务器一次,无论是否读写Session,服务器都认为该用户的Session“活跃(active)”了一次。
(5)Session有效期
由于会有越来越多的用户访问服务器,因此Session也会越来越多。为防止内存溢出,服务器会把长时间内没有活跃的Session从内存删除。这个时间就是Session的超时时间。如果超过了超时时间没访问过服务器,Session就自动失效了。
Session的超时时间为maxInactiveInterval属性,可以通过对应的getMaxInactiveInterval()获取,通过setMaxInactiveInterval(longinterval)修改。
Session的超时时间也可以在web.xml中修改。另外,通过调用Session的invalidate()方法可以使Session失效。
(6)常用方法
设置属性:session.setAttribute(String name,Object obj)
获取属性:session.getAttribute(String name)
获取SessionID:session.getId()
设置最大时效: setMaxInactiveInterval(int second)
使Session时效:invalidate()
***
### Cookie和Session的区别
#### 1、存放位置不同
Cookie保存在客户端,Session保存在服务端。
#### 2 、存取方式的不同
Cookie中只能保管ASCII字符串,假如需求存取Unicode字符或者二进制数据,需要先进行编码。Cookie中也不能直接存取Java对象。若要存储略微复杂的信息,运用Cookie是比较艰难的。
而Session中能够存取任何类型的数据,包括而不限于String、Integer、List、Map等。Session中也能够直接保管Java Bean乃至任何Java类,对象等,运用起来十分便当。能够把Session看做是一个Java容器类。
#### 3、安全性(隐私策略)的不同
Cookie存储在浏览器中,对客户端是可见的,客户端的一些程序可能会窥探、复制以至修正Cookie中的内容。而Session存储在服务器上,对客户端是透明的,不存在敏感信息泄露的风险。 假如选用Cookie,比较好的方法是,敏感的信息如账号密码等尽量不要写到Cookie中。最好是像Google、Baidu那样将Cookie信息加密,提交到服务器后再进行解密,保证Cookie中的信息只要本人能读得懂。而假如选择Session就省事多了,反正是放在服务器上,Session里任何隐私都能够有效的保护。
#### 4、有效期上的不同
只需要设置Cookie的过期时间属性为一个很大很大的数字,Cookie就可以在浏览器保存很长时间。 由于Session依赖于名为JSESSIONID的Cookie,而Cookie JSESSIONID的过期时间默许为30分钟,但是一旦关闭了浏览器(一次会话结束),该Session就会失效。
#### 5、对服务器造成的压力不同
Session是保管在服务器端的,每个用户都会产生一个Session。假如并发访问的用户十分多,会产生十分多的Session,耗费大量的内存。而Cookie保管在客户端,不占用服务器资源。假如并发阅读的用户十分多,Cookie是很好的选择。
- 第一章 配置和安装Tomcat
- 第二章 Servlet(一)
- 第三章 Servlet(二)
- 练习 一 . Servlet配置级获取初始化参数
- 第四章 JSP(一)
- 第五章 JSP(二)
- 第六章 MVC设计模式
- 第七章 Cookie
- 第八章 Session
- 练习 二 . 简易版购物车
- 第九章 EL表达式
- 第十章 JSTL
- 第十一章 过滤器
- 第十二章 监听器
- 第十三章 文件的上传与下载
- 复习总结
- 如何手动启动Tomcat
- 如何修改Tomcat端口号
- 如何在web.xml中配置Servlet
- Servlet生命周期
- load-on-startup参数
- Servlet映射路径
- POST和GET的区别
- JSP中9个隐式对象及功能
- 请求转发及请求重定向的区别
- JSP指令有哪些
- 简述对MVC设计模式的理解
- 简述Cookie机制
- 简述Session机制
- HttpSession的生命周期
- Cookie和Session有什么区别
- 简述创建过滤器步骤
- 过滤器经典案例--统一编码字符集
- getParameter与getAttribute的区别
- JSP页面中可以包含哪些元素
- web应用中,是如何跟踪用户的
- InteliJ创建web项目