servlet
【问题】
什么是 Servlet? 【参考答案】 Servlet(Server Applet)是运行在 Web 服务器或应用服务器上的 Java 程序,主要用于处理客户端发送的 HTTP 请求并生成动态的 Web 内容。它是 Java EE 规范的核心部分,充当了 Web 浏览器或其他 HTTP 客户端与 HTTP 服务器上的数据库或应用程序之间的中间层。 【延伸考点讲解】
- 核心功能:处理表单数据、管理会话状态(Session)、访问后端数据库、分发请求(MVC 模式中的 Controller)。
- 无状态性:HTTP 协议本身是无状态的,Servlet 通过 Cookie 和 Session 机制来维持客户端的状态信息。
- 并发处理:Servlet 默认以多线程方式运行,Web 容器会为每个请求分配一个线程来执行
service()方法,因此需要注意多线程下的数据一致性问题。
【问题】
说一下 Servlet 的体系结构。
【参考答案】
Servlet 的体系结构主要围绕 javax.servlet.Servlet 接口展开。所有的 Servlet 都必须直接或间接地实现该接口。
- GenericServlet:是一个协议无关的通用 Servlet,实现了
Servlet和ServletConfig接口。 - HttpServlet:继承自
GenericServlet,专门用于处理 HTTP 协议的请求,提供了doGet、doPost等方法。 【延伸考点讲解】- 核心接口与类:
Servlet接口:定义了生命周期方法(init,service,destroy)。ServletConfig接口:用于获取 Servlet 的初始化参数和ServletContext。ServletContext接口:代表整个 Web 应用的上下文,用于在不同 Servlet 间共享数据。- 多线程模型:Servlet 容器通常以单实例多线程的方式运行 Servlet,即一个 Servlet 实例处理多个并发请求,开发者需注意线程安全。
【问题】
- Applet 和 Servlet 有什么区别? 【参考答案】 Applet 是运行在客户端(通常是 Web 浏览器)中的 Java 小程序,主要用于提供交互式的用户界面。而 Servlet 是运行在服务器端的 Java 程序,负责处理客户端请求并生成动态响应。
- 运行位置:Applet 在客户端运行,Servlet 在服务端运行。
- 用户界面:Applet 具有图形用户界面(GUI),使用 AWT 或 Swing 等库;Servlet 没有用户界面,它接收 HTTP 请求并返回 HTML、JSON 等数据。
- 生命周期管理:Applet 由浏览器(或 Applet 容器)管理;Servlet 由 Web 容器(如 Tomcat)管理。
【延伸考点讲解】
- 安全性差异:Applet 运行在客户端的“沙箱”中,受严格的安全限制,不能随意访问客户端本地文件系统。Servlet 运行在服务端,具有访问服务器资源(如数据库、本地文件)的完整权限。
- 现代 Web 现状:随着 HTML5、JavaScript 和 CSS3 的普及,Applet 技术已基本被淘汰,现代浏览器已不再支持 Java 插件。而 Servlet 依然是 Java Web 开发的基础。
- 通信模型:Servlet 遵循典型的请求-响应模型(Request-Response),而 Applet 主要是为了增强页面的本地交互能力。
【问题】
- GenericServlet 和 HttpServlet 有什么区别?
【参考答案】
GenericServlet和HttpServlet都是 Servlet 接口的实现类,但它们的设计目标不同:
- GenericServlet:是一个通用的、与协议无关的 Servlet 基类。它实现了
Servlet和ServletConfig接口,要求开发者必须覆盖service()方法。它主要用于处理非 HTTP 协议(如 FTP)的请求。 - HttpServlet:继承自
GenericServlet,专门为 HTTP 协议设计。它通过覆盖service()方法实现了根据请求方法(GET, POST, PUT, DELETE 等)分发到对应的doXXX()方法上,通常我们只需要覆盖doGet()或doPost()。 【延伸考点讲解】- 模板方法设计模式:
HttpServlet的实现采用了模板方法模式。它的service(ServletRequest, ServletResponse)方法会将参数转型为 HTTP 相关的类型,并调用内部的service(HttpServletRequest, HttpServletResponse),后者再根据 HTTP Method 调用具体的doGet、doPost等。 - 抽象程度:
GenericServlet使编写 Servlet 变得更容易,因为它提供了init和destroy的默认实现,并把ServletConfig的方法暴露了出来。 - 开发实践:在几乎所有的 Web 开发场景中,我们都会选择继承
HttpServlet,因为它是专门为处理 Web 请求优化的。
- 模板方法设计模式:
【问题】
-
解释下 Servlet 的生命周期。 【参考答案】 Servlet 的生命周期由 Web 容器(如 Tomcat)管理,主要包含以下三个阶段:
- 初始化阶段:容器加载 Servlet 类并实例化后,调用
init()方法。该方法在整个生命周期中只会被调用一次,常用于初始化资源(如数据库连接、配置文件读取)。 - 运行阶段:每次客户端请求到达时,容器会调用
service()方法。service()根据请求的 HTTP 方法(GET、POST 等)将其分发给对应的doGet()、doPost()等处理逻辑。 - 销毁阶段:当 Web 应用被卸载或服务器关闭时,容器会调用
destroy()方法。该方法也只会被调用一次,用于释放 Servlet 占用的资源。 【延伸考点讲解】 - 单例模式:默认情况下,Servlet 在容器中是单例的。这意味着对于同一个 Servlet 类,容器只会创建一个实例来处理所有的并发请求。
- 加载时机:默认是第一次访问时加载。可以通过在
web.xml中配置<load-on-startup>标签来指定 Servlet 在服务器启动时就完成初始化(值越小优先级越高)。 - 线程安全:由于 Servlet 是单例多线程运行的,因此在
doGet或doPost中使用成员变量(实例变量)是不安全的,应尽量使用局部变量。
【问题】
-
doGet() 方法和 doPost() 方法有什么区别? 【参考答案】
doGet()和doPost()是HttpServlet中最常用的两个方法,其主要区别如下: - 语义不同:GET 用于从服务器获取资源,具有幂等性(多次执行结果相同且无副作用);POST 用于向服务器提交数据,通常会导致服务器状态的变化。
- 参数传递方式:GET 将参数附加在 URL 后面(Query String),以
?name=value形式展现;POST 将数据放在 HTTP 请求的消息体(Entity Body)中。 - 数据大小限制:GET 受限于 URL 的长度限制(不同浏览器和服务器限制不同,通常为 2KB-8KB);POST 理论上没有大小限制,适合传输大量数据(如文件上传)。
- 安全性:GET 参数暴露在地址栏和浏览器历史记录中,不适合传输敏感信息;POST 数据在消息体中,相对更安全(但仍需 HTTPS 加密)。 【延伸考点讲解】
- 幂等性(Idempotency):GET、HEAD、PUT、DELETE 都是幂等的,而 POST 不是。在设计 RESTful API 时应严格遵守这一原则。
- 缓存机制:GET 请求可以被浏览器主动缓存、收藏为书签;而 POST 请求默认不会被缓存。
- 编码方式:GET 只能进行 ASCII 字符编码;POST 支持多种编码类型(如
application/x-www-form-urlencoded、multipart/form-data)。
【问题】
- 什么是 Web 应用程序? 【参考答案】 Web 应用程序(Web Application)是一种可以通过 Web 浏览器访问的应用程序。它由一组静态资源(如 HTML、CSS、JS、图片)和动态资源(如 Servlet、JSP、Java 类)组成,通常打包成 WAR 文件部署在 Web 服务器(如 Tomcat)上。
- 面向表现:产生包含标记语言和动态内容的交互式页面。
- 面向服务:实现 Web 服务端点(Endpoints),如 RESTful API 或 SOAP 服务。
【延伸考点讲解】
- WAR 包结构:标准的 Java Web 应用结构包括
WEB-INF目录,其中包含web.xml(部署描述符)、classes(编译后的类文件)和lib(依赖库)。 - 部署位置:在 Servlet 规范中,Web 应用通常安装在服务器 URL 名称空间的特定子集下(Context Path)。
- 无状态性与会话:Web 应用运行在 HTTP 协议之上,因此通常需要 Session、Cookie 或 Token 机制来管理用户状态。
- WAR 包结构:标准的 Java Web 应用结构包括
【问题】
- 什么是服务端包含(Server Side Include)? 【参考答案】 服务端包含(SSI)是一种简单的、基于 HTML 注释的解释型服务端脚本语言。它最常用的场景是将一个或多个文件的内容(如公共的头部、尾部或导航栏)动态地包含到 Web 页面中。
- 工作方式:当浏览器请求一个包含 SSI 指令的页面时,服务器会解析这些指令,并将结果(通常是另一个文件的内容)插入到页面中,最后将生成的完整 HTML 发送给客户端。
- 语法示例:
<!--#include file="header.html" -->【延伸考点讲解】- 优点与局限:SSI 非常轻量,适合静态站点的简单复用。但其逻辑处理能力有限(仅支持简单的条件判断和变量),且在高并发下可能存在一定的性能开销。
- 现代替代方案:在现代开发中,SSI 已逐渐被更强大的模板引擎(如 JSP、Thymeleaf、FreeMarker)或前端组件化方案(如 React/Vue 组件、Edge Side Includes (ESI))所取代。
- 安全性:如果 SSI 指令的参数来自用户输入且未经过滤,可能会引发 SSI 注入攻击,允许攻击者读取服务器敏感文件或执行系统命令。
【问题】
- 什么是 Servlet 链(Servlet Chaining)?
【参考答案】
Servlet 链(Servlet Chaining)是将一个 Servlet 的输出作为另一个 Servlet 的输入,从而将多个 Servlet 串联起来处理同一个请求的技术。
- 工作流:第一个 Servlet 处理部分请求并生成中间结果,然后将控制权(和输出数据)传递给链中的下一个 Servlet,直到最后一个 Servlet 生成最终响应并发送给客户端。
- 实现方式:通常通过 Web 容器的配置或使用
RequestDispatcher的forward()方法来实现。 【延伸考点讲解】
- 过滤器(Filter)的演进:Servlet 链是早期 Java Web 开发中的概念。在现代 Servlet 规范中,这种拦截和串联处理的需求主要通过 Filter 链(FilterChain) 来实现,它更加灵活且解耦。
- 适用场景:数据过滤、日志记录、权限检查、内容转换(如将输出的 HTML 转换为 PDF 或压缩)。
- 优缺点:优点是提高了代码的可复用性和模块化;缺点是如果链条过长,会增加调试难度并可能影响处理性能。
【问题】
- 如何知道是哪一个客户端的机器正在请求你的 Servlet?
【参考答案】
可以通过
ServletRequest对象提供的方法来获取客户端机器的相关信息。主要方法如下:- getRemoteAddr():获取客户端主机的 IP 地址。
- getRemoteHost():获取客户端主机的主机名(如果配置了 DNS 解析且能解析成功,否则返回 IP 地址)。
- getRemotePort():获取客户端发送请求时所使用的源端口号。 【延伸考点讲解】
- 代理环境下的真实 IP:在实际应用中,由于存在 Nginx 反向代理或负载均衡,
getRemoteAddr()往往只能获取到代理服务器的 IP。此时通常需要从请求头中获取真实 IP,如X-Forwarded-For或X-Real-IP。 - 性能考量:
getRemoteHost()方法在尝试解析主机名时可能会产生较大的网络开销和延迟(DNS 反向查询),在高并发环境下建议直接使用 IP。 - 安全应用:获取客户端信息常用于权限校验(IP 白名单)、防盗链、日志记录、地理位置识别以及多因素认证等场景。
【问题】
- HTTP 响应的结构是怎么样的? 【参考答案】 HTTP 响应由四个主要部分组成:
- 状态行 (Status Line):包含 HTTP 协议版本、状态码(如 200, 404)以及对应的状态消息(Reason Phrase)。
- 响应头部 (Response Headers):包含服务器的描述信息、数据的元数据(如
Content-Type,Content-Length,Set-Cookie,Cache-Control等)。 - 空行 (Empty Line):响应头部后的一个必须的空行,用于分隔头部和主体。
- 响应主体 (Response Body):服务器返回给客户端的具体内容,如 HTML 页面、图片数据、JSON 字符串等。 【延伸考点讲解】
- 常见状态码分类:1xx(信息性)、2xx(成功)、3xx(重定向)、4xx(客户端错误)、5xx(服务器错误)。
- Servlet 处理:在 Servlet 中,通过
HttpServletResponse对象来操作响应结构,如setStatus()设置状态码、setHeader()设置头部、getWriter()或getOutputStream()写入主体。 - 性能优化:通过
Transfer-Encoding: chunked允许分块传输,或通过Content-Encoding: gzip进行数据压缩,以提高传输效率。
【问题】
- 什么是 cookie?session 和 cookie 有什么区别?
【参考答案】
Cookie 和 Session 都是 Web 开发中常用的会话跟踪技术,用于在无状态的 HTTP 协议下维持用户的状态。
- Cookie:是由服务器发送并存储在客户端浏览器上的小型文本文件。浏览器每次向同一服务器发送请求时,都会带上该 Cookie。
- Session:是存储在服务器端的会话信息。服务器为每个用户创建一个唯一的 Session ID,并通过 Cookie(通常是 JSESSIONID)发送给客户端,客户端后续请求带上此 ID 以匹配服务端的 Session。
主要区别:
- 存储位置:Cookie 存储在客户端(浏览器);Session 存储在服务端。
- 安全性:Session 安全性更高,因为敏感信息不直接暴露给客户端;Cookie 容易被截获或篡改(需配合加密或 HttpOnly)。
- 数据类型:Cookie 只能存储字符串;Session 可以存储任何 Java 对象。
- 有效期:Cookie 可以设置长期有效;Session 通常随浏览器关闭或超时而失效。 【延伸考点讲解】
- 分布式 Session 问题:在多台服务器集群环境下,Session 默认无法共享。解决方案包括:Session 复制、Session 粘滞(Sticky Sessions)或使用外部存储(如 Redis 存储 Session)。
- Cookie 的安全属性:
HttpOnly:防止 JavaScript 读取 Cookie,抵御 XSS 攻击。Secure:确保 Cookie 仅通过 HTTPS 协议传输。SameSite:防止 CSRF 攻击。
- 禁用 Cookie 后的方案:如果客户端禁用了 Cookie,Session 仍然可以通过 URL 重写(在 URL 后附加
;jsessionid=...)来维持。
【问题】 浏览器和 Servlet 通信使用的是什么协议? 【参考答案】 浏览器和 Servlet 之间使用的是 HTTP (HyperText Transfer Protocol) 协议进行通信。
- Servlet 运行在 Web 容器中,专门用于接收和响应来自客户端(通常是浏览器)的 HTTP 请求。
- 整个过程遵循“请求-响应”模型。
【延伸考点讲解】
- 协议层级:HTTP 属于应用层协议,通常建立在 TCP/IP 协议栈之上。
- 版本演进:目前主流使用的是 HTTP/1.1,而 HTTP/2 和 HTTP/3 正在普及。HTTP/2 引入了多路复用,HTTP/3 则基于 QUIC 协议。
- 无状态性:HTTP 协议本身是无状态的,这意味着服务器不会记住之前的请求。为了在多个请求之间保持状态,通常需要结合 Cookie 或 Session 技术。
【问题】 什么是 HTTP 隧道? 【参考答案】 HTTP 隧道(HTTP Tunneling)是一种利用 HTTP 或 HTTPS 协议来封装其他网络协议(如 SSH、Telnet、TCP)进行通信的技术。
- 核心逻辑:HTTP 协议作为“载体”,将非 HTTP 流量伪装成正常的 HTTP 请求。
- 作用:主要用于突破防火墙限制(防火墙通常允许 80 或 443 端口通过)或在受限网络环境中建立安全的通信链路。
【延伸考点讲解】
- 工作方式:通常使用 HTTP 的
CONNECT方法。客户端发送CONNECT请求到代理服务器,代理服务器建立到目标服务器的 TCP 连接后,开始透传后续的原始数据流。 - 应用场景:跨越严格限制的内网、访问被封锁的服务、以及在不支持特定协议的网络中通过 HTTP 管道传输数据。
- 安全风险:虽然 HTTP 隧道可以绕过防火墙,但也可能被恶意软件利用来建立隐藏的命令与控制(C&C)通道。
- 工作方式:通常使用 HTTP 的
【问题】
- sendRedirect() 和 forward() 方法有什么区别?
【参考答案】
这两者都是用于 Servlet 之间的跳转,但有本质的区别:
- 请求转发 (Forward):服务器内部行为。服务器直接访问目标地址,读取内容并发送给浏览器。浏览器地址栏 不变。可以共享
request域中的数据。 - 重定向 (Redirect):客户端行为。服务器返回 302 状态码告知浏览器去请求新地址。浏览器地址栏 改变。无法共享
request域数据。
- 请求转发 (Forward):服务器内部行为。服务器直接访问目标地址,读取内容并发送给浏览器。浏览器地址栏 不变。可以共享
| 特性 | 请求转发 (Forward) | 重定向 (Redirect) | | :— | :— | :— | | 跳转位置 | 服务器内部跳转 | 客户端重新请求 | | 请求次数 | 1 次 | 2 次 | | 地址栏 | 不变 | 变化 | | 数据共享 | 共享 request 域数据 | 不共享 | | 效率 | 较高 | 较低 | 【延伸考点讲解】
- 实现方式:Forward 使用
request.getRequestDispatcher().forward();Redirect 使用response.sendRedirect()。 - 应用场景:Forward 常用于登录成功后的内部页面跳转;Redirect 常用于注销后的页面跳转或跳转到外部网站。
- 路径问题:Forward 的路径只能是本 Web 应用内的资源;Redirect 可以是任何合法的 URL(包括外部链接)。
【问题】
- 什么是 URL 编码和 URL 解码?
【参考答案】
URL 编码(URL Encoding,也称为百分号编码)是用于在 URL 中包含非 ASCII 字符或特殊字符的一种机制。
- URL 编码:将 URL 中的特殊字符(如空格、中文、特殊符号)转换为
%后跟两位十六进制数的形式。例如,空格会被转换为%20或+。 - URL 解码:将经过编码的字符串还原为原始字符的过程。 【延伸考点讲解】
- URL 编码:将 URL 中的特殊字符(如空格、中文、特殊符号)转换为
- 必要性:URL 协议规定只能使用特定的 ASCII 字符集。非 ASCII 字符(如中文)和具有特殊含义的保留字符(如
&,=,?,/)如果在参数中直接使用,可能会导致 URL 解析错误。 - Java 实现:在 Java 中,可以使用
java.net.URLEncoder.encode(str, "UTF-8")进行编码,使用java.net.URLDecoder.decode(str, "UTF-8")进行解码。 - 常见字符转换:空格通常转为
%20,中文字符根据字符集(通常是 UTF-8)转为多个%xx组合。
【问题】
如何让页面做到自动刷新?
【参考答案】
在 Servlet 中,可以通过设置 HTTP 响应头中的 Refresh 属性来实现页面的自动刷新或定时跳转。
- 实现方式:使用
response.setHeader("Refresh", "秒数; URL=跳转地址")。 - 示例:
response.setHeader("Refresh", "5; URL=http://example.com")表示 5 秒后自动跳转到指定页面。如果省略 URL 部分,则表示每隔指定秒数刷新当前页面。 【延伸考点讲解】- HTML 实现方式:除了在服务端通过 Servlet 设置,也可以在 HTML 的
<head>标签中使用<meta http-equiv="refresh" content="5">达到同样的效果。 - JavaScript 实现方式:使用
setTimeout("location.reload()", 5000)或location.href='...'也可以实现更灵活的定时刷新或跳转逻辑。 - 应用场景:常用于支付完成后的等待页面、操作成功后的自动返回、或者简单的实时数据监控页面。
- HTML 实现方式:除了在服务端通过 Servlet 设置,也可以在 HTML 的
【问题】 如何解决 Servlet 的线程安全问题? 【参考答案】 Servlet 默认以单实例多线程的方式运行,因此如果多个线程并发访问同一个 Servlet 实例中的共享资源(如成员变量),就可能产生线程安全问题。解决办法如下:
- 避免使用成员变量:尽量不要在 Servlet 类中定义成员变量。如果需要存储数据,应将其定义在
doGet()或doPost()方法内部作为局部变量,因为局部变量是线程私有的。 - 使用同步机制:如果必须访问共享资源,可以使用
synchronized关键字对相关代码块进行加锁。但由于这会显著降低并发性能,应谨慎使用。 - 使用线程安全的对象:例如,如果需要计数,可以使用
AtomicInteger代替普通的int成员变量。 - 只读属性定义为 final:如果 Servlet 类中有一些初始化后就不再更改的属性,应将其定义为
final类型,这样在多线程环境下是安全的。 【延伸考点讲解】 - Servlet 容器的工作模型:容器(如 Tomcat)通常会为每个进来的 HTTP 请求分配一个独立的线程。如果请求访问的是同一个 Servlet,这些线程会并发执行该 Servlet 的
service()方法。 - SingleThreadModel 接口:这是早期的 Servlet 规范中提供的一个接口,用于强制 Servlet 以单线程模式运行。但由于它无法真正解决线程安全问题且严重影响性能,在 Servlet 2.4 以后已被废弃。
- 无状态设计:在开发 Servlet 时,应尽可能将其设计为“无状态”的,即不保存任何与特定请求相关的中间状态信息在实例变量中。
JSP
【问题】
- 什么是 JSP 页面?
【参考答案】
JSP(JavaServer Pages)是一种支持在 HTML、XML 等静态文本中嵌入 Java 代码和 JSP 标签的技术,用于开发动态网页。
- 本质:JSP 本质上是一个简化的 Servlet。它在第一次被访问时会被 Web 容器(如 Tomcat)翻译成一个 Java 类(Servlet 源文件)并编译为
.class文件执行。 - 构成:由静态内容(HTML/XML/CSS/JS)和动态内容(JSP 元素:如指令、脚本片段、表达式、标准标签库等)组成。 【延伸考点讲解】
- 本质:JSP 本质上是一个简化的 Servlet。它在第一次被访问时会被 Web 容器(如 Tomcat)翻译成一个 Java 类(Servlet 源文件)并编译为
- MVC 角色:在经典的 MVC(Model-View-Controller)设计模式中,JSP 通常充当 View(视图) 角色,负责数据的展示。
- 生命周期:包括编译阶段(翻译成 Servlet)、初始化阶段、执行阶段和销毁阶段。由于存在编译过程,JSP 第一次访问时通常比后续访问慢。
- 与 HTML 的区别:HTML 是静态的,所有用户看到的页面内容都相同;JSP 是动态的,可以根据用户请求和服务器端数据实时生成不同的 HTML 内容。
【问题】
- JSP 请求是如何被处理的?
【参考答案】
当浏览器发起对
.jsp文件的请求时,Web 容器(如 Tomcat)会按照以下步骤处理: - 查找 Servlet:容器检查该 JSP 是否已被翻译并编译成 Servlet 类。
- 翻译与编译:如果是第一次请求或 JSP 文件已更改,容器将 JSP 源码翻译成 Java 源代码(Servlet 类),并编译为
.class字节码文件。 - 加载与实例化:容器加载编译后的类,创建实例,并调用其初始化方法。
- 执行服务:容器调用 Servlet 的
_jspService()方法处理请求并生成响应。 - 返回响应:将生成的 HTML 内容发送回客户端浏览器。 【延伸考点讲解】
- 编译开销:由于翻译和编译过程较为耗时,JSP 页面在被修改后的首次访问会有明显的延迟。生产环境中通常会预编译 JSP 以提高性能。
- 生成的类名:在 Tomcat 中,JSP 翻译后的 Servlet 类通常位于
work目录下,类名通常以_开头(如_index_jsp)。 - JSP 引擎:专门负责处理 JSP 的组件,如 Tomcat 中的 Jasper。
【问题】
- JSP 有什么优点? 【参考答案】 JSP 相比于传统的 Servlet 具有以下显著优点:
- 开发效率高:JSP 允许在 HTML 中直接嵌入 Java 代码,更符合网页设计的习惯,尤其在处理复杂的展现层逻辑时。
- 易于维护:表现层代码(HTML)与业务逻辑(Java)可以相对分离(通过标签库和 EL 表达式),方便美工和开发人员分工。
- 预编译机制:虽然第一次访问较慢,但之后容器会直接执行编译好的
.class文件,运行速度很快。 - 强大的标签库支持:支持标准标签库(JSTL)和自定义标签,可以极大地简化页面代码,提高代码复用性。
- 跨平台与组件化:继承了 Java 的跨平台特性,并能与 JavaBean 等组件无缝结合。 【延伸考点讲解】
- JSP vs Servlet:Servlet 适合处理业务逻辑和控制流程,但输出 HTML 非常痛苦;JSP 适合数据展示,但如果嵌入过多的 Java 代码(Scriptlet)会使页面难以维护。
- 现代趋势:随着前后端分离架构(如 Vue/React + RESTful API)的流行,JSP 的使用场景正在减少。但在一些传统项目或内部管理系统中,JSP 依然具有很强的生命力。
- 安全考量:在使用 JSP 展示数据时,应注意防范 XSS 攻击,建议使用 JSTL 标签或 EL 表达式进行自动转义。
【问题】
- include 指令与 include 动作的区别?
【参考答案】
两者都用于在 JSP 页面中包含其他文件,但其执行时机和原理完全不同:
- include 指令 (
<%@ include file="..." %>):- 静态包含:在编译阶段执行。
- 原理:JSP 引擎在将 JSP 翻译成 Servlet 时,直接将目标文件的内容插入到当前位置,合并后再进行编译。
- 特点:速度快,但目标文件发生变化时,主页面可能需要重新编译才能看到变化。
- include 指令 (
- include 动作 (
<jsp:include page="..." />):- 动态包含:在请求处理阶段执行。
- 原理:主页面在执行时调用目标页面的
service()方法,并将其产生的响应结果插入到当前位置。 - 特点:支持传递参数(使用
<jsp:param>),目标页面发生变化后能立即生效。
| 特性 | include 指令 | include 动作 |
| :— | :— | :— |
| 语法 | <%@ include file="..." %> | <jsp:include page="..." /> |
| 包含时机 | 翻译/编译阶段(静态) | 请求处理阶段(动态) |
| 合并对象 | 源码合并 | 结果合并 |
| 参数传递 | 不支持 | 支持 (<jsp:param>) |
【延伸考点讲解】
- 变量冲突:由于 include 指令是源码合并,被包含文件和主文件不能有同名的变量定义,否则会报编译错误。而 include 动作是方法调用,各自拥有独立的变量作用域。
- 性能对比:静态包含在运行时没有额外开销,性能略优;动态包含每次请求都会执行调用逻辑,但更灵活。
- 最佳实践:如果包含的是静态内容(如版权声明、纯 HTML 头部),推荐使用 include 指令;如果包含的是动态内容(需要传参或经常变动),推荐使用 include 动作。
【问题】
- 什么是 Scriptlets?
【参考答案】
Scriptlets 是嵌入在 JSP 页面中的一段 Java 代码片段。
- 语法:使用
<% ... %>标签包裹。 - 作用:允许开发者在 JSP 页面中编写复杂的 Java 逻辑,如循环、条件判断、变量声明等。这些代码最终会被原封不动地放入翻译后的 Servlet 的
_jspService()方法中。 【延伸考点讲解】
- 语法:使用
- 现代开发建议:在现代 Java Web 开发中,强烈建议避免使用 Scriptlets。过多的 Scriptlets 会导致 HTML 与 Java 代码高度耦合,使页面难以阅读和维护。
- 替代方案:推荐使用 EL 表达式、JSTL 标签库 或 自定义标签 来取代 Scriptlets。这样可以实现逻辑与展示的彻底分离,更符合 MVC 架构。
- 变量作用域:在 Scriptlet 中声明的变量通常是局部变量,仅在
_jspService()方法内有效。
【问题】
- 声明(Declaration) 在哪里?
【参考答案】
JSP 声明(Declaration)用于在 JSP 页面对应的 Servlet 类中声明成员变量、成员方法或静态块。
- 语法:使用
<%! ... %>标签包裹。 - 位置:在翻译后的 Servlet 类中,这些声明会被放在
_jspService()方法之外,作为类的成员。 - 作用:声明随后的表达式或 Scriptlet 中可以使用的全局变量或方法。 【延伸考点讲解】
- 语法:使用
- 与 Scriptlet 的区别:Scriptlet (
<% ... %>) 声明的是_jspService()方法内的局部变量;而声明 (<%! ... %>) 产生的是类的成员变量(全局变量)。 - 线程安全风险:由于 JSP 在容器中默认是单例的,在
<%! ... %>中声明的变量会被所有请求共享,因此必须非常注意线程安全问题。 - 方法定义:如果需要在 JSP 中定义方法,必须使用声明标签
<%! ... %>,而不能在普通的 Scriptlet 标签中定义。
【问题】
- 什么是表达式(Expression)?
【参考答案】
JSP 表达式用于将 Java 表达式的结果直接输出到响应页面中。
- 语法:使用
<%= ... %>标签包裹。 - 原理:JSP 引擎在翻译时,会将表达式中的内容作为参数放入
out.print()方法中。 - 特点:表达式末尾 不能 加分号
;,因为它被直接作为方法的参数。 【延伸考点讲解】
- 语法:使用
- 与 Scriptlet 的区别:Scriptlet (
<% ... %>) 用于编写代码逻辑,不直接产生输出(除非显式调用out.print);表达式 (<%= ... %>) 专门用于产生输出。 - 替代方案:在现代开发中,推荐使用 EL 表达式 (
${...}) 来取代 JSP 表达式。EL 表达式语法更简洁,且具有更好的容错性和空值处理机制。 - 自动转类型:JSP 表达式会自动调用结果对象的
toString()方法将其转换为字符串输出。
【问题】
- 隐含对象是什么意思?有哪些隐含对象?
【参考答案】
JSP 隐含对象(Implicit Objects)是 JSP 容器自动创建并注入到每个 JSP 页面中的一组预定义变量。开发者无需声明即可直接使用。
- 核心隐含对象:
- request:
HttpServletRequest实例,代表客户端请求。 - response:
HttpServletResponse实例,代表服务端响应。 - session:
HttpSession实例,代表用户会话。 - application:
ServletContext实例,代表整个 Web 应用上下文。 - out:
JspWriter实例,用于向响应体输出内容。 - pageContext:
PageContext实例,提供对 JSP 页面所有范围及命名空间的访问。 - page:当前 JSP 实例本身(相当于
this)。 - config:
ServletConfig实例,代表 Servlet 配置。 - exception:
Throwable实例,仅在错误页面(isErrorPage="true")中可用。 【延伸考点讲解】 - 作用域范围:这些对象通常与 JSP 的四个作用域相关:
page,request,session,application。 - 实现原理:容器在将 JSP 翻译成 Servlet 时,会在
_jspService()方法的开头自动定义并初始化这些局部变量。 - EL 表达式对应:在 EL 表达式中,也有类似的隐式对象,如
pageContext,requestScope,sessionScope等。
【问题】 jsp 和 Servlet 有什么区别? 【参考答案】 JSP 和 Servlet 都是 Java Web 开发的核心技术,它们的主要区别在于侧重点和表现形式:
- JSP (JavaServer Pages):本质上是 Servlet,但侧重于 视图展示 (View)。它允许在 HTML 中嵌入 Java 代码,更适合编写动态页面。
- Servlet:侧重于 业务逻辑控制 (Controller)。它是在 Java 代码中编写 HTML 输出,适合处理请求、访问数据库和分发逻辑。
核心差异:
- 角色定位:在 MVC 设计模式中,Servlet 充当控制器(Controller),JSP 充当视图(View)。
- 编写方式:JSP 是在 HTML 中写 Java,Servlet 是在 Java 中写 HTML。
- 编译过程:JSP 必须先被翻译成 Servlet 源文件并编译为
.class才能运行;Servlet 直接由 Java 源文件编译而来。 【延伸考点讲解】 - JSP 引擎工作流:当第一次访问 JSP 时,Web 容器(如 Tomcat)会将其翻译为
.java文件,然后编译为.class文件,并加载执行。这也就是为什么第一次访问 JSP 会慢一些的原因。 - 前后端分离趋势:在现代开发中,JSP 已逐渐被 Thymeleaf 等模板引擎或完全的前后端分离(RESTful API + 前端框架)所取代,但在维护老项目时依然非常重要。
- 最佳实践:建议不要在 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) 时传递数据 |
【延伸考点讲解】
- 生命周期:
getParameter的数据随请求产生;getAttribute的数据只在当前的请求链中有效(即转发过程中有效,重定向后失效)。 - 重定向 vs 转发:在 转发 (Forward) 时,可以使用
getAttribute传递对象;但在 重定向 (Redirect) 时,由于是两次完全独立的请求,getAttribute的值会丢失,此时只能通过getParameter(将参数拼接到 URL 后)来传值。 - 数据转换:因为
getAttribute返回的是Object,在使用时必须注意空指针检查和正确的类型转换。
【问题】 JSP 中的四种作用域是哪几个? 【参考答案】 JSP 共有四种主要的作用域(Scope),用于在不同的生命周期和范围内共享数据:
- pageScope (页面作用域):数据仅在当前 JSP 页面有效。一旦页面执行完毕并返回响应,数据即被销毁。
- requestScope (请求作用域):数据在同一次 HTTP 请求中有效。如果发生请求转发(Forward),目标页面仍可访问该数据。
- sessionScope (会话作用域):数据在整个用户会话期间有效。只要浏览器不关闭且 Session 未超时,用户访问该应用的所有页面都能共享数据。
- applicationScope (应用作用域):数据在整个 Web 应用运行期间有效。所有用户、所有页面都共享同一份数据,直到服务器关闭。
| 作用域 | 对应对象 | 范围说明 |
| :— | :— | :— |
| page | pageContext | 当前页面 |
| request | request | 同一次请求(含转发) |
| session | session | 整个会话(单浏览器) |
| application | application | 整个应用(所有用户) |
【延伸考点讲解】
- 查找顺序:当使用 EL 表达式(如
${user})而未指定作用域时,容器会按照 page → request → session → application 的顺序从小到大进行查找,直到找到为止。 - 内存影响:作用域越大,数据驻留内存的时间越长。因此,应尽量使用小范围的作用域,以减少内存消耗。
- 线程安全:
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 才能生效。
【延伸考点讲解】
- 常用端口修改场景:在同一台服务器上启动多个 Tomcat 实例时,必须修改这些端口以避免端口冲突。
- HTTP 默认端口 80:如果希望用户直接通过域名或 IP 访问而不需要输入端口号,应将 Tomcat 的 HTTP 端口修改为 80。
- 安全配置:出于安全考虑,建议修改或禁用默认的 8005 关闭端口,或者设置复杂的
shutdown字符串,防止被恶意关闭。
【问题】 Tomcat 容器是如何创建 Servlet 类实例的?用到了什么原理? 【参考答案】 Tomcat 容器创建 Servlet 实例的过程主要依赖于 Java 反射机制。
- 解析配置:容器启动或应用部署时,解析
web.xml或@WebServlet注解中的 Servlet 注册信息。 - 类加载:使用类加载器(ClassLoader)加载 Servlet 的全类名。
- 实例化:通过反射调用该类的无参构造方法创建对象实例。
- 初始化:调用实例的
init(ServletConfig config)方法完成初始化。
实例化时机:
- 启动时实例化:如果在配置中设置了
<load-on-startup>为正数,容器会在应用启动时立即创建实例。 - 延迟实例化:如果未设置或值为负数,容器会在该 Servlet 第一次接收到请求时才创建实例。
【延伸考点讲解】
- 单例模式:默认情况下,Servlet 在容器中是单例的。这意味着对于同一个 Servlet 类,容器只会创建一个实例来处理所有的并发请求。
- 线程安全:正因为是单例多线程运行,开发者应避免在 Servlet 中定义成员变量以防止线程安全问题。
- 反射的优缺点:反射提供了极大的灵活性,允许容器在运行时动态加载和创建对象,但相比直接
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"
【延伸考点讲解】
- 堆内存与元空间:堆内存主要存放对象实例,元空间存放类元数据。合理分配比例可以减少垃圾回收(GC)的频率和停顿时间。
- 内存溢出(OOM):如果配置不当,可能会出现
java.lang.OutOfMemoryError: Java heap space(堆内存不足)或java.lang.OutOfMemoryError: Metaspace(元空间不足)。 - 调优原则:初始内存 (
-Xms) 和最大内存 (-Xmx) 建议设置为相同,以避免 JVM 在运行时频繁调整堆大小带来的性能开销。
【问题】 Tomcat 有哪几种 Connector 运行模式? 【参考答案】 Tomcat 主要支持以下三种 Connector 运行模式,它们决定了 Tomcat 处理 I/O 请求的方式:
- BIO (Blocking I/O):
- 特点:阻塞式 I/O。每个请求都会占用一个独立的线程,直到请求处理完成。
- 评价:并发性能最低,适用于请求量较小的场景。在 Tomcat 8.5 以后已被移除。
- NIO (Non-blocking I/O):
- 特点:非阻塞 I/O。基于 Java NIO 库实现,利用多路复用技术(Selector),可以使用较少的线程处理大量的并发连接。
- 评价:Tomcat 8.x 默认的运行模式,具有良好的并发性能和稳定性。
- APR (Apache Portable Runtime):
- 特点:利用 Apache 的可移植运行库,通过 JNI 调用本地(C/C++)代码来处理 I/O。
- 评价:在处理静态资源和 SSL 握手时性能极高,是高并发应用的首选模式,但需要额外安装 APR 库和 Native 组件。 【延伸考点讲解】
- 模式切换:可以通过修改
conf/server.xml中<Connector>标签的protocol属性来切换模式。例如Http11NioProtocol代表使用 NIO 模式。 - NIO2 (AIO):从 Tomcat 8 开始还支持 NIO2(异步 I/O),它进一步提升了 I/O 的处理效率,特别是在处理长连接时。
- 选择建议:大多数场景下使用默认的 NIO 即可满足需求。如果对性能有极致追求且环境允许安装 Native 库,可以考虑使用 APR。
JDBC
【问题】
- 什么是 JDBC?
【参考答案】
JDBC(Java Database Connectivity)是 Java 语言中用于连接和操作关系型数据库的一套标准 API(
java.sql和javax.sql包)。- 本质:它是一组接口规范。不同的数据库厂商(如 Oracle, MySQL, PostgreSQL)提供各自的实现类(驱动程序),Java 程序通过这些统一的接口与底层数据库通信。
- 作用:实现了 Java 程序与数据库的解耦。开发者只需学习一套 JDBC API,即可编写能够访问多种不同数据库的应用程序,而无需关心底层特定数据库的通信细节。 【延伸考点讲解】
- 核心接口:包括
DriverManager(驱动管理)、Connection(连接)、Statement(执行 SQL)、ResultSet(结果集)。 - 数据访问层演进:由于直接使用原生 JDBC 存在代码冗余、连接管理繁琐等问题,现代开发通常使用基于 JDBC 封装的框架,如 MyBatis, Spring Data JPA (Hibernate)。
- SPI 机制:JDBC 利用了 Java 的 SPI (Service Provider Interface) 机制,使得驱动程序可以被动态地加载到应用中。
【问题】
- 解释下驱动(Driver)在 JDBC 中的角色。
【参考答案】
JDBC 驱动(JDBC Driver)是连接 Java 应用程序与特定数据库管理系统(DBMS)之间的桥梁。
- 角色:它提供了
java.sql包中定义的 JDBC 接口的具体实现。 - 职责:驱动程序负责处理底层通信细节,如网络连接、数据类型转换以及将 SQL 语句转换为数据库能识别的协议。
- 必备实现:驱动必须提供对
Connection,Statement,PreparedStatement,ResultSet和Driver等核心接口的实现。 【延伸考点讲解】
- 角色:它提供了
- 四种驱动类型:
- Type 1:JDBC-ODBC 桥驱动(已废弃)。
- Type 2:本地 API 驱动(依赖本地 C/C++ 库)。
- Type 3:网络协议驱动(中间件模式)。
- Type 4:纯 Java 驱动(目前最常用,直接与数据库通信)。
- 加载机制:现代 JDBC 驱动通常利用 Java SPI 机制,在类路径下包含
META-INF/services/java.sql.Driver文件,使得DriverManager能够自动发现并加载驱动。 - 驱动注册:虽然现代版本支持自动加载,但了解
Class.forName("com.mysql.cj.jdbc.Driver")的原理依然重要,它触发了驱动类的静态代码块,从而向DriverManager注册自身。
【问题】
- Class.forName() 方法有什么作用?
【参考答案】
在 JDBC 中,
Class.forName()方法的主要作用是 动态加载数据库驱动类 并触发其 静态代码块 的执行。- 动态加载:在运行时根据字符串名称加载指定的类到 JVM 中。
- 注册驱动:所有的 JDBC 驱动类在其静态代码块中都会调用
DriverManager.registerDriver()方法将自己注册到驱动管理器中。 - 示例:
Class.forName("com.mysql.cj.jdbc.Driver")。 【延伸考点讲解】
- 自动加载机制 (JDBC 4.0+):现代 JDBC 驱动利用了 Java 的 SPI (Service Provider Interface) 机制。只要驱动 JAR 包在类路径下,
DriverManager在获取连接时会自动发现并加载驱动,因此在很多现代代码中不再显式调用Class.forName()。 - 反射原理:该方法属于 Java 反射机制的一部分。它除了加载类,还会确保该类被初始化(即执行 static 块)。
- 异常处理:调用此方法时必须捕获
ClassNotFoundException,以防止驱动类名拼写错误或缺少依赖包。
【问题】
- PreparedStatement 比 Statement 有什么优势?
【参考答案】
在 JDBC 开发中,
PreparedStatement是Statement的子接口,它具有以下显著优势: - 防止 SQL 注入:这是最重要的优势。它使用占位符
?进行参数化查询,能有效防止非法字符破坏 SQL 结构。 - 性能更高:它是预编译的。数据库会对相同的 SQL 语句进行缓存,多次执行时无需重新解析和编译,显著提升效率。
- 可读性与维护性更好:避免了繁琐的字符串拼接,代码更加简洁清晰。
- 支持多种数据类型:提供了丰富的
setXxx()方法(如setInt,setString,setDate),可以更方便地处理各种数据类型。 【延伸考点讲解】 - 工作原理:当调用
prepareStatement(sql)时,SQL 模板被发送到数据库进行预编译。之后调用setXxx()仅发送参数,减少了网络传输负担。 - 二进制大对象支持:在处理 BLOB(二进制大对象)或 CLOB(字符大对象)时,
PreparedStatement是唯一的选择。 - 缓存限制:预编译的优势依赖于数据库的缓存机制。如果 SQL 语句结构每次都发生变化(即不使用占位符),则无法享受性能提升。
【问题】
- 什么时候使用 CallableStatement?用来准备 CallableStatement 的方法是什么?
【参考答案】
CallableStatement主要用于执行数据库中的 存储过程 (Stored Procedures)。- 作用:存储过程是预先在数据库中编译好并存储的一段 SQL 逻辑,可以接受输入参数(IN),也可以返回结果或输出参数(OUT)。
- 准备方法:通过
Connection对象的prepareCall(String sql)方法来创建。 - 示例:
CallableStatement cstmt = conn.prepareCall("{call get_user_info(?, ?)}");【延伸考点讲解】
- 参数处理:支持
IN(输入)、OUT(输出)和INOUT(进出)参数。使用setXxx()设置输入参数,使用registerOutParameter()注册输出参数。 - 优势:存储过程预先编译,执行效率高;减少了 Java 应用与数据库之间的网络数据传输;通过封装业务逻辑,增强了数据的安全性和模块化。
- 调用语法:通常使用
{call procedure_name(?, ?)}这种转义语法,以保证跨数据库的兼容性。
【问题】
- 数据库连接池是什么意思?
【参考答案】
数据库连接池(Database Connection Pooling)是一种在应用服务器启动时就建立若干个数据库连接,并将它们维护在一个“池”中供应用程序复用的技术。
- 背景:传统的数据库访问方式(即用即开,用完即关)在面临高并发请求时,频繁的建立和断开连接会消耗大量的系统资源(如 CPU、内存、网络),严重降低性能。
- 原理:当应用程序需要访问数据库时,直接从连接池中“借出”一个已存在的空闲连接;使用完毕后,再将其“归还”到池中,而不是物理关闭连接。
- 作用:显著减少了连接创建和销毁的开销,提高了系统的响应速度和资源利用率。 【延伸考点讲解】
- 核心参数:
- initialSize:连接池启动时的初始连接数。
- maxActive/maxTotal:池中允许的最大活跃连接数。
- maxIdle/minIdle:最大/最小空闲连接数。
- maxWait:当连接耗尽时,应用获取连接的最大等待时间。
- 常见实现:在 Java 开发中,常用的连接池包括 HikariCP(目前性能最好、最推荐)、Druid(阿里巴巴开源,功能丰富,带监控)、DBCP 和 C3P0(较老)。
- 连接有效性检查:连接池通常会定期检查池中连接的有效性(如使用
validationQuery),防止因数据库端超时断开而导致应用拿到的连接不可用。
【问题】 数据库连接池的工作机制是什么? 【参考答案】 数据库连接池的核心机制在于“池化管理”和“连接复用”,具体步骤如下:
- 初始化:在系统启动时,连接池根据配置创建一定数量的初始连接(InitialSize),并将其保存在内存队列或集合中。
- 分配连接:当应用程序请求数据库连接时,连接池首先检查池中是否有空闲连接。如果有,则直接返回;如果没有,且当前连接数未达到最大值(MaxActive),则创建一个新连接;如果已达上限,则请求进入等待状态或抛出异常。
- 使用与归还:应用程序执行 SQL 操作后,调用
close()方法。注意,此时的close()是被连接池重写过的逻辑,它并不会物理关闭 TCP 连接,而是将连接状态标记为空闲并归还到池中。 - 管理与维护:连接池会定期检查空闲连接的有效性,并销毁长期不使用的多余连接(MinIdle 之外的连接),以维持池的健康状态。 【延伸考点讲解】
- 连接池代理:连接池通常使用 代理模式 (Proxy Pattern) 拦截
Connection.close()方法。当应用调用close时,代理对象捕获该调用并执行“回收”逻辑。 - 性能优势:规避了 TCP 三次握手和四次挥手的网络开销,以及数据库端创建进程/线程的昂贵资源消耗。
- 超时处理:连接池提供了获取连接的超时配置(MaxWait),防止因数据库响应慢或连接泄露导致应用线程长时间阻塞。
【问题】 说下原生 JDBC 操作数据库流程? 【参考答案】 原生 JDBC 操作数据库的标准流程通常分为以下六个步骤:
- 加载驱动:使用
Class.forName()加载特定数据库的驱动程序(JDBC 4.0 后可省略显式加载)。 - 建立连接:通过
DriverManager.getConnection(url, user, password)获取数据库连接对象Connection。 - 创建语句对象:根据需求通过
Connection创建Statement或PreparedStatement对象。 - 执行 SQL:调用
executeQuery()(查询)或executeUpdate()(增删改)方法执行 SQL 语句。如果是参数化查询,需先通过setXxx()设置参数。 - 处理结果集:如果是查询操作,通过遍历
ResultSet获取数据。 - 释放资源:按照“先开启后关闭”的原则,依次关闭
ResultSet、Statement和Connection(通常放在finally块中)。 【延伸考点讲解】 - try-with-resources:在 JDK 7 及以后版本,推荐使用
try-with-resources语法自动管理资源关闭,避免因手动关闭不当导致的连接泄露。 - PreparedStatement 优选:在开发中应始终优先使用
PreparedStatement而非Statement,以防止 SQL 注入并提高执行效率。 - 事务管理: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(短连接)进行控制。
【延伸考点讲解】
- Keep-Alive 存活时间:长连接不会永久保持,通常由服务器配置超时时间(Timeout)和最大请求数(Max Requests)。
- HTTP/2 多路复用:在 HTTP/1.1 的长连接中,请求是串行的;而 HTTP/2 实现了真正的多路复用,可以在同一个 TCP 连接上并发处理多个请求。
- 适用场景:短连接适用于请求频率极低、用户量巨大的简单交互;长连接适用于高频交互、需要加载大量静态资源的网页应用。
【问题】 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 共享。常见方案包括:
- 基于 Redis 的集中存储(最常用):将所有服务器的 Session 数据统一存储在 Redis 缓存集群中。各节点通过特定的拦截器(如 Spring Session)从 Redis 中读写 Session。
- Session 复制(多播/同步):当一台服务器创建或修改 Session 后,通过网络将数据同步到集群中的其他所有服务器。适用于节点较少(< 5个)的场景。
- Session 粘性(Sticky Session):在负载均衡器(如 Nginx)上配置,通过 IP Hash 或 Cookie 确保同一用户的请求始终转发到同一台服务器。缺点是服务器宕机会导致数据丢失。
- 客户端存储(基于 Cookie/JWT):将 Session 数据加密后存储在客户端 Cookie 中。优点是服务器无状态,扩展性极强;缺点是 Cookie 大小受限且存在安全性风险。 【延伸考点讲解】
- Spring Session:它是 Java 生态中解决 Session 共享的标准方案,支持 Redis、JDBC、MongoDB 等多种存储后端,且对业务代码无侵入。
- 数据序列化:在将 Session 存入 Redis 时,需要注意对象的序列化问题。建议使用 JSON 序列化以提高跨平台兼容性和可读性。
- 一致性哈希:在某些高级负载均衡方案中,利用一致性哈希算法可以更优雅地实现 Session 粘性,减少因节点变动导致的 Session 失效。
【问题】 在单点登录中,如果 cookie 被禁用了怎么办? 【参考答案】 如果浏览器禁用了 Cookie,单点登录(SSO)及普通的 Session 机制将无法通过 Cookie 自动携带 Session ID。此时可以采用以下替代方案:
- URL 重写 (URL Rewriting):将 Session ID 显式地包含在每一个 URL 链接中(如
.../page?jsessionid=XXX)。服务端通过解析 URL 获取会话标识。 - Token + 响应头/请求头:这是目前最常用的方案。用户登录后,服务端返回一个 Token(如 JWT),前端将其存储在
localStorage或sessionStorage中。后续请求通过自定义 HTTP 请求头(如Authorization: Bearer <token>)手动携带该 Token。 - 隐藏表单域:在 HTML 表单中添加一个隐藏字段(Hidden Field),每次提交表单时将会话标识传回服务器。 【延伸考点讲解】
- 安全性对比:URL 重写存在安全风险,因为 Session ID 会暴露在浏览器历史记录和服务器日志中。相比之下,使用请求头传输 Token(配合 HTTPS)更加安全。
- 无状态架构:基于 JWT 的 Token 方案通常是无状态的,服务器不需要存储会话数据,非常适合大规模分布式系统和移动端应用。
- 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>。直接搜索包含特定关键词的行。 【延伸考点讲解】- 组合技巧:经常配合管道符使用,如
tail -f error.log | grep "Exception",实时监控并只显示包含“Exception”的错误行。 - 大文件处理:禁止使用
cat或vi/vim打开超大型日志文件(如数 GB 的文件),否则可能会导致内存溢出或系统卡顿。应优先使用less或tail。 - 系统日志路径:常见的系统日志通常存放在
/var/log/目录下(如messages,secure,cron等)。
- 组合技巧:经常配合管道符使用,如
【问题】 Linux 怎么关闭进程? 【参考答案】 在 Linux 中关闭进程通常分为“查找”和“终止”两个步骤:
- 查找进程 PID:
- 使用
ps -ef | grep <process_name>或ps aux | grep <process_name>查找目标进程的进程号(PID)。 - 也可以使用
pgrep <process_name>直接获取 PID。
- 使用
- 终止进程:
kill <PID>:发送 SIGTERM 信号,尝试正常关闭进程。kill -9 <PID>:发送 SIGKILL 信号,强制立即终止进程(不推荐作为首选,可能导致数据未保存或资源未释放)。pkill <process_name>:根据进程名批量终止进程。killall <process_name>:终止所有指定名称的进程。 【延伸考点讲解】
- 信号机制:
kill命令默认发送的是信号 15(SIGTERM),它允许进程在退出前进行清理工作;-9发送的是信号 9(SIGKILL),进程无法拦截该信号,会被内核直接杀掉。 - 僵尸进程 (Zombie):如果进程已经变成僵尸状态(状态为 Z),
kill -9也无法将其杀掉。此时需要杀掉其父进程,或者等待 init 进程回收。 - 查看端口占用:如果是想关闭占用特定端口的进程,可以使用
lsof -i:<port>或netstat -nlp | grep <port>找到 PID 后再杀掉。