web

好的,让我们深入到 HTTP 协议的底层,全面解剖它的工作原理、请求-响应模型、以及各个组成部分。


一、HTTP 协议概述

HTTP(Hypertext Transfer Protocol,超文本传输协议)是应用层协议,它定义了 Web 客户端(如浏览器)和 Web 服务器之间交换数据的规则。它的核心特点是无状态(Stateless),这意味着服务器不会保存客户端的任何信息,每次请求都是独立的。


二、HTTP 请求(Request)

一个完整的 HTTP 请求由四部分组成:

1. 请求行(Request Line)

这是请求的第一行,它定义了请求的基本信息。

  • 请求方法(Method):如 GET, POST, PUT, DELETE 等,表示对资源的操作类型。
  • 请求 URL (URI):资源的地址。
  • HTTP 协议版本:如 HTTP/1.1, HTTP/2.0

示例:

GET /index.html HTTP/1.1

2. 请求头(Request Headers)

请求头提供了关于客户端、请求体和一些其他元数据的信息。它们以键值对的形式存在。

  • Host:指定请求的目标服务器的域名和端口号。
  • User-Agent:客户端的软件类型和版本,如浏览器信息。
  • Accept:客户端能够处理的媒体类型,告诉服务器希望返回什么格式的数据(如 application/json, text/html)。
  • Content-Type请求体中的数据类型,只在有请求体时使用(如 POST)。常见的有:
    • application/x-www-form-urlencoded:默认编码,用于提交表单数据。
    • multipart/form-data:用于上传文件。
    • application/json:用于前后端分离,提交 JSON 数据。
  • Content-Length:请求体的长度(字节)。
  • Cookie:客户端存储的 Cookie 信息,用于会话管理。
  • Authorization:用于身份认证,如携带 Bearer Token。
  • Referer:请求从哪个页面跳转而来。

3. 空行

一个空行用于分隔请求头和请求体。

4. 请求体(Request Body)

请求体包含客户端提交给服务器的数据,通常用于 POSTPUT 等请求。GET 请求通常没有请求体。


三、HTTP 响应(Response)

一个完整的 HTTP 响应由三部分组成:

1. 状态行(Status Line)

这是响应的第一行,定义了响应的基本信息。

  • HTTP 协议版本:如 HTTP/1.1
  • 状态码(Status Code):三位数字,表示请求结果。
  • 状态信息(Reason Phrase):对状态码的简短描述。

示例:

HTTP/1.1 200 OK

2. 响应头(Response Headers)

响应头提供了关于服务器、响应体和会话的元数据信息。

  • Content-Type响应体的数据类型。
  • Content-Length:响应体的长度。
  • Set-Cookie:服务器向客户端发送 Cookie,用于存储会话 ID 等信息。
  • Location:用于重定向,指定新的 URL。
  • Cache-Control:控制浏览器缓存行为。

3. 空行

一个空行用于分隔响应头和响应体。

4. 响应体(Response Body)

响应体包含了服务器返回给客户端的实际数据,如 HTML 网页内容、JSON 数据或图片等。


四、HTTP 方法的幂等性与安全性

  • 安全性:指请求方法不会对服务器上的资源状态产生修改。GETHEAD 方法是安全的。
  • 幂等性:指请求方法重复执行多次,对服务器上的资源状态产生的影响与执行一次的影响相同。
    • 幂等方法GET, HEAD, PUT, DELETE
    • 非幂等方法POST
方法 安全性 幂等性 典型用途
GET 获取资源
POST 创建资源
PUT 更新或替换资源
DELETE 删除资源

五、HTTP 的会话管理:Cookie

HTTP 本身是无状态的,但为了跟踪用户,引入了 Cookie。

  1. 创建:服务器通过响应头 Set-Cookie 向客户端发送一个 Cookie。
  2. 存储:浏览器接收到 Set-Cookie 后,会将其存储在本地。
  3. 携带:之后,每次对同一域名发起请求时,浏览器都会自动在请求头 Cookie 中带上这个 Cookie。

通过在 Cookie 中存储一个 SessionID,服务器就可以在后续请求中找到对应的会话数据,从而实现状态管理。


六、HTTP/1.1 与 HTTP/2 的区别

  • 多路复用(Multiplexing):HTTP/2 允许在一个 TCP 连接上同时发送多个请求和响应,解决了 HTTP/1.1 队头阻塞的问题,显著提高了性能。
  • 头部压缩(Header Compression):HTTP/2 使用 HPACK 算法压缩请求和响应头,减少了数据传输量。
  • 服务器推送(Server Push):服务器可以在客户端请求一个资源时,主动推送其他它认为客户端可能需要的资源,减少了客户端的请求次数。
  • 二进制分帧(Binary Framing):HTTP/2 将所有传输信息分割成更小的消息和帧,并采用二进制编码,使得解析更高效。

好的,我们来详细补充 HTTP/1.0 的核心概念,并对整个 HTTP 协议族进行全面的对比和深入解析。


一、HTTP 协议族演变:从 1.0 到 2.0

HTTP/1.0

HTTP/1.0 是 HTTP 协议的早期版本,它的设计相对简单,主要用于满足基本的网页浏览需求。

  • 核心特点:
    • 非持久连接(Non-persistent Connection):这是 HTTP/1.0 最显著的特点。每进行一次 HTTP 请求-响应,客户端和服务器之间就会建立一个新的 TCP 连接,并在请求完成后立即断开。
      • 缺点:每次请求都需要经过 TCP 三次握手和四次挥手的过程,这带来了巨大的性能开销。如果一个网页包含多个图片、CSS 或 JavaScript 文件,浏览器需要为每个文件单独建立和断开连接,导致页面加载速度慢。
    • 无主机头(No Host Header):请求头中没有 Host 字段。这意味着一个 IP 地址只能对应一个域名。如果服务器上有多个网站,就无法通过 IP 地址来区分它们,这在虚拟主机时代是个大问题。
    • 不支持管线化(Pipelining):客户端发送一个请求后,必须等待服务器的响应,才能发送下一个请求。

HTTP/1.1

HTTP/1.1 协议是对 1.0 的重大改进,解决了其大部分性能瓶颈。

  • 核心特点:
    • 持久连接(Persistent Connection):默认情况下,HTTP/1.1 会在一次请求-响应之后保持 TCP 连接不断开。客户端可以继续在这个连接上发送后续请求。
      • 优点:显著减少了 TCP 连接的建立和断开开销,提高了页面加载速度。这个特性也称为“Keep-Alive”。
    • 支持主机头(Host Header):请求头中引入了 Host 字段,允许在同一个 IP 地址上部署多个虚拟主机(域名)。
    • 支持管线化(Pipelining):客户端可以在收到上一个响应之前,连续发送多个请求。
      • 缺点:虽然提高了效率,但存在**队头阻塞(Head-of-Line Blocking)**问题。如果第一个请求处理时间很长,后面的请求即使已经处理完成,也必须等待它的响应,导致整个连接的效率降低。
    • 引入缓存机制:通过 Cache-Control, ETag, If-None-Match 等请求头,HTTP/1.1 提供了更完善的缓存机制,减少了不必要的请求。

HTTP/2.0

HTTP/2.0 是为了解决 HTTP/1.1 在移动互联网和高并发场景下的性能问题而设计的。它不是对 HTTP/1.1 的简单升级,而是对协议底层进行了重构。

  • 核心特点:
    • 多路复用(Multiplexing):这是 HTTP/2 的核心。它允许在一个 TCP 连接上同时处理多个 HTTP 请求和响应。
      • 如何实现?:HTTP/2 将所有数据流(Stream)分割成更小的二进制帧(Frame),每个帧都带有唯一的标识符。这样,客户端和服务器可以在同一个连接上交错发送和接收帧,然后根据标识符重新组装,从而彻底解决了 HTTP/1.1 的队头阻塞问题。
    • 头部压缩(Header Compression):HTTP/2 使用 HPACK 算法来压缩请求和响应头,尤其是对于重复发送的字段,大大减少了数据传输量。
    • 服务器推送(Server Push):服务器可以在客户端请求一个资源(如 HTML 页面)时,主动推送其他它认为客户端可能需要的资源(如 CSS 和 JS 文件),而无需客户端显式请求,进一步提高了加载速度。
    • 二进制协议:HTTP/2 是一个二进制协议,而不是文本协议,解析更高效、更不容易出错。

二、HTTP 协议版本对比总结

理解 HTTP 协议的演变历程,能让你更深刻地体会到 Web 性能优化的方向,以及现代 Web 框架如何利用这些底层协议特性来提供更高效的服务。

特性 HTTP/1.0 HTTP/1.1 HTTP/2.0
TCP 连接 非持久连接 默认持久连接 单个 TCP 连接多路复用
性能瓶颈 多次握手挥手开销 队头阻塞
主机头 不支持 支持 (Host Header) 支持
并发请求 串行(一个请求一个连接) 串行(一个连接一个请求) 并行(一个连接多个请求)
数据格式 文本协议 文本协议 二进制协议
头部 无压缩 无压缩 HPACK 算法压缩
服务器推送 不支持 不支持 支持

好的,让我们把所有关于 Servlet 的知识点整合在一起,进行一次最全面、最深入的剖析。我们将从基础概念开始,逐步深入到它的生命周期、配置、核心方法以及与 Servlet 规范相关的其他重要组件。


Servlet 概述:Java Web 的核心基石

Servlet 是 Java EE 规范中的一个核心组件,它是一个运行在服务器端的 Java 程序,用于处理 HTTP 请求和生成动态响应。你可以把它看作是所有 Java Web 框架(如 Spring MVC)的底层引擎。

它的核心作用是作为客户端(浏览器)和 Java 应用程序之间的“桥梁”。Servlet 容器(如 Tomcat、Jetty)负责监听网络请求,然后将请求分发给相应的 Servlet 进行处理。

Servlet 的生命周期:由容器严格管理

一个 Servlet 的生命周期由 Servlet 容器严格管理,通常分为三个阶段:

  1. 初始化(Initialization)
    • 时机:当 Servlet 容器第一次加载 Servlet 类并创建其实例后,立即调用 init() 方法。
    • 特性:这个方法只执行一次,通常用于加载配置文件、建立数据库连接池等一次性、耗时的任务。如果 init() 方法抛出异常,Servlet 将无法提供服务。
  2. 服务(Servicing)
    • 时机:每当一个客户端请求到达时,容器会为该请求创建一个新的线程,并调用 service() 方法。
    • 特性service() 方法是 Servlet 的核心,它会根据请求类型(如 GET、POST)自动调用相应的 doGet()doPost() 等方法。由于此方法会被多个线程并发调用,因此访问共享资源时必须注意线程安全
  3. 销毁(Destruction)
    • 时机:当 Servlet 容器关闭或决定卸载某个 Servlet 时,会调用其 destroy() 方法。
    • 特性:这个方法也只执行一次,用于释放资源,如关闭数据库连接或文件流。

Servlet 的配置和部署:两种主要方式

为了让容器知道如何加载和映射 Servlet,你需要进行配置。

  1. web.xml 配置(传统方式)
    • web.xml 文件中,使用 <servlet> 标签定义 Servlet 类,并使用 <servlet-mapping> 标签将它映射到一个 URL 路径。
    • 优点:配置集中,易于管理。
    • 缺点:如果 Servlet 很多,web.xml 文件会变得非常庞大。
  2. 注解配置(现代方式)
    • 从 Servlet 3.0 规范开始,你可以使用 @WebServlet 注解来简化配置。只需在你的 Servlet 类上添加 @WebServlet("/path") 注解,容器就会自动识别和配置它。
    • 优点:配置简单,代码和配置在一起,提高了可读性。

Servlet 的核心方法:处理请求和响应

所有 Servlet 都应该实现 javax.servlet.Servlet 接口。通常,我们更常继承 javax.servlet.http.HttpServlet,因为它提供了更方便的 HTTP 请求处理方法。

  • service(HttpServletRequest req, HttpServletResponse res):这是核心服务方法,由容器调用。它会根据请求的 HTTP 方法(GET、POST、PUT 等)来调用相应的 doGet()doPost() 等方法。
  • doGet(HttpServletRequest req, HttpServletResponse res):处理所有 GET 请求。
  • doPost(HttpServletRequest req, HttpServletResponse res):处理所有 POST 请求。
  • doPut(HttpServletRequest req, HttpServletResponse res):处理所有 PUT 请求。
  • doDelete(HttpServletRequest req, HttpServletResponse res):处理所有 DELETE 请求。

在这些方法中,你可以通过 req (请求) 和 res (响应) 对象来与客户端进行交互。

Servlet 的请求和响应处理:与客户端交互

  • 获取请求参数
    • req.getParameter("paramName"):获取单个参数值。
    • req.getParameterValues("paramName"):获取具有相同名称的多个参数值(如复选框)。
  • 设置响应头
    • res.setContentType("text/html;charset=UTF-8"):设置响应的内容类型和字符编码。
    • res.setHeader("HeaderName", "HeaderValue"):设置自定义响应头。
  • 写入响应内容
    • res.getWriter():获取一个 PrintWriter,用于向客户端发送文本响应。
    • res.getOutputStream():获取一个 ServletOutputStream,用于向客户端发送二进制响应(如图片、文件)。

Servlet 的核心组件与作用域

理解 Servlet 规范中的这些组件,能帮助你更合理地管理数据和资源。

  1. ServletContext (应用上下文)
    • 作用:代表整个 Web 应用,它的生命周期与应用相同。
    • 作用域:数据在所有 Servlet、JSP 和 Filter 之间共享
    • 用途:存储全局配置和共享数据。
  2. ServletConfig (配置对象)
    • 作用:代表一个 Servlet 独有的配置。
    • 作用域:仅在它所关联的 Servlet 内部有效。
    • 用途:获取特定 Servlet 的初始化参数。
  3. 四大作用域
    • PageContext (页面作用域):仅在 JSP 页面内有效。
    • HttpServletRequest (请求作用域):在一次完整的请求-响应周期内有效,即使请求被转发,数据依然可见。
    • HttpSession (会话作用域):在同一个浏览器会话中有效,用于存储用户特定数据。
    • ServletContext (应用作用域):在整个 Web 应用中都有效,用于存储全局数据。

好的,我们来详细、深入地聊聊 Web 开发中至关重要的两个概念:Cookie 和 Session。它们是解决 HTTP 无状态问题的核心方案,但工作机制和应用场景却大不相同。


Cookie:客户端的“小纸条”

Cookie 是服务器发送给浏览器并存储在客户端的一小段文本信息。浏览器在下次访问同一服务器时,会自动将该 Cookie 包含在请求中发送回去。

核心工作机制

  1. 服务器创建 Cookie:

    当用户首次访问网站时,服务器在响应头(Response Header)中添加一个 Set-Cookie 字段。

    比如:Set-Cookie: JSESSIONID=abcde12345; Path=/; HttpOnly

  2. 浏览器保存 Cookie:

    浏览器接收到响应后,会解析 Set-Cookie 头,并将该信息以键值对的形式存储在本地。

  3. 浏览器发送 Cookie:

    在后续的请求中,只要请求的域名和路径与 Cookie 的设置相符,浏览器就会自动在请求头(Request Header)中添加 Cookie 字段,将存储的 Cookie 信息发送给服务器。

    比如:Cookie: JSESSIONID=abcde12345

  • 优点
    • 减轻服务器压力:数据存储在客户端,服务器不需要为每个用户维护状态,适用于大规模访问。
    • 可扩展性强:无状态,天然适合分布式架构。
  • 缺点
    • 安全性差:数据以明文形式存储,容易被窃取和篡改。
    • 容量限制:单个 Cookie 的大小通常不超过 4KB,且一个域名下的 Cookie 总数也有限制。
    • 用户可禁用:如果用户禁用了浏览器 Cookie 功能,相关功能将无法使用。

Session:服务器的“个人档案”

Session 是服务器为每个用户创建的一个对象,用于存储特定用户的会话数据。它将用户的状态信息保存在服务器端,并通过一个唯一的 Session ID 来识别不同的用户。

核心工作机制

  1. 服务器创建 Session:

    当用户首次访问 Web 应用时,服务器会创建一个 HttpSession 对象,并为其分配一个唯一的 Session ID。

  2. 传递 Session ID:

    服务器将这个 Session ID 以 Cookie 的形式发送给浏览器(这个 Cookie 通常叫 JSESSIONID)。

    • 如果浏览器禁用了 Cookie,服务器可以通过 URL 重写的方式,将 Session ID 附加到每个 URL 的末尾,例如:.../index.jsp;jsessionid=abcde12345
  3. 服务器维护 Session:

    Session ID 传递到客户端后,服务器会在内存中或持久化存储中保存这个 HttpSession 对象及其数据。

  4. 识别用户:

    后续的请求中,浏览器都会带着包含 Session ID 的 Cookie。服务器通过这个 ID 就能从内存中找到对应的 HttpSession 对象,从而获取该用户的状态信息。

Session 的优缺点

  • 优点
    • 安全性高:核心数据存储在服务器,客户端只传递一个无法猜解的 ID。
    • 容量大:存储在服务器,理论上没有大小限制。
  • 缺点
    • 占用服务器资源:每个活跃的 Session 都会占用服务器内存,在高并发场景下可能成为瓶颈。
    • 分布式挑战:在多台服务器组成的集群环境中,需要额外的机制(如 Session 共享或粘性会话)来确保用户的请求总是被转发到同一个 Session 所在的服务器,或者所有服务器都能访问到 Session 数据。

特性 Cookie Session
数据存储位置 客户端(浏览器) 服务器端
安全性 较低,易被篡改 较高,数据安全
数据容量 较小(~4KB) 较大,无明显限制
性能 轻量,不占用服务器资源 占用服务器内存,可能影响性能
可扩展性 天然无状态,易于扩展 分布式环境下需要额外配置
主要用途 购物车、用户偏好、轻量级状态 登录状态、权限验证、敏感数据

好的,我已将 GET 和 POST 请求的所有核心区别整理成一个简洁明了的表格,并补充了更多细节,使其更全面。

GET 和 POST 请求的全面对比

特性 GET 请求 POST 请求 备注
基本作用 从服务器获取资源 向服务器提交数据,通常用于创建资源。 语义不同是所有区别的根本。
传参方式 参数附加在 URL 中 参数放在请求体(Request Body)中 GET 参数可见,POST 参数隐藏。
安全性 不安全,URL 暴露 相对安全,参数隐藏在请求体中 这里的“安全”指数据不被暴露。
幂等性 (多次执行结果相同) (多次执行可能产生新资源) 幂等性是 RESTful 设计的关键原则。
浏览器行为 回退无害,可被记录在历史、收藏为书签。 回退时通常会提示重新提交,不记录在历史、不可收藏。 防止意外提交导致数据重复。
缓存 可以被缓存 不会被缓存 缓存能提高性能,但仅限于只读操作。
传输数据量 有长度限制(URL长度限制) 无长度限制(取决于服务器配置) GET 不适合传输大量数据。
数据编码 只支持 URL 编码 支持多种编码(如application/json POST 灵活性高,适合各种数据类型。
发送文件 不支持 支持 (multipart/form-data) 文件必须通过请求体传输。
HTTP 报文 无请求体 有请求体 GET 请求报文更小,更轻量。
TCP/IP 协议 一次性发送所有数据包 浏览器通常会先发送请求头,服务器响应 100-continue 后再发送请求体 POST 的分步发送机制可以避免发送不必要的数据。

好的,我们将以 Servlet 的详细剖析方式,全面、深入地介绍 WebSocket。我们将从它的概念、生命周期、核心 API,到它与 HTTP 的区别以及在现代 Web 中的应用,进行系统性的梳理。


WebSocket 概述:Web 通信的革命

WebSocket 协议是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通信的协议。与传统的 HTTP 协议不同,它打破了请求-响应的单向模式,使得客户端和服务器可以实时地双向自由传输数据

它的核心作用是解决 Web 应用中实时通信的需求,例如:聊天应用、在线游戏、股票行情、实时协作工具等。它极大地减少了网络开销,提高了通信效率。

WebSocket 的核心工作原理

要理解 WebSocket,必须首先理解它与 HTTP 的关系。

  1. 基于 HTTP 的握手(Handshake)
    • WebSocket 的连接建立过程是基于 HTTP 协议的。
    • 客户端发送一个特殊的 HTTP 请求,其中包含 Upgrade: websocketConnection: Upgrade 请求头,向服务器请求将 HTTP 协议升级到 WebSocket 协议。
    • 这是一个**“握手”**过程。
  2. 协议升级与持久连接
    • 如果服务器支持 WebSocket,它会返回一个特殊的 HTTP 响应(状态码 101 Switching Protocols)。
    • 握手成功后,客户端和服务器之间的 TCP 连接将保持打开状态
    • 这时,协议就从 HTTP 升级到了 WebSocket,双方可以在这个持久的 TCP 连接上自由地双向发送数据,而无需再进行 HTTP 头部开销。
  3. 数据帧传输
    • WebSocket 协议定义了**数据帧(Data Frames)**的概念。它将数据分割成更小、更轻量级的帧,而不是像 HTTP 那样发送整个报文。
    • 这样,即使传输少量数据,也不会有很大的协议开销,非常适合实时通信。

WebSocket 的生命周期:事件驱动模型

WebSocket 的生命周期由客户端和服务器共同维护,由一系列事件驱动:

1. 连接建立(Connection Establishment)

  • 客户端:通过 new WebSocket(url) 建立连接。
  • 服务器:当客户端发送握手请求后,服务器处理并接受连接,此时触发 onopen 事件。

2. 数据传输(Data Transmission)

  • 发送:客户端和服务器都可以通过各自的 send() 方法向对方发送数据。
  • 接收:当一方接收到数据时,会触发 onmessage 事件。

3. 连接关闭(Connection Close)

  • 主动关闭:客户端或服务器可以调用 close() 方法主动关闭连接。
  • 异常关闭:连接可能因网络故障、心跳超时等原因意外关闭。
  • 事件:无论是主动还是被动,连接关闭时都会触发 onclose 事件。

WebSocket 的核心 API(以 JavaScript 为例)

WebSocket 的 API 设计非常简洁,主要基于事件监听和方法调用。

  • new WebSocket(url):创建一个 WebSocket 客户端实例。
  • websocket.onopen:连接成功建立时触发。
  • websocket.onmessage = function(event):接收到服务器数据时触发,数据在 event.data 中。
  • websocket.onerror:连接发生错误时触发。
  • websocket.onclose:连接关闭时触发。
  • websocket.send(data):向服务器发送数据。
  • websocket.close():关闭连接。

WebSocket 与 HTTP 的本质区别

特性 HTTP WebSocket 备注
通信模式 单向(请求-响应) 双向(全双工) HTTP 客户端必须先发请求,服务器才能响应。
连接状态 无状态,短连接 有状态,长连接 HTTP 每次请求都需要重新建立连接。
协议开销 (每次请求都携带头部) (握手后只传输数据帧) HTTP 适合传输大文件,WebSocket 适合小数据频繁传输。
服务器主动性 被动(无法主动推送) 主动(可随时推送数据) WebSocket 解决了 HTTP 的“服务器推”难题。
应用场景 网页浏览、API 调用、文件下载 聊天、游戏、实时数据更新

WebSocket 在现代 Java Web 中的应用

在 Java 后端,WebSocket 通常由专门的框架或容器来实现,如:

  • JavaEE 7+ 的 WebSocket API:提供了 javax.websocket 包,可以直接在 Servlet 容器中开发 WebSocket 应用。
  • Spring Framework:提供了强大的 WebSocket 支持,集成了 STOMP(Simple Text Oriented Messaging Protocol)协议,简化了消息路由和管理。

在这些框架中,你通常会定义一个 WebSocket 端点(Endpoint),类似于 Servlet,它负责处理连接的建立、消息的接收和发送、以及连接的关闭。

总而言之,WebSocket 是 HTTP 协议在实时通信领域的有力补充。它通过一次性的握手建立一个持久的双向通道,极大地提高了 Web 应用的交互性和效率,是现代 Web 架构中不可或缺的一部分。

好的,为了更好地理解 WebSocket 的工作原理,我将提供几个具体的应用场景和相应的代码示例。这些示例将涵盖客户端(JavaScript)和服务器端(Java/Spring Boot)的代码,以便你能够完整地看到双向通信是如何实现的。


示例一:实时聊天应用

这是 WebSocket 最经典的用例。

应用场景

多个用户连接到聊天室,当一个用户发送消息时,服务器将该消息实时广播给所有其他在线用户。

核心逻辑

  • 客户端:当用户在输入框中按下回车,JavaScript 将消息通过 WebSocket 连接发送给服务器。
  • 服务器:接收到消息后,遍历所有已连接的 WebSocket 会话,将消息逐一发送给每个会话。

代码示例

1. 客户端 (JavaScript)

JavaScript

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
// 连接 WebSocket 服务器
const socket = new WebSocket('ws://localhost:8080/chat');

// 监听连接成功事件
socket.onopen = function(event) {
console.log("WebSocket连接已建立");
};

// 监听接收消息事件
socket.onmessage = function(event) {
const message = event.data;
const chatBox = document.getElementById('chat-box');
chatBox.innerHTML += `<p>${message}</p>`; // 将消息添加到聊天框
};

// 监听连接关闭事件
socket.onclose = function(event) {
console.log("WebSocket连接已关闭");
};

// 监听错误事件
socket.onerror = function(event) {
console.log("WebSocket发生错误:", event);
};

// 发送消息函数
function sendMessage() {
const input = document.getElementById('message-input');
const message = input.value;
if (message) {
socket.send(message); // 通过 WebSocket 发送消息
input.value = ''; // 清空输入框
}
}

// 绑定回车事件
document.getElementById('message-input').addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
sendMessage();
}
});

2. 服务器端 (Java/Spring Boot)

Java

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
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.CopyOnWriteArraySet;

@ServerEndpoint("/chat")
@Component
public class ChatWebSocketEndpoint {

// 存储所有连接的客户端会话
private static final CopyOnWriteArraySet<Session> sessions = new CopyOnWriteArraySet<>();

/**
* 当客户端连接成功时调用
*/
@OnOpen
public void onOpen(Session session) {
sessions.add(session); // 添加新会话
System.out.println("新连接:" + session.getId());
}

/**
* 当客户端发送消息时调用
*/
@OnMessage
public void onMessage(String message, Session session) {
System.out.println("收到消息:" + message + " 来自:" + session.getId());
// 遍历所有会话,广播消息
sessions.forEach(s -> {
try {
s.getBasicRemote().sendText(message);
} catch (IOException e) {
e.printStackTrace();
}
});
}

/**
* 当连接关闭时调用
*/
@OnClose
public void onClose(Session session) {
sessions.remove(session); // 移除会话
System.out.println("连接关闭:" + session.getId());
}

/**
* 当发生错误时调用
*/
@OnError
public void onError(Session session, Throwable throwable) {
System.out.println("连接发生错误:" + session.getId());
throwable.printStackTrace();
}
}

示例二:实时股票行情或数据看板

应用场景

客户端连接到服务器,服务器定期或在数据更新时,向所有连接的客户端推送最新的股票价格或监控数据。

核心逻辑

  • 客户端:只监听 onmessage 事件,被动接收服务器推送的数据。
  • 服务器:在后台启动一个定时任务或数据监听器。当数据变化时,主动通过 WebSocket 连接将新数据发送给客户端。

代码示例

1. 客户端 (JavaScript)

JavaScript

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const socket = new WebSocket('ws://localhost:8080/stock');

socket.onopen = function(event) {
console.log("股票行情连接已建立");
};

socket.onmessage = function(event) {
// 假设服务器发送的是JSON格式的股票数据
const stockData = JSON.parse(event.data);
const stockPriceElement = document.getElementById('stock-price');
stockPriceElement.innerText = `当前价格: ${stockData.price}`;
};

// 无需 sendMessage 函数,因为是单向被动接收

2. 服务器端 (Java/Spring Boot)

Java

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
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.CopyOnWriteArraySet;

@ServerEndpoint("/stock")
@Component
public class StockWebSocketEndpoint {

private static final CopyOnWriteArraySet<Session> sessions = new CopyOnWriteArraySet<>();

@OnOpen
public void onOpen(Session session) {
sessions.add(session);
System.out.println("新股票行情订阅者:" + session.getId());
}

@OnClose
public void onClose(Session session) {
sessions.remove(session);
}

// 假设这是一个定时任务,每隔5秒向所有客户端推送数据
@Scheduled(fixedRate = 5000)
public void pushStockData() {
// 模拟获取最新股票价格
double price = Math.random() * 100 + 100;
String json = "{\"price\": " + String.format("%.2f", price) + "}";

// 遍历所有会话,推送数据
sessions.forEach(session -> {
try {
if (session.isOpen()) {
session.getBasicRemote().sendText(json);
}
} catch (IOException e) {
e.printStackTrace();
}
});
}
}

这两个例子清楚地展示了 WebSocket 如何通过持久连接双向通信来解决实时应用中的核心痛点。

好的,让我们详细、清晰地解释静态资源动态资源的概念,以及为什么某些服务器或工具只能处理静态资源。


一、静态资源 (Static Resources)

概念

静态资源是指在服务器端不需要经过任何处理或计算,就能直接返回给客户端的资源。它们的内容是固定不变的,无论何时、被谁请求,服务器返回的都是同一个文件。

常见类型

  • HTML 文件 (.html):纯 HTML 代码,浏览器直接渲染。
  • CSS 样式表 (.css):定义网页样式。
  • JavaScript 脚本 (.js):用于网页交互。
  • 图片 (.jpg, .png, .gif):图像文件。
  • 字体文件 (.woff, .ttf):网页字体。
  • 视频 (.mp4, .webm):视频文件。

工作流程

  1. 客户端向服务器请求一个 URL,例如 http://example.com/styles.css
  2. 服务器接收到请求,直接从文件系统中找到名为 styles.css 的文件。
  3. 服务器将 styles.css 的内容原封不动地返回给客户端。

特点

  • 高效:无需服务器计算,直接读取文件,响应速度极快。
  • 可缓存:由于内容不变,浏览器、代理服务器都可以对静态资源进行缓存,进一步减少网络传输。
  • 部署简单:只需将文件放在服务器的特定目录下即可。

二、动态资源 (Dynamic Resources)

概念

动态资源是指服务器端需要经过处理、计算或查询数据库,才能生成并返回给客户端的资源。它们的内容是可变的,不同的请求、不同的时间,返回的内容可能不同。

常见类型

  • Servlet/JSP:Java 后端通过 Servlet 或 JSP 动态生成 HTML。
  • PHP 脚本 (.php):PHP 后端生成 HTML。
  • Python/Django 视图:Python 后端处理请求并返回数据。
  • RESTful API:返回 JSON、XML 等格式的数据。

工作流程

  1. 客户端向服务器请求一个 URL,例如 http://example.com/user/profile?id=123
  2. 服务器接收到请求,将请求转发给后端程序(如 Servlet 容器)。
  3. 后端程序接收请求,根据请求参数 id=123 查询数据库,获取该用户的信息。
  4. 后端程序将数据渲染到 HTML 模板中,或者将数据封装成 JSON 格式。
  5. 后端程序将动态生成的内容作为响应返回给服务器。
  6. 服务器将该响应发送给客户端。

特点

  • 灵活:可以根据业务逻辑生成个性化内容。
  • 消耗资源:需要消耗 CPU、内存、数据库连接等资源进行计算。
  • 不可直接缓存:由于内容可变,通常无法像静态资源那样直接缓存。

三、是什么东西只能解析静态资源?

这个问题的核心在于**“解析”这个词。能“解析”静态资源的,通常指的是Web 服务器**。

  • Web 服务器:一种软件,它的主要职责是处理 HTTP 请求并返回文件。

1. 纯静态 Web 服务器 (Static Web Server)

这类服务器的核心功能就是提供静态文件服务。 它们只知道如何根据 URL 路径,去文件系统找到对应的文件,然后返回。

  • 工作原理:它们没有内置的后端语言解析器(如 Java 虚拟机、PHP 解释器)。当它们收到一个请求时,只会将 URL 映射到文件目录。如果文件存在,就返回;如果不存在,就返回 404 错误。
  • 代表Nginx(作为反向代理或静态文件服务器时)、Apache HTTP Server(作为静态文件服务器时)。

为什么它们不能解析动态资源?

因为它们不具备执行后端代码的能力。例如,当 Nginx 收到一个对 .jsp 文件的请求时,它不知道如何运行 Java 代码来生成 HTML。它只会把 .jsp 文件当作一个普通的文本文件返回给浏览器,浏览器也无法正确地解析。

2. 动态 Web 服务器 (Dynamic Web Server)

这类服务器通常被称为 “应用服务器”,它们在 Web 服务器的功能上,内置了后端语言的运行环境

  • 工作原理
    • 当它们收到静态资源请求时,行为与静态服务器相同:直接返回文件。
    • 当它们收到动态资源请求时,会将请求转发给内置的后端程序运行环境。例如,Tomcat 会将 .jsp 文件交给 JSP 引擎来编译和执行。
  • 代表Apache TomcatJetty

因此,在实际生产环境中,我们经常采用动静分离的架构:

  • 静态资源index.htmlstyle.css 等)交给 Nginx 这样的高效静态服务器处理。
  • 动态资源api/user, .jsp 等)交给 Tomcat 这样的应用服务器处理。

这样做的目的是,让专业的工具做专业的事:Nginx 擅长高并发的静态文件服务,而 Tomcat 则专注于复杂的动态业务逻辑。

好的,让我们来系统、全面地梳理前端的核心知识,这不仅是前端工程师的必备技能,也是后端开发者理解整个 Web 应用架构的关键。

一、基础三剑客:构建网页的基石

这三者是所有前端技术的基础,就像建筑的钢筋、水泥和水电。

1. HTML (HyperText Markup Language)

  • 是什么:超文本标记语言,用于定义网页的结构和内容。它由一系列标签(tag)组成,这些标签告诉浏览器如何组织页面内容,如段落、标题、图片、链接等。
  • 核心概念
    • 标签(Tags):如 <p> (段落), <h1> (一级标题), <img> (图片), <a> (超链接)。
    • 元素(Elements):由开始标签、内容和结束标签组成,例如 <p>这是一个段落。</p>
    • 属性(Attributes):提供关于元素的额外信息,如 <img src="image.jpg" alt="描述"> 中的 srcalt
    • 语义化(Semantic HTML):使用恰当的标签来表达内容的含义,如 <header>, <nav>, <article>, <footer>,这有助于搜索引擎优化(SEO)和无障碍访问。

2. CSS (Cascading Style Sheets)

  • 是什么:层叠样式表,用于定义网页的样式和表现。它告诉浏览器如何显示 HTML 元素,如颜色、字体、布局、大小等。
  • 核心概念
    • 选择器(Selectors):用于选中要应用样式的 HTML 元素,如 h1 (标签选择器), .my-class (类选择器), #my-id (ID 选择器)。
    • 盒模型(Box Model):每个 HTML 元素都被视为一个矩形盒子,包含 content(内容)、padding(内边距)、border(边框)和 margin(外边距)。
    • 布局(Layout)
      • 传统布局float, position, display
      • 现代布局Flexbox (弹性盒子,用于一维布局) 和 Grid (网格系统,用于二维布局)。这两种是目前最主流的布局方式。
    • 响应式设计(Responsive Design):使用 @media 查询来根据设备屏幕大小调整样式,使网页在不同设备上都能良好显示。

3. JavaScript (JS)

  • 是什么:一种高级编程语言,用于实现网页的动态行为和交互。它可以操作 HTML 和 CSS,处理用户事件,并与服务器进行通信。
  • 核心概念
    • DOM (Document Object Model) 操作:通过 document 对象,JS 可以获取、修改、添加或删除页面上的 HTML 元素。例如 document.getElementById('myId')
    • 事件处理(Event Handling):响应用户的行为,如点击、输入、鼠标移动等。例如 element.addEventListener('click', function() { ... })
    • 异步编程(Asynchronous Programming):处理耗时操作,如网络请求。
      • Callback (回调函数):传统方式。
      • Promise:ES6 引入,解决了回调地狱问题。
      • async/await:基于 Promise 的语法糖,使异步代码看起来像同步代码,更易读。
    • AJAX (Asynchronous JavaScript and XML):在不重新加载整个页面的情况下,与服务器进行异步通信。现代应用中通常使用 fetch() API 或 Axios 库来发送 JSON 数据。

二、现代前端框架:提升开发效率

为了处理日益复杂的 Web 应用,开发者通常会使用以下框架。

  • React:由 Facebook 开发,组件化声明式的 UI 库。
    • 核心组件(Components),将 UI 拆分成独立、可复用的部分。
    • 工作原理:使用虚拟 DOM (Virtual DOM),在内存中进行计算,然后只更新实际改变的部分,提高了性能。
    • 生态:庞大而活跃,有成熟的状态管理(Redux, Zustand)、路由(React Router)等解决方案。
  • Vue.js:由尤雨溪开发,易学易用,渐进式框架。
    • 核心响应式数据绑定,数据改变,视图自动更新。
    • 特点:上手快,文档友好,社区生态完善。
  • Angular:由 Google 开发,全能型框架。
    • 核心:提供了完整的解决方案,包括路由、依赖注入、状态管理等。
    • 特点:适合大型企业级应用,学习曲线相对陡峭。

三、工程化:高效协作和自动化

现代前端开发已经不仅仅是写代码,还需要使用一系列工具来提高效率。

  • 包管理器npm, yarn, pnpm。用于管理项目依赖。
  • 构建工具
    • Webpack:将多个模块打包成一个或多个文件,并能处理资源依赖、代码压缩等。
    • Vite:基于 ES Modules 的新一代构建工具,开发模式下速度极快。
  • 转译工具Babel。将 ES6+ 的代码转译为浏览器兼容的 ES5 代码。
  • 代码规范ESLint (代码风格检查), Prettier (代码格式化)。
  • 版本控制Git。用于团队协作和代码版本管理。

好的,让我们详细、全面地介绍 CSS 选择器。选择器是 CSS 的核心,掌握它才能精确地控制网页样式。我们将从基础到高级,系统地梳理不同类型的选择器及其用法。


一、基础选择器 (Basic Selectors)

这些是 CSS 中最简单、最常用的选择器,用于直接选中元素。

1. 元素选择器 (Type Selector)

  • 作用:根据元素的标签名来选择元素。
  • 语法element_name { ... }
  • 示例
    • p { color: blue; }:选中所有 <p> 元素。
    • h1 { font-size: 24px; }:选中所有 <h1> 元素。

2. 类选择器 (Class Selector)

  • 作用:根据元素的 class 属性来选择元素。
  • 语法.class_name { ... }
  • 特点:一个元素可以有多个类名,类名可以重复使用,非常灵活。
  • 示例
    • .highlight { background-color: yellow; }:选中所有 class 属性中包含 highlight 的元素。

3. ID 选择器 (ID Selector)

  • 作用:根据元素的 id 属性来选择元素。
  • 语法#id_name { ... }
  • 特点:在 HTML 文档中,一个 id 属性的值必须是唯一的
  • 示例
    • #header { height: 100px; }:选中 idheader 的唯一元素。

4. 通用选择器 (Universal Selector)

  • 作用:选择页面上的所有元素。
  • 语法* { ... }
  • 示例
    • * { margin: 0; padding: 0; }:清除所有元素的默认外边距和内边距,常用于 CSS 重置。

二、组合选择器 (Combinators)

这些选择器用于根据元素之间的关系来选择元素,如父子关系、兄弟关系等。

1. 后代选择器 (Descendant Selector)

  • 作用:选择某个元素内部的所有后代元素(包括子元素、孙子元素等)。
  • 语法ancestor descendant { ... },用空格分隔。
  • 示例
    • ul li { list-style-type: none; }:选中所有 <ul> 内部的 <li> 元素。

2. 子选择器 (Child Selector)

  • 作用:选择某个元素的直接子元素
  • 语法parent > child { ... },用 > 分隔。
  • 示例
    • ul > li { list-style-type: none; }:只选中 <ul> 的直接子元素 <li>
    • 区别:与后代选择器相比,更加精确,性能也更好。

3. 相邻兄弟选择器 (Adjacent Sibling Selector)

  • 作用:选择紧接在另一个元素后面的兄弟元素
  • 语法element + adjacent_element { ... },用 + 分隔。
  • 示例
    • h1 + p { margin-top: 0; }:选中紧跟在 <h1> 后的第一个 <p> 元素。

4. 通用兄弟选择器 (General Sibling Selector)

  • 作用:选择某个元素后面所有的兄弟元素(不限于紧邻的)。
  • 语法element ~ sibling { ... },用 ~ 分隔。
  • 示例
    • h1 ~ p { margin-top: 0; }:选中 <h1> 后的所有 <p> 元素。

三、属性选择器 (Attribute Selectors)

这些选择器根据元素的属性及其值来选择元素。

  • [attribute]:选择具有该属性的元素。
    • [title] { ... }:选中所有具有 title 属性的元素。
  • [attribute=”value”]:选择具有特定属性和值的元素。
    • input[type="text"] { ... }:选中所有 type 属性值为 text<input> 元素。
  • [attribute~=”value”]:选择属性值中包含特定独立单词的元素。
    • [class~="box"] { ... }:选中所有 class 属性中包含 box 这个独立单词的元素(如 class="card box")。
  • [attribute|=”value”]:选择属性值以特定字符串开头(后跟连字符 -)的元素。
    • [lang|="en"] { ... }:选中 lang 属性值为 enen-us 等的元素。
  • [attribute^=”value”]:选择属性值以特定字符串开头的元素。
    • a[href^="https"] { ... }:选中所有 href 属性以 https 开头的 <a> 元素。
  • [attribute$=”value”]:选择属性值以特定字符串结尾的元素。
    • img[src$=".png"] { ... }:选中所有 src 属性以 .png 结尾的 <img> 元素。
  • [attribute*=”value”]:选择属性值中包含特定字符串的元素。
    • [title*="hello"] { ... }:选中所有 title 属性值中包含 hello 的元素。

四、伪类选择器 (Pseudo-class Selectors)

伪类用于选择元素的特定状态

  • a:link:未访问的链接。
  • a:visited:已访问的链接。
  • a:hover:鼠标悬停在元素上。
  • a:active:元素被点击时。
  • element:focus:元素获得焦点时(常用于表单)。
  • :nth-child(n):选择属于其父元素的第 n 个子元素。
  • :first-child:选择属于其父元素的第一个子元素。
  • :last-child:选择属于其父元素的最后一个子元素。
  • :not(selector):选择不匹配指定选择器的元素。
  • :empty:选择没有子元素或文本内容的元素。

五、伪元素选择器 (Pseudo-element Selectors)

伪元素用于选择元素的特定部分

  • ::first-line:选择元素的第一行。
  • ::first-letter:选择元素的首个字母。
  • ::before:在元素的内容前面插入生成的内容。
  • ::after:在元素的内容后面插入生成的内容。

优先级与层叠

当多个选择器选中同一个元素并应用不同的样式时,浏览器会根据**优先级(Specificity)**来决定最终样式。

  • 优先级计算
    • !important:最高优先级。
    • 行内样式:1000
    • ID 选择器:100
    • 类、属性、伪类选择器:10
    • 元素、伪元素选择器:1
    • 通用选择器:0
  • 举例
    • p { color: red; }(优先级 1)
    • .highlight { color: blue; }(优先级 10)
    • #main p { color: green; }(优先级 101)

在这个例子中,即使 p 的样式最先声明,但 main p 的优先级最高,因此 <p id="main"> 的字体颜色最终会是绿色。

好的,让我们来全面、深入地介绍 Maven,并剖析一些关键细节以及面试中可能遇到的问题。


一、Maven 核心概念

1. 什么是 Maven?

Maven 是一个项目管理和构建自动化工具。它提供了一套标准化的项目结构、统一的构建生命周期,并依赖于一个中央仓库来管理项目所需的依赖。

它的核心思想是**“约定优于配置”(Convention over Configuration)**,这意味着它有一套默认的项目目录结构和构建流程。只要你遵循这些约定,就可以用很少的配置完成复杂的构建任务。

2. 为什么需要 Maven?

在 Maven 出现之前,Java 项目的构建非常混乱:

  • 项目结构不统一:每个项目都有自己的目录结构,新成员需要花时间熟悉。
  • 依赖管理混乱:需要手动下载所有 JAR 包,并添加到项目中,非常容易出错。
  • 构建过程不统一:编译、测试、打包等操作需要手动执行脚本,效率低下。

Maven 通过标准化解决了这些问题:

  • 标准化的项目结构:所有 Maven 项目都遵循相同的目录结构,如 src/main/javasrc/test/java 等。
  • 统一的依赖管理:通过在 pom.xml 中声明依赖,Maven 会自动从仓库下载并管理。
  • 标准化的构建生命周期:定义了一系列标准的构建阶段(如 compile, test, package),可以一键执行。

二、Maven 的核心组成部分

1. POM (Project Object Model)

  • 概念pom.xml 文件是 Maven 项目的核心配置文件。它定义了项目的元数据、依赖、插件、构建配置等所有信息。
  • 重要标签
    • <project>:根元素。
    • <groupId>, <artifactId>, <version>:项目的唯一标识符,通常称为 GAV 坐标
    • <packaging>:打包类型,如 jar, war, pom
    • <dependencies>:定义项目所需的依赖。
    • <parent>:继承父 POM,实现依赖的统一管理。
    • <build>:定义构建过程,如插件配置。

2. 依赖管理 (Dependency Management)

  • 概念:通过在 <dependencies> 中定义 GAV 坐标,Maven 会自动从本地或远程仓库下载依赖。
  • 传递性依赖:如果你的项目依赖 A,A 又依赖 B,Maven 会自动将 B 也下载下来。这是其强大的地方,但有时也可能导致版本冲突。
  • :这是一个非常重要的标签,通常在父 POM 中使用。它只定义依赖的版本,但不实际引入。子项目继承后,只需声明 <artifactId><groupId>,版本号会自动继承,这能确保整个项目所有模块的依赖版本一致。

3. 仓库 (Repositories)

Maven 仓库是用来存放所有依赖 JAR 包的地方。它分为三种:

  • 本地仓库 (Local Repository)~/.m2/repository,首次下载的依赖会存放在这里,后续构建时会优先从这里读取。
  • 远程仓库 (Remote Repository)
    • 中央仓库 (Central Repository):Maven 官方维护的公共仓库,包含了绝大多数常用的开源库。
    • 私服 (Private Repository):企业内部搭建的仓库,用于存放公司内部的 JAR 包或作为中央仓库的代理,加快下载速度。

4. 生命周期与阶段 (Lifecycle & Phases)

Maven 有一套标准的构建生命周期,分为三个:

  • clean:清理项目。
    • clean:删除 target 目录。
  • default:构建项目。
    • validate
    • compile:编译源代码。
    • test:运行测试。
    • package:打包。
    • install:安装到本地仓库。
    • deploy:部署到远程仓库。
  • site:生成项目站点。

重要规则:执行某个阶段时,它之前的所有阶段都会按顺序执行。例如,mvn package 会自动执行 compiletest


三、面试常见问题与回答技巧

1. Maven 的 GAV 坐标是什么?有什么作用?

  • 回答:GAV 坐标是 Maven 项目的唯一标识符,由 <groupId>, <artifactId>, 和 <version> 三个元素组成。
  • 作用
    • 唯一性:确保每个项目和每个版本的依赖在仓库中都是唯一的。
    • 定位:Maven 通过 GAV 坐标来查找和下载依赖。
    • 传递性:在传递性依赖中,通过 GAV 坐标来识别和处理依赖关系。

2. <dependencyManagement><dependencies> 有什么区别?

  • 回答:这是面试高频问题,需要清晰地说明两者的职责。
  • 实际引入依赖。它用于在当前项目中添加一个具体的依赖,Maven 会立即下载并使用它。
  • 只定义版本。它只声明依赖的版本,但不实际引入。其主要目的是统一管理子模块的依赖版本。子模块继承父 POM 后,只需在自己的 <dependencies> 中声明 <groupId><artifactId>,版本号会自动从父 POM 中继承,这避免了版本不一致的问题。

3. 什么是 Maven 的生命周期?mvn installmvn deploy 有什么区别?

  • 回答
  • 生命周期:Maven 有三个标准的生命周期:cleandefaultsite。其中 default 包含了从编译到部署的所有阶段。
  • mvn install:执行 default 生命周期到 install 阶段。它会将项目打包,并安装到本地仓库。这样,其他本地项目就可以依赖这个包。
  • mvn deploy:执行 default 生命周期到 deploy 阶段。它会将项目打包,并部署到远程仓库(私服或中央仓库)。这使得其他团队成员或项目可以从远程仓库获取并使用这个包。

4. Maven 依赖冲突如何解决?

  • 回答:当多个依赖间接引入了同一个库的不同版本时,就会发生依赖冲突。
  • 解决策略
    • 依赖调解(Dependency Mediation):Maven 的默认规则是“路径最短者优先”。即在依赖树中,路径最短的那个版本会被选中。
    • 手动排除(Exclusion):如果默认规则不能解决问题,可以在 <dependency> 标签内使用 <exclusions> 标签手动排除有问题的传递性依赖。
    • 手动引入(Declaration):在 <dependencies> 中明确声明需要使用的版本。Maven 的另一个规则是“最近声明者优先”,即如果两个依赖路径长度相同,先声明的那个会被使用。但更好的做法是直接在父 POM 的 <dependencyManagement> 中统一版本。

好的,我们来详细梳理你提供的这份关于会话技术的笔记,并按照你要求的逻辑,以一种更清晰、更专业的面试回答或技术讲解的方式进行重新组织和补充。


一、会话技术概述:解决 HTTP 无状态问题

HTTP 协议本身是无状态的,它不记得上一次请求的任何信息。为了在多次请求之间共享数据并识别用户,引入了两种核心的会话技术:客户端会话(Cookie)和服务器端会话(Session)。

二、客户端会话:Cookie

核心概念:Cookie 是服务器发送给浏览器并存储在客户端的一小段文本信息。浏览器在下次访问同一服务器时会自动将该 Cookie 携带在请求中。

  • 一次请求响应可以发送多个 Cookie 吗?
    • 回答可以。服务器可以在一个响应中通过多个 Set-Cookie 响应头来设置多个 Cookie。浏览器会分别存储这些 Cookie,并在后续请求中将它们全部发送给服务器。
  • Cookie 支持中文传输吗?
    • 回答在大多数现代服务器和浏览器中都支持。 在早期的 Servlet 容器(如 Tomcat 8.0 之前),Cookie 不能直接存储中文,需要手动进行 URL 编码(URLEncoder)和解码(URLDecoder)。在 Tomcat 8.0 及以后版本,容器默认支持 UTF-8 编码,可以直接存储中文。
  • Cookie 的过期时间如何设置?
    • 回答:通过 response.addCookie(cookie) 方法,并调用 cookie.setMaxAge(int expiry) 方法来设置。
      • expiry > 0:表示 Cookie 将被持久化到客户端硬盘,有效期为 expiry 秒。即使浏览器关闭,Cookie 依然存在,直到过期。
      • expiry = 0:表示立即删除该 Cookie。常用于退出登录功能。
      • expiry < 0默认值。表示 Cookie 只在内存中存在,当浏览器关闭时,该 Cookie 就会被删除。
  • 存储位置:存储在客户端(浏览器)。
  • 大小限制:单个 Cookie 最大约 4KB。
  • 数量限制:一个服务器最多可以向一个浏览器保存 20 个 Cookie,一个浏览器最多可以保存 300 个 Cookie(这些是早期的规范,现代浏览器已放宽,但仍有限制)。
  • 安全性:数据以明文形式存储,且容易被用户修改,安全性较差。

这个案例是经典的 Cookie 用法。

  • 首次访问:服务器接收请求,判断 Cookie 中没有记录上次访问时间。服务器创建一个新的 Cookie,存储当前时间,并将其发送给浏览器。
  • 再次访问:浏览器自动将上次存储的 Cookie 发送给服务器。服务器读取 Cookie,获取上次访问时间,并将其显示给用户。然后,服务器可以更新 Cookie 的时间,再次发送给浏览器。

三、服务器端会话:HttpSession

核心概念:Session 是服务器端为每个客户端创建的一个对象,用于在一次会话的多个请求之间存储和共享数据。

1. Session 的快速使用

  • 获取对象:通过 HttpServletRequest 对象的 getSession() 方法来获取 HttpSession 对象。
  • 使用对象(域对象):Session 是一个域对象,提供了以下方法:
    • void setAttribute(String name, Object value):将数据以键值对形式存储到 Session 中。
    • Object getAttribute(String name):根据键名获取存储的数据。
    • void removeAttribute(String name):根据键名删除存储的数据。

2. Session 的失效时间

  • 服务器关闭:当服务器关闭时,所有 Session 对象都会被销毁。

  • 手动失效:调用 session.invalidate() 方法,可以立即强制 Session 失效。常用于用户退出登录。

  • 默认失效时间:默认情况下,Session 有一个超时时间,通常是 30 分钟。如果客户端在 30 分钟内没有向服务器发送任何请求,Session 就会自动失效。这个时间可以在 web.xml 中配置。

  • 对的 👍,在 **Java Web 应用(Servlet 容器,比如 Tomcat)**里,Session 的默认失效时间通常是 30 分钟

    要修改它,可以在 web.xml 中配置 <session-config>

    1
    2
    3
    4
    5
    6
    7
    <web-app ...>
    <!-- session 过期时间设置(单位:分钟) -->
    <session-config>
    <session-timeout>60</session-timeout>
    </session-config>
    </web-app>

📌 说明:

  • 60 表示 Session 在 60 分钟无请求时会自动失效。
  • 默认值是 30 分钟,如果不配置,就走默认。
  • 这个配置对所有用户 Session 都生效。

🔧 在代码里动态设置(单个 Session)

除了在 web.xml 里全局配置,还可以针对某个用户的 Session 动态修改:

1
2
3
HttpSession session = request.getSession();
session.setMaxInactiveInterval(120 * 60); // 单位:秒,这里是 2 小时


🔧 特殊情况

  • 如果 session-timeout 设置为 0 → Session 永不超时(危险 ⚠️,容易导致内存泄露)。
  • 如果设置为 负数(如 -1) → 表示 Session 仅在浏览器关闭时销毁(有些容器支持,有些不支持。

3. Session 的特点

  • 存储位置:存储在服务器端。
  • 大小限制:可以存储任意类型、任意大小的数据(受限于服务器内存)。
  • 安全性:由于数据存储在服务器,客户端只传递一个 Session ID,安全性相对较高。

4. 面试题:Cookie 和 Session 的区别?

  • 存储位置:Cookie 存储在客户端,Session 存储在服务器端
  • 数据大小:Cookie 的大小有限制(约 4KB),Session 的大小没有限制
  • 安全性:Session 相对安全,Cookie 相对不安全。因为 Session ID 难以被猜测,而 Cookie 存储的数据是明文。
  • 服务器开销:Session 会占用服务器内存,在高并发场景下可能成为性能瓶颈;而 Cookie 不占用服务器资源。

四、Servlet 三大域对象的关系(按照你的逻辑)

域对象(Scope Object)是指那些可以用来在不同组件之间共享数据的对象。Servlet 规范提供了三个这样的对象,它们的生命周期不同,从而决定了数据的共享范围。

  • request 域:生命周期最短。数据只能在一次请求-响应的周期内共享。即使请求被转发(forward),数据也依然可见。
  • session 域:生命周期中等。数据可以在一次会话(多个请求)之间共享。只要浏览器不关闭,且 Session 未超时或失效,数据就一直存在。
  • servletContext 域:生命周期最长。数据可以在整个 Web 应用中共享。从 Web 应用启动到关闭,数据都一直存在。

三大域对象之间的关系是包含关系,即 request < session < servletContext。

  • request 包含于 session:一个 Session 中可以有多个请求。
  • session 包含于 servletContext:一个 Web 应用(ServletContext)中可以有多个 Session。

这个关系图清晰地展示了它们各自的生命周期和数据共享范围,是理解 Servlet 域对象的关键。

啊哈~你这个场景就是 同一个库的两个版本必须共存,典型的 “深度 JAR hell” 🚨。
比如:

  • 你的系统用到 老版本 log4j
  • 但某个第三方库硬性依赖 新版本 log4j

这时候如果放到同一个 classpath 里,肯定会冲突。解决思路主要有三类:


✅ 两个版本 JAR 共存的解决方案

1. ClassLoader 隔离(推荐方式)

利用 不同的类加载器 来隔离两个版本的依赖,让它们互不干扰。

  • Web 容器隔离

    • 把一个版本放在 WEB-INF/lib,另一个放在全局 lib/ext
    • 通过 parent-first / parent-last 策略控制加载。
  • 自定义 ClassLoader

    • URLClassLoader 指定不同版本 JAR 的路径。

    • 哪个模块需要哪个版本,就用哪个 ClassLoader。

    • 示例:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      URLClassLoader loaderV1 = new URLClassLoader(
      new URL[]{ new File("lib/lib-old.jar").toURI().toURL() },
      null // 不委托父加载器
      );

      URLClassLoader loaderV2 = new URLClassLoader(
      new URL[]{ new File("lib/lib-new.jar").toURI().toURL() },
      null
      );

      Class<?> clazz1 = loaderV1.loadClass("com.example.MyClass");
      Class<?> clazz2 = loaderV2.loadClass("com.example.MyClass");

👉 OSGi、Spring Boot 的 ClassLoader 层级就是用这种方法解决多版本依赖的。


2. 包重定位(Shading / Relocation)

把其中一个版本的包路径 改名,让 JVM 认为它是完全不同的类库。

  • 工具:

    • jarjar
    • Maven Shade(即使你不用 Maven,也可以单独跑插件)
    • Gradle Shadow
  • 原理:自动把 org.apache.commons 改成 myproject.shaded.org.apache.commons

  • 示例(JarJar 规则):

    1
    rule org.apache.log4j.** com.myapp.shaded.log4j.@1

这样,项目就能同时用 log4j-1.2log4j-2.x


3. 服务解耦 / 模块化

如果两个版本都要在 同一个 JVM 里用,但 ClassLoader 太麻烦:

  • 拆分成微服务 / 子进程,让不同进程使用不同版本的依赖。
  • 或者在 Java 9+ 模块系统 (JPMS) 下,给不同模块声明不同依赖,避免直接冲突。

🚀 实际建议

  • 如果你是 自己开发的项目 → 用 包重定位 比较简单。
  • 如果是 插件式系统 / 容器运行 → 用 ClassLoader 隔离
  • 如果是 重量级系统(比如需要不同版本的 JDBC 驱动) → 拆进程更干净。

要不要我帮你写一个 最小可运行的 Java Demo(两个版本 JAR 同时运行,通过 ClassLoader 隔离)?这样你可以直接跑起来看看效果。

好的,我将根据您提供的这份详尽的笔记,以更专业、更系统的方式,对其进行重新组织、补充和深入解析,使其成为一份高质量的技术讲解或面试回答。


一、CSS 选择器概述:精准定位网页元素

选择器是 CSS 的核心,它用于精确地选择 HTML 文档中的元素,并为其应用样式。掌握不同类型的选择器及其组合,是高效编写 CSS 代码的关键。

二、基础选择器 (Basic Selectors)

基础选择器用于根据最基本的属性(标签、类、ID)来选择元素。

1. 标签选择器 (Type Selector)

  • 原理:根据元素的标签名称来匹配。

  • 优点:简单、直接,可以一次性选中所有同类型的元素。

  • 缺点:过于笼统,无法对个别元素进行精确控制。

  • 示例

    CSS

    1
    div { color: red; } /* 所有<div>标签的字体颜色都为红色 */

2. 类选择器 (Class Selector)

  • 原理:根据元素的 class 属性值来匹配。

  • 优点

    • 高度复用:可以在多个元素上使用同一个类名。
    • 灵活:一个元素可以拥有多个类名,用空格分隔。
  • 示例

    CSS

    1
    .cl1 { color: red; } /* 所有class属性包含cl1的元素,字体颜色为红色 */

3. ID 选择器 (ID Selector)

  • 原理:根据元素的 id 属性值来匹配。

  • 优点唯一性,精确地选中文档中唯一的元素。

  • 缺点:不具复用性,一个 ID 在文档中只能出现一次。

  • 示例

    CSS

    1
    #p1 { color: red; } /* id为p1的唯一元素,字体颜色为红色 */

4. 通配符选择器 (Universal Selector)

  • 原理:匹配文档中所有的元素。

  • 优点:可以快速设置全局样式,常用于重置浏览器默认样式。

  • 缺点:性能开销大,因为浏览器需要遍历所有元素,应谨慎使用。

  • 示例

    CSS

    1
    * { margin: 0; padding: 0; } /* 清除所有元素的内外边距 */

三、组合选择器 (Combinators)

组合选择器通过符号连接多个选择器,根据元素之间的关系来匹配。

1. 交集选择器 (Intersection Selector)

  • 原理:同时满足多个选择器条件的元素。

  • 语法selector1selector2 { ... },中间没有空格

  • 示例

    CSS

    1
    div.cl1 { color: red; } /* 既是<div>标签,又拥有cl1这个class的元素 */

2. 并集选择器 (Union Selector)

  • 原理:匹配所有满足其中任意一个选择器条件的元素。

  • 语法selector1, selector2 { ... },以逗号分隔。

  • 示例

    CSS

    1
    a, p { color: red; } /* 所有<a>标签和所有<p>标签的字体颜色都为红色 */

3. 子选择器 (Child Selector)

  • 原理:匹配直接子元素。

  • 语法parent > child { ... },以 > 符号分隔。

  • 示例

    CSS

    1
    #outer > span { color: red; } /* 只选中id为outer的<div>的直接子元素<span> */

4. 后代选择器 (Descendant Selector)

  • 原理:匹配所有位于祖先元素内部的所有后代元素。

  • 语法ancestor descendant { ... },以空格分隔。

  • 示例

    CSS

    1
    #outer span { color: red; } /* 选中id为outer的<div>内部的所有<span>,无论嵌套多深 */

四、属性选择器 (Attribute Selectors)

属性选择器根据元素的属性及其值来匹配。

  • [attribute]:匹配具有该属性的元素。
    • 示例:[class] 匹配所有具有 class 属性的元素。
  • [attribute=”value”]:匹配属性值完全相等的元素。
    • 示例:[type="text"] 匹配 type 属性值恰好为 "text" 的元素。
  • [attribute~=”value”]:匹配属性值中包含该独立单词的元素。
  • [attribute^=”value”]:匹配属性值以该字符串开头的元素。
  • [attribute$=”value”]:匹配属性值以该字符串结尾的元素。
  • [attribute*=”value”]:匹配属性值中包含该字符串的任意位置的元素。

五、伪类与伪元素选择器 (Pseudo-class & Pseudo-element Selectors)

  • 伪类:用一个冒号 :,表示元素在特定状态下的样式。
    • :link:未访问的链接。
    • :visited:已访问的链接。
    • :hover:鼠标悬停状态。
    • :active:被点击状态。
    • :focus:获得焦点状态(常用于表单)。
  • 伪元素:用两个冒号 ::,表示元素的特定部分
    • ::before::after:在元素内容的前后插入内容。
    • ::first-line:元素的文本第一行。
    • ::first-letter:元素的文本第一个字母。

六、选择器优先级 (Specificity)

这是 CSS 的核心机制,决定了当多个规则应用到同一个元素时,哪一个会生效。

  • 计算规则:优先级由选择器的类型数量决定。
    1. 行内样式:优先级最高,为 1000
    2. ID 选择器:优先级为 100
    3. 类、属性、伪类选择器:优先级为 10
    4. 元素、伪元素选择器:优先级为 1
    5. 通用选择器 *:优先级为 0
  • !important:最高优先级,但会破坏层叠机制,应避免使用

如何计算:

将每个选择器的优先级数字相加,得到一个最终的权重值。权重值越高的规则越优先。

  • 示例:
    • p:权重 1
    • .cl1:权重 10
    • div.cl1:权重 1 + 10 = 11
    • #p1:权重 100
    • #outer span:权重 100 + 1 = 101

!important 会凌驾于所有这些规则之上。当你遇到样式不生效的问题时,首先要检查优先级,其次是是否有 !important 的存在。

好的,让我们来详细、深入地解释 BOMDOM 这两个 JavaScript 在浏览器中操作网页的核心概念。


BOM (Browser Object Model)

1. 概念

BOM,即浏览器对象模型,是 JavaScript 用于操作浏览器窗口的一套 API。它没有统一的标准,而是由各个浏览器厂商各自实现的。BOM 的核心是 window 对象,它既代表了浏览器窗口本身,也是 JavaScript 全局作用域的唯一对象。

2. BOM 的核心对象与功能

BOM 提供了以下关键对象,用于与浏览器进行交互:

  • window 对象
    • 全局对象window 是 JavaScript 的全局对象,所有全局变量和函数都是 window 对象的属性和方法。
    • 窗口控制window.open() (打开新窗口), window.close() (关闭当前窗口)。
    • 定时器setTimeout()setInterval(),用于延迟或重复执行代码。
    • 导航与位置window.location,用于获取和修改当前页面的 URL。
  • location 对象
    • URL 信息:提供了当前 URL 的详细信息,如 location.href (完整 URL), location.protocol (协议), location.hostname (主机名), location.pathname (路径)。
    • 页面跳转location.href = 'new_url'location.assign('new_url') (有历史记录), location.replace('new_url') (不留历史记录), location.reload() (刷新页面)。
  • history 对象
    • 历史记录:提供了对浏览器历史记录的访问。
    • 导航history.back() (后退), history.forward() (前进), history.go(n) (前进或后退 n 页)。
  • navigator 对象
    • 浏览器信息:提供了关于浏览器本身的信息,如 navigator.userAgent (用户代理字符串), navigator.platform (操作系统), navigator.onLine (是否在线)。
  • screen 对象
    • 屏幕信息:提供了关于用户屏幕的信息,如 screen.width (屏幕宽度), screen.height (屏幕高度)。

3. BOM 的特点

  • 没有标准:BOM 没有 W3C 的正式标准,不同浏览器可能在实现上存在差异。
  • 以 window 为核心:所有 BOM 对象都是 window 对象的属性。

DOM (Document Object Model)

1. 概念

DOM,即文档对象模型,是 JavaScript 用于操作 HTML 或 XML 文档的一套 API。它将整个 HTML 文档解析为一个树形结构,每个 HTML 标签、属性和文本都成为了树中的一个节点(Node)。DOM 是一个由 W3C 制定的标准。

2. DOM 的核心对象与功能

DOM 的核心是 document 对象,它是整个文档的入口。

  • document 对象
    • DOM 树的根节点:代表整个 HTML 文档。
    • 元素获取:提供了多种方法来获取页面上的元素:
      • document.getElementById('id'):通过 ID 获取单个元素。
      • document.getElementsByClassName('class'):通过类名获取元素集合。
      • document.getElementsByTagName('tag'):通过标签名获取元素集合。
      • document.querySelector('css_selector'):通过 CSS 选择器获取第一个匹配的元素。
      • document.querySelectorAll('css_selector'):通过 CSS 选择器获取所有匹配的元素。
  • 元素节点(Element Nodes)
    • 操作元素:提供了操作 HTML 元素的方法和属性:
      • element.innerHTML:获取或设置元素的 HTML 内容。
      • element.style.color:修改元素的行内样式。
      • element.setAttribute('attr', 'val'):设置元素的属性。
      • element.addEventListener('event', handler):添加事件监听器。
  • DOM 树操作
    • 创建document.createElement('tag') (创建新元素)。
    • 添加parent.appendChild(child) (添加子元素)。
    • 删除parent.removeChild(child) (删除子元素)。
    • 替换parent.replaceChild(newChild, oldChild) (替换子元素)。

3. DOM 的特点

  • 有标准:DOM 是由 W3C 制定的标准,因此在不同浏览器中的实现差异较小。
  • 树形结构:将 HTML 文档抽象为树形结构,使得我们可以通过编程的方式像操作树一样操作网页。

BOM 和 DOM 的核心区别与联系

特性 BOM (Browser Object Model) DOM (Document Object Model)
主要功能 操作浏览器窗口 操作网页文档内容
核心对象 window document
标准化 无统一标准(各浏览器实现不同) 有 W3C 标准
层级关系 window 包含了 location, history, navigator 等,也包含了 document 对象 document 包含了 HTML 元素的树形结构。

它们的关系:

BOM 是 DOM 的超集。window 对象是所有 JavaScript 对象的全局对象,它包含了 document 对象。换句话说,window.document 就是 document。因此,当我们使用 document 对象来操作网页时,实际上是通过 window 对象来访问的。

这个关系可以理解为:

window -> 浏览器窗口

document -> 窗口中加载的文档

掌握 BOM 和 DOM 是成为一个合格前端工程师的基础,它们是 JavaScript 驱动 Web 页面交互的两个最重要的工具。

好的,我将根据您提供的这份 JS 学习笔记,以一种更深入、更条理化的方式,逐一进行详细的展开和补充。这不仅是对知识点的梳理,也包含了其背后的原理和一些进阶用法,使其更具面试和实战价值。


一、JavaScript 中的数据类型

1. 基础数据类型(原始数据类型)

  • number 类型

    • 深入:JS 采用 IEEE 754 标准的双精度 64 位浮点数来表示所有数字。这意味着它没有单独的整数类型,所有数字都是浮点数。
    • NaN (Not a Number):表示非数字值。需要注意的是,typeof NaN 结果是 numberNaN 不等于自身,NaN === NaN 结果为 false
    • Infinity:表示正无穷大,+Infinity-Infinity 分别表示正负无穷大。
    • 0.1 + 0.2 !== 0.3:由于浮点数表示的精度问题,这个经典问题需要注意。
  • boolean 类型

    • 深入truefalse。在条件判断中,所有数据类型都会被隐式转换为布尔值。
  • undefined 类型

    • 深入:表示一个未定义的变量或变量未被赋值typeof undefined 结果为 undefined。它是一个值也是一个类型。
    • null:与 undefined 的区别在于,null 是一种意图,表示变量被显式地赋予了“空”值。typeof null 结果为 object,这是一个历史遗留的 bug。
  • string 类型

    • 深入:JS 中没有字符类型。字符串是不可变的,一旦创建就不能修改。

    • 反引号( ):ES6 引入,支持模板字面量。它允许在字符串中嵌入变量和表达式,并支持多行书写,极大提升了字符串拼接的便利性。

      JavaScript

      1
      2
      const name = "张三";
      console.log(`你好,我的名字是${name}。`);

2. 数据类型转换

  • 转换为 numberNumber(value)

    • 深入:会尝试将参数转换为数字。非数字字符串会返回 NaNnull 转为 0undefined 转为 NaN
  • 转换为 booleanBoolean(value)

    • 深入:所有能被转换为 false 的值被称为假值(Falsy Value)
    • 假值列表0, -0, null, false, NaN, undefined, '' (空字符串)。
    • 真值(Truthy Value):除上述假值外,所有值都是真值,包括空数组 [] 和空对象 {}
  • 转换为 stringString(value)value + ''

    • 深入value + '' 是最常用的技巧,利用了 JS 的隐式类型转换。
  • parseInt 和 parseFloat

    • 深入:这两个函数专门用于从字符串开头解析出数字。

    • parseInt:解析整数。遇到第一个非数字字符就停止解析。

      JavaScript

      1
      2
      parseInt("100px"); // 100
      parseInt("a100"); // NaN
    • parseFloat:解析浮点数。遇到第一个非数字字符(除了小数点)就停止解析。

二、JavaScript 中的弹出框

这些是浏览器提供的 BOM API。

  • alert(message)警告框。阻塞式,显示一条消息,不返回任何值。
  • prompt(message, default_value)询问框。返回用户输入的字符串,如果点击“取消”则返回 null
  • confirm(message)确认框。返回一个布尔值,用户点击“确定”返回 true,点击“取消”返回 false

三、条件运算符:=====

  • ==(宽松相等):只比较,不比较类型。它在比较前会进行隐式类型转换

    JavaScript

    1
    2
    5 == "5";     // true
    null == undefined; // true
  • ===(严格相等):既比较,也比较类型。不会进行类型转换。

    JavaScript

    1
    2
    5 === "5";    // false
    null === undefined; // false
  • 面试建议:在实际开发中,强烈建议使用 ===,以避免不必要的类型转换带来的 bug。

四、字符串和数组

  • 字符串
    • 深入string 是一个对象,拥有 length 属性和许多方法(如 split(), slice(), indexOf() 等),但它不可变
  • 数组
    • 特点
      1. 动态长度:JS 数组的长度是可变的,你可以随时添加或删除元素。
      2. 异构性:一个数组可以存放不同数据类型的元素,如 [1, "hello", true]
    • 方法
      • push(), pop():在数组末尾添加和删除。
      • unshift(), shift():在数组开头添加和删除。
      • splice():功能强大的方法,用于删除、替换或添加元素。
      • forEach(), map(), filter():常用的遍历方法。

五、函数和对象

  • 函数
    • 深入:在 JS 中,函数是一等公民(First-Class Citizens)。这意味着函数可以作为参数传递、作为返回值,也可以赋值给变量。
  • 对象
    • 深入:JS 对象是键值对的集合。键是字符串,值可以是任意类型。
    • 访问属性
      • 点语法:obj.prop
      • 中括号语法:obj['prop']。后者适用于键名包含特殊字符或动态键名的情况。

六、定时器

  • setTimeout(callback, delay)
    • 用途:只执行一次
    • 深入delay 参数是最小延迟时间。由于 JS 是单线程的,如果主线程被阻塞,callback 的执行会延迟。
  • setInterval(callback, delay)
    • 用途重复执行。
    • 深入:每次执行完回调函数后,setInterval 都会将下一个回调任务放入队列。

七、ES6 新特性(重要)

  • let 和 const
    • var 的问题:全局作用域,可以被重复声明,有变量提升。
    • let块级作用域{} 内),不能重复声明,没有变量提升。
    • const块级作用域,用于声明常量。一旦声明,其引用地址不能改变。
  • 字符串模板字面量:使用反引号( )创建多行字符串和嵌入变量。
  • 箭头函数
    • 语法const func = (param) => { ... }
    • this 指向:箭头函数没有自己的 this,它会捕获其所在上下文的 this 值。这解决了传统函数中 this 绑定复杂的问题。
  • 可变参数...args,用于函数接收不确定数量的参数。

八、DOM

  • 什么是 DOM
    • 深入:DOM 是浏览器将 HTML 文档解析后生成的树形结构,是 JavaScript 操作网页的接口。HTML 文件是文本,DOM 是一个对象,可以被 JS 编程控制。
  • DOM 操作
    • 获取标签对象
      • document.getElementById()
      • document.getElementsByClassName()
      • document.querySelector() (更常用)
    • 操作属性
      • element.attribute = '...' (例如 element.style.color = 'red')
      • element.setAttribute('attr', 'value')
    • 操作内容
      • element.innerHTML:获取或设置元素的 HTML 内容(包括子标签)。
      • element.textContent:获取或设置元素的文本内容(不含 HTML)。

好的,我将根据您提供的这份详尽的 Web 技术笔记,以一种更专业、更系统的方式,对其进行重新组织、补充和深入解析,使其成为一份高质量的技术讲解或面试回答。


一、Web 架构与网络基础

1. 常见的软件架构

  • B/S 架构 (Browser/Server)
    • 概念:浏览器和服务器架构。客户端只需安装一个通用的浏览器,业务逻辑和数据都存储在服务器端。
    • 优点:易于维护和升级,跨平台性好。
    • 缺点:对网络依赖性强,用户体验可能不如 C/S 架构。
  • C/S 架构 (Client/Server)
    • 概念:客户端和服务器架构。客户端需要安装专用的应用程序,例如桌面 QQ、微信等。
    • 优点:用户体验好,响应速度快,可以离线使用。
    • 缺点:维护和升级复杂,需要为不同平台开发不同版本。

2. 网络编程三要素

这是所有网络通信的基础,理解这三点至关重要。

  • IP 地址:设备在网络上的唯一标识,类似于你的家庭住址。它用于在网络中定位到一台具体的计算机。
  • 端口号:应用程序在计算机上的唯一标识,类似于你家里的电话号码或门牌号。一台计算机上可以运行多个应用程序,端口号用于区分它们。
  • 协议:通信规则。就像人与人交流需要遵循共同的语言一样,网络设备之间通信也需要遵循特定的协议,如 HTTP、FTP、TCP 等。

3. 资源的分类

  • 静态资源
    • 特点:内容固定,无需服务器端处理,可由浏览器直接解析。
    • 原理:当浏览器请求静态资源时,Web 服务器直接从文件系统中读取文件并返回。
    • 示例:HTML、CSS、JS、图片等。
  • 动态资源
    • 特点:内容动态生成,需要服务器端处理后才能返回给浏览器。
    • 原理:当浏览器请求动态资源时,Web 服务器将请求交给后台程序(如 Servlet 容器),后台程序执行业务逻辑,生成静态内容(如 HTML、JSON 等),然后返回给服务器,服务器再返回给浏览器。
    • 示例:Servlet、JSP、PHP、ASP 等。

二、Web 服务器

1. 作用

Web 服务器(也称为 Web 容器)是一个软件,它负责处理 HTTP 请求,并提供静态资源和动态资源。它为动态资源(如 Servlet)提供了一个运行环境。

2. 常见的 Web 服务器

  • Tomcat:Apache 基金组织开发,中小型的 JavaEE 服务器免费且开源。它是一个Servlet 容器,能够运行 Servlet 和 JSP。
  • WebSphere:IBM 公司开发,大型的 JavaEE 服务器收费。功能强大,支持完整的 JavaEE 规范。
  • WebLogic:Oracle 公司开发,大型的 JavaEE 服务器收费
  • JBoss/WildFly:开源,但其商业版收费。

三、Tomcat 的使用与配置

  • 安装与启动:Tomcat 是免安装的,解压即可。启动前需要配置 JAVA_HOME 环境变量。启动后,默认监听 8080 端口。
  • 端口号修改
    • 面试题:修改 Tomcat 端口号在哪个文件?
    • 回答:在 Tomcat 的 conf 目录下的 server.xml 文件中,修改 <Connector> 标签的 port 属性。
    • 注意:HTTP 协议的默认端口号是 80。如果将 Tomcat 端口号改为 80,那么访问时就可以省略端口号,例如 http://localhost/
  • 项目部署
    • 静态项目:将 HTML、CSS、JS 等文件直接放到 webapps 目录下的文件夹中。
    • 动态项目:将包含 WEB-INF 文件夹的整个项目目录放到 webapps 目录下。
    • WEB-INF:这是动态项目的核心目录,具有特殊作用,外部无法直接通过 URL 访问该目录下的资源,保证了项目的安全性。
      • classes:存放所有编译后的 .class 字节码文件。
      • lib:存放项目依赖的第三方 .jar 包。
      • web.xml:Web 项目的核心配置文件,用于配置 Servlet、监听器、过滤器等。

四、Servlet 深入解析

1. Servlet 的概念和本质

  • 概念:Servlet 是运行在服务器端的 Java 程序,用于处理客户端请求并生成动态响应。它不是一个独立的程序,必须部署到支持 Servlet 的容器中(如 Tomcat)才能运行。
  • 本质:Servlet 的本质是一个接口。所有自定义的 Servlet 类都必须实现 javax.servlet.Servlet 接口

2. Servlet 的执行原理(详细解释)

这是一个非常重要的面试点,需要从请求-响应的整个流程来详细阐述。

  1. 客户端请求:用户在浏览器中输入 URL,向服务器发送一个 HTTP 请求。
  2. Web 服务器接收请求:Web 服务器(Tomcat)接收到这个请求。
  3. Servlet 容器处理:Tomcat 会根据请求 URL,在 web.xml 或通过注解(如 @WebServlet)查找匹配的 Servlet。
  4. Servlet 实例创建
    • 如果是第一次访问该 Servlet,Servlet 容器会创建一个该 Servlet 的实例。
    • 面试点:Servlet 是单例的,一个 Servlet 在容器中只会被创建一次。
  5. init() 方法执行
    • 在 Servlet 实例创建后,容器会立即调用它的 init() 方法。
    • init() 方法只在 Servlet 的生命周期中执行一次,用于完成一些初始化工作,如加载配置文件、数据库连接等。
  6. service() 方法执行
    • 每次客户端请求该 Servlet 时,容器都会调用它的 service() 方法。
    • service() 方法根据请求的 HTTP 方法(GET、POST 等),将请求分发给相应的 doGet()doPost() 方法。
    • 面试点service() 方法是处理请求的核心方法,它是多线程的,每个请求都会在新线程中执行 service() 方法。
  7. destroy() 方法执行
    • 当 Servlet 容器关闭,或者决定卸载该 Servlet 时,会调用其 destroy() 方法。
    • destroy() 方法也只执行一次,用于释放资源,如关闭数据库连接池。

总结:Servlet 的生命周期是:创建实例 -> 调用 init() (只一次) -> 调用 service() (多次) -> 调用 destroy() (只一次) -> 销毁实例

好的,我们来将“网络编程三要素”这一部分进行更深入、更详细的展开,并补充常见应用的默认端口号,使其更具实用性和面试价值。


一、网络编程三要素:深入解析

网络编程的本质就是让不同的计算机上的应用程序能够进行通信。要实现这一点,必须解决三个核心问题:

  1. 找到对方计算机:IP 地址
  2. 找到对方计算机上的应用程序:端口号
  3. 以什么样的规则进行通信:协议

这三者缺一不可。

1. IP 地址 (Internet Protocol Address)

  • 概念:IP 地址是分配给连接到网络中的设备(如计算机、手机、服务器)的一串数字标识。
  • 作用:它用于唯一地标识网络上的一个设备。数据包在网络中传输时,就是根据 IP 地址来路由和寻址的。
  • 版本
    • IPv4:由 32 位二进制数组成,通常表示为四个十进制数,用点分隔(例如 192.168.1.1)。由于地址资源枯竭,现在已经不够用。
    • IPv6:由 128 位二进制数组成,地址空间巨大,足以满足未来需求。
  • 类型
    • 公网 IP:在互联网上是唯一的,可以直接访问。
    • 内网 IP:在局域网内是唯一的,不能直接在互联网上访问。例如 192.168.x.x10.x.x.x

2. 端口号 (Port Number)

  • 概念:端口号是用于区分一台计算机上不同应用程序的数字标识。它的范围是从 065535
  • 作用:当一个数据包到达一台计算机时,操作系统会检查其目的端口号,然后将数据包交给监听该端口号的相应应用程序。
  • 与 IP 地址的关系:IP 地址解决了“数据包发送到哪台计算机”的问题,而端口号则解决了“数据包发送到这台计算机上的哪个应用程序”的问题。两者结合起来才能唯一确定一个网络连接的端点。

3. 协议 (Protocol)

  • 概念:协议是网络通信中数据传输的规则和约定。它定义了数据如何打包、传输、路由和接收。
  • 作用:确保通信双方能够理解彼此发送的数据。没有协议,数据包就是一堆无意义的字节。
  • 分层:网络协议通常是分层的,最经典的是 TCP/IP 协议栈
    • 应用层决定数据内容,如 HTTP、FTP、SMTP。
    • 传输层决定数据如何传输,如 TCP 和 UDP。
      • TCP (Transmission Control Protocol):面向连接、可靠、有序。适用于对数据完整性要求高的场景,如文件传输、网页浏览。
      • UDP (User Datagram Protocol):无连接、不可靠、速度快。适用于对实时性要求高的场景,如在线视频、游戏。
    • 网络层决定数据如何路由,如 IP 协议。
    • 数据链路层/物理层:负责物理设备的通信。

二、常见应用的默认端口号

了解这些默认端口号,可以帮助你更好地理解网络协议和服务。

  • Web 服务
    • HTTP (HyperText Transfer Protocol):80
    • HTTPS (HTTP Secure):443
    • Tomcat (默认):8080
    • WebLogic (默认):7001
  • 文件传输
    • FTP (File Transfer Protocol):21 (控制连接)
    • SFTP (SSH File Transfer Protocol):22
  • 远程登录与管理
    • SSH (Secure Shell):22
    • Telnet23
    • RDP (Remote Desktop Protocol):3389
  • 数据库服务
    • MySQL3306
    • PostgreSQL5432
    • SQL Server1433
    • Oracle1521
  • 邮件服务
    • SMTP (Simple Mail Transfer Protocol):25 (发送邮件)
    • POP3 (Post Office Protocol 3):110 (接收邮件)
    • IMAP (Internet Message Access Protocol):143 (接收邮件)
  • 其他常见服务
    • DNS (Domain Name System):53
    • Redis6379
    • MongoDB27017
    • Kafka9092

为什么需要默认端口号?

为了方便用户。当你在浏览器中访问 www.example.com 时,你不需要手动输入 www.example.com:80,因为浏览器知道 HTTP 协议的默认端口就是 80。如果服务器的端口不是默认端口,你就必须手动指定,比如 www.example.com:8080。

好的,我将根据您提供的这份详尽的 Web 技术笔记,以一种更专业、更系统的方式,对其进行重新组织、补充和深入解析,使其成为一份高质量的技术讲解或面试回答。


一、Servlet 剩余部分

1. Servlet 的生命周期方法

这是理解 Servlet 工作原理的核心。

  • init() 方法
    • 特点:只执行一次,用于初始化 Servlet 实例。
    • 执行时机
      • 默认(懒加载):第一次被访问时执行。这是一种“按需加载”的策略,节省了服务器启动时的资源。
      • 预加载:可以在 web.xml 中通过 <load-on-startup> 标签来设置。如果值为非负整数(0或正数),Servlet 容器将在服务器启动时立即创建并初始化该 Servlet。这适用于需要立即提供服务、启动耗时较长的 Servlet。
  • service() 方法
    • 特点:每次客户端请求该 Servlet 时,都会执行一次。
    • 原理:它是 Servlet 接口的核心方法,用于处理请求。对于 HttpServlet 来说,它会根据 HTTP 请求方法(GET、POST 等)来分发请求给相应的 doGet()doPost() 方法。
    • 重要性service() 方法是多线程的。Servlet 容器会为每个请求创建一个新线程来执行 service() 方法,确保并发访问时互不影响。
  • destroy() 方法
    • 特点:只执行一次,在 Servlet 实例正常销毁时调用。
    • 执行时机:通常在 Web 应用关闭或 Servlet 容器关闭时。用于释放资源,如关闭数据库连接、文件句柄等。

2. Servlet 的实现方式:XML vs. 注解

  • XML 配置方式:在 web.xml 文件中,通过 <servlet><servlet-mapping> 标签来配置 Servlet 的名称、类和访问路径。这是 Servlet 2.x 及以前版本的主流方式。

  • 注解方式:自 Servlet 3.0 开始引入,可以使用 @WebServlet 注解来代替 XML 配置。

    • 优点:简化了配置,代码和配置更集中,提高了开发效率。
    • 示例

    Java

    1
    2
    @WebServlet("/demo")
    public class MyServlet extends HttpServlet { ... }

3. Servlet 的体系结构

  • Servlet 接口:所有 Servlet 的顶层接口,定义了 init(), service(), destroy() 等核心方法。
  • GenericServlet 抽象类:实现了 Servlet 接口,并提供了 init(), destroy() 的空实现,以及一些通用方法。开发者可以继承它来编写协议无关的 Servlet。
  • HttpServlet 抽象类:继承自 GenericServlet,专门用于处理 HTTP 请求。它重写了 service() 方法,并根据请求方法分发给 doGet(), doPost() 等具体方法。
  • 总结:在 Web 项目中,我们几乎总是处理 HTTP 请求,因此继承 HttpServlet 是最常用、最推荐的方式

二、HTTP 协议

1. 概念与特点

  • 概念超文本传输协议,是 Web 应用层协议,基于 TCP/IP。它规定了客户端和服务器之间的通信格式。
  • 特点
    • 基于 TCP/IP:它位于 TCP/IP 协议栈的应用层,利用了 TCP 的可靠传输特性。
    • 请求/响应模型:客户端发送请求,服务器返回响应,一次请求只对应一次响应。
    • 无状态:这是 HTTP 的核心特征。服务器不保留任何关于客户端过去请求的信息。每个请求都是独立的。
    • 无状态带来的问题:服务器无法识别多个请求是否来自同一个用户,因此需要引入会话技术(如 Cookie 和 Session)来解决。

2. HTTP 协议的数据格式

HTTP 协议是文本格式,由请求格式响应格式两部分组成。

  • 请求格式
    1. 请求行请求方式 虚拟路径/资源路径[?参数] 请求协议/版本
    2. 请求头:键值对形式,提供额外信息。
      • Host:目标主机名。
      • User-Agent:浏览器类型和版本。
      • Referer:请求来源地址,常用于防盗链。
    3. 请求空行:一个空行,用于分隔请求头和请求体。
    4. 请求体只有 POST 请求才有,用于封装请求参数。

3. ServletRequest 对象

ServletRequest 是 Servlet 容器在接收到请求后,封装请求信息的对象。

  • 获取请求行信息
    • getMethod():获取请求方式(GET/POST)。
    • getContextPath():获取虚拟路径。
    • getRequestURI():获取 URI,如 /web02/demo4
    • getRequestURL():获取 URL,如 http://localhost:8080/web02/demo4
  • 获取请求头信息
    • getHeader(name):根据请求头名获取值。
    • getHeaderNames():获取所有请求头名。
  • 获取请求体信息
    • getReader() (字符流) 和 getInputStream() (字节流),用于读取 POST 请求的请求体内容。
  • 获取请求参数(通用)
    • getParameter(name):获取单个参数值。
    • getParameterValues(name):获取参数值数组,用于复选框等。
  • 中文乱码
    • GET 请求:在 Tomcat 8.x 及以后版本,默认已解决。
    • POST 请求:需要手动设置编码,request.setCharacterEncoding("UTF-8");

4. request 请求转发

  • 概念:是一种服务器内部的资源跳转方式。请求从一个 Servlet 转发到另一个 Servlet 或 JSP,浏览器地址栏不会发生改变
  • 特点
    1. 地址栏不变:用户不知道发生了跳转。
    2. 一次请求/响应:整个转发过程发生在一次 HTTP 请求和一次 HTTP 响应中。
    3. 共享数据:由于是同一次请求,request 对象中的数据在转发前后是共享的。

5. request 作为域对象

  • 概念request 是一种域对象,其作用范围是一次请求-响应的生命周期
  • 共享数据:可以使用 setAttribute(), getAttribute(), removeAttribute() 方法在这次请求的生命周期内共享数据。
  • 应用场景:常用于在 Servlet 和 JSP 之间传递数据。
  • 与会话技术的联系request 是三大域对象(request, session, servletContext)中作用范围最小的一个。

好的,我将根据您提供的这份详尽的 Web 技术笔记,以一种更专业、更系统的方式,对其进行重新组织、补充和深入解析,使其成为一份高质量的技术讲解或面试回答。


一、HTTP 协议的响应格式

1. 响应格式组成

HTTP 响应格式由四部分组成:响应行响应头响应空行响应体。这与 HTTP 请求格式相呼应。

2. 响应行

  • 格式协议/版本号 状态码 状态描述。例如:HTTP/1.1 200 OK
  • 状态码:一个三位数的数字,服务器用于告诉浏览器本次响应的状态。
    • 2xx 成功
      • 200 OK:请求成功,一切正常。
    • 3xx 重定向
      • 302 Found:重定向。服务器告诉浏览器,资源临时移动到另一个位置,请重新发起请求。
      • 304 Not Modified:访问缓存。服务器告诉浏览器,请求的资源没有更新,可以使用浏览器本地的缓存副本。
    • 4xx 客户端错误
      • 404 Not Found:找不到资源。请求的路径没有对应的资源。
      • 405 Method Not Allowed:请求方法不被允许。例如,客户端用 POST 请求访问了只支持 GET 方法的 Servlet。
    • 5xx 服务器端错误
      • 500 Internal Server Error:服务器内部错误。通常是服务器端的代码出现了异常。
  • 面试题:列举 5 个常见的状态码:200、302、304、404、405、500。

3. 响应头

  • 作用:以键值对形式提供关于响应的额外信息。
  • 常见响应头
    • Content-Type:告诉浏览器响应体的数据类型和字符编码,例如 text/html;charset=utf-8
    • Content-disposition
      • inline (默认):浏览器在当前页面内直接打开。
      • attachment;filename=xxx:浏览器将数据作为附件下载。
    • Location:与 3xx 状态码配合使用,用于告诉浏览器重定向到哪个 URL。

二、ServletResponse (response)

  • 简介ServletResponse 对象由 Tomcat 创建,封装了服务器发送给客户端的响应消息。
  • 相关方法
    • 设置响应行setStatus(int sc)
    • 设置响应头setHeader(String name, String value)
    • 设置响应体
      • getWriter():获取字符输出流,用于发送文本数据。
      • getOutputStream():获取字节输出流,用于发送字节数据(如图片、视频)。

三、转发(Forward)和重定向(Redirect)的区别

这是一个非常重要的面试题,需要从多个角度进行对比。

特性 转发(Forward) 重定向(Redirect)
发生方 服务器内部 浏览器
地址栏 不变 会改变
请求次数 一次请求,一次响应 两次请求,两次响应
共享数据 request 对象共享数据 request 对象不共享数据
跳转范围 只能在当前 Web 项目内部 可以跳出项目,访问外部资源
调用方 RequestDispatcher (request) ServletResponse (response)
底层实现 request.getRequestDispatcher().forward() response.sendRedirect()
  • 重定向的实现原理
    1. 服务器收到请求,执行 response.sendRedirect(),并发送 302 状态码和 Location 响应头(值为新的 URL)。
    2. 浏览器收到响应,解析到 302Location 头后,会自动向新的 URL 发起第二次请求。

四、ServletContext

1. 简介与特点

  • 作用:代表整个 Web 应用。可以用来和服务器容器进行通信。
  • 特点
    • 单例:一个 Web 应用只有一个 ServletContext 对象。
    • 作用域:是最大的域对象,其作用范围是整个 Web 应用,从应用启动到关闭。

2. 获取 ServletContext 对象

  • 通过 requestrequest.getServletContext()
  • 通过 HttpServletgetServletContext()

3. ServletContext 的方法

  • 获取文件 MIME 类型getMimeType(String file),返回文件的类型,如 image/jpeg
  • 作为域对象:提供了 setAttribute(), getAttribute(), removeAttribute() 方法,用于在整个 Web 应用范围内共享数据。
    • 示例:您提供的代码片段完美地展示了如何使用 ServletContext 来统计服务器的访问次数。这是 ServletContext 作为域对象最经典的用法。
  • 获取文件真实路径getRealPath(String path),将相对路径转换为服务器上的绝对路径。

五、会话(Session)

1. 概念与作用

  • 概念:在 HTTP 协议的无状态特性基础上,通过会话技术将多次请求和响应联系起来,形成一个完整的会话。
  • 作用:在一次会话范围内的多次请求之间共享数据,从而识别用户。
  • 实现方式
    • 客户端会话(Cookie):数据存储在客户端浏览器。
    • 服务器端会话(Session):数据存储在服务器端。

2. 客户端会话:Cookie

  • 快速开始
    1. 创建new Cookie(name, value)
    2. 发送response.addCookie(cookie),将 Cookie 发送给浏览器。
    3. 获取request.getCookies(),从请求中获取所有 Cookie。
  • 特点:数据存储在客户端,有大小限制,安全性低。

这是一个非常好的问题,它触及了 Web 开发中一个容易混淆但至关重要的概念。enctypeContent-Type 确实有相似之处,但它们的作用域和角色是完全不同的。

让我来详细解释一下它们的区别:


一、enctype

  • 作用域:HTML <form> 标签的属性。
  • 角色:客户端表单的编码方式
  • 概念enctype (Encoding Type) 告诉浏览器如何将 <form> 表单中的数据编码成 HTTP 请求体中的数据。它只作用于表单提交这个行为。
  • 常见值
    • application/x-www-form-urlencoded:默认值,用于文本数据。
    • multipart/form-data:用于文件上传。
    • text/plain:用于纯文本提交。

简单来说,enctype 是“表单数据在浏览器端如何打包”的规则。

二、Content-Type

  • 作用域:HTTP 请求头或响应头。
  • 角色通信数据的媒体类型
  • 概念Content-Type 告诉接收方(服务器或浏览器)请求体或响应体中的数据是什么格式。它是一个通用的 HTTP 头部,不仅限于表单提交。
  • 常见值
    • text/html:HTML 文档。
    • application/json:JSON 格式数据。
    • image/jpeg:JPEG 图片。
    • application/xml:XML 文档。
    • text/plain:纯文本。

简单来说,Content-Type 是“我发送给你的数据是什么类型”的声明。

三、核心区别与联系

特性 enctype Content-Type
位置 HTML <form> 标签的属性 HTTP 请求头或响应头
谁定义 开发者在 HTML 中设置 发送方(浏览器或服务器)在 HTTP 头部中设置
作用 告诉浏览器如何打包表单数据 告诉接收方如何解析数据
关系 enctype 的值决定了 HTTP 请求头中 Content-Type 的值。 Content-Type 是 HTTP 协议的一部分,enctype 是 HTML 的一部分。

举例说明

假设你有一个包含用户名和头像上传的表单:

HTML

1
2
3
4
5
<form action="/profile" method="post" enctype="multipart/form-data">
<input type="text" name="username">
<input type="file" name="avatar">
<button type="submit">提交</button>
</form>

当用户点击提交时,浏览器会做两件事:

  1. 根据 enctype=”multipart/form-data” 规则,浏览器会生成一个**多部分(multipart)**的请求体,包含用户名和头像文件的二进制数据。

  2. 在发送 HTTP 请求时,浏览器会在请求头中自动添加一个 Content-Type 字段,其值为与 enctype 匹配的 multipart/form-data

    HTTP

    1
    2
    3
    4
    POST /profile HTTP/1.1
    Host: example.com
    Content-Type: multipart/form-data; boundary=----WebKitFormBoundary...
    ...

总结

  • enctype 是一个前端概念,它是 HTML 表单的属性,用于指导浏览器如何构建请求体。
  • Content-Type 是一个 HTTP 协议概念,它是 HTTP 头部的一个字段,用于告知数据格式。
  • 两者之间存在联动关系:你在 HTML 中设置的 enctype 值,会最终影响浏览器在 HTTP 请求中设置的 Content-Type 头的值。

好的,我来为你分别列出 enctypeContent-Type 各自常见的属性值,并简要说明其用途。


一、enctype 的常见值

enctype 是 HTML <form> 标签的属性,用于定义表单数据提交的编码方式。

  1. application/x-www-form-urlencoded
    • 用途:这是 enctype默认值。它将表单数据编码为键值对字符串,其中所有特殊字符都会被 URL 编码(例如空格被 %20 替代)。
    • 适用场景:只包含文本数据的普通表单提交。
  2. multipart/form-data
    • 用途:将表单数据分割成多个部分,每个部分都有独立的 Content-TypeContent-Disposition 头部。这种格式能够同时处理文本和二进制数据
    • 适用场景文件上传
  3. text/plain
    • 用途:将表单数据以纯文本格式发送,不进行任何编码。数据以键值对的形式,用换行符分隔。
    • 适用场景:不常用,主要用于调试目的。

二、Content-Type 的常见值

Content-Type 是 HTTP 头部字段,用于指定消息体的媒体类型(MIME 类型)。

1. 文本类型 (text/)

  • text/plain:纯文本。
  • text/html:HTML 文档。这是浏览器渲染网页的默认类型。
  • text/css:CSS 样式表。
  • text/javascript:JavaScript 代码。

2. 应用类型 (application/)

  • application/json:JSON 格式的数据。目前最常用的前后端数据交互格式。
  • application/xml:XML 格式的数据。
  • application/pdf:PDF 文档。
  • application/octet-stream:通用的二进制流数据。通常用于强制浏览器下载未知类型的文件,因为它告诉浏览器“这是一个原始的字节流,请不要尝试解析它”。
  • application/x-www-form-urlencoded:与 enctype 的默认值对应,表明请求体是 URL 编码的键值对。
  • application/javascript:与 text/javascript 类似,更推荐使用。

3. 图片类型 (image/)

  • image/jpeg:JPEG/JPG 格式的图片。
  • image/png:PNG 格式的图片。
  • image/gif:GIF 格式的图片。
  • image/svg+xml:SVG 矢量图。

4. 音视频类型

  • audio/mpeg:MP3 音频文件。
  • video/mp4:MP4 视频文件。

5. 多部分类型 (multipart/)

  • multipart/form-data:用于文件上传,与 enctype 的值对应。
  • multipart/byteranges:用于分块下载,支持断点续传。

好的,这是一个非常经典的面试题,也是理解 Web 基础知识的关键。我会详细、清晰地解释 URL 和 URI,并用通俗易懂的方式区分它们。


一、核心概念

  • URI (Uniform Resource Identifier) - 统一资源标识符
    • 概念:URI 是一个用于标识互联网上任何资源的字符串。它不仅仅能标识网页,还可以标识文件、服务、电子邮箱等。
    • 作用标识。URI 就像一个资源的“身份证号”,它能唯一地识别一个资源,但不一定告诉我们如何访问它。
  • URL (Uniform Resource Locator) - 统一资源定位符
    • 概念:URL 是一个用于定位互联网上资源的字符串。它是 URI 的一个子集
    • 作用定位。URL 就像一个资源的“详细地址”,它不仅标识了资源,还提供了如何访问该资源的完整信息,包括协议、主机名、端口号和路径等。

二、URL 和 URI 的关系

用一个比喻来理解:

  • URI 就像一个人的名字张三这个名字可以标识这个人,但你不知道他在哪里、怎么找到他。
  • URL 就像一个人的家庭住址北京市海淀区中关村大街1号。这个地址不仅标识了这个人,还告诉了你如何定位他。

因此,所有的 URL 都是 URI,但并非所有的 URI 都是 URL。

三、URL 和 URI 的具体结构

URI 的结构:

一个 URI 通常由两部分组成:

scheme:[//authority][path][?query][#fragment]

  • scheme:协议,如 http, ftp, mailto
  • path:资源路径。

URL 的结构:

URL 包含了 URI 的所有组成部分,并加入了定位信息。

scheme://host:port/path?query#fragment

  • scheme:协议。如 http, https, ftp
  • host:主机名或 IP 地址。
  • port:端口号(可选,如果使用默认端口则可以省略)。
  • path:资源路径。
  • query:查询参数,以 ? 开始。
  • fragment:片段标识符,以 # 开始。

四、举例说明

字符串 类别 解释
https://www.google.com/search?q=url+uri URLURI 既是 URI(标识),也是 URL(定位),提供了完整的访问信息。
urn:isbn:0451450523 URI不是 URL urn:isbn 是一个 URN (Uniform Resource Name),它是 URI 的另一种类型,用于标识一本书。它标识了资源,但没有提供如何访问它的位置信息。
/images/logo.png URI不是 URL 这是一个相对路径,它标识了资源,但没有完整的定位信息(如协议、主机名),需要结合当前页面的 URL 才能确定完整位置。
mailto:test@example.com URI不是 URL 标识了一个邮箱资源,但无法通过它来定位到具体的文件或服务器。

五、面试回答总结

  1. 开门见山:URL 是 URI 的子集。

  2. 核心区别

    • URI 是标识符,它只负责标识一个资源,不提供如何访问它的信息。
    • URL 是定位符,它在标识资源的同时,还提供了定位该资源的完整信息,如协议、主机名等。
  3. 举例

    • URL:https://www.example.com/index.html,包含了协议、主机、路径,可以明确地定位一个资源。
    • URI:urn:isbn:0451450523,它标识了一本书,但你无法通过它来访问这本书的电子版。
  4. 关系图

    1
    2
    3
    4
    5
    6
    7
    8
    9
    +-------------+
    | URI |
    | |
    | +----------+---------+
    | | URL | URN |
    | | (定位) | (命名) |
    | +----------+---------+
    | |
    +-------------+

    用这个图能非常清晰地展现它们之间的关系。