接上次博客:初阶JavaEE(14)表白墙程序-CSDN博客
你还记得我们之前提到的Cookie吗?
Cookie是HTTP请求header中的一个属性,是一种用于在浏览器和服务器之间持久存储数据的机制,允许网站在多次HTTP请求之间保持状态它。
我们先来回忆一些知识点:
总之,网页无法访问主机的文件要想存储数据就得通过其他的方式。Cookie中保存的数据也是键值对的格式(用户自定义)。我们最终还是需要把这个键值对发送回服务器,因为服务器要使用Cookie来完成一些业务逻辑。
其中有一个特殊的情况,我们是会使用Cookie来存储当前用户的信息。
回忆我们之前举过的例子: 去医院挂号的就诊卡就相当于Cookie,里面存储了用户的身份信息。
挂号与Cookie设置:
就诊过程与Cookie的使用:
注销操作与Cookie销毁:
新就诊卡与新Cookie:
那除了身份标识,剩下的信息:基本信息、当前诊断信息、开的药品信息单子、既往病历、账户余额……,每个用户/患者都有一份这样的数据,这些数据在服务器中如何组织呢?
这些信息必然是要存储在数据库的,但是也不仅仅存储在数据库中。
在服务器代码的逻辑展开执行的过程中,这些数据也就会被从数据库中查询出来。
我们先把数据临时的保存到某个内存结构中,后续有什么修改之类的,就会去修改内存、重写写入数据库。
什么内存结构呢?——Session (会话)。
这两者之间往往会配合使用,在cookie中存储的用户身份标识,也经常会被理解成Session ld。服务器会存储很多的Session,每个用户都有一个自己的Session,也有不同的Session ld。
服务器会通过类似于哈希表这样的键值对来存储Session,Session ld就是key,Session 本身就是value,在Session里面又可以存储各种用户自身的信息(程序猿自定义的)。

截止到现在,我们已经学到的大量的概念都是和“键值对”有关的:
是的,许多计算机科学和编程中的概念都与键值对(key-value pairs)有关,这种数据结构非常常见,用于存储和组织各种类型的数据。在不同的上下文中,键值对可以被用来处理和表示不同种类的信息,如:
Query String: 在URL中,查询字符串通常是一组键值对,用于向服务器传递参数和数据。
Header: HTTP请求和响应头部是由键值对组成的,它们包含了关于请求或响应的元信息。
Body (Form): 在HTML表单提交中,表单字段通常用键值对的方式传递给服务器,以便处理表单数据。
Body (JSON): JSON(JavaScript Object Notation)是一种数据格式,它以键值对的形式表示数据对象的属性和值。
Cookie: Cookie 是一种客户端存储数据的机制,通常由键值对组成,用于跟踪用户和存储会话信息。
Session: 会话数据通常是通过键值对的方式管理,用于在用户与服务器之间保持状态和信息。
Session 数据内容: Session 数据内容通常以键值对的形式组织,其中键表示属性或字段名称,值表示相应的数据值,用于存储用户状态和相关信息。
键值对是一种灵活且通用的数据结构,可用于处理各种不同的数据需求,从简单的查询参数到复杂的数据对象。它们在编程和数据存储中起到关键作用,使数据的组织和访问更加方便和有效。
综上,可以说,键值对贯穿整个编程生涯,是一个非常重要的概念。
我们可以通过Servlet API 来操作上述结构:
Cookie是浏览器的机制,Servlet提供了API来获取Cookie;
Session是服务器的机制,Servlet内部已经实现好了,也提供了API供我们使用。
我们马上来看看Session的具体内容和相关使用。
1、HttpSession getSession() 方法:
2、Cookie[ ] getCookies() 方法:
这两个方法是Servlet开发中非常常用的,它们允许开发者在处理HTTP请求时获取和操作与会话和Cookie相关的数据。getSession() 用于处理与会话相关的操作,而getCookies() 用于获取并解析请求中的Cookie数据。
void addCookie(Cookie cookie)
一个 HttpSession 对象里面包含多个键值对, 我们可以往 HttpSession 中存任何我们需要的信息。
1、Object getAttribute(String name) 方法:
2、void setAttribute(String name, Object value) 方法:
3、boolean isNew() 方法:
这些方法一起提供了方便的方式来管理会话中的数据,并根据需要检查和操作会话状态。
每个 Cookie 对象就是一个键值对。
1、String getName() 方法:
2、String getValue() 方法:
3、setValue(String newValue) 方法:
这些方法使得在Servlet中可以方便地操作Cookie对象的名称和值,允许你根据需要获取、设置和更新Cookie的内容。
总之,
我们基于上述的API就可以实现用户登录了!
接下来我们就来写一个“登录”程序:
实现的大致顺序如下:
1、登录页面的构建:

2、通过一个Servlet处理上述的登录请求:通过这个Servlet读取用户名和密码,并且验证是否登陆成功。如果登录成功,就会给当前这个用户创建一个对话,并且把得到的Session ID通过Cookie返回给客户端(客户端就把Cookie保存起来了)。并且还会保存一些用户当前的信息。
3、 网站主页,通过另一个Servlet生成的动态页面,把刚才这里的用户数据给显示到页面上。
实现简单的用户登录逻辑 。
这个代码中主要是通过 HttpSession 类完成, 并不需要我们手动操作 Cookie 对象。
你懂的,先把该配置的目录还有文件配置好:

- html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>登录title>
- head>
- <body>
-
- <form action="login" method="post">
- <input type="text" name="username">
- <input type="text" name="password">
- <input type="submit" value="登录">
- form>
- body>
- html>
配置好Tomcat之后运行起来是这样的:

此处我们可以约定一下:

- package org.example;
-
- 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("/login")
- public class LoginServlet extends HttpServlet {
- @Override
- protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
- // 1. 读取请求传来的参数 (用户名和密码)
- // 最好先给请求设置一下字符集. 否则如果 username 是中文, 此处的 getParameter 可能会乱码.
- req.setCharacterEncoding("utf8");
- String username = req.getParameter("username");
- String password = req.getParameter("password");
- // 2. 验证用户名密码, 是否是正确的. 一般来说, 验证用户名密码, 是要通过数据库的.
- // 此处为了简单一点, 先把用户名和密码, 写死. 比如此处假设正确的用户名是 zhangsan, 正确的密码是 123
- // 此处还要注意, 上述 getParameter 可能会拿到 null . 为了避免空指针异常, 下面这种比较方式是更合适的写法.
- // equqls内部能够针对参数为null做好处理
- if (!"zhangsan".equals(username) || !"123".equals(password)) {
- // 登录失败
- // 给用户返回一个提示.
- resp.setContentType("text/html; charset=utf8");
- resp.getWriter().write("当前的用户名或者密码错误!");
- return;
- }
- // 3. 登录成功了, 给这个用户创建一个会话出来.
- // 可以给会话中保存一些自定义的数据, 通过 Attribute 的方式来保存.
- //
- HttpSession session = req.getSession(true);
- // 此处 Attribute 也是键值对. 这里的内容存储什么都可以. 程序员自定义的.
- // 这样的数据存储好了之后, 后续跳转到其他的页面, 也随时可以把这个数据从会话中取出来.
- session.setAttribute("username", username);
- session.setAttribute("loginTime", System.currentTimeMillis());
- // 4. 此时相当于登录成功!! 让页面跳转到网站首页.
- resp.sendRedirect("index");
- }
- }

服务器中可能有多个 Servlet 类,这些 Servlet 要处理不同的请求,但是现在这些 Servlet 都可以获取到刚刚这个 session 对象,进一步的就能拿到这里存储的 Attribute,也就实现了不同Servlet之间共享数据的效果。
当用户成功登录并创建了一个会话(HttpSession)后,会话中的属性(Attribute)可以在应用程序的不同Servlet中共享。每个Servlet都可以通过会话对象访问和修改这些属性,以提供一致的用户体验。
这对于在不同页面之间传递用户数据和状态信息非常有用,例如用户身份认证信息、购物车内容、用户首选项等。而且,这种数据的存储和访问是线程安全的,因为Servlet容器会确保会话的正确管理。

具体分析一下我们的代码:
Servlet声明和URL映射:
处理POST请求:
获取请求参数:
验证用户名和密码:
创建会话(Session):
登录成功:
这段代码只是一个非常简单的用户登录处理Servlet示例,它给我们演示了如何从请求中获取参数、验证用户名和密码,如何创建和使用会话对象,以及如何将用户重定向到其他页面。在实际应用中,验证通常会与数据库交互,并会有更复杂的登录逻辑。
- package org.example;
-
- 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;
-
- // 通过这个 Servlet 生成一个 主页
- @WebServlet("/index")
- public class IndexServlet extends HttpServlet {
- @Override
- protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
- // 1. 先获取到当前用户对应的会话对象. 生成的页面要根据当前用户信息来构造.
- HttpSession session = req.getSession(false);
- if (session == null) {
- // sessionId 不存在, 或者 sessionId 没有在 hash 表中查到.
- resp.setContentType("text/html; charset=utf8");
- resp.getWriter().write("您当前尚未登录!");
- return;
- }
- // 2. 从会话中拿到之前存储的用户信息
- // 此处的强转, 需要程序员自行保证, 类型是靠谱的.
- String username = (String) session.getAttribute("username");
- Long loginTime = (Long) session.getAttribute("loginTime");
- // 3. 生成一个页面, 把上述数据显示到页面上.
- resp.setContentType("text/html; charset=utf8");
- String respBody = "欢迎你 " + username + "! 上次登录时间为: " + loginTime;
- resp.getWriter().write(respBody);
- }
- }


具体分析一下上述代码:
Servlet声明和URL映射:
处理GET请求:
获取当前用户的会话对象:
从会话中获取用户信息:
生成页面响应:
这段代码实现了一个根据用户登录状态动态生成主页的功能。如果用户已登录,它会显示欢迎消息和上次登录时间;如果用户尚未登录,它会显示一个提示消息。该示例演示了如何使用HttpSession来管理用户会话状态,以及如何在不同Servlet之间共享数据以提供个性化用户体验。内部的哈希表和SessionID的细节由Servlet容器自动处理,开发者只需要关注会话和数据的操作。
全部实现完成以后我们就可以来看看实现效果了:

1、点击登录之后就会触发一个前端的POST请求:
2、到了服务器这边:

3、重定向到主页(index) :

这个东西的应用在于:
持久的登录状态: 一旦用户成功登录,通常会话将维持一段时间,使用户在之后的访问中继续保持登录状态。这可以通过Cookie和Session来实现。Cookie通常用于在客户端存储会话标识,而Session在服务器端存储用户数据。这种持久的登录状态使得用户无需在每个页面请求中重新登录。
会话过期时间: 会话的持续时间是可以设置的。不同网站可以根据需求设置不同的会话过期时间。一些网站可能会设置长期的会话,以提供长时间的登录状态,而其他敏感性较高的网站可能会设置较短的会话,以增强安全性。
Cookie的过期时间: Cookie是在客户端存储的,可以设置Cookie的过期时间。一旦Cookie的过期时间到了,浏览器会自动删除该Cookie。这可以在创建Cookie时通过setMaxAge方法来设置。
Session的过期时间: Session是在服务器端管理的,也可以设置会话的过期时间。当会话过期,服务器会自动删除会话及其数据。过期时间可以通过Servlet容器或编程方式来设置。
共用计算机的风险: 如果多个人在同一台公共计算机上使用同一个登录状态的帐户,会产生潜在的安全风险。这是因为后续用户无需再次登录,可能会有权限进行一些敏感操作。因此,对于共用计算机,特别是在公共场所,用户应该注销或清除会话和Cookie,以确保不会被其他人滥用。
总之,通过Cookie和Session的合理管理,可以实现用户的登录状态维护,会话过期时间的设置允许网站根据需求进行调整,以平衡用户便利性和安全性。对于共用计算机的风险,用户应当采取适当的措施,如注销或清除登录状态,以保护账户的安全。
上传文件也是日常开发中的一类常见需求。 在 Servlet 中也进行了支持。
1、Part getPart(String name):
Part filePart = request.getPart("file");
2、Collection
- Collection
parts = request.getParts(); - for (Part part : parts) {
- // 处理每个上传的文件(Part)
- }
这些方法使你能够有效地处理文件上传,并在Servlet中访问上传的文件的内容、文件名、大小等信息。在处理文件上传时,确保验证文件类型、限制文件大小,并处理潜在的安全风险,以保护应用程序的安全性。
1、String getSubmittedFileName():
2、String getContentType():
3、long getSize():
4、void write(String path):
- Part filePart = request.getPart("file");
- String fileName = filePart.getSubmittedFileName();
- String contentType = filePart.getContentType();
- long fileSize = filePart.getSize();
-
- // 将文件保存到服务器上
- String savePath = "path/to/save/uploads/" + fileName;
- filePart.write(savePath);
这些Part类的方法允许你访问上传文件的信息和内容,从而进行文件上传处理,包括获取文件名、文件类型、文件大小,以及将文件数据保存到服务器文件系统中。
我们可以简单运用一下这些API,实现程序,通过网页提交一个图片到服务器上。
1. 创建 upload.html, 放到 webapp 目录中。
- <form action="upload" enctype="multipart/form-data" method="POST" accept-charset="UTF-8">
- <meta charset="UTF-8">
- <input type="file" name="MyImage">
- <input type="submit" value="提交图片">
- form>
文件上传通常通过POST请求的表单来实现,同时需要确保表单使用multipart/form-data编码类型以支持文件上传。
这是因为multipart/form-data允许在HTTP请求中传输二进制数据,包括文件。


2. 创建 UploadServlet 类 。
- package org.example;
-
- import javax.servlet.annotation.MultipartConfig;
- import javax.servlet.annotation.WebServlet;
- import javax.servlet.http.Part;
- import java.io.*;
- import java.net.URLDecoder;
- import javax.servlet.ServletException;
- import javax.servlet.http.HttpServlet;
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
- @MultipartConfig
- @WebServlet("/upload")
- public class FileUploadServlet extends HttpServlet {
- protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
- request.setCharacterEncoding("UTF-8");
- response.setCharacterEncoding("UTF-8");
-
- // 获取上传的文件
- Part filePart = request.getPart("MyImage");
-
- // 获取文件名
- String fileName = getSubmittedFileName(filePart);
- // 在获取文件名之前,对文件名进行解码
- String decodedFileName = URLDecoder.decode(fileName, "UTF-8");
-
- // 指定服务器上的保存路径
- String savePath = getServletContext().getRealPath("/uploads/") + File.separator + fileName;
-
- // 将文件保存到服务器
- try (InputStream input = filePart.getInputStream();
- OutputStream output = new FileOutputStream(savePath)) {
- int bytesRead;
- byte[] buffer = new byte[4096];
- while ((bytesRead = input.read(buffer)) != -1) {
- output.write(buffer, 0, bytesRead);
- }
- }
- response.setContentType("text/html; charset=utf8");
- response.getWriter().write("文件 " + fileName + " 上传成功!");
- }
-
- private String getSubmittedFileName(Part part) {
- for (String cd : part.getHeader("content-disposition").split(";")) {
- if (cd.trim().startsWith("filename")) {
- String fileName = cd.substring(cd.indexOf('=') + 1).trim().replace("\"", "");
- return fileName;
- }
- }
- return null;
- }
- }
-
这段代码是一个Servlet,用于处理文件上传的POST请求。
Servlet映射:
doPost方法:
Part对象)。文件保存:
响应:
文件上传功能:
getSubmittedFileName方法:
总之,这段代码实现了文件上传的功能,包括接收上传的文件、获取文件名、将文件保存到服务器,并向客户端发送成功消息。这是一个基本的文件上传示例,可以根据需要进行进一步的定制和改进,例如添加文件类型验证、文件大小限制等。
需要注意的是:
先在我们的wepapp目录下构建一个新的子目录“upload”,用来存放我们要上传的图片文件(我已经提交了一些,你只需要创建出空的目录即可):



不管是哪个地方的图片,都可以顺利的被提交到我们的upload目录下:
我们也可以抓个包看看:

可以看到 Content-Type 为 multipart/form-data ,这样的请求中带有一个
boundary = ---- WebKitFormBoundaryxt86TAqMzDsINVMA ,
这个 boundary 在 body 这边作为一个 “分隔线”, 分隔线下面是上传的文件的属性和文件内容。
最后的最后,我把一些常用代码片段罗列在这里, 后续我们写代码的时候可以在这个基础上拷贝过去直接修改:
- "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.0modelVersion>
-
- <groupId>org.examplegroupId>
- <artifactId>LoginartifactId>
- <version>1.0-SNAPSHOTversion>
-
- <properties>
- <maven.compiler.source>8maven.compiler.source>
- <maven.compiler.target>8maven.compiler.target>
- <project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
- properties>
- <dependencies>
-
- <dependency>
- <groupId>javax.servletgroupId>
- <artifactId>javax.servlet-apiartifactId>
- <version>4.0.1version>
- <scope>providedscope>
- dependency>
-
-
- <dependency>
- <groupId>com.fasterxml.jackson.coregroupId>
- <artifactId>jackson-databindartifactId>
- <version>2.15.0version>
- dependency>
-
-
-
- <dependency>
- <groupId>mysqlgroupId>
- <artifactId>mysql-connector-javaartifactId>
- <version>5.1.47version>
- dependency>
-
-
- dependencies>
- project>
- web-app PUBLIC
- "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
- "http://java.sun.com/dtd/web-app_2_3.dtd" >
- <web-app>
- <display-name>Archetype Created Web Applicationdisplay-name>
- web-app>
- @WebServlet("/hello")
- public class HelloServlet extends HttpServlet {
- @Override
- protected void doGet(HttpServletRequest req, HttpServletResponse resp)
- throws ServletException, IOException {
- resp.getWriter().write("hello");
- }
- }
- @WebServlet("/getParameter")
- public class GetParameter extends HttpServlet {
- @Override
- protected void doGet(HttpServletRequest req, HttpServletResponse resp)
- throws ServletException, IOException {
- String contentType = req.getHeader("Content-Type");
- // 或者使用
- String contentType = req.getContentType();
- }
- }
- @WebServlet("/getParameter")
- public class GetParameter extends HttpServlet {
- @Override
- protected void doGet(HttpServletRequest req, HttpServletResponse resp)
- throws ServletException, IOException {
- String userId = req.getParameter("userId");
- String classId = req.getParameter("classId");
- }
- }
- @WebServlet("/postParameter")
- public class PostParameter extends HttpServlet {
- @Override
- protected void doPost(HttpServletRequest req, HttpServletResponse resp)
- throws ServletException, IOException {
- resp.setContentType("text/html; charset=utf-8");
- req.setCharacterEncoding("utf-8");
- String userId = req.getParameter("userId");
- String classId = req.getParameter("classId");
- resp.getWriter().write("userId: " + userId + ", " + "classId: " +
- classId);
- }
- }
- @WebServlet("/statusServlet")
- public class StatusServlet extends HttpServlet {
- @Override
- protected void doGet(HttpServletRequest req, HttpServletResponse resp)
- throws ServletException, IOException {
- resp.setStatus(200);
- }
- }
- @WebServlet("/autoRefreshServlet")
- public class AutoRefreshServlet extends HttpServlet {
- @Override
- protected void doGet(HttpServletRequest req, HttpServletResponse resp)
- throws ServletException, IOException {
- resp.setHeader("Refresh", "1");
- }
- }
- @WebServlet("/redirectServlet")
- public class RedirectServlet extends HttpServlet {
- @Override
- protected void doGet(HttpServletRequest req, HttpServletResponse resp)
- throws ServletException, IOException {
- resp.sendRedirect("http://www.sogou.com");
- }
- }
- <form action="login" method="POST">
- <input type="text" name="username">
- <input type="password" name="password">
- <input type="submit" value="提交">
- form>
- @WebServlet("/login")
- public class LoginServlet extends HttpServlet {
- @Override
- protected void doPost(HttpServletRequest req, HttpServletResponse resp)
- throws ServletException, IOException {
- HttpSession session = req.getSession(true);
- session.setAttribute("username", "admin");
- session.setAttribute("loginCount", "0");
- }
- }
- @WebServlet("/login")
- public class LoginServlet extends HttpServlet {
- HttpSession session = req.getSession(false);
- if (session == null) {
- // 用户没有登陆, 重定向到 login.html
- resp.sendRedirect("login.html");
- return;
- }
- // 如果已经登陆, 则从 Session 中取出数据
- String userName = (String)session.getAttribute("username");
- String countString = (String)session.getAttribute("loginCount");
- }
- @MultipartConfig
- @WebServlet("/upload")
- public class UploadServlet extends HttpServlet {
- @Override
- protected void doPost(HttpServletRequest req, HttpServletResponse resp)
- throws ServletException, IOException {
- Part part = req.getPart("MyImage");
- System.out.println(part.getSubmittedFileName());
- System.out.println(part.getContentType());
- System.out.println(part.getSize());
- part.write("d:/MyImage.jpg");
- resp.getWriter().write("upload ok");
- }
- }
- <form action="upload" enctype="multipart/form-data" method="POST">
- <input type="file" name="MyImage">
- <input type="submit" value="提交图片">
- form>