前言
时隔一个月,我们来继续学习JavaWeb,今天来学习web的会话技术。
会话技术
概念
一次会话,即浏览器第一次给服务器资源发送请求,会话建立,直到有一方断开为止。
功能
在一次会话的多次请求间共享数据
方式
- 客户端会话技术:Cookie
- 服务器端会话技术:Session
注意:这个名是Java中的名字,可能在别的地方就换了名字了
Cookie
概念
Cookie是一门客户端会话技术,
步骤
- 创建Cookie对象,绑定数据
- new Cookie(String name, String value)
- 发送Cookie对象
- void response.addCookie(Cookie cookie)
- 获取Cookie,拿到数据
- Cookie[] request.getCookies()
实践
新建一个JavaEE项目,web模板,下面是包pers.luoluo.cookie下类的内容:
提一下,在IDEA下配置快速Servlet模板:
点开设置=》文件和代码模板=》其他=》web=》Java Code Template =》Servlet Annotated Class。
原本内容:
@javax.servlet.annotation.WebServlet(name = "${Entity_Name}") public class ${Class_Name} extends javax.servlet.http.HttpServlet { protected void doPost(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException, java.io.IOException { } protected void doGet(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException, java.io.IOException { } }
改为:
@javax.servlet.annotation.WebServlet("/${Class_Name}") public class ${Class_Name} extends javax.servlet.http.HttpServlet { protected void doPost(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException, java.io.IOException { } protected void doGet(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException, java.io.IOException { doPost(request,response); } }
这样就直接将文件名作为对应的web目录,并且get方法也可以post处理。
WebServlet注解就代替了我们在web.xml中注册了。
好了,我们来新建包下的Servlet文件,
然后我们来做两个Servlet:
public class CookieDemo extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//1.创建Cookie对象
Cookie c=new Cookie("msg","hello");
//2.发送Cookie
response.addCookie(c);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request, response);
}
}
public class CookieDemo2 extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//3.获取Cookie
Cookie[] cookies = request.getCookies();
if(cookies!=null) {
for (Cookie co : cookies) {
System.out.println(co.getName());
System.out.println(co.getValue());
}
}else{
System.out.println("没有得到Cookies");
}
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request, response);
}
}
先访问第一个Servlet,然后我们再访问第二个,就会发现控制台打印了一些内容:
JSESSIONID
4922E953525AE64A45F4AA9A19BB72B1
msg
hello
Idea-57b4784d
62f72ff5-7097-467f-89d9-e99f767d338d
其中,中间那俩就是我们设置的Cookie了
另外第一个JSESSIONID是Servlet容器(Tomcat)带有的Cookie,详情可以看这里
Idea-57b4784d是IDEA带有的一个Cookie
Cookie原理
Cookie是基于响应头set-cookie和请求头cookie实现。
cookie的补充
发送多个cookie
//1.创建Cookie对象
Cookie c1=new Cookie("msg","hello");
Cookie c2=new Cookie("ads","there");
//2.发送Cookie
response.addCookie(c1);
response.addCookie(c2);
很简单
cookie的有效期
- 默认情况下,当浏览器关闭,Cookie数据被销毁
- 持久化存储:
- setMaxAge(int seconds)
- seconds为正数:将Cookie数据写到硬盘的文件中。持久化存储。seconds为cookie的存储时间。
- seconds为负数:默认情况(当浏览器关闭,Cookie数据被销毁)。
- seconds为零:删除持久化的cookie信息
- setMaxAge(int seconds)
cookie对中文的兼容
在tomcat 8.0之前,cookie中不能直接存储中文数据,需要对中文数据进行转码(一般采用URL编码)。
在tomcat 8.0之后,cookie可以使用中文。
cookie是否共享
假如在一个tomcat服务器中,部署了多个web项目,那么在这些web项目中cookie能不能共享?
- setPath(String path):设置cookie的获取范围。默认情况下会设置当前的虚拟目录。
如果要共享,则可以将path设置为“/”。
cookie 的作用范围:只能作用于当前目录跟当前的子目录, 不能作用于上一级的目录,/代表当前站点的根目录,但是可以通过setPath() 来设置作用范围,request.getContentType() 代表项目根目录。
对于不同的tomcat来说,也是可以实现cookie的共享:
- setDomain(String path):如果设置一级域名相同,那么多个服务器之间cookie可以共享
- setDomain(“.baidu.com”),则tieba.baidu.com和news.baidu.com中cookie可以共享。
Cookie的特点和作用
特点
- cookie存储数据在客户端浏览器
- 浏览器对于单个cookie的大小有限制(一般:4kb) 以及 对同一个域名下的总cookie数量也有限制(一般:20个)
一般浏览器下都可以看到各个网站中cookie的信息。
作用
- cookie一般用于存储少量的不太敏感的数据
- 在不登录的情况下,完成服务器对客户端的身份识别(自动登录、保存设置)
案例
我们来实现一个案例,要求是记录上一次的访问时间。
具体需求:访问一个servlet,如果是第一次访问,则提示:您好,欢迎您首次访问。
如果不是第一次访问,则提示:欢迎回来,您上次访问时间为:显示时间字符串。
@WebServlet("/CookieTest")
public class CookieTest extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//设置响应的消息体的数据格式以及编码
response.setContentType("text/html;charset=utf-8");
//标记变量
boolean flag=false;
//1.获取所有的cookie
Cookie[] cookies = request.getCookies();
//2.遍历cookie数组
if(cookies!=null&&cookies.length>0){
for(Cookie cookie:cookies) {
//3.获取cookie的名称
String name = cookie.getName();
//4.判断名称是否是lastTime
if("lastTime".equals(name)){
flag=true;
//获取Cookie的value时间
String value= URLDecoder.decode(cookie.getValue(),"utf-8");
response.getWriter().write(
"<div style='text-align:center'>" +
"<h2>欢迎回来,您上次访问时间为:"+value+
"</h2></div>");
//设置cookie的value
//获取当前时间的字符串,重新设置Cookie的值,重新发送cookie
Date date=new Date();
//设置时间格式
SimpleDateFormat sdf=
new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
String str_date=sdf.format(date);
cookie.setValue(URLEncoder.encode(str_date,"utf-8"));
//设置cookie的存活时间(一个月)
cookie.setMaxAge(60*60*24*30);
//响应传回去cookie,因为key相同,故客户端中会覆盖上一次的value
response.addCookie(cookie);
}
}
}
if(cookies==null||cookies.length==0||!flag){
//第一次访问
response.getWriter().write(
"<div style='text-align:center'>" +
"<h2>首次访问,欢迎光临"+
"</h2></div>");
//设置cookie的value
//获取当前时间的字符串,重新设置Cookie的值,重新发送cookie
Date date=new Date();
//设置时间格式
SimpleDateFormat sdf=
new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
String str_date=URLEncoder.encode(sdf.format(date),"utf-8");
Cookie cookie=new Cookie("lastTime",str_date);
//设置cookie的存活时间(一个月)
cookie.setMaxAge(60*60*24*30);
//响应传回去cookie,因为key相同,故客户端中会覆盖上一次的value
response.addCookie(cookie);
}
}
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doPost(request, response);
}
}
Session
学完了我们客户端会话技术——Cookie,我们再来学习对服务端的会话技术——Session。
概念
服务端会话技术,在一次会话的多次请求间共享数据,将数据保存在服务器端的对象中。
入门
在Javaweb中我们主要是利用HttpSession对象,这也正是我们的jsp四大域对象之一:
HttpSession对象:
- Object getAttribute(String name)
- void setAttribute(String name,Object value)
- void removeAttribute(String name)
代码:
@WebServlet("/SessionTest")
public class SessionTest extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//1.获取session
HttpSession session = request.getSession();
//2.存储数据
session.setAttribute("msg","hello session");
}
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doPost(request, response);
}
}
@WebServlet("/SessionTest2")
public class SessionTest2 extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//1.获取session
HttpSession session = request.getSession();
//2.获取数据
Object msg = session.getAttribute("msg");
System.out.println(msg);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doPost(request, response);
}
}
先访问SessionTest,再访问SessionTest2,控制台就会打印hello session。
原理
我们发现,程序中得到的session只有一个,那么服务器是如何保证只有一个session的呢?
利用Cookie,即可以说 Session依赖于Cookie
当客户端第一次请求服务端的时候,服务端会创建一个新的Session对象,设置ID为xxxxxxx,并给客户端响应一个Cookie,JSESSIONID=xxxxxxx,后来在本次会话中,当客户端再次请求服务端,便会发送这个Cookie,然后服务端的request.getSession()就会根据Cookie找到ID为xxxxxxx的那个Session对象,如此一来,就保证了Session的唯一性。
细节补充
断开一次客户端
- 当客户端关闭后,服务端不关闭,客户端再连接,默认情况下,两次的session不是同一个。
解决方法:
手动给他写JSESSIONID,设置持久化在本地
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
HttpSession session = request.getSession();
Cookie c=new Cookie("JSESSIONID",session.getId());
c.setMaxAge(60*60); //一个小时
response.addCookie(c);
}
断开一次服务端
- 当服务端关闭后,客户端不关闭,客户端再连接,两次的session不是同一个。
session存在于服务端中,服务端关闭,session自然就销毁了。
不过,虽然两次的session不是同一个,但是要确保数据的不丢失。
这里我们介绍两个东西
-
- session的钝化
- 在服务器正常关闭之前,将session对象序列化到硬盘上
- session的活化
- 在服务器启动后,将session文件转化为内存中的session对象即可
- session的钝化
值得一提的是,Tomcat已经牛逼到可以帮助我们自动实现session的钝化和活化,IDEA只能实现session的钝化,而原生的Tomcat可以实现钝化和活化两个过程。
我们来做个试验,找到你的IDEA的项目目录下的out目录,找到打包出来的文件,将jsp和WEB-INF目录的那一层目录的上一层目录直接复制到外边,然后将这个粘贴的目录先重命名,再进行压缩打包,打包完成后修改一下后缀为.war,例如我的最后叫web.war。
然后将这个文件移动到Tomcat的目录的webapps目录下,如果现在开着tomcat,请先停掉。
然后在tomcat目录的bin目录下,利用startup.bat启动tomcat,不出意外,在webapps目录下已经有刚才的war文件对应的解压文件夹了。
这时我们访问SessionTest,再访问SessionTest2,会发现startup的控制台里输出了hello session,看来项目部署成功。
这时我们打开tomcat目录下的work目录,找到对应的项目目录,此时里边应该空空如也,这个目录是用来存放程序运行过程中动态生成的一些数据,我们的session钝化后的文件将会放入其中,现在还没有对session钝化,故对应目录空空如也。
现在我们通过正常方式关闭tomcat(利用bin/shutdown.bat),然后我们会发现work目录下的项目目录就会生成一个文件,这个文件就是session钝化后的文件,此时再用bin/startup.bat启动你的tomcat,然后你会发现这个文件又消失了,因为tomcat将这个文件活化了,将文件的数据加载进入了内存,加载进入内存后,内存空间虽然变了,但ID还是一样的,故客户端的请求中的cookie信息照旧可以拿到自己的session。
此时直接访问SessionTest2,会直接打印hello session,可见我们的session被成功保存了。
反观IDEA,只能实现session的钝化,例如启动IDEA后,找到CATALINA_BASE目录,CATALINA_BASE目录下的work下面会找到存放session钝化文件的地方(钝化文件:SESSIONS.ser),但是当你启动你的IDEA,你会发现CATALINA_BASE目录下的work会被删除,再被重建,此时没有了session钝化文件,也没有还原session,故IDEA不能活化session。
失效时间
session什么时候会被销毁呢?
- 服务器关闭
- session对象调用public void invalidate() 方法自杀
- session默认的失效时间:30分钟
在tomcat目录的web.xml下,有一个session-config,这个内部的值就是session的失效时间,可以去修改。
session的特点
session用于存储一次会话的多次请求的数据,存在服务器端
session可以存储任意类型,任意大小的数据
session与cookie的区别:
- session存储数据在服务器端,cookie在客户端
- session没有数据大小限制,cookie有
- session数据安全,cookie相对不安全
案例
需求:
- 访问一个带有验证码的登录页面login.jsp
- 用户输入用户名,密码以及验证码
- 如果用户名和密码输入有误,跳转登录页面,提示:用户名或密码错误
- 如果验证码输入有误,跳转登录页面,提示:验证码错误
- 如果全部输入正确,则跳转到主页success.jsp,显示:用户名,欢迎您
开始动手,首先在pers.luoluo.servlet包下有一个验证码的servlet——CheckCodeServlet.java:
@WebServlet("/checkCodeServlet")
public class CheckCodeServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
int width = 100;
int height = 50;
//1.创建一对象,在内存中图片(验证码图片对象)
BufferedImage image = new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB);
//2.美化图片
//2.1 填充背景色
Graphics g = image.getGraphics();//画笔对象
g.setColor(Color.PINK);//设置画笔颜色
g.fillRect(0,0,width,height);
//2.2画边框
g.setColor(Color.BLUE);
g.drawRect(0,0,width - 1,height - 1);
String str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghigklmnopqrstuvwxyz0123456789";
//生成随机角标
Random ran = new Random();
for (int i = 1; i <= 4; i++) {
int index = ran.nextInt(str.length());
//获取字符
char ch = str.charAt(index);//随机字符
//2.3写验证码
g.drawString(ch+"",width/4*i,height/2);
}
//2.4画干扰线
g.setColor(Color.GREEN);
//随机生成坐标点
for (int i = 0; i < 10; i++) {
int x1 = ran.nextInt(width);
int x2 = ran.nextInt(width);
int y1 = ran.nextInt(height);
int y2 = ran.nextInt(height);
g.drawLine(x1,y1,x2,y2);
}
//3.将图片输出到页面展示
ImageIO.write(image,"jpg",response.getOutputStream());
}
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
this.doPost(request,response);
}
}
好了,当请求checkCodeServlet,就会返回一个验证码
接下来我们来写login.jsp:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>登录</title>
<script type="text/javascript">
window.onload=function(){
document.getElementById("img").onclick=function(){
//设置当前Dom对象的src属性
/* 这里有一个小技巧,如果给url赋相同的值,浏览器不会重新
发出请求,因此用js的内置对象Date生成一个时间戳生成一
个即时毫秒数做url中的参数。
防止SRC访问本地的缓存文件。这样实现网页的局部刷新。
* */
this.src="/web/checkCodeServlet?time="+new Date().getTime();
}
}
</script>
<style type="text/css">
span{
color:red;
}
</style>
</head>
<body>
<form action="/web/loginServlet" method="post">
<table align="center" style="margin-top: 10%">
<tr>
<td>用户名</td>
<td><input type="text" name="username"></td>
</tr>
<tr>
<td>密码</td>
<td><input type="password" name="password"></td>
</tr>
<tr>
<td>验证码</td>
<td><input type="text" name="checkCode"></td>
</tr>
<tr>
<td colspan="2"><img id="img" src="/web/checkCodeServlet"/></td>
</tr>
<tr>
<td colspan="2"><input type="submit" value="登录"></td>
</tr>
</table>
</form>
<div style="text-align: center">
<span><%= request.getAttribute("cc_error")==null?
"": request.getAttribute("cc_error")%></span>
</div>
<div style="text-align: center">
<span><%= request.getAttribute("login_error")==null?
"": request.getAttribute("login_error") %></span>
</div>
</body>
</html>
再来写一个success.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<div style="text-align: center">
<h1><%=request.getSession().getAttribute("user")%>,欢迎您!</h1>
</div>
</body>
</html>
然后我们开始写servlet,首先优化一下上面的验证码的servlet—CheckCodeServlet.java,主要就是添加一个记录生成验证码,并保存到session域对象
@WebServlet("/checkCodeServlet")
public class CheckCodeServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
int width = 100;
int height = 50;
//1.创建一对象,在内存中图片(验证码图片对象)
BufferedImage image = new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB);
//2.美化图片
//2.1 填充背景色
Graphics g = image.getGraphics();//画笔对象
g.setColor(Color.PINK);//设置画笔颜色
g.fillRect(0,0,width,height);
//2.2画边框
g.setColor(Color.BLUE);
g.drawRect(0,0,width - 1,height - 1);
String str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghigklmnopqrstuvwxyz0123456789";
//生成随机角标
Random ran = new Random();
StringBuilder sb=new StringBuilder();
for (int i = 1; i <= 4; i++) {
int index = ran.nextInt(str.length());
//获取字符
char ch = str.charAt(index);//随机字符
//将验证码码值存到session中
sb.append(ch);
//2.3写验证码
g.drawString(ch+"",width/5*i,height/2);
}
String checkCode_session=sb.toString();
//将验证码存入session
request.getSession().setAttribute("checkCode_session",checkCode_session);
//2.4画干扰线
g.setColor(Color.GREEN);
//随机生成坐标点
for (int i = 0; i < 10; i++) {
int x1 = ran.nextInt(width);
int x2 = ran.nextInt(width);
int y1 = ran.nextInt(height);
int y2 = ran.nextInt(height);
g.drawLine(x1,y1,x2,y2);
}
//3.将图片输出到页面展示
ImageIO.write(image,"jpg",response.getOutputStream());
}
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
this.doPost(request,response);
}
}
下面是LoginServlet.java,主要负责验证登录逻辑:
@WebServlet("/loginServlet")
public class LoginServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//1.设置request编码
request.setCharacterEncoding("utf-8");
//2.获取参数Map
String name=request.getParameter("username");
String password=request.getParameter("password");
String checkCode=request.getParameter("checkCode");
//3.先判断验证码是否正确
//3.1 获取生成的验证码
HttpSession session = request.getSession();
String checkCode_session = (String)session.getAttribute("checkCode_session");
//删除session中存储的验证码,确保验证码的一次性
session.removeAttribute("checkCode_session");
//3.2 判断验证码是否正确
if(checkCode_session!=null&&checkCode_session.equalsIgnoreCase(checkCode)){
//3.3 忽略大小写进行比较,比较成功
//验证码一致,调用UserDao查询数据库(此时先省略)
if("tom".equals(name)&&"123".equals(password)){
//登录成功
//存储信息,用户信息
session.setAttribute("user",name);
//重定向到success.jsp
response.sendRedirect(request.getContextPath()+"/success.jsp");
}else{
//登录失败
request.setAttribute("login_error","用户名或密码错误");
//转发到登录页面
request.getRequestDispatcher("/login.jsp").forward(request,response);
}
}else{
//验证码不一致
//存储提示信息到request
request.setAttribute("cc_error","验证码错误");
//转发到登录页面
request.getRequestDispatcher("/login.jsp").forward(request,response);
}
}
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doPost(request, response);
}
}
补充BeanUtils
作用
这部分内容和web的会话部分毫无联系,但是为了节省纸张我就写到同一个文章中了~
用来封装JavaBean的工具包
- JavaBeam:标准的Java类
-
- 要求:
- 类必须被public修饰
- 必须提供空参的构造器
- 成员变量必须使用private修饰
- 提供公共setter和getter方法
- 功能:封装数据
- 要求:
- 概念
-
- 成员变量:成员变量
- 属性:setter和getter方法截取后的产物
- 例如:getUsername()–>Username–>username
这个概念其实就是C#中字段和属性的概念
一般要求变量和属性要相同,即username变量对应有username属性(username属性又对应getUsername方法),但是有的时候也可以定义的不一样,例如getTest(){ return gender; },方法对应的属性是test,可以操作的变量实际上是gender,不过一般建议成员变量和属性一样,例如我们的BeanUtils就是去操作属性而不是变量。
- 常用方法
- setProperty:给JavaBean对象的某个属性赋值(注意是对属性操作)
- getProperty:得到JavaBean对象的某个属性的值(注意是对属性操作)
- populate:将Map集合的键值对信息封装到对应的JavaBean对象中
下载
来到官网,下载下面的二进制压缩包到本地,然后解压即可。
此外,还需要一个commons-logging.jar包,以及一个commons-collections包(collection包要使用4.x以下的版本,不使用collection包或者使用高版本的包会报错缺少FastHashMap)
使用
首先我们在pers.luoluo.domain包下有一个JavaBean:
public class User {
private String username;
private String 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;
}
}
然后在我们的WEB-INF目录下的lib目录下导入commons-beanutils-1.9.4.jar并且添加到项目,同时还要导入commons-logging包和commos-collection包。
然后我们来看看web文件,例如如下的servlet中,对数据的封装就是传统的简单封装:
String name=request.getParameter("username");
String password=request.getParameter("password");
User loginUser=new User();
loginUser.setUsername(name);
loginUser.setUsername(password);
这种封装,利用request取得name和password,依次赋值给User的对象,一旦遇到特别多的参数,就会麻烦很多,我们来试试使用BeanUtils
Map<String, String[]> map =request.getParameterMap();
User loginUser=new User();
try {
BeanUtils.populate(loginUser,map);
} catch (IllegalAccessException e) {
System.out.println("报错");
e.printStackTrace();
} catch (InvocationTargetException e) {
System.out.println("报错");
e.printStackTrace();
}
System.out.println(loginUser.getUsername()+"" +
":"+loginUser.getPassword());
这样就能成功封装了。
商业转载 请联系作者获得授权,非商业转载 请标明出处,谢谢