0、Preface
本文档是我在公司工作后产生很多疑问去学习的。我是在有java核心jdk基础加上一些javaweb入门基础上直接在公司开始学习开发框架,并且是先Springboot,然后很多东西不理解又补了spring\spring mvc。但即便是再补上这两个知识体系,还是会有很多不理解的地方。封装带来便捷,同时也掩盖了很多东西,这些会带来认知和学习上的不便利。在我初步掌握“使用SpringBoot”的基础上,我的认知推动我来学习Servlet,至少对它有所了解。
完成文档时我工作时间差十天到5个月,我这个水平和资历也无法产出自己的东西。所以我这部分的内容大多都是结合我接触的两个课程和课程文档缝合的,中间遇到让我感觉疑惑的地方我也会找资料补充。
在我写完这部分文档后,我对文档的评价是,这是一个加深Servlet认知的文档,适合新手学Servlet前后来阅读。学习前阅读可以知道servelt大致是个什么东西,学习后来看可以当成一个总结。如果你也和我一样,没有学servlet直接跳到框架,想要了解数据是如何在前后端传递的。也可以看一看。
我希望有读者可以完善这个文档,特别是基于servlet包的各种测试。文档中提供的源码比较少。因为这类课程用的都是javaweb技术来解释servlet。我在5.1中提供了基于springboot的servlet技术测试demo。
真希望有很多把读者当成白痴的资料跟文档啊,那我我一定有更多的学习热情。抱歉这句话根文档内容无关,纯属个人感慨。
适合阅读人群
新学习servlet
想要了解servlet
阅读顺序:最好是先看看第六部分。其余部分随意。
资料内容与课程来源
- 这可能是B站讲的最好的Servlet教程,5小时打通Servlet全套教程丨2022最新版,轻松掌握servlet基础+案例实操_哔哩哔哩_bilibili
- 黑马程序员2023新版JavaWeb开发教程,实现javaweb企业开发全流程(涵盖Spring+MyBatis+SpringMVC+SpringBoot等)_哔哩哔哩_bilibili
- Chartgpt(感谢AI在五个月内极大缩减了我的学习成本)
- 网页资料
1、servlet是什么?
Servlet是一种用于在Web服务器上运行的Java程序。它是Java Servlet API的一部分,用于处理客户端发送的HTTP请求并生成响应。Servlet可以接收来自Web浏览器或其他HTTP客户端的请求,并根据请求的内容生成动态的HTML页面或其他类型的响应。Servlet通常用于构建Web应用程序,可以处理用户登录、数据查询、表单提交等功能。
Servlet:是Server和Applet的缩写,是服务端小程序的意思。使用 Java 语言编写的服务器端程序,可以像生成动态的 WEB 页,Servlet 主要运行在服务器端,并由服务器调用执行, 是一种按照 Servlet 标准来开发的类。 是 SUN 公司提供的一门用于开发动态 Web 资源的技术。(言外之意:要实现 web 开发,需要实现 Servlet 标准)。
Servlet 本质上也是 Java 类,但要遵循 Servlet 规范进行编写,没有 main()方法,它的创建、使用、销毁都由 Servlet 容器进行管理(如 Tomcat)。(言外之意:写自己的类,不用写 main 方法,别人自动调用)
这里我们可能会产生疑问,Servlet是不是一个java库呢,就像javaIO库一样,IO库帮我们解决数据的输入输出处理问题,Servlet 帮助我们解决与HTTP协议通信的问题。这样抽象的理解虽然可以,但是也有本质上的问题存在。
Servlet本身不是一个Java库,而是Java编程语言的一种规范或接口(也就是说具体的实现需要我们自己去做,可以回忆一下接口技术的作用),用于处理客户端请求并生成响应。它定义了服务器端组件的行为和规则,用于构建Web应用程序。Servlet规范是由Java社区制定的,并由Java Servlet API(javax.servlet包)提供实现。
简单来说,Servlet是一种Java编程模型,用于处理Web请求和响应。开发人员通过实现Servlet接口来创建Servlet,然后部署到支持Servlet规范的Web服务器中(如Tomcat、Jetty等)。这样,当Web服务器收到客户端请求时,Servlet容器会负责调用相应的Servlet来处理请求并返回响应。
虽然Servlet本身不是一个Java库,但Java Servlet API可以看作是一个Java库,它提供了实现Servlet规范所需的类和接口。因此,通过使用Servlet API,开发人员可以轻松地创建和部署基于Java的Web应用程序。
tip:在这里我给了很多关于Servlet概念上的解释,不是为了混淆概念迷惑读者。计算机很多东西本身就很抽象,从不同的角度出发做出的解释往往各不相同
2、servlet与http协议的关系!
Servlet 是和 HTTP 协议是紧密联系的,其可以处理 HTTP 协议相关的所有内容。这也是 Servlet 应用广泛的原因之一。
提供了 Servlet 功能的服务器,叫做 Servlet 容器,其常见容器有很多,如 Tomcat, Jetty, WebLogic Server, WebSphere, JBoss 等等。
HTTP(超文本传输协议)是用于在网络上传输超文本(例如网页)的协议。而Servlet是在服务器端处理HTTP请求和响应的Java程序。
tip:图片在这个网站制作https://www.processon.com/,很有用的一个网站
具体来说,当浏览器向服务器发送一个HTTP请求时,服务器会将这个请求交给相应的Servlet来处理。Servlet根据请求的内容进行逻辑处理,可能会访问数据库、读取文件,或者进行其他的计算和操作。处理完成后,Servlet会生成一个HTTP响应,并将响应发送回浏览器。
通过这种方式,Servlet充当了服务器和浏览器之间的桥梁,帮助实现了客户端(浏览器)与服务器之间的通信和数据交互。Servlet不仅可以处理普通的文本数据,还可以处理图片、音频、视频等多种类型的数据,使得网页应用程序能够呈现更加丰富和复杂的内容。
因此,可以说Servlet是基于HTTP协议的,它利用HTTP协议的请求和响应机制来实现网页应用程序的交互和动态内容生成。Servlet的出现为Java Web应用的开发提供了强大的支持,使得开发者可以通过Java编写动态、高效的网络应用程序
3、HTTP协议
HTTP 协议(Hypertext Transfer Protocol, 超文本传输协议),是一个客户端请求和响应的标准协议,这个协议详细规定了浏览器和万维网服务器之间互相通信的规则。用户输入地址和端口号之后就可以从服务器上取得所需要的网页信息。
通信规则规定了客户端发送给服务器的内容格式,也规定了服务器发送给客户端的内容格式。客户端发送给服务器的格式叫"请求协议";服务器发送给客户端的格式叫"响应协议"。
3.1 浏览器中的书写格式
服务器端资源需要通过浏览器进行操作,此时由浏览器将我们给出的请求解析为满足 HTTP 协议的格式并发出。我们发出的请求格式需要按照浏览器规定的格式来书写,在浏览器中书写格式如下:
当浏览器获取到信息以后,按照特定格式解析并发送即可。接收到服务器端给出的响应时,也按照HTTP 协议进行解析获取到各个数据,最后按照特定格式展示给用户。
3.2 HTTP协议的特点
1.支持客户/服务器模式。
2.简单快速:客户向服务器请求服务时,只需传送请求方法和路径。请求方法常用的 有 GET、POST。每种方法规定了客户与服务器联系的类型不同。由于 HTTP 协议简单,使得HTTP服务器的程序规模小,因而通信速度很快。
3.灵活:HTTP 允许传输任意类型的数据对象。传输的类型由Content-Type加以标记。
4.无连接:无连接是表示每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间,HTTP1.1 版本后支持可持续连接。通过这种连接,就有可能在建立一个 TCP 连接后,发送请求并得到回应,然后发送更多的请求并得到更多的回应.通过把建立和释放 TCP 连接的开销分摊到多个请求上,则对于每个请求而言,由于 TCP 而造成的相对开销被大大地降低了。而且, 还可以发送流水线请求,也就是说在发送请求 1 之后的回应到来之前就可以发送请求 2 也可以认为,一次连接发送多个请求,由客户机确认是否关闭连接,而服务器会认为这些请求分别来自不同的客户端。
5.无状态:HTTP 协议是无状态协议。无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送 的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快。
3.3 HTTP之URL
HTTP(超文本传输协议)是一个基于请求与响应模式的、应用层的协议,常基于 TCP 的连接方式,绝大多数的 Web 开发,都是构建在 HTTP 协议之上的 Web 应用。
HTTP URL (URL 是一种特殊类型的 URI,包含了用于查找某个资源的足够的信息)的格式 如下:
http://host[:port]/[abc_path]
http://IP(主机名/域名):端口/访问的资源路径
- http 表示要通过 HTTP 协议来定位网络资源;
- host 表示合法的 Internet 主机域名或 者 IP 地址;
- port 指定一个端口号,为空则使用缺省(即默认)端口 80;
- abs_path 指定请求资源的 URI; 如果 URL 中没有给出 abs_path,那么当它作为请求 URI 时,必须以“/”的形式给出,通常 这个工作浏览器自动帮我们完成。
3.4 HTTP-请求协议
浏览器和服务器是按照HTTP协议进行数据通信的。HTTP协议又分为:请求协议和响应协议
- 请求协议:浏览器将数据以请求格式发送到服务器
包括:请求行、请求头 、请求体
- 响应协议:服务器将数据以响应格式返回给浏览器
包括:响应行 、响应头 、响应体
在HTTP1.1版本中,浏览器访问服务器的几种方式
请求方式 | 请求说明 |
---|---|
GET | 获取资源。 向特定的资源发出请求。例:http://localhoust:8080/user?name=1 |
POST | 传输实体主体。 向指定资源提交数据进行处理请求(例:上传文件),数据被包含在请求体中。 |
OPTIONS | 返回服务器针对特定资源所支持的HTTP请求方式。 因为并不是所有的服务器都支持规定的方法,为了安全有些服务器可能会禁止掉一些方法,例如:DELETE、PUT等。那么OPTIONS就是用来询问服务器支持的方法。 |
HEAD | 获得报文首部。 HEAD方法类似GET方法,但是不同的是HEAD方法不要求返回数据。通常用于确认URI的有效性及资源更新时间等。 |
PUT | 传输文件。 PUT方法用来传输文件。类似FTP协议,文件内容包含在请求报文的实体中,然后请求保存到URL指定的服务器位置。 |
DELETE | 删除文件。 请求服务器删除Request-URI所标识的资源 |
TRACE | 追踪路径。 回显服务器收到的请求,主要用于测试或诊断 |
CONNECT | 要求用隧道协议连接代理。 HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器 |
实际中常用的也就put和get两种,此处了解即可。
3.4.1 GET方式的请求协议
tip:这张图从其他资料里整过来的,实际上在我的edg浏览器中,url后面并没有显示协议版本
- 请求行 :HTTP请求中的第一行数据。由: 请求方式 、 资源路径 、 协议/版本 组成(之间使用空格分隔)
请求方式:GET
资源路径:/brand/findAll?name=OPPO&status=1
请求路径:/brand/findAll
请求参数:name=OPPO&status=1
请求参数是以key=value形式出现,多个请求参数之间使用 & 连接,请求路径和请求参数之间使用 ? 连接
协议/版本:HTTP/1.1
- 请求头 :第二行开始,上图黄色部分内容就是请求头。格式为key: value形式
http是个无状态的协议,所以在请求头设置浏览器的一些自身信息和想要响应的形式。这样
服务器在收到信息后,就可以知道是谁,想干什么了
- 请求体 :存储请求参数
GET请求的请求参数在请求行中,故不需要设置请求体
常见的HTTP请求头有:
Host: 表示请求的主机名
User-Agent: 浏览器版本。 例如:Chrome浏览器的标识类似Mozilla/5.0...Chrome/79 ,IE浏览器的标识类似Mozilla/5.0 (Windows NT ...)like
Gecko
Accept:表示浏览器能接收的资源类型,如text/*,image/*或者*/*表示所有;
Accept-Language:表示浏览器偏好的语言,服务器可以据此返回不同语言的网页;
Accept-Encoding:表示浏览器可以支持的压缩类型,例如gzip, deflate等。
Content-Type:请求主体的数据类型
Content-Length:数据主体的大小(单位:字节)
3.4.2 POST方式的请求协议
tip:需要注意的是,现在浏览器检查工具显示的页面已经不是这种形式,不过内容大体上都是这些
- 请求行(以上图中红色部分):包含请求方式、资源路径、协议/版本
请求方式:POST
资源路径:/brand
协议/版本:HTTP/1.1
-
请求头(以上图中黄色部分)
-
请求体(以上图中绿色部分) :存储请求参数
-
请求体和请求头之间是有一个空行隔开(作用:用于标记请求头结束)
GET请求和POST请求的区别:
区别方式 | GET请求 | POST请求 |
---|---|---|
请求参数 | 请求参数在请求行中。 例:/brand/findAll?name=OPPO&status=1 | 请求参数在请求体中 |
请求参数长度 | 请求参数长度有限制(浏览器不同限制也不同) | 请求参数长度没有限制 |
安全性 | 安全性低。原因:请求参数暴露在浏览器地址栏中。 | 安全性相对高 |
3.4.3 HTTP响应协议
与HTTP的请求一样,HTTP响应的数据也分为3部分:响应行、响应头 、响应体
- 响应行(以上图中红色部分):响应数据的第一行。响应行由 协议及版本 、 响应状态码 、 状态码描述 组成
协议/版本:HTTP/1.1
响应状态码:200
状态码描述:OK
- 响应头(以上图中黄色部分):响应数据的第二行开始。格式为key:value形式
http是个无状态的协议,所以可以在请求头和响应头中设置一些信息和想要执行的动作,这样,对方在收到信息后,就可以知道你是谁,你想干什么
常见的HTTP响应头有
Content-Type:表示该响应内容的类型,例如text/html,image/jpeg ;
Content-Length:表示该响应内容的长度(字节数);
Content-Encoding:表示该响应压缩算法,例如gzip ;
Cache-Control:指示客户端应如何缓存,例如max-age=300表示可以最多缓存300秒 ;
Set-Cookie: 告诉浏览器为当前页面所在的域设置cookie ;
- 响应体(以上图中绿色部分): 响应数据的最后一部分。存储响应的数据
响应体和响应头之间有一个空行隔开(作用:用于标记响应头结束)
3.4.4 响应状态码
状态码分类 | 说明 |
---|---|
1xx | 响应中 — 临时状态码。表示请求已经接受,告诉客户端应该继续请求或者如果已经完成则忽略 |
2xx | 成功 — 表示请求已经被成功接收,处理已完成 |
3xx | 重定向 — 重定向到其它地方,让客户端再发起一个请求以完成整个处理 |
4xx | 客户端错误 — 处理发生错误,责任在客户端,如:客户端的请求一个不存在的资源,客户端未被授权,禁止访问等 |
5xx | 服务器端错误 — 处理发生错误,责任在服务端,如:服务端抛出异常,路由出错,HTTP版本不支持等 |
关于响应状态码,我们先主要认识三个状态码,其余的等后期用到了再去掌握:
- 200 ok 客户端请求成功
- 404 Not Found 请求资源不存在
- 500 Internal Server Error 服务端发生不可预期的错误
4、Tomcat服务器
4.1 服务器概述
- 服务器硬件
指的也是计算机,只不过服务器要比我们日常使用的计算机大很多
服务器,也称伺服器。是提供计算服务的设备。由于服务器需要响应服务请求,并进行处理,因此一般来说服务器应具备承担服务并且保障服务的能力。
服务器的构成包括处理器、硬盘、内存、系统总线等,和通用的计算机架构类似,但是由于需要提供高可靠的服务,因此在处理能力、稳定性、可靠性、安全性、可扩展性、可管理性等方面要求较高。
在网络环境下,根据服务器提供的服务类型不同,可分为:文件服务器,数据库服务器,应用程序服务器,WEB服务器等。
服务器只是一台设备,必须安装服务器软件才能提供相应的服务。
- 服务器软件
服务器软件:基于ServerSocket编写的程序
服务器软件本质是一个运行在服务器设备上的应用程序
能够接收客户端请求,并根据请求给客户端响应数据
4.2 Web服务器概述
Web服务器是一个应用程序(软件),对HTTP协议的操作进行封装,使得程序员不必直接对协议进行操作(不用程序员自己写代码去解析http协议规则),让Web开发更加便捷。主要功能是"提供网上信息浏览服务"
Web服务器是安装在服务器端的一款软件,将来我们把自己写的Web项目部署到Tomcat服务器软件中,
当Web服务器软件启动后,部署在Web服务器软件中的页面就可以直接通过浏览器来访问了。
Web服务器软件使用步骤
- 准备静态资源
- 下载安装Web服务器软件
- 将静态资源部署到Web服务器上
- 启动Web服务器使用浏览器访问对应的资源
4.3 什么是Tomcat服务器
Tomcat 是一个符合 JavaEE WEB 标准的最小的 WEB 容器,所有的 JSP 程序一定要有 WEB 容器的支持才能运行,而且在给定的 WEB 容器里面都会支持事务处理操作。
Tomcat 是由 Apache 提供的(www.apache.org)提供的可以用安装版和解压版,安装版可以在服务中出现一个 Tomcat 的服务,免安装没有,开发中使用免安装版。 Tomcat 简单的说就是一个运行 Java 的网络服务器,底层是 Socket 的一个程序,它也是 JSP 和 Servlet 的一个容器。 Tomcat 是 Apache 软件基金会(Apache Software Foundation)的 Jakarta 项目中的一个核心项目,由 Apache、Sun和其他一些公司及个人共同开发而成。
由于有了 Sun 的参与和支持,最新的 Servlet 和 JSP 规范总是能在 Tomcat 中得到体现。因为Tomcat 技术先进、性能稳定,而且免费,因而深受 Java 爱好者的喜爱并得到了部分软件开发商的认可,成为目前比较流行的 Web 应用服务器。Tomcat 服务器是一个免费的开放源代码的 Web 应用服务器,属于轻量级应用服务器, 在中小型系统和并发访问用户不是很多的场合下被普遍使用,是开发和调试 JSP 程序的首选。 对于一个初学者来说,可以这样认为,当在一台机器上配置好 Apache 服务器,可利用它响应 HTML(标准通用标记语言下的一个应用)页面的访问请求。实际上 Tomcat 部分是 Apache 服务器的扩展,但它是独立运行的,所以当你运行 tomcat 时,它实际上作为一个与 Apache 独立的进程单独运行的。当配置正确时,Apache 为 HTML 页面服务,而 Tomcat 实际上是在运行 JSP 页面和 Servlet。另外,Tomcat 和 IIS 等 Web 服务器一样,具有处理 HTML 页面的功能,另外它还是 一个 Servlet 和 JSP 容器,独立的 Servlet 容器是 Tomcat 的默认模式。不过,Tomcat 处理静态 HTML 的能力不如 Apache 服务器。
tip:由于Tomcat只支持Servlet/JSP少量JavaEE规范,所以是一个开源免费的轻量级Web服务器。
4.4 待补充
对于Tomcat的下载安装以及在idea中的集成,暂时不进行补充。也有一个原因就是我在测试时直接用的springboot项目是内嵌Tomcat的。
5、Servlet
在我阅读的资料中,测试Servlet基本都是使用javaee项目,然后还需要配置Tomcat,但是我觉得很麻烦,我们的目的并不是要学习原生态的Servlet是如何工作的,作为我个人来说,毕业在上班之前学的是老一套的流水线方案,先java基础,然后javaweb,然后框架啥的,但是这种学习路线耗时特别长,而且很乏味,一时间做不出东西,在学生阶段没有人带领导致学起来枯燥乏味不说,也迟迟见不到成果,学起来很痛苦。当我进入公司后,带我的大哥直接让我看springboot。这让我在很短的时间内取得成果,同时也积累了无数个为什么。所以我在工作中是围绕着springboot以及springcloud为核心不停的去补充学习。属于一个逆向的学习。这也是为什么我会回头来看Servlet。
所以在测试的时候我直接选择用springboot项目测试
5.1 Servlet测试Demo
5.1.1 新一个springboot项目
本人新建项目配置
jdk 1.8
类型:maven
spring boot 版本:2.7.14
依赖选择:Spring Web
5.1.2 新建类继承HttpServlet,并覆写Service方法
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServlet;
import java.io.IOException;
//实现 Servlet 规范,即继承 HttpServlet 类,该类中已经完成了通信的规则,我们只需要进行业务的实现即可。
public class ServletTestDemo extends HttpServlet {
/**
*满足 Servlet 规范只是让我们的类能够满足接收请求的要求,接收到请求后需要对请求进行分析,以
*及进行业务逻辑处理,计算出结果,则需要添加代码,在规范中有一个叫做 service的方法,专门用来做
*请求处理的操作,业务代码则可以写在该方法中。
*/
@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
System.out.println("Hello Servlet!");
res.getWriter().write("Hello world");
}
}
5.1.3 注册bean
在启动类中通过ServletRegistrationBean
或ServletRegistration
完成Servlet的注册和配置(当然你也可以选择单独编写一个配置类)
//启动类注册
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@Bean
public ServletRegistrationBean<ServletTestDemo> servletRegistrationBean() {
return new ServletRegistrationBean<>(new ServletTestDemo(), "/ser01"); // 注册Servlet并映射到指定路径
}
}
//配置类注册
@Configuration
public class ServletConfig {
@Bean
public ServletRegistrationBean<ServletTestDemo> servletRegistrationBean() {
return new ServletRegistrationBean<>(new ServletTestDemo(), "/ser01"); // 注册Servlet并映射到指定路径
}
}
5.1.4 启动项目并进行测试
控制台
浏览器
到此,我们就完成了一个简单的servlet测试。刚做完,你会感觉,咦,前面一大堆,怎么到实现这里就这么点东西,Servlet就这儿?有手就行。但是当你再去回顾这个流程的时候,你会发现很多问题。为什么我输入这个url后,我的代码逻辑就运行了呢?ServletResponse的实例对象是如何携带我的信息传输给浏览器的。在计算机中,那些让你感觉上手容易的东西,背后往往封装了很多东西。确实,封装好的工具给我们的工作带来了便捷,但是对于我们的学习确带来了巨大的阻力。
5.2 Servlet的工作流程
1. 客户端发送HTTP请求
客户端通过浏览器或其他HTTP客户端发送一个HTTP请求到服务器。请求中包含了URL、请求方法(GET、POST等)、请求头、请求参数等信息。
2. Servlet容器接收请求
Servlet容器是Web服务器的一部分,它监听特定的端口(通常是80或8080)来接收HTTP请求。当有请求到达时,Servlet容器负责处理该请求。
3. 寻找匹配的Servlet
Servlet容器根据请求的URL路径(或其他配置方式)来查找匹配的Servlet。每个Servlet都会在部署描述符(如web.xml)或通过注解的方式指定它可以处理的URL路径。
4. 创建或复用Servlet实例
一旦找到匹配的Servlet,Servlet容器会创建该Servlet的实例(如果是第一次请求该Servlet)或者复用现有的实例(如果该Servlet已经被初始化过了)。
5. 初始化Servlet
如果是第一次创建该Servlet的实例,Servlet容器会调用init()
方法来初始化Servlet。在这个阶段,Servlet可以执行一些初始化工作,比如读取配置文件、建立数据库连接等。
6. 调用Servlet的service方法
一旦Servlet被初始化,Servlet容器会将HTTP请求的ServletRequest和ServletResponse对象传递给Servlet的service()
方法。在这个方法中,Servlet可以根据请求的类型(GET、POST等)来处理请求,并生成响应。
7. 生成HTTP响应
Servlet的service()
方法会生成一个HTTP响应,该响应包含了状态码、响应头、响应体等信息。响应体通常是HTML文档、JSON数据或其他内容,它会返回给客户端。注意:由服务器讲response缓冲区的数据取出,以http响应的格式发送给浏览器。
8. 销毁Servlet(可选)
在Servlet容器关闭时,或者当Servlet不再被需要时,Servlet容器会调用destroy()
方法来销毁Servlet。在这个方法中,Servlet可以执行一些资源释放和清理工作。
9. 返回HTTP响应
Servlet容器将生成的HTTP响应返回给客户端,客户端根据响应内容进行相应的处理,通常是显示网页内容或执行其他操作。
tip:需要注意的是,Servlet容器负责管理Servlet的生命周期,它会自动地初始化和销毁Servlet,以及管理Servlet实例的复用,开发人员只需专注于编写Servlet的业务逻辑。
5.3 Servlet的生命周期
Servlet没有 main()方法,不能独立运行,它的运行完全由 Servlet 引擎来控制和调度。 所谓生命周期,指的是 servlet 容器何时创建 servlet 实例、何时调用其方法进行请求的处理、 何时并销毁其实例的整个过程。
1. 加载
当Servlet容器启动时,会读取部署描述符(通常是web.xml文件)或扫描注解,以查找并加载Servlet类。
2. 实例化
一旦找到Servlet类,Servlet容器会创建Servlet的实例。这通常发生在第一次接收到与该Servlet相关的请求时。每个Servlet都有一个实例,但可以处理多个请求。
3. 初始化
在创建Servlet实例后,Servlet容器会调用Servlet的init()
方法。在这个方法中,你可以执行一些初始化的任务,例如加载配置、建立数据库连接等。init()
方法只会在Servlet的生命周期中调用一次。
4.处理请求
一旦Servlet初始化完成,Servlet容器会开始处理与该Servlet匹配的每个请求。对于每个请求,Servlet容器都会创建一个ServletRequest和ServletResponse对象,并将它们传递给Servlet的service()
方法。
5. 服务(service)
   service()
方法是Servlet的核心方法,它处理来自客户端的请求并生成响应。根据请求的类型(GET、POST等),你可以在service()
方法中编写相应的业务逻辑。容器调用 servlet 对象的 service()方法,处理请求的方法在整个生命周期中可以被多次调用; HttpServlet 的 service()方法,会依据请求方式来调用doGet()或者 doPost()方法。但是, 这两个 do 方法默认情况下,会抛出异常,需要子类去 override。
6. 销毁
当Servlet容器关闭时,或者在Servlet不再需要的情况下(例如应用程序被卸载),Servlet容器会调用Servlet的destroy()
方法。在这个方法中,你可以执行资源释放和清理操作,例如关闭数据库连接、释放文件句柄等。destroy()
方法只会在Servlet生命周期中调用一次。
7. 卸载
在Servlet容器关闭或Web应用程序被卸载时,Servlet的实例会被销毁,从内存中移除。
tips:
上述的生命周期可以通过 Servlet 中的生命周期方法来观察。在 Servlet 中有三个生命周 期方法,不由用户手动调用,而是在特定的时机有容器自动调用,观察这三个生命周期方法 即可观察到Servlet 的生命周期。(感兴趣的话可以在上面的例子中覆写init和destroy方法自己测试)
Servlet容器负责管理Servlet的生命周期,开发人员通常只需要实现init()
、service()
和destroy()
方法,以及处理业务逻辑,无需手动管理Servlet的生命周期。
为了更直观的显示上述周期,用以下图例
tip:虽然这个图是我照着资料抄的,不过也花费了我一番功夫。
-
Web Client 向 Servlet 容器(Tomcat)发出 Http 请求
-
Servlet 容器接收 Web Client 的请求
-
Servlet 容器创建一个 HttpServletRequest 对象,将 Web Client 请求的信息封装到这个对象 中
-
Servlet 容器创建一个 HttpServletResponse 对象
-
Servlet 容器调HttpServlet 对象service 方法,把 Request 与 Response 作为参数,传给HttpServlet
-
HttpServlet 调用 HttpServletRequest 对象的有关方法,获取 Http 请求信息
-
HttpServlet 调用 HttpServletResponse 对象的有关方法,生成响应数据
-
Servlet 容器把 HttpServlet 的响应结果传给 Web Client
5.4 HttpServletRequest对象
HttpServletRequest 对象:主要作用是用来接收客户端发送过来的请求信息,例如:请求的参数,发送的头信息等都属于客户端发来的信息,service()方法中形参接收的是 HttpServletRequest 接口的实例化对象,表示该对象主要应用在 HTTP 协议上,该对象是由 Tomcat 封装好传递过来。HttpServletRequest 是 ServletRequest 的子接口,ServletRequest 只有一个子接口,就是HttpServletRequest。
既然只有一个子接口为什么不将两个接口合并为一个?
从长远上讲:现在主要用的协议是 HTTP 协议,但以后可能出现更多新的协议。若以后想要支持这种新协议,只需要直接继承 ServletRequest 接口就行了。
在 HttpServletRequest 接口中,定义的方法很多,但都是围绕接收客户端参数的。但是怎么拿到该对象呢?不需要,直接在 Service 方法中由容器传入过来,而我们需要做的就是取出对象中的数据,进行分析、处理。
5.4.1 接收请求
1.常用方法
方法名 | 方法作用 |
---|---|
getRequestURL() | 获取客户端发出请求时的完整URL |
getRequestURI() | 获取请求行中的资源名称部分(项目名称开始) |
getQueryString() | 获取请求中的参数部分 |
getMethod() | 获取客户端请求方式 |
getContextPath() | 获取HTTP版本号 |
getProtocol() | 获取webapp名字 |
2.示例
// 获取客户端请求的完整URL (从http开始,到?前面结束)
String url = request.getRequestURL().toString();
System.out.println("获取客户端请求的完整URL:" + url);
// 获取客户端请求的部分URL (从站点名开始,到?前面结束)
String uri = request.getRequestURI();
System.out.println("获取客户端请求的部分URL:" + uri);
// 获取请求行中的参数部分
String queryString = request.getQueryString();
System.out.println("获取请求行中的参数部分:" + queryString);
// 获取客户端的请求方式
String method = request.getMethod();
System.out.println("获取客户端的请求方式:" + method);
// 获取HTTP版本号
String protocol = request.getProtocol();
System.out.println("获取HTTP版本号:" + protocol);
// 获取webapp名字 (站点名)
String webapp = request.getContextPath();
System.out.println("获取webapp名字:" + webapp);
5.4.2 获取请求参数
1.方法
方法名 | 方法作用 |
---|---|
getParameter(name) | 获取指定名称参数 |
getParameterValues(String name) | 获得指定名称参数的所有值 |
2.示例
// 获取指定名称的参数,返回字符串
String uname = request.getParameter("uname");
System.out.println("uname的参数值:" + uname);
// 获取指定名称参数的所有参数值,返回数组
String[] hobbys = request.getParameterValues("hobby");
System.out.println("获取指定名称参数的所有参数值:" + Arrays.toString(hobbys));
5.4.3 请求乱码问题
由于现在的 request 属于接收客户端的参数,所以必然有其默认的语言编码,主要是由于在解析过程中默认使用的编码方式为 ISO-8859-1(此编码不支持中文),所以解析时一定会出现乱码。要想解决这种乱码问题,需要设置 request 中的编码方式,告诉服务器以何种方式来解析数据。或者在接收到乱码数据以后,再通过相应的编码格式还原。
方式一
request.setCharacterEncoding("UTF-8");
这种方式只针对 POST 有效(必须在接收所有的数据之前设定)
方式二
new String(request.getParameter(name).getBytes("ISO-8859-1"),"UTF-8");
借助了String 对象的方法,该种方式对任何请求有效,是通用的。
Tomcat8起,以后的GET方式请求是不会出现乱码的。
5.4.4 请求转发
请求转发(Request Forwarding)是指将一个客户端的请求转发给另一个资源进行处理的过程。在请求转发中,客户端只发送一次请求,服务器将该请求转发给其他资源进行处理,然后将处理结果返回给客户端。请求转发,是一种服务器的行为,当客户端请求到达后,服务器进行转发,此时会将请求对象进行保存,地址栏中的 URL 地址不会改变,得到响应后,服务器端再将响应发送给客户端,从始至终只有一个请求发出。
在 Java Web 开发中,请求转发是通过 Servlet 的 RequestDispatcher
接口来实现的。通过调用 RequestDispatcher
对象的 forward()
方法,可以将请求转发给另一个 Servlet、JSP 页面或其他资源进行处理。
请求转发的特点包括:
- 客户端只发送一次请求,URL 地址不会发生变化。
- 转发过程在服务器端完成,客户端感知不到。
- 转发过程中,请求和响应对象共享同一个请求上下文。
使用请求转发可以实现以下功能:
- 模块化开发:将不同的功能模块拆分成多个 Servlet 或 JSP 页面,通过请求转发将请求分发给不同的模块进行处理。
- 数据共享:在转发过程中,可以将请求中的数据传递给其他资源进行处理,然后将处理结果返回给客户端。
- 代码重用:可以将一些通用的处理逻辑封装在一个 Servlet 或 JSP 页面中,通过请求转发在不同的地方进行复用。
请求转发的语法示例:
RequestDispatcher dispatcher = request.getRequestDispatcher("/targetUrl");
dispatcher.forward(request, response);
其中,/targetUrl
是转发目标的 URL 地址,request
和 response
是当前请求和响应的对象。通过调用 forward()
方法,将请求转发给目标 URL 进行处理。
需要注意的是,请求转发只能在同一个 Web 应用程序内部进行,不能跨越不同的 Web 应用程序。
5.4.5 request作用域
通过该对象可以在一个请求中传递数据,作用范围:在一次请求中有效,即服务器跳转有效。
// 设置域对象内容
request.setAttribute(String name, String value);
// 获取域对象内容
request.getAttribute(String name);
// 删除域对象内容
request.removeAttribute(String name);
request 域对象中的数据在一次请求中有效,则经过请求转发,request 域中的数据依然存在,则在请求转发的过程中可以通过 request 来传输/共享数据。
5.5 HttpServletResponse对象
Web服务器收到客户端的http请求,会针对每一次请求,分别创建一个用于代表请求的 request 对象和代表响应的 response 对象。request 和 response 对象代表请求和响应:获取客户端数据,需要通过 request 对象;向客户端输出数据,需要通过 response 对象。HttpServletResponse 的主要功能用于服务器对客户端的请求进行响应,将 Web 服务器处理后的结果返回给客户端。service()方法中形参接收的是 HttpServletResponse 接口的实例化对象,这个对象中封装了向客户端发送数据、发送响应头,发送响应状态码的方法。
5.5.1 响应数据
接收到客户端请求后,可以通过 HttpServletResponse 对象直接进行响应,响应时需要获取输出流。
有两种形式:
getWriter() 获取字符流(只能响应回字符)
getOutputStream() 获取字节(能响应一切数据)
响应回的数据到客户端被浏览器解析。
注意:两者不能同时使用。
// 字符输出流
PrintWriter writer = response.getWriter();
writer.write("Hello");
writer.write("<h2>Hello</h2>");
// 字节输出流
ServletOutputStream out = response.getOutputStream();
out.write("Hello".getBytes());
out.write("<h2>Hello</h2>".getBytes());
设置响应类型,默认是字符串
// 设置响应MIME类型
response.setHeader("content-type","text/html"); // html
5.5.2 响应乱码问题
在响应中,如果我们响应的内容中含有中文,则有可能出现乱码。这是因为服务器响应的数据也会经过网络传输,服务器端有一种编码方式,在客户端也存在一种编码方式,当两端使用的编码方式不同时则出现乱码。
getWriter()的字符乱码
对于 getWriter()获取到的字符流,响应中文必定出乱码,由于服务器端在进行编码时默认会使用 ISO-8859-1 格式的编码,该编码方式并不支持中文。要解决该种乱码只能在服务器端告知服务器使用一种能够支持中文的编码格式,比如我们通常用的"UTF-8"。
response.setCharacterEncoding("UTF-8");
此时还只完成了一半的工作,要保证数据正确显示,还需要指定客户端的解码方式。
response.setHeader("content-type", "text/html;charset=UTF-8");
两端指定编码后,乱码就解决了。一句话:保证发送端和接收端的编码一致
// 设置服务端的编码
response.setCharacterEncoding("UTF-8");
// 设置客户端的响应类型及编码
response.setHeader("content-type","text/html;charset=UTF-8");
// 得到字符输出流
PrintWriter writer = response.getWriter();
writer.write("<h2>你好</h2>");
以上两端编码的指定也可以使用一句替代,同时指定服务器和客户端
response.setContentType("text/html;charset=UTF-8");
getOutputStream()字节乱码
对于 getOutputStream()方式获取到的字节流,响应中文时,由于本身就是传输的字节, 所以此时可能出现乱码,也可能正确显示。当服务器端给的字节恰好和客户端使用的编码方式一致时则文本正确显示,否则出现乱码。无论如何我们都应该准确掌握服务器和客户端使用的是那种编码格式,以确保数据正确显示。
指定客户端和服务器使用的编码方式一致。
response.setHeader("content-type","text/html;charset=UTF-8");
// 设置客户端的编码及响应类型
ServletOutputStream out = response.getOutputStream();
response.setHeader("content-type","text/html;charset=UTF-8");
out.write("<h2>你好</h2>".getBytes("UTF-8"));
同样也可以使用一句替代
// 设置客户端与服务端的编码
response.setContentType("text/html;charset=UTF-8");
总结:要想解决响应的乱码,只需要保证使用支持中文的编码格式。并且保证服务器端 和客户端使用相****同的编码方式即可。
5.6 重定向
5.6.1 什么是重定向
重定向是一种服务器指导,客户端的行为。客户端发出第一个请求,被服务器接收处理后,服务器会进行响应,在响应的同时,服务器会给客户端一个新的地址(下次请求的地址response.sendRedirect(url);),当客户端接收到响应后,会立刻、马上、自动根据服务器给的新地址发起第二个请求,服务器接收请求并作出响应,重定向完成。
从描述中可以看出重定向当中有两个请求存在,并且属于客户端行为。
// 重定向跳转到index.jsp
response.sendRedirect("index.jsp");
地址栏最终看到的地址是和第一次请求地址不同的,地址栏已经发生了变化。
5.6.2 请求转发与重定向的区别
请求转发和重定向比较
请求转发 | 重定向 |
---|---|
一次请求,数据再request域中共享 | 两次请求,request域中数据不共享 |
服务器器端行为 | 客户端行为 |
地址栏不发生变化 | 地址栏发生变化 |
觉地址定位到站点后 | 绝对值可写到http:// |
两者都可跳转,根据实际需求选取即可。
5.7 Cookie对象
5.7.1 什么是Cookie
Cookie是浏览器提供的一种技术,通过服务器的程序能将一些只须保存在客户端,或者在客户端进行处理的数据,放在本地的计算机上,不需要通过网络传输,因而提高网页处理的效率,并且能够减少服务器的负载,但是由于 Cookie 是服务器端保存在客户端的信息, 所以其安全性也是很差的。例如常见的记住密码则可以通过 Cookie 来实现。有一个专门操作Cookie的类 javax.servlet.http.Cookie。随着服务器端的响应发送给客户端,保存在浏览器。当下次再访问服务器时把Cookie再带回服务器。
Cookie 的格式:键值对用“=”链接,多个键值对间通过“;”隔开。
5.7.2 Cookie的创建和发送
通过 new Cookie(“key”,“value”);来创建一个 Cookie 对象,要想将 Cookie 随响应发送到客户端,需要先添加到 response 对象中,response.addCookie(cookie);此时该 cookie 对象则随着响应发送至了客户端。在浏览器上可以看见。
// 创建Cookie对象
Cookie cookie = new Cookie("uname","zhangsan");
// 发送Cookie对象
response.addCookie(cookie);
5.7.2 Cookie的获取
在服务器端只提供了一个 getCookies()的方法用来获取客户端回传的所有 cookie 组成的一个数组,如果需要获取单个 cookie 则需要通过遍历,getName()获取 Cookie 的名称,getValue()获取 Cookie 的值。
// 获取Cookie数组
Cookie[] cookies = request.getCookies();
// 判断数组是否为空
if (cookies != null && cookies.length > 0) {
// 遍历Cookie数组
for (Cookie cookie : cookies){
System.out.println(cookie.getName());
System.out.println(cookie.getValue());
}
}
5.7.3 Cookie设置到期时间
除了 Cookie 的名称和内容外,我们还需要关心一个信息,到期时间,到期时间用来指定该 cookie 何时失效。默认为当前浏览器关闭即失效。我们可以手动设定 cookie 的有效时间(通过到期时间计算),通过 setMaxAge(int time);方法设定 cookie 的最大有效时间,以秒为单位。
到期时间的取值
- 负整数
若为负数,表示不存储该 cookie。cookie 的 maxAge 属性的默认值就是-1,表示只在浏览器内存中存活,一旦关闭浏览器窗口,那么 cookie 就会消失。
- 正整数
若大于 0 的整数,表示存储的秒数。表示 cookie 对象可存活指定的秒数。当生命大于 0 时,浏览器会把 Cookie 保存到硬盘上,就算关闭浏览器,就算重启客户端电脑,cookie 也会存活相应的时间。
- 零
若为 0,表示删除该 cookie。cookie 生命等于 0 是一个特殊的值,它表示 cookie 被作废!也就是说,如果原来浏览器已经保存了这个 Cookie,那么可以通过 Cookie 的 setMaxAge(0)来删除这个 Cookie。 无论是在浏览器内存中,还是在客户端硬盘上都会删除这个 Cookie。
5.7.4 设置Cookie对象指定时间后失效
// 创建Cookie对象
Cookie cookie = new Cookie("uname","zhangsan");
// 设置Cookie 3天后失效
cookie.setMaxAge(3 * 24 * 60 * 60);
// 发送Cookie对象
response.addCookie(cookie);
5.7.5 Cookie的注意点
- Cookie保存在当前浏览器中。
在一般的站点中常常有记住用户名这样一个操作,该操作只是将信息保存在本机上,换电脑以后这些信息就无效了。而且 cookie 还不能跨浏览器。
- Cookie存中文问题
Cookie 中不能出现中文,如果有中文则通过 URLEncoder.encode()来进行编码,获取时通过URLDecoder.decode()来进行解码。
String name = "姓名";
String value = "张三";
// 通过 URLEncoder.encode()来进行编码
name = URLEncoder.encode(name);
value = URLEncoder.encode(value);
// 创建Cookie对象
Cookie cookie = new Cookie(name,value);
// 发送Cookie对象
response.addCookie(cookie);
// 获取时通过 URLDecoder.decode()来进行解码
URLDecoder.decode(cookie.getName());
URLDecoder.decode(cookie.getValue());
- 同名Cookie问题
如果服务器端发送重复的Cookie那么会覆盖原有的Cookie。
- 浏览器存放Cookie的数量
不同的浏览器对Cookie也有限定,Cookie的存储有是上限的。Cookie是存储在客户端(浏览器)的,而且一般是由服务器端创建和设定。后期结合Session来实现回话跟踪。
5.7.6 Cookie的路径
Cookie的setPath设置cookie的路径,这个路径直接决定服务器的请求是否会从浏览器中加载某些
cookie。
**情景一:**当前服务器下任何项目的任意资源都可获取Cookie对象
/* 当前项目路径为:s01 */
Cookie cookie = new Cookie("xxx","XXX");
// 设置路径为"/",表示在当前服务器下任何项目都可访问到Cookie对象
cookie.setPath("/");
response.addCookie(cookie);
**情景二:**当前项目下的资源可获取Cookie对象 (默认不设置Cookie的path)
/* 当前项目路径为:s01 */
Cookie cookie = new Cookie("xxx","XXX");
// 设置路径为"/s01",表示在当前项目下任何项目都可访问到Cookie对象
cookie.setPath("/s01"); // 默认情况,可不设置path的值
response.addCookie(cookie);
**情景三:**指定项目下的资源可获取Cookie对象
/* 当前项目路径为:s01 */
Cookie cookie = new Cookie("xxx","XXX");
// 设置路径为"/s02",表示在s02项目下才可访问到Cookie对象
cookie.setPath("/s02"); // 只能在s02项目下获取Cookie,就算cookie是s01产生的,s01也不能
获取它
response.addCookie(cookie);
**情景四:**指定目录下的资源可获取Cookie对象
/* 当前项目路径为:s01 */
Cookie cookie = new Cookie("xxx","XXX");
// 设置路径为"/s01/cook",表示在s02/cook目录下才可访问到Cookie对象
cookie.setPath("/s01/cook");
response.addCookie(cookie);
如果我们设置path,如果当前访问的路径包含了cookie的路径(当前访问路径在cookie路径基础上要比cookie的范围小)cookie就会加载到request对象之中。
cookie的路径指的是可以访问该cookie的顶层目录,该路径的子路径也可以访问该cookie。
总结:当访问的路径包含了cooki的路径时,则该请求将带上该cookie;如果访问路径不包含cookie路径,则该请求不会携带该cookie。
5.8 HttpSession对象
HttpSession对象是 javax.servlet.http.HttpSession 的实例,该接口并不像 HttpServletRequest 或HttpServletResponse 还存在一个父接口,该接口只是一个纯粹的接口。这因为 session 本身就属于HTTP 协议的范畴。
对于服务器而言,每一个连接到它的客户端都是一个 session,servlet 容器使用此接口创建 HTTP 客户端和 HTTP 服务器之间的会话。会话将保留指定的时间段,跨多个连接或来自用户的页面请求。一个会话通常对应于一个用户,该用户可能多次访问一个站点。可以通过此接口查看和操作有关某个会话的信息,比如会话标识符、创建时间和最后一次访问时间。在整个 session 中,最重要的就是属性的操作。
session 无论客户端还是服务器端都可以感知到,若重新打开一个新的浏览器,则无法取得之前设置的 session,因为每一个 session 只保存在当前的浏览器当中,并在相关的页面取得。
Session 的作用就是为了标识一次会话,或者说确认一个用户;并且在一次会话(一个用户的多次请求)期间共享数据。我们可以通过 request.getSession()方法,来获取当前会话的 session 对象。
// 如果session对象存在,则获取;如果session对象不存在,则创建
HttpSession session = request.getSession();
5.8.1 标识符 JSESSIONID
Session 既然是为了标识一次会话,那么此次会话就应该有一个唯一的标志,这个标志就是sessionId。
每当一次请求到达服务器,如果开启了会话(访问了 session),服务器第一步会查看是否从客户端回传一个名为 JSESSIONID 的 cookie,如果没有则认为这是一次新的会话,会创建 一个新的 session 对象,并用唯一的 sessionId 为此次会话做一个标志。如果有 JESSIONID 这 个cookie回传,服务器则会根据 JSESSIONID 这个值去查看是否含有id为JSESSION值的session 对象,如果没有则认为是一个新的会话,重新创建一个新的 session 对象,并标志此次会话; 如果找到了相应的 session 对象,则认为是之前标志过的一次会话,返回该 session 对象,数据达到共享。这里提到一个叫做 JSESSIONID 的 cookie,这是一个比较特殊的 cookie,当用户请求服务器时,如果访问了 session,则服务器会创建一个名为 JSESSIONID,值为获取到的 session(无论是获取到的还是新创建的)的 sessionId 的 cookie 对象,并添加到 response 对象中,响应给客户端,有效时间为关闭浏览器。
所以 Session 的底层依赖 Cookie 来实现。
5.8.2 session域对象
Session 用来表示一次会话,在一次会话中数据是可以共享的,这时 session 作为域对象存在,可以通过 setAttribute(name,value) 方法向域对象中添加数据,通过 getAttribute(name) 从域对象中获取数据,通过 removeAttribute(name) 从域对象中移除数据。
// 获取session对象
HttpSession session = request.getSession();
// 设置session域对象
session.setAttribute("uname","admin");
// 获取指定名称的session域对象
String uname = (String) request.getAttribute("uname");
// 移除指定名称的session域对象
session.removeAttribute("uname");
数据存储在 session 域对象中,当 session 对象不存在了,或者是两个不同的 session 对象时,数据也就不能共享了。这就不得不谈到 session 的生命周期。
5.8.3 session对象的销毁
1. 默认时间到期
当客户端第一次请求 servlet 并且操作 session 时,session 对象生成,Tomcat 中 session 默认的存活时间为 30min,即你不操作界面的时间,一旦有操作,session 会重新计时。那么 session 的默认时间可以改么?答案是肯定的。可以在 Tomcat 中的 conf 目录下的 web.xml 文件中进行修改。
<!-- session 默认的最大不活动时间。单位:分钟。 -->
<session-config>
<session-timeout>30</session-timeout>
</session-config>
2. 自己设定到期时间
当然除了以上的修改方式外,我们也可以在程序中自己设定 session 的生命周期,通过session.setMaxInactiveInterval(int) 来设定 session 的最大不活动时间,单位为秒。
// 获取session对象
HttpSession session = request.getSession();
// 设置session的最大不活动时间
session.setMaxInactiveInterval(15); // 15秒
我们也可以通过 getMaxInactiveInterval() 方法来查看当前 Session 对象的最大不活动时间。
// 获取session的最大不活动时间
int time = session.getMaxInactiveInterval();
3. 立刻失效
或者我们也可以通过 session.invalidate() 方法让 session 立刻失效
// 销毁session对象
session.invalidate();
4. 关闭浏览器
从前面的 JESSIONID 可知道,session 的底层依赖 cookie 实现,并且该 cookie 的有效时间为关闭浏览器,从而 session 在浏览器关闭时也相当于失效了(因为没有 JSESSION 再与之对应)。
5. 关闭服务器
当关闭服务器时,session 销毁。Session 失效则意味着此次会话结束,数据共享结束。
5.9 ServletContext对象
每一个 web 应用都有且仅有一个ServletContext 对象,又称 Application 对象,从名称中可知,该对象是与应用程序相关的。在 WEB 容器启动的时候,会为每一个 WEB 应用程序创建一个对应的ServletContext 对象。该对象有两大作用,第一、作为域对象用来共享数据,此时数据在整个应用程序中共享; 第二、该对象中保存了当前应用程序相关信息。例如可以通过 getServerInfo() 方法获取当前服务器信息 ,getRealPath(String path) 获取资源的真实路径等。
5.9.1 ServletContext对象的获取
1. 通过request对象获取
ServletContext servletContext = request.getServletContext();
2. 通过session对象获取
ServletContext servletContext = request.getSession().getServletContext();
3. 通过servletConfig对象获取
ServletConfig servletConfig = getServletConfig();
ServletContext servletContext = servletConfig.getServletContext();
4. 直接获取
Servlet 类中提供了直接获取 ServletContext 对象的方法
ServletContext servletContext = getServletContext();
5.9.2 常用方法
// 获取项目存放的真实路径
String realPath = request.getServletContext().getRealPath("/");
// 获取当前服务器的版本信息
String serverInfo = request.getServletContext().getServerInfo();
5.9.3 ServletContext域对象
ServletContext 也可当做域对象来使用,通过向 ServletContext 中存取数据,可以使得整个应用程序共享某些数据。当然不建议存放过多数据,因为 ServletContext 中的数据一旦存储进去没有手动移除将会一直保存。
注:在原来资料里,上面这句话给我带来很大疑问,所以我又去查询了一些资料补充上面这句话的内容。
一旦数据存储在ServletContext中,它将会一直保存在其中,直到Web应用程序被关闭或重新部署。这是因为ServletContext对象是在整个Web应用程序的生命周期中存在的,它在应用程序启动时创建,在应用程序关闭时销毁。
所以,如果你将数据存储在ServletContext中,它将一直存在,直到应用程序停止运行。如果需要在某个时刻手动移除ServletContext中的数据,可以使用removeAttribute()方法来删除指定的属性,或者通过设置属性为null来清空数据。
请注意,由于ServletContext是在整个应用程序中共享的,因此在使用它存储数据时需要注意线程安全性。确保在多线程环境下正确地同步对ServletContext中数据的访问和修改。
5.9.4 小结:Servlet三大域对象
- request域对象
在一次请求中有效。请求转发有效,重定向失效。
- session域对象
在一次会话中有效。请求转发和重定向都有效,session销毁后失效。
- servletContext域对象
在整个应用程序中有效。服务器关闭后失效。
5.10 文件上传和下载
注:这里的代码我没有测
在上网的时候我们常常遇到文件上传的情况,例如上传头像、上传资料等;当然除了上传,遇见下载的情况也很多,接下来看看我们 servlet 中怎么实现文件的上传和下载。
5.10.1 文件上传
文件上传涉及到前台页面的编写和后台服务器端代码的编写,前台发送文件,后台接收并保存文件,这才是一个完整的文件上传。
1.前台页面
在做文件上传的时候,会有一个上传文件的界面,首先我们需要一个表单,并且表单的请求方式为POST;其次我们的 form 表单的 enctype 必须设为"multipart/form-data",即enctype=“multipart/form-data”,意思是设置表单的类型为文件上传表单。默认情况下这个表单类型是 “application/x-www-form-urlencoded”, 不能用于文件上传。只有使用了multipart/form-data 才能完整地传递文件数据。
<!--
文件上传表单
1. 表单提交类型 method="post"
2. 表单类型 enctype="multipart/form-data"
3. 表单元素类型 文件域设置name属性值
-->
<form method="post" action="uploadServlet" enctype="multipart/form-data">
姓名:<input type="text" name="uname" > <br>
文件:<input type="file" name="myfile" > <br>
<button type="submit">提交</button>
</form>
2.后台实现
使用注解 @MultipartConfig 将一个 Servlet 标识为支持文件上传。 Servlet 将 multipart/form-data 的 POST 请求封装成 Part,通过 Part 对上传的文件进行操作。
package com.xxxx.servlet;
import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;
import java.io.IOException;
@WebServlet("/uploadServlet")
@MultipartConfig // 如果是文件上传表单,一定要加这个注解
public class UploadServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest request, HttpServletResponse
response) throws ServletException, IOException {
// 设置请求的编码格式
request.setCharacterEncoding("UTF-8");
// 获取普通表单项 (文本框)
String uname = request.getParameter("uname"); // "uname"代表的是文本框的
name属性值
// 通过 getPart(name) 方法获取Part对象 (name代表的是页面中file文件域的name属性
值)
Part part = request.getPart("myfile");
// 通过Part对象,获取上传的文件名
String fileName = part.getSubmittedFileName();
// 获取上传文件需要存放的路径 (得到项目存放的真实路径)
String realPath = request.getServletContext().getRealPath("/");
// 将文件上传到指定位置
part.write(realPath + fileName);
}
}
5.10.2 文件下载
文件下载,即将服务器上的资源下载(拷贝)到本地,我们可以通过两种方式下载。第一种是通过超链接本身的特性来下载;第二种是通过代码下载。
1.超链接下载
当我们在 HTML 或 JSP 页面中使用a标签时,原意是希望能够进行跳转,但当超链接遇到浏览器不识别的资源时会自动下载;当遇见浏览器能够直接显示的资源,浏览器就会默认显示出来,比如 txt、png、jpg 等。当然我们也可以通过 download 属性规定浏览器进行下载。但有些浏览器并不支持。
默认下载
<!-- 当超链接遇到浏览器不识别的资源时,会自动下载 -->
<a href="test.zip">超链接下载</a>
指定属性下载
<!-- 当超链接遇到浏览器识别的资源时,默认不会下载。通过download属性可进行下载 -->
<a href="test.txt" download>超链接下载</a>
download 属性可以不写任何信息,会自动使用默认文件名。如果设置了download属性的值,则使用设置的值做为文件名。当用户打开浏览器点击链接的时候就会直接下载文件。
2.后台实现下载
实现步骤
- 需要通过 response.setContentType 方法设置 Content-type 头字段的值, 为浏览器无法使用某种方式或激活某个程序来处理的 MIME 类型,例 如 “application/octet-stream” 或 “application/xmsdownload” 等。
- 需要通过 response.setHeader 方法设置 Content-Disposition 头的值 为 “attachment;filename=文件名”
- 读取下载文件,调用 response.getOutputStream 方法向客户端写入附件内容。
package com.xxxx.servlet;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class DownloadServlet extends HttpServlet {
protected void service(HttpServletRequest request, HttpServletResponse
response) throws ServletException, IOException {
// 设置请求的编码
request.setCharacterEncoding("UTF-8");
// 获取文件下载路径
String path = getServletContext().getRealPath("/");
// 获取要下载的文件名
String name = request.getParameter("fileName");
// 通过路径得到file对象
File file = new File(path + name);
// 判断file对象是否存在,且是否是一个标准文件
if (file.exists() && file.isFile()) {
// 设置响应类型 (浏览器无法使用某种方式或激活某个程序来处理的类型)
response.setContentType("application/x-msdownload");
// 设置头信息
response.setHeader("Content-Disposition", "attachment;filename=" +name);
// 得到输入流
InputStream is = new FileInputStream(file);
// 得到输出流
ServletOutputStream os = response.getOutputStream();
// 定义byte数组
byte[] car = new byte[1024];
// 定义长度
int len = 0;
// 循环 输出
while ((len = is.read(car)) != -1) {
os.write(car, 0, len);
}
// 关闭流 释放资源
os.close();
is.close();
} else {
System.out.println("文件不存在,下载失败!");
}
}
}
6、学习时产生的问题
6.1. 什么是java编程模型
Java编程模型是一种指导开发人员在Java编程语言下设计和构建应用程序的方法论。它是一组规范和准则,旨在提供一种结构化的方法来解决特定类型的问题或应用场景。Java编程模型通常是由Java社区或其他相关组织提出和推广的,旨在促进代码的可读性、可维护性和可扩展性。
Java编程模型涉及许多方面,包括:
- 设计模式:Java编程模型鼓励使用常见的设计模式,如单例模式、工厂模式、观察者模式等,以解决在软件设计中常见的问题。
- 面向对象编程:Java是一门面向对象的编程语言,因此Java编程模型强调使用类、对象、继承、封装和多态等面向对象的概念。
- Java API和框架:Java编程模型鼓励开发人员利用Java标准库和第三方框架来加快应用程序的开发过程,提高开发效率和代码质量。
- 并发编程:Java编程模型指导如何处理并发编程问题,包括使用线程、锁、并发集合等来实现多线程应用程序。
- 分层架构:Java编程模型鼓励将应用程序分解为多个层次,例如表示层、业务逻辑层和数据访问层,以提高应用程序的可维护性和可扩展性。
- 安全性:Java编程模型强调安全编码实践,以防止常见的安全漏洞,如跨站脚本攻击(XSS)、SQL注入等。
总体而言,Java编程模型提供了一种通用的方法来设计、组织和编写Java应用程序,以满足不同类型的需求,并使代码易于理解、维护和扩展。然而,具体的Java编程模型可能因项目的规模、类型和需求而有所不同。
6.2. HttpSession对象是由springboot自动管理还是服务器管理
HttpSession对象是由服务器管理的,而不是由Spring Boot自动管理。
在Java Web应用中,HttpSession对象是由Servlet容器(如Tomcat、Jetty等)提供和管理的。当客户端发送HTTP请求到服务器时,Servlet容器会创建一个唯一的HttpSession对象,并将其与客户端的请求关联起来。服务器会为每个客户端维护一个唯一的Session ID,并将该ID存储在客户端的Cookie中或作为URL的一部分发送给客户端。
Spring Boot框架提供了对Servlet容器的封装和集成,以便更方便地使用HttpSession对象。在Spring Boot应用中,您可以通过注入HttpServletRequest对象来获取HttpSession对象,如下所示:
@Autowired
private HttpServletRequest request;
public void someMethod() {
HttpSession session = request.getSession();
// 使用HttpSession对象进行操作
}
通过上述代码,您可以在Spring Boot应用中获取到HttpSession对象,并使用它来存储和获取会话数据。
需要注意的是,Spring Boot提供了许多其他的会话管理选项,例如使用Spring Session来将会话数据存储在Redis、MongoDB等外部存储中,以实现分布式会话管理。这些选项可以根据您的需求进行配置和使用。
6.3. 为社么在springboot中我没有手动保存HttpSession对象,这个对象携带的信息也能正确返回到客户端?
在Spring Boot中,你可能没有手动保存HttpSession对象,但仍然可以正确返回到客户端的原因是因为Spring Boot框架默认启用了Session机制,并使用了默认的Session管理器。
当你在Spring Boot应用中使用Session时,框架会自动创建和管理HttpSession对象。在处理HTTP请求期间,Spring Boot会根据请求中的Session ID来获取相应的HttpSession对象,并将其绑定到当前线程上下文中。这样,在处理请求的各个组件(如Controller、Service等)中,您可以通过注入HttpServletRequest对象来获取当前请求的HttpSession对象。
当你在处理请求时,Spring Boot会自动将HttpSession对象中的属性值绑定到响应中,以便正确返回给客户端。这是通过Servlet容器(如Tomcat、Jetty等)提供的机制来实现的。当请求处理完成后,Servlet容器会将HttpSession对象中的属性值更新到相应的Cookie中或URL的一部分,以便在下一次请求时能够正确地恢复会话状态。
需要注意的是,如果你在Spring Boot应用中使用了分布式会话管理方案(如Spring Session + Redis),则会话数据将存储在外部存储(如Redis)中,而不是在HttpSession对象中。在这种情况下,框架会使用Session ID来将请求与相应的会话数据关联起来,并在需要时从外部存储中获取会话数据。这样,即使应用程序重启或会话迁移到其他服务器,会话数据仍然可以正确地返回给客户端。
总之,Spring Boot框架默认启用了Session机制,并使用Servlet容器提供的机制来管理和返回HttpSession对象的属性值。这样,即使您没有手动保存HttpSession对象,会话数据仍然可以正确返回给客户端。
6.4. 互联网中的协议是否可以理解为一个复杂的类,包括基本属性,扩展属性,和方法
在某种程度上,可以将互联网中的协议理解为一个复杂的类,包括基本属性、扩展属性和方法。下面是一些类比的解释:
-
基本属性:协议的基本属性可以类比为类的成员变量或属性。这些属性描述了协议的基本特征,例如协议的名称、版本号、作者等。
-
扩展属性:协议的扩展属性可以类比为类的静态变量或常量。这些属性描述了协议的更多细节和特性,例如协议的数据格式、传输方式、安全性要求等。
-
方法:协议的方法可以类比为类的成员方法。这些方法定义了协议的行为和操作,例如建立连接、发送数据、接收数据、关闭连接等。
需要注意的是,互联网中的协议并不是以面向对象的方式实现的,而是以一系列规范和约定的形式存在。因此,将其类比为一个复杂的类只是为了更好地理解其结构和功能,并不是实际的实现方式。
6.5.什么叫做域对象
域对象(Scope Object)是在Java Web开发中用于在不同组件之间共享数据的一种机制。它可以存储和获取数据,使得不同的组件(如Servlet、JSP等)能够共享数据并进行交互。
在Java Web中,有以下几种域对象:
1. ServletContext域对象:它是在整个Web应用程序范围内共享数据的最大域对象。在ServletContext中存储的数据可以被同一个Web应用程序中的所有Servlet和JSP访问。在Java Web应用程序中,每个应用程序只有一个ServletContext对象。无论有多少个用户同时访问同一个服务器,它们都会共享同一个ServletContext对象。
2. HttpSession域对象:它是在单个用户会话范围内共享数据的域对象。在HttpSession中存储的数据可以被同一个用户的不同请求访问,直到会话结束或超时。
3. HttpServletRequest域对象:它是在单个请求范围内共享数据的域对象。在HttpServletRequest中存储的数据只能在同一个请求中的不同组件(如过滤器、Servlet、JSP等)之间共享。
4. PageContext域对象:它是在JSP页面范围内共享数据的域对象。在PageContext中存储的数据可以被同一个JSP页面中的不同标签(如脚本、表达式等)访问。
通过使用这些域对象,可以在不同的组件之间传递数据,实现数据的共享和交互。