Cookie&Session
目标:
- 理解什么是会话跟踪技术
- 掌握Cookie的使用
- 掌握Session的使用
- 完善用户登录注册案例的功能
# 会话跟踪技术概述
会话跟踪这个词,我们比较陌生,这四个字我们需要拆开来解释,首先要理解什么是会话,然后再去理解会话跟踪。
- 会话:用户打开浏览器,访问Web服务器的资源,会话建立,直到有一方断开连接,会话结束。在一次会话中可以包含多次请求和响应。
- 从浏览器发出请求到服务器端响应数据给前端之后,一次会话(在浏览器和服务器之间)就被建立了
- 会话建立后,如果浏览器或服务器端都没有被关闭,则会话就会保持
- 浏览器和服务器就可以继续使用该会话进行发送请求和响应,上述的整个过程就被称之为会话。
 
下面我们用实际场景来理解下会话,比如我们在访问京东的时候,当打开浏览器进入京东首页后,浏览器和京东的服务器之间就建立了一次会话,后面的搜索商品,查看商品详情,加入购物车等都是在这一次会话中完成的。
思考:下图建立了几个会话?

每个浏览器都会与服务器建立一个会话,加起来总共是3个会话
- 会话跟踪:一种维护浏览器状态的方法,服务器需要识别多次请求是否来自于同一个浏览器,以便在同一次会话的多次请求间共享数据。
- 服务器会收到多个请求,这多个请求可能来自多个浏览器,如上图中的6个请求来自3个浏览器
- 服务器需要用来识别请求是否来自同一个浏览器
- 服务器用来识别浏览器的过程,这个过程就是会话跟踪
- 服务器识别浏览器后就可以在同一个会话中的多次请求之间来共享数据
 
思考:为什么要在一个会话中的多次请求之间共享数据?有了数据共享功能我们可以实现哪些功能?
- 购物车:加入购物车和去购物车结算是两次请求,但是后面的请求要展示前一次请求所添加的商品,就要用到数据共享  
- 页面展示用户登录信息:很多网站,登录后访问多个功能发送多次请求后,浏览器是哪个都会有当前登录用户的信息用户名、头像等信息  
- 网站登录页面的记住我功能:当用户登录成功后,勾选记住我按钮后下次再登录的时候,网站就会自动填充用户名和密码,简化用户的登录操作,多次登录就会有多次登录请求,它们之间也涉及到共享数据  
- 登录页面的验证码功能:生成验证码和输入验证码点击注册也是两次请求,这两次请求的数据之间要进行对比,相同则允许注册,不同则拒绝注册,该功能的实现也需要在同一次会话中共享数据  
通过上面几个例子的讲解,相信大家对会话跟踪技术已经有了简单的理解,该技术在实际开发中也非常重要,接下来我们就来学习下会话跟踪技术,在开始学习之前,我们思考一个问题:为什么浏览器和服务器之间不支持数据共享?
- 浏览器和服务器之间使用的是HTTP请求来进行数据传输
- HTTP协议是无状态的,每次浏览器向服务器请求时,服务器都会将该请求视为新的请求
- HTTP协议设计成无状态的目的是让每次请求之间相互独立,互不影响
- 请求和请求之间相互独立后,就无法实现多次请求之间的数据共享
分析完具体的原因后,那么浏览器和服务器之间该如何实现会话跟踪呢?具体的实现方式有:
- 客户端会话跟踪技术:Cookie
- 服务器端会话跟踪技术:Session
这两个技术都可以实现会话跟踪,它们之间最大的区别:Cookie是存储在浏览器端,而Session是存储在服务器端
具体的学习步骤为:
- Cookie的基本使用、原理、使用细节
- Session的基本使用、原理、使用细节
- Cookie和Session的综合案例
小结
在本小节中,我们主要介绍了什么是会话和会话跟踪技术,需要注意的是:
- HTTP协议是无状态的,靠HTTP协议是无法实现会话跟踪
- 要实现会话跟中,需要用到Cookie和Session
接下来,我们先从Cookie开始学习
# Cookie
学习Cookie,我们主要解决下面几个问题:
- 什么是Cookie?
- Cookie如何使用
- Cookie是如何实现的?
- Cookie的使用有哪些注意事项?
# Cookie基本使用
- 概念:
Cookie:客户端会话技术,将数据保存到客户端,以后每次请求都携带Cookie数据进行访问。
- Cookie的工作流程:

- 服务器端提供了两个Servlet,分别是ServletA和ServletB
- 浏览器发送HTTP请求1给服务器,服务器ServletA接收请求并进行业务处理
- 服务器ServletA在处理的过程中可以创建一个Cookie对象并将name=zs的数据存入Cookie
- 服务器端ServletA在响应数据的时候,会把Cookie对象响应给浏览器
- 浏览器接收到响应数据,会把Cookie对象中的数据存储到浏览器内存中,此时浏览器和服务器就建立了一次会话
- 在同一次会话中浏览器再次发送HTTP请求2给服务器ServletB,此时浏览器会携带Cookie对象中的所有数据
- ServletB接收到请求和数据后,就可以获取到存储在- Cookie对象中的数据,这样一个会话中的多次请求之间就实现了数据共享。
- Cookie的基本使用:
对于Cookie的使用,我们的关注点应该放在后台代码如何操作Cookie,对于Cookie的操作主要分为两类:发送Cookie和获取Cookie。
发送Cookie:
- 创建Cookie对象,并设置数据:
Cookie cookie = new Cookie("key", "value");
- 发送Cookie到客户端:使用Response对象
response.addCookie(cookie);
介绍完发送Cookie对应的步骤后,下面通过一个案例来学习Cookie的发送
案例需求:在Servlet中生成Cookie对象并存入数据,然后将数据发送给浏览器
具体步骤为:
- 创建Maven项目,并在
pom.xml中添加所需依赖- 编写
Servlet类,名称为AServlet- 在
AServlet中创建Cookie对象,存入数据,发送给前端- 启动测试,在浏览器查看
Cookie对象中的值
- 创建Maven项目,在pom.xml添加依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.itheima</groupId>
    <artifactId>Course</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>
    <dependencies>
        <!-- servlet -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <scope>provided</scope>
        </dependency>
        <!-- jsp -->
        <dependency>
            <groupId>javax.servlet.jsp</groupId>
            <artifactId>jsp-api</artifactId>
            <version>2.2</version>
            <scope>provided</scope>
        </dependency>
        <!-- jstl -->
        <dependency>
            <groupId>jstl</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>
        <dependency>
            <groupId>taglibs</groupId>
            <artifactId>standard</artifactId>
            <version>1.1.2</version>
        </dependency>
    </dependencies>
</project>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
- 编写AServlet类,在com.itheima.web包下创建该类
package com.itheima.web;
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("/aServlet")
public class AServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    }
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) 
    throws ServletException, IOException {
        this.doGet(request, response);
    }
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
- 在AServlet中创建Cookie对象,存入数据,发送给前端
package com.itheima.web;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/aServlet")
public class AServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 1. 创建Cookie对象
        Cookie cookie = new Cookie("username", "sunyy");
        // 2. 发送Cookie
        response.addCookie(cookie);
    }
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) 
    throws ServletException, IOException {
        this.doGet(request, response);
    }
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
- 启动服务器,访问http://localhost/course/aServlet,在浏览器查看Cookie对象中的值,Chrome浏览器查看Cookie,可以通过使用快捷键F12或者Ctrl+Shift+i组合键

获取Cookie:
- 获取客户端携带的所有 - Cookie数据,使用Request对象- Cookie[] cookies = request.getCookies();1
- 遍历数组,获取每一个 - Cookie对象:- for循环
- 使用 - Cookie对象的方法获取数据- cookie.getName(); cookie.getValue();1
 2
介绍完获取Cookie对应的步骤后,同样我们通过一个案例来学习如何获取Cookie:
案例需求:在Servlet中获取前一个案例存入在Cookie对象中的数据
实现步骤:
- 编写一个新的
BServlet类- 在
BServlet中使用request对象获取Cookie数组,遍历数组,从数据中获取指定名称对应的值- 启动测试,在控制台打印出获取到的值
- 编写一个新的BServlet
package com.itheima.web;
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("/bServlet")
public class BServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    }
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) 
    throws ServletException, IOException {
        this.doGet(request, response);
    }
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
- 在BServlet中使用request对象获取Cookie数组,遍历数组,从数据中获取指定名称对应的值
package com.itheima.web;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/bServlet")
public class BServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 1. 获取Cookie数组
        Cookie[] cookies = request.getCookies();
        // 2. 遍历数组
        for (Cookie cookie : cookies) {
            // 3. 获取数据
            String name = cookie.getName();
            if ("username".equals(name)) {
                String value = cookie.getValue();
                System.out.println(name + ": " + value);
                break;
            }
        }
    }
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) 
    throws ServletException, IOException {
        this.doGet(request, response);
    }
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
- 启动服务器,访问http://localhost/course/bServlet,查看控制台打印的内容

思考
在访问AServlet和BServlet的中间把浏览器关闭,重启浏览器后访问BServlet能否获取到Cookie中的数据?
这个问题我们会在Cookie的使用细节中讲解,大家可以先动手试一下。
小结
在本小节中,主要讲解了Cookie的基本使用,主要包含两部分内容
- 发送Cookie:- 创建Cookie对象,并设置值:Cookie cookie = new Cookie("key", "value")
- 发送Cookie到客户端使用的是Response对象:response.addCookie(cookie)
 
- 创建
- 获取Cookie:- 使用Request对象获取Cookie数组:Cookie[] cookies = request.getCookies();
- 遍历Cookie数组
- 获取数组中每个Cookie对象的值:cookie.getName()和cookie.getValue()
 
- 使用
# Cookie原理分析
上面我们介绍了Cookie的基本使用,那么Cookie的底层到底是如何实现一次会话多次请求之间共享数据的呢?
Cookie的实现是基于HTTP协议的,其中涉及到HTTP协议的两个头信息:
- 响应头:set-cookie
- 请求头:cookie

- 前面的案例已经实现,AServlet给前端发送Cookie,BServlet从request中获取Cookie数据的功能
- AServlet响应数据的时候,Tomcat服务器基于HTTP协议响应数据
- 当Tomcat发现后端需要返回Cookie对象,它就会在响应头中添加一行数据Set-Cookie:username=sunyy
- 浏览器获取响应结果,从响应头中可以获取到Set-Cookie对应的数据username=sunyy,并将数据存储在浏览器的内存中
- 浏览器再次发送请求给BServlet的时候,浏览器会自动在请求头中添加Cookie:username=sunyy发送给BServlet
- Request对象会把请求头中- cookie对应的值封装成一个个- Cookie对象,最终形成一个数组
- BServlet通过- Request对象获取到- Cookie[]后,就可以从中获取自己需要的数据
下面,使用上面的案例,我们把上述的结论验证一下:
- 访问AServlet对应的地址:http://localhost/course/aServlet,通过F12或者Ctrl+Shift+i组合键打开浏览器开发者工具

- 访问BServlet对应的地址:http://localhost/course/bServlet

# Cookie的使用细节
本节我们主要讲解两个知识,第一个是Cookie的存活时间,第二个是Cookie如何存储中文。
- Cookie的存活时间
前面让大家思考了一个问题:

- 浏览器发送请求给AServlet,它会响应一个存有username=sunyy的Cookie对象给浏览器
- 浏览器接收到响应数据将Cookie存储浏览器内存中
- 当浏览器再次发送请求给BServlet,BServlet就可以使用Request对象获取到Cookie数据
- 在发送请求到BServlet之前,如果把浏览器关闭再打开进行访问,BServlet能否同样获取到Cookie的数据?
注意
浏览器关闭只的是软件关闭,不是关闭选项卡。
针对上面的问题,通过演示发现,BServlet中无法再获取到Cookie数据,那么是什么原因导致BServlet无法获取到Cookie数据呢?
答案
默认情况下,Cookie存储在浏览器内存中,当浏览器关闭,内存释放,此时Cookie将被销毁。
如果使用这种默认情况的Cookie来共享数据,有些需求就无法实现,比如:

上图这个网站的登录页面有记住我的功能,这个功能大家应该在很多网站都见到过
- 第一次输入用户名和密码并勾选记住我,然后登录
- 下次再登录的时候,用户名和密码就会被自动填充,不需要重新输入登录
- 比如记住我这个功能需要记住用户名和密码一个星期,那么使用默认情况下的Cookie就会出现问题
- 因为默认情况,浏览器关闭,Cookie会从浏览器内存中删除,对于记住我功能就无法实现
那么,Cookie怎么才能持久化存储呢?
要想解决这个问题,我们要先看下Cookie给我们提供了哪些方法,也就是API。

上图中我们找到一个方法:setMaxAge,设置Cookie的存活时间
setMaxAge(int seconds)

参数值为:
- 正整数:将Cookie写入浏览器所在电脑的硬盘,持久化存储,到时间就自动删除。
- 负数:默认值,Cookie在当前浏览器内存中,当浏览器关闭,Cookie即被销毁
- 0:删除对应的Cookie
下面,我们就在AServlet中设置下Cookie的存活时间。
package com.itheima.web;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/aServlet")
public class AServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 1. 创建Cookie对象
        Cookie cookie = new Cookie("username", "sunyy");
        // 设置Cookie的存活时间为1周,7天
        cookie.setMaxAge(24*60*60*7);
        // 2. 发送Cookie
        response.addCookie(cookie);
    }
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) 
    throws ServletException, IOException {
        this.doGet(request, response);
    }
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
修改后,启动服务器进行测试,访问http://localhost/course/aServlet
- 访问AServlet后,把浏览器关闭重启,再访问BServlet,如果可以再控制台打印username: sunyy,说明Cookie没有随着浏览器关闭被销毁
- 通过浏览器查看Cookie的内容,可以看到Cookie的相关信息

- Cookie存储中文
在Tomcat 8之前Cookie不能直接存储中文,之后Cookie支持中文数据,下面的内容大家了解一下即可:
如果我们日后工作中遇到Cookie存储中文报错的情况可以通过下面的思路进行解决:
- 在
AServlet中对中文进行URL编码,采用URLEncoder.encode(),将编码后的值存储Cookie- 在
BServlet中获取Cookie中的值,获取的值为URL编码后的值- 将获取的值进行
URL解码,采用URLDecoder.decode()就可以获取到对应的中文值
- 在AServlet中对中文进行URL编码
package com.itheima.web;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
@WebServlet("/aServlet")
public class AServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 1. 创建Cookie对象
        String value = "张三";
        value = URLEncoder. encode(value, "UTF-8");
        Cookie cookie = new Cookie("username", value);
        // 设置Cookie的存活时间为1周,7天
        cookie.setMaxAge(24*60*60*7);
        // 2. 发送Cookie
        response.addCookie(cookie);
    }
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) 
    throws ServletException, IOException {
        this.doGet(request, response);
    }
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
- 在BServlet中获取值,并对获取到的值进行解码
package com.itheima.web;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLDecoder;
@WebServlet("/bServlet")
public class BServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 1. 获取Cookie数组
        Cookie[] cookies = request.getCookies();
        // 2. 遍历数组
        for (Cookie cookie : cookies) {
            // 3. 获取数据
            String name = cookie.getName();
            if ("username".equals(name)) {
                String value = cookie.getValue();
                value = URLDecoder.decode(value, "UTF-8");
                System.out.println(name + ": " + value);
                break;
            }
        }
    }
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) 
    throws ServletException, IOException {
        this.doGet(request, response);
    }
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
- 启动服务器,先后访问AServlet和BServlet,查看效果
 
 
小结
Cookie使用细节中,我们将来Cookie的存活时间和存储中文
- 存活时间,需要掌握setMaxAge()的使用
- 存储中文,需要了解URL编码和解码的使用
# Session
Cookie已经能完成一次会话多次请求之间的数据共享,前面我们还提到到过Session也可以实现同样的功能,那么:
- 什么是Session?
- Session如何使用?
- Session是如何实现的?
- Session的使用需要注意哪些内容?
# Session基本使用
- 概念:
Session:服务端会话跟踪技术,将数据保存在服务器端
- Session是存储在服务器端而- Cookie是存储在客户端
- 存储在客户端的数据容易被窃取和截获,存在很多不安全的因素
- 存储在服务器端的数据与客户端相比要更安全一些
- Session的工作流程

- 在服务器端的AServlet获取一个Session对象,把数据存入其中
- 在服务器端的BServlet获取到相同的Session对象,从中取出数据
- 这样就可以实现一次会话多次请求之间的数据共享
- 那么问题随之而来,如何保证AServlet和BServlet使用的是同一个Session对象(后面原理分析章节会进行详细介绍)?
- Session的基本使用
在JavaEE中提供了HttpSession接口,通过该接口来实现一次会话多次请求之间共享数据。
具体步骤为:
- 使用 - request对象,获取- Session对象- HttpSession session = request.getSession();1
- Session对象提供的功能:- 存储数据到session域中
 - void setAttribute(String name, Object o);1- 根据key,获取值
 - Object getAttribute(String name);1- 根据key,删除键值对
 - void removeAttribute(String name);1
- 存储数据到
那么介绍完Session相关的API后,下面我们通过一个简单的案例来完成Session的使用。
案例需求:在一个Servlet中网Session中存入数据,在另一个Servlet中获取Session中存入的数据
- 创建名为
SessionDemo1的Servlet类- 创建名为
SessionDemo2的Servlet类- 在
SessionDemo1的方法中:获取Session对象,存储数据- 在
SessionDemo2的方法中:获取Session对象,获取数据- 启动服务器,测试查看效果
- 创建SessionDemo1
package com.itheima.web;
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("/demo1")
public class SessionDemo1 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    }
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) 
    throws ServletException, IOException {
        this.doGet(request, response);
    }
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
- 创建SessionDemo2
package com.itheima.web;
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("/demo2")
public class SessionDemo2 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    }
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) 
    throws ServletException, IOException {
        this.doGet(request, response);
    }
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
- 在SessionDemo1中:获取Session对象,存储数据
package com.itheima.web;
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;
import java.io.IOException;
@WebServlet("/demo1")
public class SessionDemo1 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 1. 获取Session对象
        HttpSession session = request.getSession();
        // 2. 存储数据
        session.setAttribute("username", "zhangsan");
    }
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) 
    throws ServletException, IOException {
        this.doGet(request, response);
    }
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
- 在SessionDemo2中:获取Session对象,获取数据
package com.itheima.web;
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;
import java.io.IOException;
@WebServlet("/demo2")
public class SessionDemo2 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 1. 获取Session独享
        HttpSession session = request.getSession();
        // 2. 获取数据
        Object username = session.getAttribute("username");
        System.out.println(username);
    }
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) 
    throws ServletException, IOException {
        this.doGet(request, response);
    }
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
- 启动服务器,测试查看效果
- 先访问http://localhost/course/demo1,将数据存入session
- 再访问http://localhost/course/demo2,从session中获取数据
- 查看Idea控制台
 
- 先访问http://localhost/course/demo1,将数据存入

通过上面的案例,我们可以看到Session可以在一次会话的两次请求之间共享数据。
小结
Session重点要掌握的:
- Session的获取- HttpSession session = request.getSession();1
- Session常用方法的使用- void setAttribute(String name, Object o); Object getAttribute(String name);1
 2
 3
注意:Session总可以存储的是对象类型的数据,它可以存储任意类型的数据类型。
# Session原理分析
问题:Session的底层是如何实现一次会话多个请求之间共享数据的?
Session要想实现一次会话多次请求之间共享数据,就必须要保证多次请求获取Session的对象是同一个。

那么我们在上面案例中两个Servlet获取到的session是一个对象么?下面我们就来验证一下:在两个Servlet总分别打印下Session对象
SessionDemo1:
package com.itheima.web;
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;
import java.io.IOException;
@WebServlet("/demo1")
public class SessionDemo1 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 1. 获取Session对象
        HttpSession session = request.getSession();
        System.out.println(session);
        // 2. 存储数据
        session.setAttribute("username", "zhangsan");
    }
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) 
    throws ServletException, IOException {
        this.doGet(request, response);
    }
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
SessionDemo2:
package com.itheima.web;
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;
import java.io.IOException;
@WebServlet("/demo2")
public class SessionDemo2 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 1. 获取Session独享
        HttpSession session = request.getSession();
        System.out.println(session);
        // 2. 获取数据
        Object username = session.getAttribute("username");
        System.out.println(username);
    }
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) 
    throws ServletException, IOException {
        this.doGet(request, response);
    }
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
启动测试,分别访问:http://localhost/course/demo1,http://localhost/course/demo2

通过控制台输出结果可以得到如下结论:
- 两个Servlet类中获取的Session对象是同一个
- 把demo1和demo2请求刷新多次,控制台最终打印的结果都是同一个
那么如果新打开浏览器,或换其他浏览器访问demo1或者demo2,打印在控制台的Session还是同一个对象吗?

测试的结果:如果是不同浏览器或者重新打开浏览器后,打印的Session就不一样了。
注意
在一台电脑上演示,如果是相同的浏览器必须把浏览器关闭再重新打开,才算新开浏览器。淡然也可以使用不同的浏览器进行测试,像我就是用了IE浏览器。
那么最最核心的问题来了,Session是如何保证在一次会话中获取的Session对象是同一个?

- demo1在第一次获取- session对象的时候,- session对象会有一个唯一的标识,假设是- id:10 
- demo1在- session中存入数据并处理完业务后,需要通过Tomcat服务器响应结果给浏览器
- Tomcat服务器发现业务处理中使用了 - session对象,就会把- session的唯一标识- id:10当做一个- cookie,添加- Set-Cookie:JESSIONID=10到响应头中,并响应给浏览器
- 浏览器接收到响应结果后,会把响应头中的 - cookie数据存储到浏览器内存中
- 浏览器在同一会话中访问 - demo2的时候,会把- cookie中的数据按照- cookie: JESSIONID=10的格式添加到请求头中并发送给服务器Tomcat 
- demo2获取到请求后,从请求头中读取- cookie中的- JESSIONID值为- 10,如果找到了,就直接返回该对象,如果没有则新创建- session对象
- 关闭打开浏览器后,因为浏览器的 - cookie被销毁,所以就没有- JESSIONID的数据,服务端获取到的- session就是一个全新的- session对象
由上面的分析过程,我们可以得出一个结论:Session是基于Cookie实现的
# Session使用细节
本节我们会主要讲解两个知识:
- Session的钝化和活化
- Session的销毁
- Session钝化与活化 
假设我们在淘宝中买东西,此时访问淘宝的用户很多,虽然session数量没有上限,但随session数量的增多,会导致内存无法承受,此时如果有一些session长时间都没有活动。服务器就会将这些很久没有活动的session放到硬盘上,让内存给空出来,之后需要再次访问这放到硬盘上session,从硬盘找那个去除放到内存里面即可,这样用户就不会感觉自己掉线,那么这个过程就是session的钝化和活化。
Session的钝化和活化就是把session保存到硬盘,再从硬盘读取到内存中。但是它们和Session序列化、反序列化还不一样,Session序列化、反序列化是关闭和启动服务器,Session钝化和活化是没有关闭和启动服务器就完成了。
- Session销毁
session的销毁会有两种方式:
- 默认情况下,无操作,30分钟自动销毁
- 这个是小时间,是可以通过配置进行修改的 - 在项目的web.xml中配置
 - <?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <session-config> <session-timeout>100</session-timeout> </session-config> </web-app>1
 2
 3
 4
 5
 6
 7
 8
 9- 如果没有配置,默认是30分钟,默认值是在Tomcat的web.xml配置文件中写死的
  
- 在项目的
- 调用 - Session对象的- invalidate()方法进行销毁- 在SessionDemo2中添加session的销毁方法
 - package com.itheima.web; 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; import java.io.IOException; @WebServlet("/demo2") public class SessionDemo2 extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 1. 获取Session独享 HttpSession session = request.getSession(); System.out.println(session); // 销毁session session.invalidate(); // 2. 获取数据 Object username = session.getAttribute("username"); System.out.println(username); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }1
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35- 启动访问测试,先访问demo1将数据存入session,再次访问demo2从session中获取数据,因为在获取数据前我们销毁了session,所以会报错。
  - 该销毁方法一般会用在用户退出的时候,将session销毁
 
- 在
 
Cookie和Session小结
Cookie和Session都是来完成一次会话内多次请求间共享数据的。
- 区别:
- 存储位置:Cookie将数据存储在客户端,Session将数据存储在服务器端
- 安全性:Cookie不安全,Session安全
- 数据大小:Cookie最大3KB,Session大小无限制
- 存储时间:Cookie可以通过setMaxAge()长期存储,Session默认30分钟
- 服务器性能:Cookie不占服务器资源,Session占用服务器资源
 
- 存储位置:
- 应用场景:
- 购物车:使用Cookie来存储
- 展示登录用户信息:使用Session存储
- 记住我:Cookie
- 验证码:Session
 
- 购物车:使用
- 结论:
- Cookie是用来保证用户在未登录情况下的身份识别
- Session一般用来保存用户登录后的数据
 
# 用户登录注册案例
# 需求分析
需求说明:
- 完成用户登录功能,如果用户勾选记住我,下层访问登录页面时自动填充用户名和密码
- 完成注册功能,并实现验证码功能

# 用户登录功能
- 需求:

- 用户登录成功后,跳转到列表页面,并在页面上展示当前登录的用户名称
- 用户登录失败后,跳转会登录页面,并在页面上展示对应的错误信息
- 实现流程:

- 前端通过表单发送请求和数据给Web层的LoginServlet
- 在LoginServlet中接收请求和数据用户名和密码
- LoginServlet接收到请求和数据后,调用- Service层完成根据用户名和密码查询用户对象
- 在Service层需要编写UserService类,在类中实现login方法,方法中调用Dao层的UserMapper
- 在UserMapper接口中,声明一个根据用户名和密码查询用户的方法
- Dao层把数据查询出来以后,将返回数据封装到- User对象,将对象交给- Service层
- Service层将数据返回给- Web层
- Web层获取到- User对象后,判断- User对象,如果为- Null,则将错误信息响应给登录页面,如果不为- Null,则跳转到列表页面,并把当前登录用户的信息存入- Session发送到列表页面。
- 具体实现:
- 环境准备 - pom.xml
 - <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.itheima</groupId> <artifactId>Course</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> </properties> <dependencies> <!--mybatis 依赖--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.5</version> </dependency> <!--mysql 驱动--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.48</version> </dependency> <!-- servlet --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency> <!-- jsp --> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>jsp-api</artifactId> <version>2.2</version> <scope>provided</scope> </dependency> <!-- jstl --> <dependency> <groupId>jstl</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <dependency> <groupId>taglibs</groupId> <artifactId>standard</artifactId> <version>1.1.2</version> </dependency> </dependencies> </project>1
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58- mybatis-config.xml
 - <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <!-- 起别名 --> <typeAliases> <package name="com.itheima.pojo" /> </typeAliases> <!-- environments:develop配置数据库连接环境信息。 --> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <!--数据库连接信息--> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql:///db1?useSSL=false&useServerPrepStmts=true"/> <property name="username" value="root"/> <property name="password" value="1234"/> </dataSource> </environment> </environments> <mappers> <!--加载sql映射文件--> <!--Mapper代理方式--> <package name="com.itheima.mapper"/> </mappers> </configuration>1
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30- 建表
 - -- 创建用户表 CREATE TABLE tb_user( id int primary key auto_increment, username varchar(20) unique, password varchar(32) ); -- 添加数据 INSERT INTO tb_user(username,password) values('zhangsan','123'),('lisi','234'); SELECT * FROM tb_user;1
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
- 创建实体类 - User.java
package com.itheima.pojo;
public class User {
    private Integer id;
    private String username;
    private String password;
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    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;
    }
    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
- 编写 - Dao层代码- UserMapper.java
 - package com.itheima.mapper; import com.itheima.pojo.User; import org.apache.ibatis.annotations.Param; public interface UserMapper { /** * 根据用户名和密码查询用户 * @param username * @param password * @return */ User select(@Param("username") String username, @Param("password")String password); /** * 根据用户名查询用户对象 * @param username * @return */ User selectByUsername(String username); /** * 添加用户 * @param user */ void add(User user); }1
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28- UserMapper.xml
 - <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.itheima.mapper.UserMapper"> <resultMap id="UserMap" type="User"> <id column="id" property="id" /> <result column="username" property="username" /> <result column="password" property="password" /> </resultMap> <sql id="User_Columns"> id, username, password </sql> <insert id="add"> insert into tb_user(username, password) values(#{username}, #{password}) </insert> <select id="select" resultMap="UserMap"> select <include refid="User_Columns" /> from tb_user where username = #{username} and password = #{password} </select> <select id="selectByUsername" resultMap="UserMap"> select <include refid="User_Columns" /> from tb_user where username = #{username} </select> </mapper>1
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
- 编写工具类 
package com.itheima.util;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;
/**
 * MyBatis工具类
 */
public class MyBatisUtil {
    private static SqlSessionFactory factory;
    /**
     * 静态代码块会随着类的加载而自动执行,且只执行一次
     */
    static {
        try {
            String resource = "mybatis-config.xml";
            InputStream is = Resources.getResourceAsStream(resource);
            factory = new SqlSessionFactoryBuilder().build(is);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public static SqlSessionFactory getSqlSessionFactory() {
        return factory;
    }
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
- 编写Service层代码
package com.itheima.service;
import com.itheima.mapper.UserMapper;
import com.itheima.pojo.User;
import com.itheima.util.MyBatisUtil;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
public class UserService {
    private SqlSessionFactory factory = MyBatisUtil.getSqlSessionFactory();
    /**
     * 登录方法
     * @param username
     * @param password
     * @return
     */
    public User login(String username, String password) {
        // 1. 获取SqlSession
        SqlSession sqlSession = factory.openSession();
        // 2. 获取UserMapper
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        // 3. 调用方法
        User user = mapper.select(username, password);
        // 4. 释放资源
        sqlSession.close();
        // 5. 返回结果
        return user;
    }
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
- 完成页面和Web层代码的编写 - 将课程资料中登录注册案例下面的静态页面拷贝到我们的webapp目录下
  - 将login.html内容修改为login.jsp
 - <%@ page contentType="text/html;charset=utf-8" language="java" %> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>login</title> <link href="css/login.css" rel="stylesheet"> </head> <body> <div id="loginDiv" style="height: 350px"> <form action="loginServlet" id="form"> <h1 id="loginMsg">LOGIN IN</h1> <div id="errorMsg">用户名或密码不正确</div> <p>Username:<input id="username" name="username" type="text"></p> <p>Password:<input id="password" name="password" type="password"></p> <p>Remember:<input id="remember" name="remember" type="checkbox"></p> <div id="subDiv"> <input type="submit" class="button" value="login up"> <input type="reset" class="button" value="reset">    <a href="register.html">没有账号?</a> </div> </form> </div> </body> </html>1
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29- 创建LoginServlet
 - package com.itheima.web; import com.itheima.pojo.User; import com.itheima.service.UserService; 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; import java.io.IOException; @WebServlet("/loginServlet") public class LoginServlet extends HttpServlet { private UserService service = new UserService(); @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 1. 获取用户名和密码 String username = request.getParameter("username"); String password = request.getParameter("password"); // 2. 调用service查询 User user = service.login(username, password); // 3. 判断 if (user != null) { // 登录成功,跳转(重定向)到查询所有品牌数据的页面 // 将登录成功后的user对象,存储到session中 HttpSession session = request.getSession(); session.setAttribute("user", user); String contextPath = request.getContextPath(); response.sendRedirect(contextPath + "/selectAllServlet"); } else { // 登录失败 // 存储错误信息到request request.setAttribute("login_msg", "用户名或密码错误"); // 跳转到login.jsp request.getRequestDispatcher("/login.jsp").forward(request, response); } } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }1
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54- 在brand.jsp中<body>标签下添加欢迎当前用户的提示信息:
 - <h1>${user.username},欢迎您!</h1>1- 修改login.jsp,将错误信息使用EL表达式获取
 - 修改前内容:<div id="errorMsg">用户名和密码不正确</div> 修改后内容:<div id="errorMsg">${login_msg}</div>1
 2
- 将课程资料中登录注册案例下面的静态页面拷贝到我们的
- 启动服务器,测试查看效果 - 进入登录页面,输入错误的用户名和密码
  - 输入正确的用户名和密码
  
小结
- 在LoginServlet中,将登录成功的用户数据存入session,在品牌列表页面中获取当前登录用户信息进行展示
- 在LoginServlet中,将登录失败的错误信息存入到request中,如果存入到session中就会出现这次会话的所有请求都出现登录失败的错误提示信息,因此不用存入到session中。
# 记住我-设置Cookie
需求:登录成功并勾选了Remeber后,后端返回给前端的Cookie数据就存储好了,接下来在页面获取Cookie中的数据,并把数据设置到登录页面的用户名和密码框中。

对应上面的需求,最大的问题就是:如何自动填充用户名和密码?
实现流程分析:
因为记住我功能要实现的效果是,用户把浏览器关闭即使过一段时间在来访问登录页面,用户名和密码也可以自动填充,所以需要将登录信息存入一个可以长久保存,并且能够在浏览器关闭重新启动后亦然有效的地方,其实就是我们前面讲的Cookie,所以:
- 需要将用户名和密码写入Cookie中,并且持久化存储Cookie
- 接着就是从Cookie中获取数据,并设置到用户名和密码框中
- 那么,何时将用户名和密码写入Cookie呢?
- 用户必须成功登录后才需要写入
- 用户必须在登录页面勾选了记住我的复选框
 

- 前端需要在发送请求的时候,多携带一个用户是否勾选Remmeber的数据
- LoginServlet获取到数据后,调用- Service完成用户名和密码的判断
- 登录成功,并且用户在前端勾选了记住我,那么就需要在Cookie中写入用户名和密码,并设置Cookie的存活时间
- 设置成功后,将数据响应给前端
具体实现:
- 设置login.jsp记住我复选框的值
<%@ page contentType="text/html;charset=utf-8" language="java" %>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>login</title>
    <link href="css/login.css" rel="stylesheet">
</head>
<body>
<div id="loginDiv" style="height: 350px">
    <form action="loginServlet" id="form">
        <h1 id="loginMsg">LOGIN IN</h1>
        <div id="errorMsg">${login_msg}</div>
        <p>Username:<input id="username" name="username" type="text"></p>
        <p>Password:<input id="password" name="password" type="password"></p>
        <p>Remember:<input id="remember" name="remember" type="checkbox" value="1"></p>
        <div id="subDiv">
            <input type="submit" class="button" value="login up">
            <input type="reset" class="button" value="reset">   
            <a href="register.html">没有账号?</a>
        </div>
    </form>
</div>
</body>
</html>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
- 在LoginServlet获取复选框的值并在登录成功后设置Cookie
package com.itheima.web;
import com.itheima.pojo.User;
import com.itheima.service.UserService;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
import java.io.IOException;
@WebServlet("/loginServlet")
public class LoginServlet extends HttpServlet {
    private UserService service = new UserService();
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 1. 获取用户名和密码
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        // 获取复选框的数据
        String remember = request.getParameter("remember");
        // 2. 调用service查询
        User user = service.login(username, password);
        // 3. 判断
        if (user != null) {
            // 登录成功,跳转(重定向)到查询所有品牌数据的页面
            // 将登录成功后的user对象,存储到session中
            HttpSession session = request.getSession();
            session.setAttribute("user", user);
            if ("1".equals(remember)) {
                // 候选了 记住我
                // 创建Cookie对象
                Cookie ckUsername = new Cookie("username", username);
                Cookie ckPassword = new Cookie("password", password);
                // 设置Cookie的存活时间
                ckUsername.setMaxAge(24 * 60 * 60 * 7);
                ckPassword.setMaxAge(24 * 60 * 60 * 7);
                // 响应中添加Cookie数据
                response.addCookie(ckUsername);
                response.addCookie(ckPassword);
            }
            String contextPath = request.getContextPath();
            response.sendRedirect(contextPath + "/selectAllServlet");
        } else {
            // 登录失败
            // 存储错误信息到request
            request.setAttribute("login_msg", "用户名或密码错误");
            // 跳转到login.jsp
            request.getRequestDispatcher("/login.jsp").forward(request, response);
        }
    }
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) 
    throws ServletException, IOException {
        this.doGet(request, response);
    }
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
- 启动服务器,访问页面查看效果:只有当用户名和密码输入正确,并且勾选了Remember的复选框,在响应头中才可以看到cookie的相关数据

# 记住我-获取Cookie
需求:登录成功并勾选了Remeber后,后端返回给前端的Cookie数据就已经存储好了,接下来就需要在页面获取Cookie中的数据,并把数据设置到登录页面的用户名和密码框中

那么,如何在页面获取Cookie中的值呢?
实现流程分析:

- 在login.jsp页面的登录表单用户名输入框使用value给表单元素添加默认值,value可以使用${cookie.username.value}
- 在login.jsp页面的登录表单密码输入框使用value给表单元素添加默认值,value可以使用${cookie.password.value}
具体实现:
- 修改login.jsp页面
<%@ page contentType="text/html;charset=utf-8" language="java" %>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>login</title>
    <link href="css/login.css" rel="stylesheet">
</head>
<body>
<div id="loginDiv" style="height: 350px">
    <form action="loginServlet" id="form">
        <h1 id="loginMsg">LOGIN IN</h1>
        <div id="errorMsg">${login_msg}</div>
        <p>Username:<input id="username" name="username" type="text" vlaue="${cookie.username.value}"></p>
        <p>Password:<input id="password" name="password" type="password" value="${cookie.password.value}"></p>
        <p>Remember:<input id="remember" name="remember" type="checkbox" value="1"></p>
        <div id="subDiv">
            <input type="submit" class="button" value="login up">
            <input type="reset" class="button" value="reset">   
            <a href="register.html">没有账号?</a>
        </div>
    </form>
</div>
</body>
</html>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
- 重启服务器,访问登录页面查看效果

# 用户注册功能
需求:
- 注册功能:保存用户信息到数据库
- 验证码功能:
- 展示验证码:展示验证码图片,点击可以切换
- 校验验证码:验证码填写错误,注册失败
 

实现流程分析:

- 前端通过表单发送请求和数据给Web层的RegisterServlet
- 在RegisterServlet中接收数据用户名和密码
- RegisterServlet接收到请求后,调用- Service层完成用户信息的保存
- 在Service层需要编写UserService类,在类中实现register方法,此时需要判断用户是否已经存在,如果不存在,则保存用户数据,存在在注册失败
- 在UserMapper接口中,声明两个方法,一个是根据用户名查询用户信息方法,另一个是保存用户信息方法
- 在UserService类中保存成功则返回true,失败则返回false,将数据返回给Web层
- Web层获取到结果后,如果返回的是true,则提示注册成功,并转发到登录页面,如果返回false则提示用户名已存在并转发到注册页面
具体实现:
- Dao层代码如下 
- 编写Service层代码 
package com.itheima.service;
import com.itheima.mapper.UserMapper;
import com.itheima.pojo.User;
import com.itheima.util.MyBatisUtil;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
public class UserService {
    private SqlSessionFactory factory = MyBatisUtil.getSqlSessionFactory();
    /**
     * 登录方法
     * @param username
     * @param password
     * @return
     */
    public User login(String username, String password) {
        // 1. 获取SqlSession
        SqlSession sqlSession = factory.openSession();
        // 2. 获取UserMapper
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        // 3. 调用方法
        User user = mapper.select(username, password);
        // 4. 释放资源
        sqlSession.close();
        // 5. 返回结果
        return user;
    }
    /**
     * 注册
     * @param user
     * @return
     */
    public boolean register(User user) {
        // 1. 获取SqlSession
        SqlSession sqlSession = factory.openSession(true);
        // 2. 获取UserMapper
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        // 3. 判断用户名是否存在
        User user1 = mapper.selectByUsername(user.getUsername());
        if (user1 == null) {
            // 用户名不存在,注册
            mapper.add(user);
        }
        return user1 == null;
    }
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
- 完成页面和Web层代码编写 - 将register.html修改为register.jsp
 - <%@ page contentType="text/html;charset=utf-8" language="java" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>欢迎注册</title> <link href="css/register.css" rel="stylesheet"> </head> <body> <div class="form-div"> <div class="reg-content"> <h1>欢迎注册</h1> <span>已有帐号?</span> <a href="login.jsp">登录</a> </div> <form id="reg-form" action="registerServlet" method="post"> <table> <tr> <td>用户名</td> <td class="inputs"> <input name="username" type="text" id="username"> <br> <span id="username_err" class="err_msg" style="display:none;">用户名不太受欢迎</span> </td> </tr> <tr> <td>密码</td> <td class="inputs"> <input name="password" type="password" id="password"> <br> <span id="password_err" class="err_msg" style="display: none">密码格式有误</span> </td> </tr> <tr> <td>验证码</td> <td class="inputs"> <input name="checkCode" type="text" id="checkCode"> <img src="imgs/a.jpg"> <a href="#" id="changeImg">看不清?</a> </td> </tr> </table> <div class="buttons"> <input value="注 册" type="submit" id="reg_btn"> </div> <br class="clear"> </form> </div> </body> </html>1
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50- 编写RegisterServlet
 - package com.itheima.web; import com.itheima.pojo.User; import com.itheima.service.UserService; 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("/registerServlet") public class RegisterServlet extends HttpServlet { private UserService userService = new UserService(); @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 1. 获取用户名和密码 String username = request.getParameter("username"); String password = request.getParameter("password"); User user = new User(); user.setUsername(username); user.setPassword(password); // 2. 调用Service层方法 boolean flag = userService.register(user); // 3. 判断注册成功与否 if (flag) { // 注册成功,跳转登录页面 request.setAttribute("register_msg", "注册成功,请登录"); request.getRequestDispatcher("/login.jsp").forward(request, response); } else { // 注册失败,跳转到注册页面 request.setAttribute("register_msg", "用户名已存在"); request.getRequestDispatcher("/register.jsp").forward(request, response); } } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }1
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47- 注册失败后需要在注册页面展示后台返回的错误信息,需要修改register.jsp
 - 修改前:<span id="username_err" class="err_msg" style="display:none">用户名不太受欢迎</span> 修改后:<span id="username_err" class="err_msg">${register_msg}</span>1
 2- 如果注册成功,需要把注册成功信息展示到登录页面,所以也需要修改login.jsp
 - 修改前:<div id="errorMsg">${login_msg}</div> 修改后:<div id="errorMsg">${login_msg}${register_msg}</div>1
 2- 修改login.jsp,将注册跳转地址修改为register.jsp
 - 修改前:<a href="register.html">没有账号?</a> 修改后:<a href="register.jsp">没有账号?</a>1
 2- 启动服务器,测试
 - 如果注册的用户名已存在:  - 如果注册的用户名可用,注册成功:  
- 将
# 展示验证码
需求分析:展示验证码,展示验证码图片,点击图片可以切换

验证码的生成是通过工具类来实现的,具体的工具类参考课程资料,针对该工具类我们可以编写一个测试方法:
package com.itheima.util;
import org.junit.Test;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
public class CheckCodeUtilTest {
    @Test
    public void testCaptcha() throws IOException {
        // 生成验证码图片位置
        OutputStream fos = new FileOutputStream("D://itcast//Temp//catcha.jpg");
        // captchaCode为最终验证码的数据
        String captchaCode = CaptchaUtil.outputVerifyImage(100, 50, fos, 4);
        System.out.println(captchaCode);
    }
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
生成验证码以后,我们就可以知晓:
- 验证码就是使用Java代码生成的一张图片
- 验证码的作用:防止机器自动注册,攻击服务器
实现流程分析:

- 前端发送请求给CheckCodeServlet
- CheckCodeServlet接收到请求后,生成验证码图片,将图片用- Response对象的输出流写到前端页面
思考:如何将图片写到前端浏览器?
- Java中已经有工具类生成验证码图片,测试类中只是把这个图片生成到磁盘上
- 生成磁盘的过程中使用的是OutputStream流,如何把这个图片生成到页面上?
- 前面在介绍Response对象的时候,它有一个方法可以获取其字节输出流,getOutputStream()
- 通过上面的介绍,我们可以把写往磁盘的流对象更换成Response的字节流,即可完成将图片响应给前端的工作
具体实现:
- 修改register.jsp页面,将验证码图片改为从后台获取
<%@ page contentType="text/html;charset=utf-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>欢迎注册</title>
    <link href="css/register.css" rel="stylesheet">
</head>
<body>
<div class="form-div">
    <div class="reg-content">
        <h1>欢迎注册</h1>
        <span>已有帐号?</span> <a href="login.jsp">登录</a>
    </div>
    <form id="reg-form" action="registerServlet" method="post">
        <table>
            <tr>
                <td>用户名</td>
                <td class="inputs">
                    <input name="username" type="text" id="username">
                    <br>
                    <span id="username_err" class="err_msg">${register_msg}</span>
                </td>
            </tr>
            <tr>
                <td>密码</td>
                <td class="inputs">
                    <input name="password" type="password" id="password">
                    <br>
                    <span id="password_err" class="err_msg" style="display: none">密码格式有误</span>
                </td>
            </tr>
            <tr>
                <td>验证码</td>
                <td class="inputs">
                    <input name="checkCode" type="text" id="checkCode">
                    <img id="checkCodeImg" src="checkCodeServlet">
                    <a href="#" id="changeImg">看不清?</a>
                </td>
            </tr>
        </table>
        <div class="buttons">
            <input value="注 册" type="submit" id="reg_btn">
        </div>
        <br class="clear">
    </form>
</div>
<script>
    document.getElementById("changeImg").onclick = function() {
        // 路径后面添加时间戳的目的是避免浏览器缓存静态资源,导致验证码图片不刷新
        document.getElementById("checkCodeImg").src = "checkCodeServlet?" + new Date().getMilliseconds();
    }
</script>
</body>
</html>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
- 编写CheckCodeServlet类,用来接收请求并生成验证码
package com.itheima.web;
import com.itheima.util.CheckCodeUtil;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
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("/checkCodeServlet")
public class CheckCodeServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 生成验证码
        ServletOutputStream os = response.getOutputStream();
        String checkCode = CheckCodeUtil.outputVerifyImage(100, 50, os, 4);
    }
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) 
    throws ServletException, IOException {
        this.doGet(request, response);
    }
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 校验验证码
需求:
- 判断程序生成的验证码和用户输入的验证码是否一样,如果不一样,则阻止注册
- 验证码图片访问和提交注册表单是两次请求,所以要将程序生成的验证码存入Session中

思考:为什么要把验证码数据存入到Session中?
- 生成验证码和校验验证码是两次请求,此处就需要在一个会话的两次请求之间共享数据
- 验证码属于安全类数据,因此我们要选中Session来存储验证码数据
实现流程分析:

- 在CheckCodeServlet中生成验证码的时候,将验证码数据存入Session对象
- 前端将验证码和注册数据提交到后台,交给RegisterServlet类
- RegisterServlet类接收到请求和数据后,其中就有验证码,然后和- Session中的验证码进行对比
- 如果一致,完成注册逻辑,如果不一致,提示错误信息
具体实现:
- 修改CheckCodeServlet类,将验证码存入Session对象
package com.itheima.web;
import com.itheima.util.CheckCodeUtil;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
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;
import java.io.IOException;
@WebServlet("/checkCodeServlet")
public class CheckCodeServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 生成验证码
        ServletOutputStream os = response.getOutputStream();
        String checkCode = CheckCodeUtil.outputVerifyImage(100, 50, os, 4);
        // 存入Session
        HttpSession session = request.getSession();
        session.setAttribute("checkCodeGen", checkCode);
    }
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) 
    throws ServletException, IOException {
        this.doGet(request, response);
    }
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
- 在RegisterServlet中,获取页面输入的和session对象中的验证码,进行对比
package com.itheima.web;
import com.itheima.pojo.User;
import com.itheima.service.UserService;
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;
import java.io.IOException;
@WebServlet("/registerServlet")
public class RegisterServlet extends HttpServlet {
    private UserService userService = new UserService();
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 1. 获取用户名和密码
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        User user = new User();
        user.setUsername(username);
        user.setPassword(password);
        // 获取用户输入的验证码
        String checkCode = request.getParameter("checkCode");
        // 从Session中获取程序生成的验证码
        HttpSession session = request.getSession();
        String checkCodeGen = (String) session.getAttribute("checkCodeGen");
        // 比对
        if (!checkCodeGen.equalsIgnoreCase(checkCode)) {
            request.setAttribute("register_msg", "验证码错误");
            request.getRequestDispatcher("/register.jsp").forward(request, response);
            // 验证码不匹配,不允许注册
            return;
        }
        // 2. 调用Service层方法
        boolean flag = userService.register(user);
        // 3. 判断注册成功与否
        if (flag) {
            // 注册成功,跳转登录页面
            request.setAttribute("register_msg", "注册成功,请登录");
            request.getRequestDispatcher("/login.jsp").forward(request, response);
        } else {
            // 注册失败,跳转到注册页面
            request.setAttribute("register_msg", "用户名已存在");
            request.getRequestDispatcher("/register.jsp").forward(request, response);
        }
    }
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) 
    throws ServletException, IOException {
        this.doGet(request, response);
    }
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59

至此,用户的注册登录功能就完成了。
