文章

八股文-web

八股文-web

servlet

【问题】

什么是 Servlet? 【参考答案】 Servlet(Server Applet)是运行在 Web 服务器或应用服务器上的 Java 程序,主要用于处理客户端发送的 HTTP 请求并生成动态的 Web 内容。它是 Java EE 规范的核心部分,充当了 Web 浏览器或其他 HTTP 客户端与 HTTP 服务器上的数据库或应用程序之间的中间层。 【延伸考点讲解】

  1. 核心功能:处理表单数据、管理会话状态(Session)、访问后端数据库、分发请求(MVC 模式中的 Controller)。
  2. 无状态性:HTTP 协议本身是无状态的,Servlet 通过 Cookie 和 Session 机制来维持客户端的状态信息。
  3. 并发处理:Servlet 默认以多线程方式运行,Web 容器会为每个请求分配一个线程来执行 service() 方法,因此需要注意多线程下的数据一致性问题。

【问题】 说一下 Servlet 的体系结构。 【参考答案】 Servlet 的体系结构主要围绕 javax.servlet.Servlet 接口展开。所有的 Servlet 都必须直接或间接地实现该接口。

  • GenericServlet:是一个协议无关的通用 Servlet,实现了 Servlet 和 ServletConfig 接口。
  • HttpServlet:继承自 GenericServlet,专门用于处理 HTTP 协议的请求,提供了 doGet、doPost 等方法。 【延伸考点讲解】
    1. 核心接口与类:
    • Servlet 接口:定义了生命周期方法(init, service, destroy)。
    • ServletConfig 接口:用于获取 Servlet 的初始化参数和 ServletContext。
    • ServletContext 接口:代表整个 Web 应用的上下文,用于在不同 Servlet 间共享数据。
      1. 多线程模型:Servlet 容器通常以单实例多线程的方式运行 Servlet,即一个 Servlet 实例处理多个并发请求,开发者需注意线程安全。

【问题】

  1. Applet 和 Servlet 有什么区别? 【参考答案】 Applet 是运行在客户端(通常是 Web 浏览器)中的 Java 小程序,主要用于提供交互式的用户界面。而 Servlet 是运行在服务器端的 Java 程序,负责处理客户端请求并生成动态响应。
  • 运行位置:Applet 在客户端运行,Servlet 在服务端运行。
  • 用户界面:Applet 具有图形用户界面(GUI),使用 AWT 或 Swing 等库;Servlet 没有用户界面,它接收 HTTP 请求并返回 HTML、JSON 等数据。
  • 生命周期管理:Applet 由浏览器(或 Applet 容器)管理;Servlet 由 Web 容器(如 Tomcat)管理。 【延伸考点讲解】
    1. 安全性差异:Applet 运行在客户端的“沙箱”中,受严格的安全限制,不能随意访问客户端本地文件系统。Servlet 运行在服务端,具有访问服务器资源(如数据库、本地文件)的完整权限。
    2. 现代 Web 现状:随着 HTML5、JavaScript 和 CSS3 的普及,Applet 技术已基本被淘汰,现代浏览器已不再支持 Java 插件。而 Servlet 依然是 Java Web 开发的基础。
    3. 通信模型:Servlet 遵循典型的请求-响应模型(Request-Response),而 Applet 主要是为了增强页面的本地交互能力。

【问题】

  1. GenericServlet 和 HttpServlet 有什么区别? 【参考答案】 GenericServlet 和 HttpServlet 都是 Servlet 接口的实现类,但它们的设计目标不同:
  • GenericServlet:是一个通用的、与协议无关的 Servlet 基类。它实现了 Servlet 和 ServletConfig 接口,要求开发者必须覆盖 service() 方法。它主要用于处理非 HTTP 协议(如 FTP)的请求。
  • HttpServlet:继承自 GenericServlet,专门为 HTTP 协议设计。它通过覆盖 service() 方法实现了根据请求方法(GET, POST, PUT, DELETE 等)分发到对应的 doXXX() 方法上,通常我们只需要覆盖 doGet() 或 doPost()。 【延伸考点讲解】
    1. 模板方法设计模式:HttpServlet 的实现采用了模板方法模式。它的 service(ServletRequest, ServletResponse) 方法会将参数转型为 HTTP 相关的类型,并调用内部的 service(HttpServletRequest, HttpServletResponse),后者再根据 HTTP Method 调用具体的 doGet、doPost 等。
    2. 抽象程度:GenericServlet 使编写 Servlet 变得更容易,因为它提供了 init 和 destroy 的默认实现,并把 ServletConfig 的方法暴露了出来。
    3. 开发实践:在几乎所有的 Web 开发场景中,我们都会选择继承 HttpServlet,因为它是专门为处理 Web 请求优化的。

【问题】

  1. 解释下 Servlet 的生命周期。 【参考答案】 Servlet 的生命周期由 Web 容器(如 Tomcat)管理,主要包含以下三个阶段:

  2. 初始化阶段:容器加载 Servlet 类并实例化后,调用 init() 方法。该方法在整个生命周期中只会被调用一次,常用于初始化资源(如数据库连接、配置文件读取)。
  3. 运行阶段:每次客户端请求到达时,容器会调用 service() 方法。service() 根据请求的 HTTP 方法(GET、POST 等)将其分发给对应的 doGet()、doPost() 等处理逻辑。
  4. 销毁阶段:当 Web 应用被卸载或服务器关闭时,容器会调用 destroy() 方法。该方法也只会被调用一次,用于释放 Servlet 占用的资源。 【延伸考点讲解】
  5. 单例模式:默认情况下,Servlet 在容器中是单例的。这意味着对于同一个 Servlet 类,容器只会创建一个实例来处理所有的并发请求。
  6. 加载时机:默认是第一次访问时加载。可以通过在 web.xml 中配置 <load-on-startup> 标签来指定 Servlet 在服务器启动时就完成初始化(值越小优先级越高)。
  7. 线程安全:由于 Servlet 是单例多线程运行的,因此在 doGet 或 doPost 中使用成员变量(实例变量)是不安全的,应尽量使用局部变量。

【问题】

  1. doGet() 方法和 doPost() 方法有什么区别? 【参考答案】 doGet() 和 doPost() 是 HttpServlet 中最常用的两个方法,其主要区别如下:

  2. 语义不同:GET 用于从服务器获取资源,具有幂等性(多次执行结果相同且无副作用);POST 用于向服务器提交数据,通常会导致服务器状态的变化。
  3. 参数传递方式:GET 将参数附加在 URL 后面(Query String),以 ?name=value 形式展现;POST 将数据放在 HTTP 请求的消息体(Entity Body)中。
  4. 数据大小限制:GET 受限于 URL 的长度限制(不同浏览器和服务器限制不同,通常为 2KB-8KB);POST 理论上没有大小限制,适合传输大量数据(如文件上传)。
  5. 安全性:GET 参数暴露在地址栏和浏览器历史记录中,不适合传输敏感信息;POST 数据在消息体中,相对更安全(但仍需 HTTPS 加密)。 【延伸考点讲解】
  6. 幂等性(Idempotency):GET、HEAD、PUT、DELETE 都是幂等的,而 POST 不是。在设计 RESTful API 时应严格遵守这一原则。
  7. 缓存机制:GET 请求可以被浏览器主动缓存、收藏为书签;而 POST 请求默认不会被缓存。
  8. 编码方式:GET 只能进行 ASCII 字符编码;POST 支持多种编码类型(如 application/x-www-form-urlencoded、multipart/form-data)。

【问题】

  1. 什么是 Web 应用程序? 【参考答案】 Web 应用程序(Web Application)是一种可以通过 Web 浏览器访问的应用程序。它由一组静态资源(如 HTML、CSS、JS、图片)和动态资源(如 Servlet、JSP、Java 类)组成,通常打包成 WAR 文件部署在 Web 服务器(如 Tomcat)上。
  • 面向表现:产生包含标记语言和动态内容的交互式页面。
  • 面向服务:实现 Web 服务端点(Endpoints),如 RESTful API 或 SOAP 服务。 【延伸考点讲解】
    1. WAR 包结构:标准的 Java Web 应用结构包括 WEB-INF 目录,其中包含 web.xml(部署描述符)、classes(编译后的类文件)和 lib(依赖库)。
    2. 部署位置:在 Servlet 规范中,Web 应用通常安装在服务器 URL 名称空间的特定子集下(Context Path)。
    3. 无状态性与会话:Web 应用运行在 HTTP 协议之上,因此通常需要 Session、Cookie 或 Token 机制来管理用户状态。

【问题】

  1. 什么是服务端包含(Server Side Include)? 【参考答案】 服务端包含(SSI)是一种简单的、基于 HTML 注释的解释型服务端脚本语言。它最常用的场景是将一个或多个文件的内容(如公共的头部、尾部或导航栏)动态地包含到 Web 页面中。
  • 工作方式:当浏览器请求一个包含 SSI 指令的页面时,服务器会解析这些指令,并将结果(通常是另一个文件的内容)插入到页面中,最后将生成的完整 HTML 发送给客户端。
  • 语法示例:<!--#include file="header.html" --> 【延伸考点讲解】
    1. 优点与局限:SSI 非常轻量,适合静态站点的简单复用。但其逻辑处理能力有限(仅支持简单的条件判断和变量),且在高并发下可能存在一定的性能开销。
    2. 现代替代方案:在现代开发中,SSI 已逐渐被更强大的模板引擎(如 JSP、Thymeleaf、FreeMarker)或前端组件化方案(如 React/Vue 组件、Edge Side Includes (ESI))所取代。
    3. 安全性:如果 SSI 指令的参数来自用户输入且未经过滤,可能会引发 SSI 注入攻击,允许攻击者读取服务器敏感文件或执行系统命令。

【问题】

  1. 什么是 Servlet 链(Servlet Chaining)? 【参考答案】 Servlet 链(Servlet Chaining)是将一个 Servlet 的输出作为另一个 Servlet 的输入,从而将多个 Servlet 串联起来处理同一个请求的技术。
    • 工作流:第一个 Servlet 处理部分请求并生成中间结果,然后将控制权(和输出数据)传递给链中的下一个 Servlet,直到最后一个 Servlet 生成最终响应并发送给客户端。
    • 实现方式:通常通过 Web 容器的配置或使用 RequestDispatcher 的 forward() 方法来实现。 【延伸考点讲解】
  2. 过滤器(Filter)的演进:Servlet 链是早期 Java Web 开发中的概念。在现代 Servlet 规范中,这种拦截和串联处理的需求主要通过 Filter 链(FilterChain) 来实现,它更加灵活且解耦。
  3. 适用场景:数据过滤、日志记录、权限检查、内容转换(如将输出的 HTML 转换为 PDF 或压缩)。
  4. 优缺点:优点是提高了代码的可复用性和模块化;缺点是如果链条过长,会增加调试难度并可能影响处理性能。

【问题】

  1. 如何知道是哪一个客户端的机器正在请求你的 Servlet? 【参考答案】 可以通过 ServletRequest 对象提供的方法来获取客户端机器的相关信息。主要方法如下:
    • getRemoteAddr():获取客户端主机的 IP 地址。
    • getRemoteHost():获取客户端主机的主机名(如果配置了 DNS 解析且能解析成功,否则返回 IP 地址)。
    • getRemotePort():获取客户端发送请求时所使用的源端口号。 【延伸考点讲解】
  2. 代理环境下的真实 IP:在实际应用中,由于存在 Nginx 反向代理或负载均衡,getRemoteAddr() 往往只能获取到代理服务器的 IP。此时通常需要从请求头中获取真实 IP,如 X-Forwarded-For 或 X-Real-IP。
  3. 性能考量:getRemoteHost() 方法在尝试解析主机名时可能会产生较大的网络开销和延迟(DNS 反向查询),在高并发环境下建议直接使用 IP。
  4. 安全应用:获取客户端信息常用于权限校验(IP 白名单)、防盗链、日志记录、地理位置识别以及多因素认证等场景。

【问题】

  1. HTTP 响应的结构是怎么样的? 【参考答案】 HTTP 响应由四个主要部分组成:
  2. 状态行 (Status Line):包含 HTTP 协议版本、状态码(如 200, 404)以及对应的状态消息(Reason Phrase)。
  3. 响应头部 (Response Headers):包含服务器的描述信息、数据的元数据(如 Content-Type, Content-Length, Set-Cookie, Cache-Control 等)。
  4. 空行 (Empty Line):响应头部后的一个必须的空行,用于分隔头部和主体。
  5. 响应主体 (Response Body):服务器返回给客户端的具体内容,如 HTML 页面、图片数据、JSON 字符串等。 【延伸考点讲解】
  6. 常见状态码分类:1xx(信息性)、2xx(成功)、3xx(重定向)、4xx(客户端错误)、5xx(服务器错误)。
  7. Servlet 处理:在 Servlet 中,通过 HttpServletResponse 对象来操作响应结构,如 setStatus() 设置状态码、setHeader() 设置头部、getWriter() 或 getOutputStream() 写入主体。
  8. 性能优化:通过 Transfer-Encoding: chunked 允许分块传输,或通过 Content-Encoding: gzip 进行数据压缩,以提高传输效率。

【问题】

  1. 什么是 cookie?session 和 cookie 有什么区别? 【参考答案】 Cookie 和 Session 都是 Web 开发中常用的会话跟踪技术,用于在无状态的 HTTP 协议下维持用户的状态。
    • Cookie:是由服务器发送并存储在客户端浏览器上的小型文本文件。浏览器每次向同一服务器发送请求时,都会带上该 Cookie。
    • Session:是存储在服务器端的会话信息。服务器为每个用户创建一个唯一的 Session ID,并通过 Cookie(通常是 JSESSIONID)发送给客户端,客户端后续请求带上此 ID 以匹配服务端的 Session。

主要区别:

  1. 存储位置:Cookie 存储在客户端(浏览器);Session 存储在服务端。
  2. 安全性:Session 安全性更高,因为敏感信息不直接暴露给客户端;Cookie 容易被截获或篡改(需配合加密或 HttpOnly)。
  3. 数据类型:Cookie 只能存储字符串;Session 可以存储任何 Java 对象。
  4. 有效期:Cookie 可以设置长期有效;Session 通常随浏览器关闭或超时而失效。 【延伸考点讲解】
  5. 分布式 Session 问题:在多台服务器集群环境下,Session 默认无法共享。解决方案包括:Session 复制、Session 粘滞(Sticky Sessions)或使用外部存储(如 Redis 存储 Session)。
  6. Cookie 的安全属性:
    • HttpOnly:防止 JavaScript 读取 Cookie,抵御 XSS 攻击。
    • Secure:确保 Cookie 仅通过 HTTPS 协议传输。
    • SameSite:防止 CSRF 攻击。
  7. 禁用 Cookie 后的方案:如果客户端禁用了 Cookie,Session 仍然可以通过 URL 重写(在 URL 后附加 ;jsessionid=...)来维持。

【问题】 浏览器和 Servlet 通信使用的是什么协议? 【参考答案】 浏览器和 Servlet 之间使用的是 HTTP (HyperText Transfer Protocol) 协议进行通信。

  • Servlet 运行在 Web 容器中,专门用于接收和响应来自客户端(通常是浏览器)的 HTTP 请求。
  • 整个过程遵循“请求-响应”模型。 【延伸考点讲解】
    1. 协议层级:HTTP 属于应用层协议,通常建立在 TCP/IP 协议栈之上。
    2. 版本演进:目前主流使用的是 HTTP/1.1,而 HTTP/2 和 HTTP/3 正在普及。HTTP/2 引入了多路复用,HTTP/3 则基于 QUIC 协议。
    3. 无状态性:HTTP 协议本身是无状态的,这意味着服务器不会记住之前的请求。为了在多个请求之间保持状态,通常需要结合 Cookie 或 Session 技术。

【问题】 什么是 HTTP 隧道? 【参考答案】 HTTP 隧道(HTTP Tunneling)是一种利用 HTTP 或 HTTPS 协议来封装其他网络协议(如 SSH、Telnet、TCP)进行通信的技术。

  • 核心逻辑:HTTP 协议作为“载体”,将非 HTTP 流量伪装成正常的 HTTP 请求。
  • 作用:主要用于突破防火墙限制(防火墙通常允许 80 或 443 端口通过)或在受限网络环境中建立安全的通信链路。 【延伸考点讲解】
    1. 工作方式:通常使用 HTTP 的 CONNECT 方法。客户端发送 CONNECT 请求到代理服务器,代理服务器建立到目标服务器的 TCP 连接后,开始透传后续的原始数据流。
    2. 应用场景:跨越严格限制的内网、访问被封锁的服务、以及在不支持特定协议的网络中通过 HTTP 管道传输数据。
    3. 安全风险:虽然 HTTP 隧道可以绕过防火墙,但也可能被恶意软件利用来建立隐藏的命令与控制(C&C)通道。

【问题】

  1. sendRedirect() 和 forward() 方法有什么区别? 【参考答案】 这两者都是用于 Servlet 之间的跳转,但有本质的区别:
    • 请求转发 (Forward):服务器内部行为。服务器直接访问目标地址,读取内容并发送给浏览器。浏览器地址栏 不变。可以共享 request 域中的数据。
    • 重定向 (Redirect):客户端行为。服务器返回 302 状态码告知浏览器去请求新地址。浏览器地址栏 改变。无法共享 request 域数据。

| 特性 | 请求转发 (Forward) | 重定向 (Redirect) | | :— | :— | :— | | 跳转位置 | 服务器内部跳转 | 客户端重新请求 | | 请求次数 | 1 次 | 2 次 | | 地址栏 | 不变 | 变化 | | 数据共享 | 共享 request 域数据 | 不共享 | | 效率 | 较高 | 较低 | 【延伸考点讲解】

  1. 实现方式:Forward 使用 request.getRequestDispatcher().forward();Redirect 使用 response.sendRedirect()。
  2. 应用场景:Forward 常用于登录成功后的内部页面跳转;Redirect 常用于注销后的页面跳转或跳转到外部网站。
  3. 路径问题:Forward 的路径只能是本 Web 应用内的资源;Redirect 可以是任何合法的 URL(包括外部链接)。

【问题】

  1. 什么是 URL 编码和 URL 解码? 【参考答案】 URL 编码(URL Encoding,也称为百分号编码)是用于在 URL 中包含非 ASCII 字符或特殊字符的一种机制。
    • URL 编码:将 URL 中的特殊字符(如空格、中文、特殊符号)转换为 % 后跟两位十六进制数的形式。例如,空格会被转换为 %20 或 +。
    • URL 解码:将经过编码的字符串还原为原始字符的过程。 【延伸考点讲解】
  2. 必要性:URL 协议规定只能使用特定的 ASCII 字符集。非 ASCII 字符(如中文)和具有特殊含义的保留字符(如 &, =, ?, /)如果在参数中直接使用,可能会导致 URL 解析错误。
  3. Java 实现:在 Java 中,可以使用 java.net.URLEncoder.encode(str, "UTF-8") 进行编码,使用 java.net.URLDecoder.decode(str, "UTF-8") 进行解码。
  4. 常见字符转换:空格通常转为 %20,中文字符根据字符集(通常是 UTF-8)转为多个 %xx 组合。

【问题】 如何让页面做到自动刷新? 【参考答案】 在 Servlet 中,可以通过设置 HTTP 响应头中的 Refresh 属性来实现页面的自动刷新或定时跳转。

  • 实现方式:使用 response.setHeader("Refresh", "秒数; URL=跳转地址")。
  • 示例:response.setHeader("Refresh", "5; URL=http://example.com") 表示 5 秒后自动跳转到指定页面。如果省略 URL 部分,则表示每隔指定秒数刷新当前页面。 【延伸考点讲解】
    1. HTML 实现方式:除了在服务端通过 Servlet 设置,也可以在 HTML 的 <head> 标签中使用 <meta http-equiv="refresh" content="5"> 达到同样的效果。
    2. JavaScript 实现方式:使用 setTimeout("location.reload()", 5000) 或 location.href='...' 也可以实现更灵活的定时刷新或跳转逻辑。
    3. 应用场景:常用于支付完成后的等待页面、操作成功后的自动返回、或者简单的实时数据监控页面。

【问题】 如何解决 Servlet 的线程安全问题? 【参考答案】 Servlet 默认以单实例多线程的方式运行,因此如果多个线程并发访问同一个 Servlet 实例中的共享资源(如成员变量),就可能产生线程安全问题。解决办法如下:

  1. 避免使用成员变量:尽量不要在 Servlet 类中定义成员变量。如果需要存储数据,应将其定义在 doGet() 或 doPost() 方法内部作为局部变量,因为局部变量是线程私有的。
  2. 使用同步机制:如果必须访问共享资源,可以使用 synchronized 关键字对相关代码块进行加锁。但由于这会显著降低并发性能,应谨慎使用。
  3. 使用线程安全的对象:例如,如果需要计数,可以使用 AtomicInteger 代替普通的 int 成员变量。
  4. 只读属性定义为 final:如果 Servlet 类中有一些初始化后就不再更改的属性,应将其定义为 final 类型,这样在多线程环境下是安全的。 【延伸考点讲解】
  5. Servlet 容器的工作模型:容器(如 Tomcat)通常会为每个进来的 HTTP 请求分配一个独立的线程。如果请求访问的是同一个 Servlet,这些线程会并发执行该 Servlet 的 service() 方法。
  6. SingleThreadModel 接口:这是早期的 Servlet 规范中提供的一个接口,用于强制 Servlet 以单线程模式运行。但由于它无法真正解决线程安全问题且严重影响性能,在 Servlet 2.4 以后已被废弃。
  7. 无状态设计:在开发 Servlet 时,应尽可能将其设计为“无状态”的,即不保存任何与特定请求相关的中间状态信息在实例变量中。

JSP

【问题】

  1. 什么是 JSP 页面? 【参考答案】 JSP(JavaServer Pages)是一种支持在 HTML、XML 等静态文本中嵌入 Java 代码和 JSP 标签的技术,用于开发动态网页。
    • 本质:JSP 本质上是一个简化的 Servlet。它在第一次被访问时会被 Web 容器(如 Tomcat)翻译成一个 Java 类(Servlet 源文件)并编译为 .class 文件执行。
    • 构成:由静态内容(HTML/XML/CSS/JS)和动态内容(JSP 元素:如指令、脚本片段、表达式、标准标签库等)组成。 【延伸考点讲解】
  2. MVC 角色:在经典的 MVC(Model-View-Controller)设计模式中,JSP 通常充当 View(视图) 角色,负责数据的展示。
  3. 生命周期:包括编译阶段(翻译成 Servlet)、初始化阶段、执行阶段和销毁阶段。由于存在编译过程,JSP 第一次访问时通常比后续访问慢。
  4. 与 HTML 的区别:HTML 是静态的,所有用户看到的页面内容都相同;JSP 是动态的,可以根据用户请求和服务器端数据实时生成不同的 HTML 内容。

【问题】

  1. JSP 请求是如何被处理的? 【参考答案】 当浏览器发起对 .jsp 文件的请求时,Web 容器(如 Tomcat)会按照以下步骤处理:
  2. 查找 Servlet:容器检查该 JSP 是否已被翻译并编译成 Servlet 类。
  3. 翻译与编译:如果是第一次请求或 JSP 文件已更改,容器将 JSP 源码翻译成 Java 源代码(Servlet 类),并编译为 .class 字节码文件。
  4. 加载与实例化:容器加载编译后的类,创建实例,并调用其初始化方法。
  5. 执行服务:容器调用 Servlet 的 _jspService() 方法处理请求并生成响应。
  6. 返回响应:将生成的 HTML 内容发送回客户端浏览器。 【延伸考点讲解】
  7. 编译开销:由于翻译和编译过程较为耗时,JSP 页面在被修改后的首次访问会有明显的延迟。生产环境中通常会预编译 JSP 以提高性能。
  8. 生成的类名:在 Tomcat 中,JSP 翻译后的 Servlet 类通常位于 work 目录下,类名通常以 _ 开头(如 _index_jsp)。
  9. JSP 引擎:专门负责处理 JSP 的组件,如 Tomcat 中的 Jasper。

【问题】

  1. JSP 有什么优点? 【参考答案】 JSP 相比于传统的 Servlet 具有以下显著优点:
  2. 开发效率高:JSP 允许在 HTML 中直接嵌入 Java 代码,更符合网页设计的习惯,尤其在处理复杂的展现层逻辑时。
  3. 易于维护:表现层代码(HTML)与业务逻辑(Java)可以相对分离(通过标签库和 EL 表达式),方便美工和开发人员分工。
  4. 预编译机制:虽然第一次访问较慢,但之后容器会直接执行编译好的 .class 文件,运行速度很快。
  5. 强大的标签库支持:支持标准标签库(JSTL)和自定义标签,可以极大地简化页面代码,提高代码复用性。
  6. 跨平台与组件化:继承了 Java 的跨平台特性,并能与 JavaBean 等组件无缝结合。 【延伸考点讲解】
  7. JSP vs Servlet:Servlet 适合处理业务逻辑和控制流程,但输出 HTML 非常痛苦;JSP 适合数据展示,但如果嵌入过多的 Java 代码(Scriptlet)会使页面难以维护。
  8. 现代趋势:随着前后端分离架构(如 Vue/React + RESTful API)的流行,JSP 的使用场景正在减少。但在一些传统项目或内部管理系统中,JSP 依然具有很强的生命力。
  9. 安全考量:在使用 JSP 展示数据时,应注意防范 XSS 攻击,建议使用 JSTL 标签或 EL 表达式进行自动转义。

【问题】

  1. include 指令与 include 动作的区别? 【参考答案】 两者都用于在 JSP 页面中包含其他文件,但其执行时机和原理完全不同:
    • include 指令 (<%@ include file="..." %>):
      • 静态包含:在编译阶段执行。
      • 原理:JSP 引擎在将 JSP 翻译成 Servlet 时,直接将目标文件的内容插入到当前位置,合并后再进行编译。
      • 特点:速度快,但目标文件发生变化时,主页面可能需要重新编译才能看到变化。
  • include 动作 (<jsp:include page="..." />):
    • 动态包含:在请求处理阶段执行。
    • 原理:主页面在执行时调用目标页面的 service() 方法,并将其产生的响应结果插入到当前位置。
    • 特点:支持传递参数(使用 <jsp:param>),目标页面发生变化后能立即生效。

| 特性 | include 指令 | include 动作 | | :— | :— | :— | | 语法 | <%@ include file="..." %> | <jsp:include page="..." /> | | 包含时机 | 翻译/编译阶段(静态) | 请求处理阶段(动态) | | 合并对象 | 源码合并 | 结果合并 | | 参数传递 | 不支持 | 支持 (<jsp:param>) | 【延伸考点讲解】

  1. 变量冲突:由于 include 指令是源码合并,被包含文件和主文件不能有同名的变量定义,否则会报编译错误。而 include 动作是方法调用,各自拥有独立的变量作用域。
  2. 性能对比:静态包含在运行时没有额外开销,性能略优;动态包含每次请求都会执行调用逻辑,但更灵活。
  3. 最佳实践:如果包含的是静态内容(如版权声明、纯 HTML 头部),推荐使用 include 指令;如果包含的是动态内容(需要传参或经常变动),推荐使用 include 动作。

【问题】

  1. 什么是 Scriptlets? 【参考答案】 Scriptlets 是嵌入在 JSP 页面中的一段 Java 代码片段。
    • 语法:使用 <% ... %> 标签包裹。
    • 作用:允许开发者在 JSP 页面中编写复杂的 Java 逻辑,如循环、条件判断、变量声明等。这些代码最终会被原封不动地放入翻译后的 Servlet 的 _jspService() 方法中。 【延伸考点讲解】
  2. 现代开发建议:在现代 Java Web 开发中,强烈建议避免使用 Scriptlets。过多的 Scriptlets 会导致 HTML 与 Java 代码高度耦合,使页面难以阅读和维护。
  3. 替代方案:推荐使用 EL 表达式、JSTL 标签库 或 自定义标签 来取代 Scriptlets。这样可以实现逻辑与展示的彻底分离,更符合 MVC 架构。
  4. 变量作用域:在 Scriptlet 中声明的变量通常是局部变量,仅在 _jspService() 方法内有效。

【问题】

  1. 声明(Declaration) 在哪里? 【参考答案】 JSP 声明(Declaration)用于在 JSP 页面对应的 Servlet 类中声明成员变量、成员方法或静态块。
    • 语法:使用 <%! ... %> 标签包裹。
    • 位置:在翻译后的 Servlet 类中,这些声明会被放在 _jspService() 方法之外,作为类的成员。
    • 作用:声明随后的表达式或 Scriptlet 中可以使用的全局变量或方法。 【延伸考点讲解】
  2. 与 Scriptlet 的区别:Scriptlet (<% ... %>) 声明的是 _jspService() 方法内的局部变量;而声明 (<%! ... %>) 产生的是类的成员变量(全局变量)。
  3. 线程安全风险:由于 JSP 在容器中默认是单例的,在 <%! ... %> 中声明的变量会被所有请求共享,因此必须非常注意线程安全问题。
  4. 方法定义:如果需要在 JSP 中定义方法,必须使用声明标签 <%! ... %>,而不能在普通的 Scriptlet 标签中定义。

【问题】

  1. 什么是表达式(Expression)? 【参考答案】 JSP 表达式用于将 Java 表达式的结果直接输出到响应页面中。
    • 语法:使用 <%= ... %> 标签包裹。
    • 原理:JSP 引擎在翻译时,会将表达式中的内容作为参数放入 out.print() 方法中。
    • 特点:表达式末尾 不能 加分号 ;,因为它被直接作为方法的参数。 【延伸考点讲解】
  2. 与 Scriptlet 的区别:Scriptlet (<% ... %>) 用于编写代码逻辑,不直接产生输出(除非显式调用 out.print);表达式 (<%= ... %>) 专门用于产生输出。
  3. 替代方案:在现代开发中,推荐使用 EL 表达式 (${...}) 来取代 JSP 表达式。EL 表达式语法更简洁,且具有更好的容错性和空值处理机制。
  4. 自动转类型:JSP 表达式会自动调用结果对象的 toString() 方法将其转换为字符串输出。

【问题】

  1. 隐含对象是什么意思?有哪些隐含对象? 【参考答案】 JSP 隐含对象(Implicit Objects)是 JSP 容器自动创建并注入到每个 JSP 页面中的一组预定义变量。开发者无需声明即可直接使用。
    • 核心隐含对象:
  2. request:HttpServletRequest 实例,代表客户端请求。
  3. response:HttpServletResponse 实例,代表服务端响应。
  4. session:HttpSession 实例,代表用户会话。
  5. application:ServletContext 实例,代表整个 Web 应用上下文。
  6. out:JspWriter 实例,用于向响应体输出内容。
  7. pageContext:PageContext 实例,提供对 JSP 页面所有范围及命名空间的访问。
  8. page:当前 JSP 实例本身(相当于 this)。
  9. config:ServletConfig 实例,代表 Servlet 配置。
  10. exception:Throwable 实例,仅在错误页面(isErrorPage="true")中可用。 【延伸考点讲解】
  11. 作用域范围:这些对象通常与 JSP 的四个作用域相关:page, request, session, application。
  12. 实现原理:容器在将 JSP 翻译成 Servlet 时,会在 _jspService() 方法的开头自动定义并初始化这些局部变量。
  13. EL 表达式对应:在 EL 表达式中,也有类似的隐式对象,如 pageContext, requestScope, sessionScope 等。

【问题】 jsp 和 Servlet 有什么区别? 【参考答案】 JSP 和 Servlet 都是 Java Web 开发的核心技术,它们的主要区别在于侧重点和表现形式:

  • JSP (JavaServer Pages):本质上是 Servlet,但侧重于 视图展示 (View)。它允许在 HTML 中嵌入 Java 代码,更适合编写动态页面。
  • Servlet:侧重于 业务逻辑控制 (Controller)。它是在 Java 代码中编写 HTML 输出,适合处理请求、访问数据库和分发逻辑。

核心差异:

  1. 角色定位:在 MVC 设计模式中,Servlet 充当控制器(Controller),JSP 充当视图(View)。
  2. 编写方式:JSP 是在 HTML 中写 Java,Servlet 是在 Java 中写 HTML。
  3. 编译过程:JSP 必须先被翻译成 Servlet 源文件并编译为 .class 才能运行;Servlet 直接由 Java 源文件编译而来。 【延伸考点讲解】
  4. JSP 引擎工作流:当第一次访问 JSP 时,Web 容器(如 Tomcat)会将其翻译为 .java 文件,然后编译为 .class 文件,并加载执行。这也就是为什么第一次访问 JSP 会慢一些的原因。
  5. 前后端分离趋势:在现代开发中,JSP 已逐渐被 Thymeleaf 等模板引擎或完全的前后端分离(RESTful API + 前端框架)所取代,但在维护老项目时依然非常重要。
  6. 最佳实践:建议不要在 JSP 中编写复杂的业务逻辑(Scriptlet),而应将其放在 Servlet 或 Service 层中处理。

【问题】 request.getAttribute() 和 request.getParameter() 有何区别? 【参考答案】 这两个方法都用于从请求中获取数据,但它们的数据来源和用途完全不同:

  • getParameter():
    • 来源:获取客户端发送的 请求参数(通过 URL 查询字符串或表单提交的数据)。
    • 返回值:始终返回 String 类型。
    • 操作:只有 get 方法,没有 set 方法。
  • getAttribute():
    • 来源:获取存储在 请求作用域 (Request Scope) 中的对象。
    • 返回值:返回 Object 类型,需要进行强制类型转换。
    • 操作:配合 setAttribute() 使用,通常用于在不同组件(如从 Servlet 到 JSP)之间传递复杂对象。

| 特性 | getParameter() | getAttribute() | | :— | :— | :— | | 数据来源 | 客户端(URL 或表单) | 服务器内部(请求域) | | 返回类型 | String | Object | | 设置方法 | 无(由客户端提交) | 有 (setAttribute) | | 典型场景 | 获取用户输入 | 在转发 (Forward) 时传递数据 | 【延伸考点讲解】

  1. 生命周期:getParameter 的数据随请求产生;getAttribute 的数据只在当前的请求链中有效(即转发过程中有效,重定向后失效)。
  2. 重定向 vs 转发:在 转发 (Forward) 时,可以使用 getAttribute 传递对象;但在 重定向 (Redirect) 时,由于是两次完全独立的请求,getAttribute 的值会丢失,此时只能通过 getParameter(将参数拼接到 URL 后)来传值。
  3. 数据转换:因为 getAttribute 返回的是 Object,在使用时必须注意空指针检查和正确的类型转换。

【问题】 JSP 中的四种作用域是哪几个? 【参考答案】 JSP 共有四种主要的作用域(Scope),用于在不同的生命周期和范围内共享数据:

  1. pageScope (页面作用域):数据仅在当前 JSP 页面有效。一旦页面执行完毕并返回响应,数据即被销毁。
  2. requestScope (请求作用域):数据在同一次 HTTP 请求中有效。如果发生请求转发(Forward),目标页面仍可访问该数据。
  3. sessionScope (会话作用域):数据在整个用户会话期间有效。只要浏览器不关闭且 Session 未超时,用户访问该应用的所有页面都能共享数据。
  4. applicationScope (应用作用域):数据在整个 Web 应用运行期间有效。所有用户、所有页面都共享同一份数据,直到服务器关闭。

| 作用域 | 对应对象 | 范围说明 | | :— | :— | :— | | page | pageContext | 当前页面 | | request | request | 同一次请求(含转发) | | session | session | 整个会话(单浏览器) | | application | application | 整个应用(所有用户) | 【延伸考点讲解】

  1. 查找顺序:当使用 EL 表达式(如 ${user})而未指定作用域时,容器会按照 page → request → session → application 的顺序从小到大进行查找,直到找到为止。
  2. 内存影响:作用域越大,数据驻留内存的时间越长。因此,应尽量使用小范围的作用域,以减少内存消耗。
  3. 线程安全:applicationScope 是全局共享的,存在严重的线程安全风险;sessionScope 对单个用户是安全的,但如果用户开启多个标签页,也需注意数据一致性。

Tomcat

【问题】 Tomcat 的缺省端口是多少,怎么修改? 【参考答案】 Tomcat 默认的 HTTP 访问端口是 8080。除此之外,Tomcat 还会占用以下几个核心端口:

  • 8005:用于接收关闭服务器指令的端口(Shutdown Port)。
  • 8009:用于 AJP 协议连接器的端口,通常用于与 Apache HTTP Server 等前端服务器通信。
  • 8443:默认的 HTTPS 访问端口(需手动开启和配置证书)。

修改方法: 所有的端口配置都在 Tomcat 安装目录下的 conf/server.xml 文件中。找到对应的 <Connector> 或 <Server> 标签,修改其 port 属性值即可。修改完成后需要重启 Tomcat 才能生效。 【延伸考点讲解】

  1. 常用端口修改场景:在同一台服务器上启动多个 Tomcat 实例时,必须修改这些端口以避免端口冲突。
  2. HTTP 默认端口 80:如果希望用户直接通过域名或 IP 访问而不需要输入端口号,应将 Tomcat 的 HTTP 端口修改为 80。
  3. 安全配置:出于安全考虑,建议修改或禁用默认的 8005 关闭端口,或者设置复杂的 shutdown 字符串,防止被恶意关闭。

【问题】 Tomcat 容器是如何创建 Servlet 类实例的?用到了什么原理? 【参考答案】 Tomcat 容器创建 Servlet 实例的过程主要依赖于 Java 反射机制。

  1. 解析配置:容器启动或应用部署时,解析 web.xml 或 @WebServlet 注解中的 Servlet 注册信息。
  2. 类加载:使用类加载器(ClassLoader)加载 Servlet 的全类名。
  3. 实例化:通过反射调用该类的无参构造方法创建对象实例。
  4. 初始化:调用实例的 init(ServletConfig config) 方法完成初始化。

实例化时机:

  • 启动时实例化:如果在配置中设置了 <load-on-startup> 为正数,容器会在应用启动时立即创建实例。
  • 延迟实例化:如果未设置或值为负数,容器会在该 Servlet 第一次接收到请求时才创建实例。 【延伸考点讲解】
    1. 单例模式:默认情况下,Servlet 在容器中是单例的。这意味着对于同一个 Servlet 类,容器只会创建一个实例来处理所有的并发请求。
    2. 线程安全:正因为是单例多线程运行,开发者应避免在 Servlet 中定义成员变量以防止线程安全问题。
    3. 反射的优缺点:反射提供了极大的灵活性,允许容器在运行时动态加载和创建对象,但相比直接 new 对象,反射会有微小的性能开销,且在编译期无法发现类不存在等问题。

【问题】 怎样进行内存调优? 【参考答案】 Tomcat 的内存调优主要是通过调整 JVM 的启动参数来实现的。

  • 配置位置:在 Linux 系统中修改 bin/catalina.sh,在 Windows 系统中修改 bin/catalina.bat。
  • 关键变量:调整 JAVA_OPTS 变量。
  • 常用参数:
    • -Xms:设置 JVM 初始堆内存大小。
    • -Xmx:设置 JVM 最大堆内存大小。
    • -Xmn:设置年轻代大小(通常为整个堆的 1/3 到 1/4)。
    • -XX:MetaspaceSize:设置元空间(Metaspace)的初始值(JDK 8+)。
    • -XX:MaxMetaspaceSize:设置元空间的最大值。

示例配置: JAVA_OPTS="-Xms512m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=256m" 【延伸考点讲解】

  1. 堆内存与元空间:堆内存主要存放对象实例,元空间存放类元数据。合理分配比例可以减少垃圾回收(GC)的频率和停顿时间。
  2. 内存溢出(OOM):如果配置不当,可能会出现 java.lang.OutOfMemoryError: Java heap space(堆内存不足)或 java.lang.OutOfMemoryError: Metaspace(元空间不足)。
  3. 调优原则:初始内存 (-Xms) 和最大内存 (-Xmx) 建议设置为相同,以避免 JVM 在运行时频繁调整堆大小带来的性能开销。

【问题】 Tomcat 有哪几种 Connector 运行模式? 【参考答案】 Tomcat 主要支持以下三种 Connector 运行模式,它们决定了 Tomcat 处理 I/O 请求的方式:

  1. BIO (Blocking I/O):
    • 特点:阻塞式 I/O。每个请求都会占用一个独立的线程,直到请求处理完成。
    • 评价:并发性能最低,适用于请求量较小的场景。在 Tomcat 8.5 以后已被移除。
  2. NIO (Non-blocking I/O):
    • 特点:非阻塞 I/O。基于 Java NIO 库实现,利用多路复用技术(Selector),可以使用较少的线程处理大量的并发连接。
    • 评价:Tomcat 8.x 默认的运行模式,具有良好的并发性能和稳定性。
  3. APR (Apache Portable Runtime):
    • 特点:利用 Apache 的可移植运行库,通过 JNI 调用本地(C/C++)代码来处理 I/O。
    • 评价:在处理静态资源和 SSL 握手时性能极高,是高并发应用的首选模式,但需要额外安装 APR 库和 Native 组件。 【延伸考点讲解】
  4. 模式切换:可以通过修改 conf/server.xml 中 <Connector> 标签的 protocol 属性来切换模式。例如 Http11NioProtocol 代表使用 NIO 模式。
  5. NIO2 (AIO):从 Tomcat 8 开始还支持 NIO2(异步 I/O),它进一步提升了 I/O 的处理效率,特别是在处理长连接时。
  6. 选择建议:大多数场景下使用默认的 NIO 即可满足需求。如果对性能有极致追求且环境允许安装 Native 库,可以考虑使用 APR。

JDBC

【问题】

  1. 什么是 JDBC? 【参考答案】 JDBC(Java Database Connectivity)是 Java 语言中用于连接和操作关系型数据库的一套标准 API(java.sql 和 javax.sql 包)。
    • 本质:它是一组接口规范。不同的数据库厂商(如 Oracle, MySQL, PostgreSQL)提供各自的实现类(驱动程序),Java 程序通过这些统一的接口与底层数据库通信。
    • 作用:实现了 Java 程序与数据库的解耦。开发者只需学习一套 JDBC API,即可编写能够访问多种不同数据库的应用程序,而无需关心底层特定数据库的通信细节。 【延伸考点讲解】
  2. 核心接口:包括 DriverManager(驱动管理)、Connection(连接)、Statement(执行 SQL)、ResultSet(结果集)。
  3. 数据访问层演进:由于直接使用原生 JDBC 存在代码冗余、连接管理繁琐等问题,现代开发通常使用基于 JDBC 封装的框架,如 MyBatis, Spring Data JPA (Hibernate)。
  4. SPI 机制:JDBC 利用了 Java 的 SPI (Service Provider Interface) 机制,使得驱动程序可以被动态地加载到应用中。

【问题】

  1. 解释下驱动(Driver)在 JDBC 中的角色。 【参考答案】 JDBC 驱动(JDBC Driver)是连接 Java 应用程序与特定数据库管理系统(DBMS)之间的桥梁。
    • 角色:它提供了 java.sql 包中定义的 JDBC 接口的具体实现。
    • 职责:驱动程序负责处理底层通信细节,如网络连接、数据类型转换以及将 SQL 语句转换为数据库能识别的协议。
    • 必备实现:驱动必须提供对 Connection, Statement, PreparedStatement, ResultSet 和 Driver 等核心接口的实现。 【延伸考点讲解】
  2. 四种驱动类型:
    • Type 1:JDBC-ODBC 桥驱动(已废弃)。
    • Type 2:本地 API 驱动(依赖本地 C/C++ 库)。
    • Type 3:网络协议驱动(中间件模式)。
    • Type 4:纯 Java 驱动(目前最常用,直接与数据库通信)。
  3. 加载机制:现代 JDBC 驱动通常利用 Java SPI 机制,在类路径下包含 META-INF/services/java.sql.Driver 文件,使得 DriverManager 能够自动发现并加载驱动。
  4. 驱动注册:虽然现代版本支持自动加载,但了解 Class.forName("com.mysql.cj.jdbc.Driver") 的原理依然重要,它触发了驱动类的静态代码块,从而向 DriverManager 注册自身。

【问题】

  1. Class.forName() 方法有什么作用? 【参考答案】 在 JDBC 中,Class.forName() 方法的主要作用是 动态加载数据库驱动类 并触发其 静态代码块 的执行。
    • 动态加载:在运行时根据字符串名称加载指定的类到 JVM 中。
    • 注册驱动:所有的 JDBC 驱动类在其静态代码块中都会调用 DriverManager.registerDriver() 方法将自己注册到驱动管理器中。
    • 示例:Class.forName("com.mysql.cj.jdbc.Driver")。 【延伸考点讲解】
  2. 自动加载机制 (JDBC 4.0+):现代 JDBC 驱动利用了 Java 的 SPI (Service Provider Interface) 机制。只要驱动 JAR 包在类路径下,DriverManager 在获取连接时会自动发现并加载驱动,因此在很多现代代码中不再显式调用 Class.forName()。
  3. 反射原理:该方法属于 Java 反射机制的一部分。它除了加载类,还会确保该类被初始化(即执行 static 块)。
  4. 异常处理:调用此方法时必须捕获 ClassNotFoundException,以防止驱动类名拼写错误或缺少依赖包。

【问题】

  1. PreparedStatement 比 Statement 有什么优势? 【参考答案】 在 JDBC 开发中,PreparedStatement 是 Statement 的子接口,它具有以下显著优势:
  2. 防止 SQL 注入:这是最重要的优势。它使用占位符 ? 进行参数化查询,能有效防止非法字符破坏 SQL 结构。
  3. 性能更高:它是预编译的。数据库会对相同的 SQL 语句进行缓存,多次执行时无需重新解析和编译,显著提升效率。
  4. 可读性与维护性更好:避免了繁琐的字符串拼接,代码更加简洁清晰。
  5. 支持多种数据类型:提供了丰富的 setXxx() 方法(如 setInt, setString, setDate),可以更方便地处理各种数据类型。 【延伸考点讲解】
  6. 工作原理:当调用 prepareStatement(sql) 时,SQL 模板被发送到数据库进行预编译。之后调用 setXxx() 仅发送参数,减少了网络传输负担。
  7. 二进制大对象支持:在处理 BLOB(二进制大对象)或 CLOB(字符大对象)时,PreparedStatement 是唯一的选择。
  8. 缓存限制:预编译的优势依赖于数据库的缓存机制。如果 SQL 语句结构每次都发生变化(即不使用占位符),则无法享受性能提升。

【问题】

  1. 什么时候使用 CallableStatement?用来准备 CallableStatement 的方法是什么? 【参考答案】 CallableStatement 主要用于执行数据库中的 存储过程 (Stored Procedures)。
    • 作用:存储过程是预先在数据库中编译好并存储的一段 SQL 逻辑,可以接受输入参数(IN),也可以返回结果或输出参数(OUT)。
    • 准备方法:通过 Connection 对象的 prepareCall(String sql) 方法来创建。
    • 示例:CallableStatement cstmt = conn.prepareCall("{call get_user_info(?, ?)}"); 【延伸考点讲解】
  2. 参数处理:支持 IN(输入)、OUT(输出)和 INOUT(进出)参数。使用 setXxx() 设置输入参数,使用 registerOutParameter() 注册输出参数。
  3. 优势:存储过程预先编译,执行效率高;减少了 Java 应用与数据库之间的网络数据传输;通过封装业务逻辑,增强了数据的安全性和模块化。
  4. 调用语法:通常使用 {call procedure_name(?, ?)} 这种转义语法,以保证跨数据库的兼容性。

【问题】

  1. 数据库连接池是什么意思? 【参考答案】 数据库连接池(Database Connection Pooling)是一种在应用服务器启动时就建立若干个数据库连接,并将它们维护在一个“池”中供应用程序复用的技术。
    • 背景:传统的数据库访问方式(即用即开,用完即关)在面临高并发请求时,频繁的建立和断开连接会消耗大量的系统资源(如 CPU、内存、网络),严重降低性能。
    • 原理:当应用程序需要访问数据库时,直接从连接池中“借出”一个已存在的空闲连接;使用完毕后,再将其“归还”到池中,而不是物理关闭连接。
    • 作用:显著减少了连接创建和销毁的开销,提高了系统的响应速度和资源利用率。 【延伸考点讲解】
  2. 核心参数:
    • initialSize:连接池启动时的初始连接数。
    • maxActive/maxTotal:池中允许的最大活跃连接数。
    • maxIdle/minIdle:最大/最小空闲连接数。
    • maxWait:当连接耗尽时,应用获取连接的最大等待时间。
  3. 常见实现:在 Java 开发中,常用的连接池包括 HikariCP(目前性能最好、最推荐)、Druid(阿里巴巴开源,功能丰富,带监控)、DBCP 和 C3P0(较老)。
  4. 连接有效性检查:连接池通常会定期检查池中连接的有效性(如使用 validationQuery),防止因数据库端超时断开而导致应用拿到的连接不可用。

【问题】 数据库连接池的工作机制是什么? 【参考答案】 数据库连接池的核心机制在于“池化管理”和“连接复用”,具体步骤如下:

  1. 初始化:在系统启动时,连接池根据配置创建一定数量的初始连接(InitialSize),并将其保存在内存队列或集合中。
  2. 分配连接:当应用程序请求数据库连接时,连接池首先检查池中是否有空闲连接。如果有,则直接返回;如果没有,且当前连接数未达到最大值(MaxActive),则创建一个新连接;如果已达上限,则请求进入等待状态或抛出异常。
  3. 使用与归还:应用程序执行 SQL 操作后,调用 close() 方法。注意,此时的 close() 是被连接池重写过的逻辑,它并不会物理关闭 TCP 连接,而是将连接状态标记为空闲并归还到池中。
  4. 管理与维护:连接池会定期检查空闲连接的有效性,并销毁长期不使用的多余连接(MinIdle 之外的连接),以维持池的健康状态。 【延伸考点讲解】
  5. 连接池代理:连接池通常使用 代理模式 (Proxy Pattern) 拦截 Connection.close() 方法。当应用调用 close 时,代理对象捕获该调用并执行“回收”逻辑。
  6. 性能优势:规避了 TCP 三次握手和四次挥手的网络开销,以及数据库端创建进程/线程的昂贵资源消耗。
  7. 超时处理:连接池提供了获取连接的超时配置(MaxWait),防止因数据库响应慢或连接泄露导致应用线程长时间阻塞。

【问题】 说下原生 JDBC 操作数据库流程? 【参考答案】 原生 JDBC 操作数据库的标准流程通常分为以下六个步骤:

  1. 加载驱动:使用 Class.forName() 加载特定数据库的驱动程序(JDBC 4.0 后可省略显式加载)。
  2. 建立连接:通过 DriverManager.getConnection(url, user, password) 获取数据库连接对象 Connection。
  3. 创建语句对象:根据需求通过 Connection 创建 Statement 或 PreparedStatement 对象。
  4. 执行 SQL:调用 executeQuery()(查询)或 executeUpdate()(增删改)方法执行 SQL 语句。如果是参数化查询,需先通过 setXxx() 设置参数。
  5. 处理结果集:如果是查询操作,通过遍历 ResultSet 获取数据。
  6. 释放资源:按照“先开启后关闭”的原则,依次关闭 ResultSet、Statement 和 Connection(通常放在 finally 块中)。 【延伸考点讲解】
  7. try-with-resources:在 JDK 7 及以后版本,推荐使用 try-with-resources 语法自动管理资源关闭,避免因手动关闭不当导致的连接泄露。
  8. PreparedStatement 优选:在开发中应始终优先使用 PreparedStatement 而非 Statement,以防止 SQL 注入并提高执行效率。
  9. 事务管理:JDBC 默认开启自动提交(Auto-commit)。如果需要手动管理事务,需调用 connection.setAutoCommit(false),并在操作完成后显式调用 commit() 或 rollback()。

【问题】 HTTP 的长连接和短连接区别? 【参考答案】 HTTP 的长连接(Persistent Connection)和短连接本质上是指底层 TCP 连接 的复用方式。

  • 短连接 (Short Connection):
    • 特点:客户端和服务器每进行一次 HTTP 操作,就建立一次连接,任务结束即中断连接(四次挥手)。
    • 版本:HTTP/1.0 默认使用短连接。
    • 缺点:建立和销毁连接开销大,频繁握手浪费资源。
  • 长连接 (Long Connection):
    • 特点:数据传输完成后,TCP 连接并不立即断开,而是在同域名下的后续请求中复用该通道。
    • 版本:HTTP/1.1 及以后版本默认开启长连接。
    • 优点:减少了 TCP 握手次数,降低了延迟,提高了传输效率。

控制方式: 通过请求头/响应头中的 Connection: keep-alive(长连接)或 Connection: close(短连接)进行控制。 【延伸考点讲解】

  1. Keep-Alive 存活时间:长连接不会永久保持,通常由服务器配置超时时间(Timeout)和最大请求数(Max Requests)。
  2. HTTP/2 多路复用:在 HTTP/1.1 的长连接中,请求是串行的;而 HTTP/2 实现了真正的多路复用,可以在同一个 TCP 连接上并发处理多个请求。
  3. 适用场景:短连接适用于请求频率极低、用户量巨大的简单交互;长连接适用于高频交互、需要加载大量静态资源的网页应用。

【问题】 HTTP 常见的状态码有哪些? 【参考答案】 HTTP 状态码由三位数字组成,第一位数字定义了响应的类别。常见的状态码包括:

  • 2xx (成功):
    • 200 OK:请求成功,最常见的状态码。
  • 3xx (重定向):
    • 301 Moved Permanently:永久重定向,请求的资源已永久移动到新位置。
    • 302 Found:临时重定向,资源临时移动。
  • 4xx (客户端错误):
    • 400 Bad Request:请求语法错误,服务器无法理解。
    • 401 Unauthorized:请求要求身份验证。
    • 403 Forbidden:服务器理解请求但拒绝执行(权限不足)。
    • 404 Not Found:请求的资源不存在。
  • 5xx (服务器错误):
    • 500 Internal Server Error:服务器内部错误,无法完成请求。
    • 503 Service Unavailable:服务器目前无法使用(过载或维护)。 【延伸考点讲解】 1. 301 与 302 的 SEO 影响:301 会将原 URL 的权重转移到新 URL,而 302 则不会。因此,永久性更换域名应使用 301。 2. 401 与 403 的区别:401 代表“未认证”(未登录),通常需要用户提供凭据;403 代表“未授权”(已登录但权限不够),服务器拒绝访问。 3. Restful API 实践:在现代 API 开发中,应严格遵守状态码语义,如创建成功应返回 201 Created,删除成功可返回 204 No Content。

【问题】 session 共享怎么做的(分布式如何实现 session 共享)? 【参考答案】 在分布式或集群环境下,由于 HTTP 请求可能被负载均衡到不同的服务器,传统的单机 Session 无法生效,需要实现 Session 共享。常见方案包括:

  1. 基于 Redis 的集中存储(最常用):将所有服务器的 Session 数据统一存储在 Redis 缓存集群中。各节点通过特定的拦截器(如 Spring Session)从 Redis 中读写 Session。
  2. Session 复制(多播/同步):当一台服务器创建或修改 Session 后,通过网络将数据同步到集群中的其他所有服务器。适用于节点较少(< 5个)的场景。
  3. Session 粘性(Sticky Session):在负载均衡器(如 Nginx)上配置,通过 IP Hash 或 Cookie 确保同一用户的请求始终转发到同一台服务器。缺点是服务器宕机会导致数据丢失。
  4. 客户端存储(基于 Cookie/JWT):将 Session 数据加密后存储在客户端 Cookie 中。优点是服务器无状态,扩展性极强;缺点是 Cookie 大小受限且存在安全性风险。 【延伸考点讲解】
  5. Spring Session:它是 Java 生态中解决 Session 共享的标准方案,支持 Redis、JDBC、MongoDB 等多种存储后端,且对业务代码无侵入。
  6. 数据序列化:在将 Session 存入 Redis 时,需要注意对象的序列化问题。建议使用 JSON 序列化以提高跨平台兼容性和可读性。
  7. 一致性哈希:在某些高级负载均衡方案中,利用一致性哈希算法可以更优雅地实现 Session 粘性,减少因节点变动导致的 Session 失效。

【问题】 在单点登录中,如果 cookie 被禁用了怎么办? 【参考答案】 如果浏览器禁用了 Cookie,单点登录(SSO)及普通的 Session 机制将无法通过 Cookie 自动携带 Session ID。此时可以采用以下替代方案:

  1. URL 重写 (URL Rewriting):将 Session ID 显式地包含在每一个 URL 链接中(如 .../page?jsessionid=XXX)。服务端通过解析 URL 获取会话标识。
  2. Token + 响应头/请求头:这是目前最常用的方案。用户登录后,服务端返回一个 Token(如 JWT),前端将其存储在 localStorage 或 sessionStorage 中。后续请求通过自定义 HTTP 请求头(如 Authorization: Bearer <token>)手动携带该 Token。
  3. 隐藏表单域:在 HTML 表单中添加一个隐藏字段(Hidden Field),每次提交表单时将会话标识传回服务器。 【延伸考点讲解】
  4. 安全性对比:URL 重写存在安全风险,因为 Session ID 会暴露在浏览器历史记录和服务器日志中。相比之下,使用请求头传输 Token(配合 HTTPS)更加安全。
  5. 无状态架构:基于 JWT 的 Token 方案通常是无状态的,服务器不需要存储会话数据,非常适合大规模分布式系统和移动端应用。
  6. SSO 核心本质:单点登录的核心是“受信任的第三方认证”和“令牌传递”,只要能通过某种手段(Cookie、Header、Parameter)安全地传递身份令牌,SSO 就能实现。

Linux

【问题】 说一下常用的 Linux 命令? 【参考答案】 在日常开发和运维中,常用的 Linux 命令可以按功能分为以下几类:

  • 文件与目录操作:
    • ls:列出目录内容(-a 显示隐藏文件,-l 显示详细信息)。
    • cd:切换工作目录。
    • pwd:显示当前工作目录的绝对路径。
    • mkdir / rmdir:创建 / 删除空目录。
    • touch:创建空文件或更新文件时间戳。
    • cp / mv / rm:复制 / 移动(重命名)/ 删除文件或目录。
  • 文件查看与编辑:
    • cat:连接文件并打印到标准输出。
    • tail:查看文件尾部内容(-f 动态追踪日志)。
    • grep:文本搜索工具,支持正则表达式。
    • vim / vi:命令行文本编辑器。
  • 压缩与打包:
    • tar:打包工具(-xvf 解包,-zcvf 打包并 gzip 压缩)。
  • 系统状态与进程管理:
    • ps:查看当前进程状态(-ef 或 -aux)。
    • top:实时显示系统资源占用情况(CPU、内存)。
    • kill:终止进程(-9 强制终止)。
    • netstat / ss:查看网络连接和端口占用。 【延伸考点讲解】 1. 管道符 |:Linux 的核心哲学之一。可以将前一个命令的输出作为后一个命令的输入,例如 ps -ef | grep java。 2. 权限管理:使用 chmod 修改文件权限,使用 chown 修改文件所属用户/组。 3. 帮助命令:遇到不熟悉的命令,可以使用 man <command> 或 <command> --help 查看详细文档。

【问题】 Linux 中如何查看日志? 【参考答案】 在 Linux 中查看日志有多种方式,取决于你想要查看的内容量以及是否需要实时监控:

  • 实时查看:tail -f <file_path>。这是最常用的命令,用于实时滚动打印文件的新增内容,非常适合排查运行中的系统问题。
  • 查看尾部内容:tail -n <number> <file_path>。查看文件末尾的指定行数(如 tail -n 100 查看最后 100 行)。
  • 分页查看:less <file_path>。支持上下翻页、关键词搜索(输入 / 开启搜索),且不会一次性加载整个大文件,性能较好。
  • 关键词搜索:grep "keyword" <file_path>。直接搜索包含特定关键词的行。 【延伸考点讲解】
    1. 组合技巧:经常配合管道符使用,如 tail -f error.log | grep "Exception",实时监控并只显示包含“Exception”的错误行。
    2. 大文件处理:禁止使用 cat 或 vi/vim 打开超大型日志文件(如数 GB 的文件),否则可能会导致内存溢出或系统卡顿。应优先使用 less 或 tail。
    3. 系统日志路径:常见的系统日志通常存放在 /var/log/ 目录下(如 messages, secure, cron 等)。

【问题】 Linux 怎么关闭进程? 【参考答案】 在 Linux 中关闭进程通常分为“查找”和“终止”两个步骤:

  1. 查找进程 PID:
    • 使用 ps -ef | grep <process_name> 或 ps aux | grep <process_name> 查找目标进程的进程号(PID)。
    • 也可以使用 pgrep <process_name> 直接获取 PID。
  2. 终止进程:
    • kill <PID>:发送 SIGTERM 信号,尝试正常关闭进程。
    • kill -9 <PID>:发送 SIGKILL 信号,强制立即终止进程(不推荐作为首选,可能导致数据未保存或资源未释放)。
    • pkill <process_name>:根据进程名批量终止进程。
    • killall <process_name>:终止所有指定名称的进程。 【延伸考点讲解】
  3. 信号机制:kill 命令默认发送的是信号 15(SIGTERM),它允许进程在退出前进行清理工作;-9 发送的是信号 9(SIGKILL),进程无法拦截该信号,会被内核直接杀掉。
  4. 僵尸进程 (Zombie):如果进程已经变成僵尸状态(状态为 Z),kill -9 也无法将其杀掉。此时需要杀掉其父进程,或者等待 init 进程回收。
  5. 查看端口占用:如果是想关闭占用特定端口的进程,可以使用 lsof -i:<port> 或 netstat -nlp | grep <port> 找到 PID 后再杀掉。

本文由作者按照 CC BY 4.0 进行授权