> 文档中心 > Servlet API 详解

Servlet API 详解

目录

 1. Servlet 运行原理

1.1 Tomcat 的定位

1.2 Tomcat 的伪代码

1.3 总结

2. Servlet API 详解

2.1 HttpServlet

 2.2 HttpServletRequest

 2.2 HttpServletResponse

3.Cookie和Session

3.1 Cookie

3.2 理解会话机制 (Session)

3.3 Cookie 和 Session 的区别

3.4  核心方法

3.4 实现登录页面

 4.上传文件

4.1核心方法

4.2 代码示例


1. Servlet 运行原理

Servlet 的代码中我们并没有写 main 方法 , 那么对应的 doGet 代码是如何被调用的呢 ? 响应又是如何返回给浏览器的? 这其中就涉及到了 Tomcat 。

1.1 Tomcat 的定位

我们自己的实现是在 Tomcat 基础上运行的。

 

当浏览器给服务器发送请求的时候 , Tomcat 作为 HTTP 服务器 , 就可以 接收到这个请求。 HTTP 协议作为一个应用层协议 ,, 需要底层协议栈来支持工作。 如下图所示 :

 更详细的交互过程可以参考下图:

 1) 接收请求:

-用户在浏览器输入一个 URL, 此时浏览器就会构造一个 HTTP 请求。 -这个 HTTP 请求会经过网络协议栈逐层进行 封装 成二进制的 bit , 最终通过物理层的硬件设备转换成光信号/ 电信号传输出去。 -这些承载信息的光信号 / 电信号通过互联网上的一系列网络设备 , 最终到达目标主机 ( 这个过程也需要网络层和数据链路层参与)。 -服务器主机收到这些光信号 / 电信号 , 又会通过网络协议栈逐层进行 分用 , 层层解析 , 最终还原成 HTTP 请求 . 并交给 Tomcat 进程进行处理 ( 根据端口号确定进程 )。 -Tomcat 通过 Socket 读取到这个请求 ( 一个字符串 ), 并按照 HTTP 请求的格式来解析这个请求 , 根据请求中的 Context Path 确定一个 webapp, 再通过 Servlet Path 确定一个具体的类 .。 再根据当前请求的方法 (GET/POST/...), 决定调用这个类的 doGet 或者 doPost 等方法 . 此时我们的代码中的 doGet / doPost 方法的第一个参数 HttpServletRequest 就包含了这个 HTTP 请求的详细信息。

 2) 根据请求计算响应

在我们的 doGet / doPost 方法中, 就执行到了我们自己的代码. 我们自己的代码会根据请求中的一

些信息 , 来给 HttpServletResponse 对象设置一些属性 . 例如状态码 , header, body 等。 3) 返回响应 -我们的 doGet / doPost 执行完毕后 , Tomcat 就会自动把 HttpServletResponse 这个我们刚设置 好的对象转换成一个符合 HTTP 协议的字符串 , 通过 Socket 把这个响应发送出去 -此时响应数据在服务器的主机上通过网络协议栈层层 封装 , 最终又得到一个二进制的 bit , 通过 物理层硬件设备转换成光信号 / 电信号传输出去。 -这些承载信息的光信号 / 电信号通过互联网上的一系列网络设备 , 最终到达浏览器所在的主机 ( 这个 过程也需要网络层和数据链路层参与 )。 -浏览器主机收到这些光信号 / 电信号 , 又会通过网络协议栈逐层进行 分用 , 层层解析 , 最终还原成 HTTP 响应 , 并交给浏览器处理。 -浏览器也通过 Socket 读到这个响应 ( 一个字符串 ), 按照 HTTP 响应的格式来解析这个响应 . 并且把 body 中的数据按照一定的格式显示在浏览器的界面上。

1.2 Tomcat 的伪代码

所谓 "伪代码", 并不是一些语法严谨, 功能完备的代码, 只是通过这种形式来大概表达某种逻辑。 1) Tomcat 初始化流程

class Tomcat { // 1.用来存储所有的 Servlet 对象 private List instanceList = new ArrayList(); public void start() {       // 根据约定,读取 WEB-INF/web.xml 配置文件;        // 并解析被 @WebServlet 注解修饰的类               // 假定这个数组里就包含了我们解析到的所有被 @WebServlet 注解修饰的类.         Class[] allServletClasses = ...;                // 2.实例化出所有的 Servlet 对象出来;        for (Class cls : allServletClasses) {            // 这里是利用 java 中的反射特性做的           // 实际上还得涉及一个类的加载问题,因为我们的类字节码文件,是按照约定的            // 方式(全部在 WEB-INF/classes 文件夹下)存放的,所以 tomcat 内部是            // 实现了一个自定义的类加载器(ClassLoader)用来负责这部分工作。                        Servlet ins = cls.newInstance();            instanceList.add(ins);       }                // 3.调用每个 Servlet 对象的 init() 方法,这个方法在对象的生命中只会被调用这一次;        for (Servlet ins : instanceList) {            ins.init();       }                // 4.启动一个 HTTP 服务器        // 并用线程池的方式分别处理每一个 Request        ServerSocket serverSocket = new ServerSocket(8080);        // 实际上 tomcat 不是用的固定线程池,这里只是为了说明情况        ExecuteService pool = Executors.newFixedThreadPool(100);                while (true) {            Socket socket = ServerSocket.accept();            // 每个请求都是用一个线程独立支持,这里体现了我们 Servlet 是运行在多线程环境下的            pool.execute(new Runnable() {               doHttpRequest(socket);            });       }        // 5.调用每个 Servlet 对象的 destroy() 方法,这个方法在对象的生命中只会被调用这一次;        for (Servlet ins : instanceList) {            ins.destroy();       }   }        public static void main(String[] args) {        new Tomcat().start();   }}

在上述代码中:

Tomcat 的代码中内置了 main 方法 . 当我们启动 Tomcat 的时候 , 就是从 Tomcat main 方法开 始执行的。 被@webServlet 注释修饰的类会在Tomcat 启动的时候就被获取到,并集中管理。Tomcat 通过 反射这样的语法机制来创建被 @webServlet  注解修饰的类的实例。 这些实例被创建完了之后 , 会点调用其中的 init 方法进行初始化。 ( 这个方法是 HttpServlet 自带的 , 我们自己写的类可以重写 init) 这些实例被销毁之前 , 会调用其中的 destory 方法进行收尾工作。 ( 这个方法是 HttpServlet 自带的 , 我们自己写的类可以重写 destory) Tomcat 内部也是通过 Socket API 进行网络通信。 Tomcat 为了能同时相应多个 HTTP 请求 , 采取了多线程的方式实现 . 因此 Servlet 是运行在 多线程 环境 下的。 2) Tomcat 处理请求流程

class Tomcat {    void doHttpRequest(Socket socket) {        // 参照HTTP 服务器类似的原理,进行 HTTP 协议的请求解析,和响应构建        HttpServletRequest req = HttpServletRequest.parse(socket);        HttpServletRequest resp = HttpServletRequest.build(socket);                // 判断 URL 对应的文件是否可以直接在我们的根路径上找到对应的文件,如果找到,就是静态内容        // 直接使用我们学习过的 IO 进行内容输出        if (file.exists()) {            // 返回静态内容            return;        }                // 走到这里的逻辑都是动态内容了                // 按照 URL -> servlet-name -> Servlet 对象的链条        // 最终找到要处理本次请求的 Servlet 对象        Servletins = findInstance(req.getURL());                // 调用 Servlet 对象的 service 方法        // 这里就会最终调用到我们自己写的 HttpServlet 的子类里的方法了        try {            ins.service(req, resp);         } catch (Exception e) {            // 返回 500 页面,表示服务器内部错误        }    }}

在上述代码中

Tomcat Socket 中读到的 HTTP 请求是一个字符串, 然后会按照 HTTP 协议的格式解析成一个 HttpServletRequest 对象。 Tomcat 会根据 URL 中的 path 判定这个请求是请求一个静态资源还是动态资源。如果是静态资源, 直接找到对应的文件把文件的内容通过 Socket 返回。如果是动态资源, 才会执行到 Servlet 的相关 逻辑。 Tomcat 会根据 URL 中的 Context Path Servlet Path 确定要调用哪个 Servlet 实例的 service 方法,通过 service 方法, 就会进一步调用到我们之前写的 doGet 或者 doPost 3) Servlet service 方法的实现 Servlet service 方法内部会根据当前请求的方法 , 决定调用其中的某个 doXXX 方法 . 在调用 doXXX 方法的时候 , 就会触发 多态 机制 , 从而执行到我们自己写的子类中的 doXXX 方法。

class Servlet {    public void service(HttpServletRequest req, HttpServletResponse resp) {       String method = req.getMethod();        if (method.equals("GET")) {            doGet(req, resp);        } else if (method.equals("POST")) {            doPost(req, resp);        } else if (method.equals("PUT")) {            doPut(req, resp);        } else if (method.equals("DELETE")) {            doDelete(req, resp);        }         ......    }}

1.3 总结

关于Servlet的关键方法,主要有三个,可以理解成Servlet 的生命周期(什么时候该做什么)

1)init: 初始化阶段,对象创建好了之后,就会执行到.用户可以重写这个方法,来执行一-些初始化逻辑。
2)service: 在处理请求阶段来调用.每次来个请求都要调用一次service
3) destroy: 退出主循环,tomcat结束之前会调用,用来释放资源~
 

2. Servlet API 详解

2.1 HttpServlet

我们写 Servlet 代码的时候 , 首先第一步就是先创建类 , 继承自 HttpServlet, 并重写其中的某些方法。 HttpServlet 的实例只是在程序启动时创建一次, 而不是每次收到 HTTP 请求都重新创建实例。   核心方法  实 际开发的时候主要通过继承这个HttpServlet 类,重写 doXXX 方法, 来被Tomcat执行到。其中很少会重写 init / destory / service。 通过继承、重写方法,其中就涉及到了多态,如下

 代码示例: 处理 POST 请求

代码中写入 resp.setContentType("text/html; charset = utf8"),是因为Windows默认编码是gbk,而idea的编码是 utf8,因此写入让浏览器使用uft8来读。

@WebServlet("/method")public class MethodServlet extends HttpServlet {    @Override    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //super.doPost(req, resp); //防止浏览器乱码 resp.setContentType("text/html; charset = utf8"); resp.getWriter().write("POST 响应");    }}

构造POST请求 引入标签依赖   https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js

 其中url 就是代码中@WebServlet("/method") 里面的method

        $.ajax({     type: 'post',     url: 'method',     success: function(body) {  console.log(body);     } });    

 浏览器中的响应如下,按快捷键F12即可看到浏览器控制台的中响应

 

 2.2 HttpServletRequest

Tomcat 通过 Socket API 读取 HTTP 请求 ( 字符串 ), 并且按照 HTTP 协议的格式把字符串解析成 HttpServletRequest 对象。 HttpServletRequest对应到一个HTTP请求,HTTP请求有什么,这里就有什么
HttpServletResponse对应到一个HTTP响应,HTTP响应中有什么,这里就有什么

核心方法

方法 描述
String getProtocol() 返回请求协议的名称和版本。
String getMethod() 返回请求的 HTTP 方法的名称,例如, GET POST PUT
String getRequestURI() 从协议名称直到 HTTP 请求的第一行的查询字符串中,返回该请求的 URL 的一部分。
String getContextPath() 返回指示请求上下文的请求 URI 部分。
String getQueryString() 返回包含在路径后的请求 URL 中的查询字符串。
Enumeration getParameterNames() 返回一个 String 对象的枚举,包含在该请求中包含的参数的名称。
String getParameter(String name) 以字符串形式返回请求参数的值,或者如果参数不存在则返回null。
String[] getParameterValues(String name) 返回一个字符串对象的数组,包含所有给定的请求参数的值, 如果参数不存在则返回 null
Enumeration getHeaderNames() 返回一个枚举,包含在该请求中包含的所有的头名。
String getHeader(String name) 以字符串形式返回指定的请求头的值
String getCharacterEncoding() 返回请求主体中使用的字符编码的名称。
String getContentType() 返回请求主体的 MIME 类型,如果不知道类型则返回 null
Int getContentLength() 以字节为单位返回请求主体的长度,并提供输入流,或者如果 长度未知则返回 -1
InputStream getInputStream() 用于读取请求的 body 内容 . 返回一个 InputStream 对象 .

通过这些方法可以获取到一个请求中的各个方面的信息。 注意:请求对象是服务器收到的内容, 不应该修改。因此上面的方法也都只是 "读" 方法, 而不是 "写" 方法。 (1)代码示例 : 打印请求信息

@WebServlet("/showRequestServlet")public class ShowRequestServlet extends HttpServlet {    @Override    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append("

首行部分

"); //请求协议的名称和版本 stringBuilder.append(req.getProtocol()); stringBuilder.append("
"); //从协议名称直到 HTTP 请求的第一行的查询字符串中,返回该请求的 URL 的一部分。 stringBuilder.append(req.getRequestURI()); stringBuilder.append("
"); //指示请求上下文的请求 URI 部分 stringBuilder.append(req.getContextPath()); stringBuilder.append("
"); //包含在路径后的请求 URL 中的查询字符串 stringBuilder.append(req.getQueryString()); stringBuilder.append("
"); stringBuilder.append("

header 部分

"); Enumeration headerNames = req.getHeaderNames(); while (headerNames.hasMoreElements()) { String headerName = headerNames.nextElement(); String headerValue = req.getHeader(headerName); stringBuilder.append(headerName + ";" + headerValue + "
"); } //使用utf8编码 resp.setContentType("text/html;charset = utf8"); //读取请求 resp.getWriter().write(stringBuilder.toString()); }}

上述API能够让我们拿到HTTP请求的各个方面内容。
但是却没那么常用,更常用的,其实是getParameter这个方法。 (获取到query string中的详细内容)
  读取到的信息如下

(2) 代码示例: 获取 GET 请求中的参数

GET 请求中的参数一般都是通过 query string 传递给服务器的。   形如https://v.bitedu.vip/personInf/student?userId=123&classId=456,此时浏览器通过 query string 给服务器传递了两个参数 , userId classId, 值分别是 123 和 456。在服务器端就可以通过getParameter 来获取到参数的值。创建 GetParameterServlet 类

@WebServlet("/getParameter")public class GetParameterServlet extends HelloServlet{    @Override    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{ // 预期浏览器传来一个形如这样的请求: /getParameter?userId=123&classId=456 //以字符串形式返回请求参数的值,或者如果参数不存在则返回null。 String userId = req.getParameter("userId"); String classId = req.getParameter("classId"); resp.getWriter().write("userId=" + userId + ",classId=" + classId);    }}

以下是query string 给服务器没传参和传参的页面响应,参数是可以手动输入的。

(3)代码示例 : 获取 POST 请求中的参数

  POST 请求的参数一般通过 body 传递给服务器. body 中的数据格式有很多种.

 1)采用 form 表单形式

body>                   

创建PostGetParameterServlet 类

@WebServlet("/postGetParameter")public class PostGetParameterServlet extends HttpServlet {    @Override    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 服务器也是通过 req.getParameter 来获取到内容的. String userId = req.getParameter("userId"); String classId = req.getParameter("classId"); resp.getWriter().write("userId=" + userId + ",classId" + classId);    }}

获取结果

(4)代码示例 : 获取 POST 请求中的参数 (2)

要想构造一个 json 格式的请求, 就需使用 ajax 。

1)创建 text3.html用于发送请求,以下是body 部分

                             let userIdInput = document.querySelector('#userId'); let classIdInput = document.querySelector('#classId'); let button = document.querySelector('#submit'); button.onclick = function() {     $.ajax({  type: 'post',  url: 'postJson',  contentType: 'application/json',  data: JSON.stringify({      userId: userIdInput.value,      classId: classIdInput.value  }),  success: function(body) {      console.log(body);  }     }); }    

2)其中 pom.xml 需要引入新的依赖,在maven 中央仓库里面找

 代码片段如下

          com.fasterxml.jackson.core     jackson-databind     2.12.6.1     

 3)创建 PostJsonServlet 类

class User {    public int userId;    public int classId;}@WebServlet("/postJson")public class PostJsonServlet extends HttpServlet {    //1.创建一个Json 核心对象    private ObjectMapper objectMapper = new ObjectMapper();    @Override    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //2. 读取 body 中的请求, 然后使用 ObjectMapper 来解析成需要的对象. // readValue 就是把 JSON 格式的字符串, 转成 Java 的对象 // 第一个参数, 表示对哪个字符串进行转换. 这个参数可以填写成一个 String, 也可以填一个 InputStream 对象, 还可以填一个 File // 第二个参数, 表示要把这个 JSON 格式的字符串, 转成哪个 Java 对象 User user = objectMapper.readValue(req.getInputStream(), User.class); resp.getWriter().write("userId: " + user.userId + ", classId: " + user.classId);    }}

4)代码解析

在java后端代码中,通过jackson来进行处理。
需要使用jackson ,把请求body中的数据读取出来,并且解析成Java中的对象。

5)请求结果

因为当前使用的是ajax的方式来提交数据,这个操作默认不会产生页面跳转,就和form风格差别很大。

 6)使用postman的请求

 2.2 HttpServletResponse

Servlet 中的 doXXX 方法的目的就是根据请求计算得到相应 , 然后把响应的数据设置到 HttpServletResponse 对象中。 然后 Tomcat 就会把这个 HttpServletResponse 对象按照 HTTP 协议的格式 , 转成一个字符串 , 并通过 Socket 写回给浏览器。 核心方法

方法 描述
void setStatus(int sc) 为该响应设置状态码
void setHeader(String name, String value) 设置一个带有给定的名称和值的 header. 如果 name 已经存在 , 则覆盖旧的值
void addHeader(String name, String value) 添加一个带有给定的名称和值的 header. 如果 name 已经存在 , 不覆盖旧的值, 并列添加新的键值对
void setContentType(String type) 设置被发送到客户端的响应的内容类型。
void setCharacterEncoding(String charset) 设置被发送到客户端的响应的字符编码( MIME 字符集)例如,UTF-8
void sendRedirect(String location) 使用指定的重定向位置 URL 发送临时重定向响应到客户端。
PrintWriter getWriter() 用于往 body 中写入文本格式数据 .
OutputStream getOutputStream() 用于往 body 中写入二进制格式数据 .

注意: 响应对象是服务器要返回给浏览器的内容, 这里的重要信息都是程序猿设置的. 因此上面的方法都是 "写" 方法。 注意: 对于状态码/响应头的设置要放到 getWriter / getOutputStream 之前. 否则可能设置失效。

(1) 代码示例: 设置状态码

实现一个程序, 用户在浏览器通过参数指定要返回响应的状态码。

创建 StatusServlet 类

@WebServlet("/status")public class StatusServlet extends HttpServlet {    @Override    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //状态码自己输入,200、404 等等都可以 resp.setStatus(200); resp.getWriter().write("hello 200");    }}

响应效果如下

服务器返回的状态码,只是在告诉浏览器,当前的响应是个什么状态,并不影响浏览器照常去显示body中的内容。
 

 (2)代码示例: 自动刷新

实现一个程序 , 让浏览器每秒钟自动刷新一次 . 并显示当前的时间戳。 创建 AutoRefreshServlet

@WebServlet("/autoRefresh")public class AutoRefreshServlet extends HttpServlet {    @Override    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //一毫秒刷新一次 resp.setHeader("Refresh","1"); resp.getWriter().write("timeStamp: " + System.currentTimeMillis());    }}

通过 HTTP 响应报头中的 Refresh 字段,可以控制浏览器自动刷新的时机。  通过 Date 类的 getTime 方法可以获取到当前时刻的毫秒级时间戳。  

 (3)代码示例: 重定向

实现一个程序,返回一个重定向 HTTP 响应, 自动跳转到另外一个页面。构造重定向的响应302.

创建 RedirectServlet

@WebServlet("/redirect")public class RedirectServlet extends HttpServlet {    @Override    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 在这里返回一个 302 重定向响应, 让浏览器, 自动跳转到 百度 主页 //resp.setStatus(302); //resp.setHeader("Location","https://www.baidu.com/" ); // Servlet 提供了一个更简便的实现重定向的写法. resp.sendRedirect("https://www.baidu.com");    }}

浏览器页面响应

抓包结果

3.Cookie和Session

3.1 Cookie

HTTP 协议自身是属于 " 无状态 " 协议。 " 无状态 " 的含义指的是 : 默认情况下 HTTP 协议的客户端和服务器之间的这次通信 , 和下次通信之间没有直接的联系。

举个例子 : 1. 到了医院先挂号: 挂号时候需要提供身份证 , 同时得到了一张 " 就诊卡 ", 这个就诊卡就相当于 患者的 " 令牌 ". 2. 后续去各个科室进行检查 , 诊断 , 开药等操作 , 都不必再出示身份证了 , 只要凭就诊卡即可识别 出当前患者的身份。 3. 看完病了之后 , 不想要就诊卡了 , 就可以注销这个卡 . 此时患者的身份和就诊卡的关联就销毁 .。( 类似于网站的注销操作 ) 4. 又来看病 , 可以办一张新的就诊卡 , 此时就得到了一个新的 " 令牌 "。 此时在服务器这边就需要记录令牌信息 , 以及令牌对应的用户信息 , 这个就是 Session 机制所做的工作。

3.2 理解会话机制 (Session)

服务器同一时刻收到的请求是很多的。 服务器需要清除的区分清楚每个请求是从属于哪个用户, 就需要在服务器这边记录每个用户令牌以及用户的信息的对应关系。 在上面的例子中 , 就诊卡就是一张 " 令牌 ". 要想让这个令牌能够生效 , 就需要医院这边通过系统记录 每个就诊卡和患者信息之间的关联关系。 会话的本质就是一个 " 哈希表 ", 存储了一些键值对结构。  key 就是令牌的 ID(token/sessionId),value 就是用户信息( 用户信息可以根据需求灵活设计。 sessionId 是由服务器生成的一个 " 唯一性字符串 ", session 机制的角度来看 , 这个唯一性字符串 称为 "sessionId". 但是站在整个登录流程中看待 , 也可以把这个唯一性字符串称为 "token"。 sessionId token 就可以理解成是同一个东西的不同叫法 ( 不同视角的叫法 )。

当用户登陆的时候 , 服务器在 Session 中新增一个新记录 , 并把 sessionId / token 返回给客户端 . ( 例如通过 HTTP 响应中的 Set-Cookie 字段返回 )。 客户端后续再给服务器发送请求的时候 , 需要在请求中带上 sessionId/ token. ( 例如通过 HTTP 求中的 Cookie 字段带上 )。 服务器收到请求之后 , 根据请求中的 sessionId / token Session 信息中获取到对应的用户信息 , 再进行后续操作。 Servlet Session 默认是保存在内存中的 . 如果重启服务器则 Session 数据就会丢失。

3.3 Cookie Session 的区别

1. Cookie 是客户端的机制。 Session 是服务器端的机制。 2. Cookie Session 经常会在一起配合使用。 但是不是必须配合。      完全可以用 Cookie 来保存一些数据在客户端 . 这些数据不一定是用户身份信息 , 也不一定是     token / sessionId Session 中的 token / sessionId 也不需要非得通过 Cookie / Set-Cookie 传递

3.4  核心方法

HttpServletRequest 类中的相关方法

方法 描述
HttpSession getSession() 在服务器中获取会话 . 参数如果为 true, 则当不存在会话时新建会话 ; 参数如果为 false, 则当不存在会话时返回 null
Cookie[] getCookies() 返回一个数组 , 包含客户端发送该请求的所有的 Cookie 对象 . 会自动把Cookie 中的格式解析成键值对。

Cookie[] getCookies():HTTP请求中的cookie字段就是按照键值对的方式来组织的。这里的这些键值对,大概的格式,使用;来分割多个键值对,使用=来分割键和值。这些键值对都会在请求中通过cookie字段传给服务器。服务器收到请求之后,就会进行解析,解析成上述看到的Cookie[这样的形式。

HttpSession:这个对象本质上也是一个"键值对"的结构。

在调用getSession的时候具体要做的事情:

1)创建会话

首先先获取到请求中cookie里面的sessionld字段~~ (相当于会话的身份标识)
判定这个sessionld是否在当前服务器上存在。
如果不存在,则进入创建会话逻辑。
创建会话,会创建一个HttpSession对象,并且生成一个sessionld (是一个很长的数字,通常是用十六进制来表示,能够保证唯一性)
接下来就会把这个sessionld作为key,把这个HttpSession对象,作为value, 把这个键值对,给保存到服务器内存的一个“哈希表"这样的结构中。
再然后,服务器就会返回-个HTTP响应把sessionld通过Set-Cookie字段返回给浏览器。浏览器就可以保存这个sessionld到Cookie中了。

2)获取会话

先获取到请求中的cookie里面的sessionld字段 (也就是会话的身份标识)
判定这个sessionld是否在当前服务器上存在(也就是在这个哈希表中是否有)
如果有,就直接查询出这个HttpSession对象,并且通过返回值返回回去。

HttpServletResponse类中的相关方法

方法 描述
void addCookie(Cookie cookie) 把指定的 cookie 添加到响应中

响应中就可以根据addCookie这个方法,来添加一一个Cookie信息到响应报文中。
这里添加进来的键值对,就会作为HTTP响应中的Set-Cookie字段来表示。

Cookie类中的相关方法

每个 Cookie 对象就是一个键值对.

方法 描述
String getName() 该方法返回 cookie 的名称。名称在创建后不能改变。 ( 这个值是 Set-Cooke 字段设置给浏览器的 )
String getValue() 该方法获取与 cookie 关联的值
void setValue(StringnewValue) 该方法设置与 cookie 关联的值

HTTP Cooke 字段中存储的实际上是多组键值对 . 每个键值对在 Servlet 中都对应了一个 Cookie 对象。

3.4 实现登录页面

实现简单的用户登陆逻辑,这个代码中主要是通过 HttpSession  类完成. 并不需要我们手动操作 Cookie 对象

1)创建 login.html, 放到 webapp 目录中

                Document           

2) 创建 LoginServlet

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 {//处理用户请求 String username = req.getParameter("username"); String password = req.getParameter("password"); //判定用户名或者密码是否正确 // 正常来说这个判定操作是要放到数据库中进行存取的. // 此处为了简单, 就直接在代码里写死了. 假设有效的用户名和密码是 "Fly", "123" if ("Fly".equals(username) && "123".equals(password)) {     //登录成功     //创建会话,并保存必要的身份信息     HttpSession httpSession = req.getSession(true);     //往会话中存储键值对,必要的身份信息     httpSession.setAttribute("username",username);     //初始情况下,把登录次位设为0     httpSession.setAttribute("count",0);     //重定向,页面跳转     resp.sendRedirect("index"); } else {     //登录失败,使用utf8编码,不然会乱码     resp.setContentType("text/html;charset=utf8");     resp.getWriter().write("登录失败!请重新检查再输入"); }    }}

在这个代码中是看不到 "哈希表", 也看不到 sessionId 这样的概念的. getSession 操作内部提取到请求中的 Cookie 里的 sessionId, 然后查找哈希表, 获取到对应的 HttpSession 对象。

此处的 getSession 参数为 true, 表示查找不到 HttpSession 时会创建新的 HttpSession 对象, 并生成一个 sessionId, 哈希表中, 并且把 sessionId 通过 Set-Cookie 返回给浏览器.

3) 创建 IndexServlet

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("/index")public class IndexServlet extends HttpServlet {    @Override    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 返回一个主页. (主页就是一个简单的 html 片段) // 此处需要得到用户名是啥, 从 HttpSession 中就能拿到. // 此处 getSession 的参数必须是 false. 前面在登录过程中, 已经创建过会话了. 此处是要直接获取到之前的会话. HttpSession session = req.getSession(false); String username = (String) session.getAttribute("username"); // 从会话中取出 count. Integer count = (Integer) session.getAttribute("count"); count += 1; // 把自增之后的值写回到会话中. session.setAttribute("count",count); resp.setContentType("text/html;charset=utf8"); resp.getWriter().write("

欢迎你! " + username + " 这是第 " + count + " 次访问主页

"); }

getSession 参数为 false , 不会创建新的 HttpSession, 而是返回上面 LoginServlet 类创建的HttpSession 对象

 4)登录成功效果

此处默认username = Fly, password = 123, 所以登录成功,点击刷新页面还可以看到访问的次数

 抓包结果,三次交互

 5) 登录失败效果

此处输入的 username 不正确,所以登录失败

 4.上传文件

上传文件也是日常开发中的一类常见需求 . Servlet 中也进行了支持。

4.1核心方法

HttpServletRequest 类方法

方法 描述
Part getPart(String name) 获取请求中给定 name 的文件
Collection getParts() 获取所有的文件

 Part  类方法

方法 描述
String getSubmittedFileName() 获取提交的文件名
String getContentType() 获取提交的文件类型
long getSize() 获取文件的大小
void write(String path) 把提交的文件数据写入磁盘文件

4.2 代码示例

实现程序 , 通过网页提交一个图片到服务器上 1) 创建 upload.html   放到 webapp 目录中。

          

上传文件一般通过 POST 请求的表单实现

 2) 创建 UploadServlet

@MultipartConfig //要给这个类加上这个注解,来开启对于上传文件的支持   //否则getPart调用的时候就会抛出异常!@WebServlet("/upload")public class UploadServlet extends HttpServlet {    @Override    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //获取请求给定 name 的名字 Part part = req.getPart("MyImage"); //获取提交的文件名 System.out.println(part.getSubmittedFileName()); //获取提交的文件类型 System.out.println(part.getContentType()); //获取文件的大小 System.out.println(part.getSize()); //把提交的文件数据并重命名写入磁盘文件 part.write("d:/fly/ff.jpg"); resp.setContentType("text/html; charset=utf8"); resp.getWriter().write("上传成功!");    }}

上传效果

d 盘中生成了  ff.jpg

此时可以看到服务器端的打印日志

 

开发者涨薪指南 Servlet API 详解 48位大咖的思考法则、工作方式、逻辑体系