百味皆苦 java后端开发攻城狮

八股文-web

2019-04-08
百味皆苦

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,实现了 ServletServletConfig 接口。
  • HttpServlet:继承自 GenericServlet,专门用于处理 HTTP 协议的请求,提供了 doGetdoPost 等方法。 【延伸考点讲解】
    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 有什么区别? 【参考答案】 GenericServletHttpServlet 都是 Servlet 接口的实现类,但它们的设计目标不同:
  • GenericServlet:是一个通用的、与协议无关的 Servlet 基类。它实现了 ServletServletConfig 接口,要求开发者必须覆盖 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 调用具体的 doGetdoPost 等。
    2. 抽象程度GenericServlet 使编写 Servlet 变得更容易,因为它提供了 initdestroy 的默认实现,并把 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 是单例多线程运行的,因此在 doGetdoPost 中使用成员变量(实例变量)是不安全的,应尽量使用局部变量。

【问题】

  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-urlencodedmultipart/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 容器的配置或使用 RequestDispatcherforward() 方法来实现。 【延伸考点讲解】
  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-ForX-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. requestHttpServletRequest 实例,代表客户端请求。
  3. responseHttpServletResponse 实例,代表服务端响应。
  4. sessionHttpSession 实例,代表用户会话。
  5. applicationServletContext 实例,代表整个 Web 应用上下文。
  6. outJspWriter 实例,用于向响应体输出内容。
  7. pageContextPageContext 实例,提供对 JSP 页面所有范围及命名空间的访问。
  8. page:当前 JSP 实例本身(相当于 this)。
  9. configServletConfig 实例,代表 Servlet 配置。
  10. exceptionThrowable 实例,仅在错误页面(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.sqljavax.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, ResultSetDriver 等核心接口的实现。 【延伸考点讲解】
  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 开发中,PreparedStatementStatement 的子接口,它具有以下显著优势:
  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(阿里巴巴开源,功能丰富,带监控)、DBCPC3P0(较老)。
  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 创建 StatementPreparedStatement 对象。
  4. 执行 SQL:调用 executeQuery()(查询)或 executeUpdate()(增删改)方法执行 SQL 语句。如果是参数化查询,需先通过 setXxx() 设置参数。
  5. 处理结果集:如果是查询操作,通过遍历 ResultSet 获取数据。
  6. 释放资源:按照“先开启后关闭”的原则,依次关闭 ResultSetStatementConnection(通常放在 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),前端将其存储在 localStoragesessionStorage 中。后续请求通过自定义 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. 大文件处理:禁止使用 catvi/vim 打开超大型日志文件(如数 GB 的文件),否则可能会导致内存溢出或系统卡顿。应优先使用 lesstail
    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 后再杀掉。


Similar Posts

上一篇 八股文-spring

Comments