JavaWeb编程急速入门(Servlet进阶)

一、Web请求与响应解析

1、请求与响应的结构

1.1、URL与URI

URL统一资源定位符,表示Web应用对外暴露的访问地址

示例:http://localhost:8080/index.html

URI统一资源标示符,表示Web应用资源的访问路径

示例:/index.html

1.2、HTTP请求的结构

HTTP请求包含三部分:请求行、请求头、请求体

1.3、Web请求(Request)

当你在浏览器中输入一个网址(URL)并按下回车,浏览器会构建一个HTTP请求并发送到服务器。这个请求包含以下几个关键部分:

1. 请求行(Request Line)

这是请求的第一行,包含三个基本部分:

  • 方法(Method):表示请求的操作类型。常见的有:

    • GET:请求获取指定的资源。

    • POST:向指定资源提交数据(例如提交表单或上传文件)。

    • PUT:替换指定资源的所有当前表示。

    • DELETE:删除指定的资源。

    • HEAD:类似于GET,但只请求响应头,不返回响应体。

    • PATCH:对资源进行部分修改。

  • 请求目标(Request Target):通常是指请求的URL路径和查询字符串(例如 /index.html 或 /search?q=hello)。

  • HTTP版本:使用的HTTP协议版本,如 HTTP/1.1 或 HTTP/2

示例
GET /index.html HTTP/1.1
2. 请求头(Request Headers)

紧接着请求行之后,是若干行键值对(Key-Value),每行一个头字段。它们向服务器传递关于请求的附加信息(元数据)和客户端的详细信息。

常见且重要的请求头:

  • Host:指定请求的服务器的域名和端口号(HTTP/1.1必需字段)。Host: www.example.com

  • User-Agent:告诉服务器客户端的类型(浏览器、操作系统等)。User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36...

  • Accept:声明客户端可以处理的内容类型。Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8

  • Accept-Language:声明客户端偏好的语言。Accept-Language: en-US,en;q=0.5

  • Accept-Encoding:声明客户端可以处理的压缩编码。Accept-Encoding: gzip, deflate, br

  • Content-Type:(用于POST/PUT等方法)请求体的媒体类型。Content-Type: application/json

  • Content-Length:(用于POST/PUT等方法)请求体的长度(字节)。

  • Authorization:包含用于验证客户端身份的凭证(如Bearer Token)。Authorization: Bearer abc123...

  • Cookie:将之前服务器通过Set-Cookie发送的Cookie信息回传给服务器。Cookie: sessionId=abc123; username=john_doe

  • Connection:控制本次请求/响应后连接是否保持。Connection: keep-alive

3. 空行(Blank Line)

请求头结束后,必须有一个空行(即连续的回车换行符 \r\n),用来分隔头部和 body。

4. 请求体(Request Body)

也叫消息体(Message Body),不是所有请求都有。GETHEADDELETE 等通常没有请求体。POSTPUT 等方法通常包含请求体,用于发送数据,如表单数据、JSON、XML、文件等。

示例(JSON格式的请求体):

{
  "username": "john_doe",
  "email": "john@example.com"
}

1.4、Web响应(Response)

服务器处理完请求后,会返回一个HTTP响应。它的结构与请求非常相似。

1. 状态行(Status Line)

响应的第一行,包含三个部分:

  • HTTP版本:如 HTTP/1.1

  • 状态码(Status Code):一个三位数字,表示请求的结果。

    • 1xx:信息性状态码。

    • 2xx:成功(如 200 OK)。

    • 3xx:重定向(如 301 Moved Permanently302 Found)。

    • 4xx:客户端错误(如 404 Not Found)。

    • 5xx:服务器错误(如 500 Internal Server Error)。

  • 原因短语(Reason Phrase):状态码的简短文字描述。

示例:

HTTP/1.1 200 OK
2. 响应头(Response Headers)

与请求头类似,是服务器返回的元数据,描述了响应体的信息以及服务器的指令。

常见且重要的响应头:

  • Content-Type:响应体的媒体类型。Content-Type: text/html; charset=UTF-8

  • Content-Length:响应体的长度(字节)。

  • Content-Encoding:响应体使用的压缩编码(如gzip)。Content-Encoding: gzip

  • Server:服务器软件信息。Server: nginx/1.18.0

  • Set-Cookie:服务器要求客户端设置一个或多个Cookie。Set-Cookie: sessionId=abc123; Path=/; HttpOnly

  • Cache-Control:指示客户端如何缓存响应。Cache-Control: max-age=3600

  • Location:(用于重定向)指定重定向的目标URL。

  • Access-Control-Allow-Origin:(CORS)指定哪些站点可以跨域访问资源。Access-Control-Allow-Origin: *

3. 空行(Blank Line)

同样,响应头结束后必须有一个空行。

4. 响应体(Response Body)

服务器返回的实际内容,如HTML文档、图片、JSON数据、CSS文件等。浏览器会根据 Content-Type 来决定如何呈现它。

示例(HTML格式的响应体):

<!DOCTYPE html>
<html>
<head>
    <title>Example Page</title>
</head>
<body>
    <h1>Hello, World!</h1>
</body>
</html>

2、巧用请求头开发多端应用

这是一个非常实用且高级的前后端协作技巧。“巧用请求头开发多端应用” 的核心思想是:后端通过解析客户端(浏览器、App、小程序等)发来的HTTP请求头中的特定信息,来识别客户端的类型或意图,从而动态地返回最适合该客户端的内容(HTML、JSON、资源文件等)。

核心原理

后端不再仅仅根据URL返回固定内容,而是增加一个逻辑层:

  1. 解析请求头:从收到的请求中读取特定的Header字段。

  2. 识别客户端:根据Header值判断请求来自哪种设备或环境。

  3. 动态响应:根据识别结果,返回差异化的内容。

这种方法相比为不同端设立不同域名(如 m.example.com 和 www.example.com)或不同路径(如 /api/mobile/ 和 /api/web/),维护起来更加灵活和统一。

3、请求转发与响应重定向

多个Servlet之间跳转有两种方式:

request.getRequestDispatcher().forward() - 请求转发

request.sendRedirect() - 响应重定向

简单总结:

  • 用转发:当你想要在服务器内部“偷偷”完成工作,并且需要保持请求数据和浏览器URL不变时。

  • 用重定向:当你需要改变浏览器地址栏的URL,或要跳转到外部站点,或完成表单提交后防止重复提交时。

1、请求转发 (Forward)

工作原理
  1. 浏览器发送一个请求到服务器(Request 1)。

  2. 服务器上的Servlet(或Controller)接收到请求后,在服务器内部将请求转发给另一个资源(如另一个Servlet、JSP页面)进行处理。

  3. 最终由这个目标资源生成响应内容。

  4. 服务器将最终响应返回给浏览器。
    整个过程对浏览器是透明的,它只知道发起了一次请求,并收到了一次响应。

关键特点
  • 地址栏不变:浏览器显示的是最初请求的URL,而不是最终处理请求的资源的URL。

  • 一次请求一次响应:减少了网络通信,效率更高。

  • 数据共享:因为是一次请求,request对象(以及其中的属性,如setAttribute的数据)在整个转发链中都是同一个,可以传递数据。

  • 只能访问本站资源:转发只能发生在当前Web应用内部。

代码示例 (Java Servlet)
// 在第一个Servlet中
protected void doGet(HttpServletRequest request, HttpServletResponse response) {
    // 在请求中设置一些数据,传递给下一个资源
    request.setAttribute("message", "Hello from Forward!");
    
    // 获取请求转发器,指向`/destination`这个资源
    RequestDispatcher dispatcher = request.getRequestDispatcher("/destination");
    // 执行转发
    dispatcher.forward(request, response);
}

// 在 `/destination` 这个Servlet或JSP中,可以获取到`message`属性
String msg = (String) request.getAttribute("message"); // "Hello from Forward!"
适用场景
  • MVC模式中的视图分发:Controller处理完业务逻辑后,将数据放入请求域,然后转发给JSP或Thymeleaf等视图模板进行渲染。

  • 组件化处理:将一个复杂请求拆分成多个Servlet协同处理,每个Servlet负责一个子任务,然后转发给下一个。

  • 隐藏内部资源:保护JSP等资源的真实路径,用户只能访问Controller的URL。

2、响应重定向 (Redirect)

工作原理
  1. 浏览器发送一个请求到服务器(Request 1)。

  2. 服务器处理请求后,返回一个特殊的响应(状态码302/301) 和一个 Location 响应头(包含了新的URL)。

  3. 浏览器接收到这个响应后,自动根据 Location 头中的地址,发起第二次请求(Request 2)到新的URL。

  4. 新URL所在的服务器处理请求并返回最终响应。
    浏览器发起了两次请求,地址栏的URL会变成第二次请求的地址。

关键特点
  • 地址栏变化:浏览器最终显示的是重定向后的新URL。

  • 两次请求两次响应:比转发多一次网络往返,效率稍低。

  • 数据不共享:两次请求是完全独立的。第一个请求的request对象和其中的属性在第二次请求中无法获取。需要通过Session或URL参数来传递数据。

  • 可以重定向到任意URL:可以是同一应用、不同应用,甚至是完全不同的网站(如 https://www.google.com)。

代码示例 (Java Servlet)
protected void doGet(HttpServletRequest request, HttpServletResponse response) {
    // 方法一:设置状态码和Location头
    response.setStatus(HttpServletResponse.SC_FOUND); // 302
    response.setHeader("Location", "/new-location");
    
    // 方法二(推荐):使用便捷方法
    response.sendRedirect("/new-location"); // 内部会设置状态码和Location头
    
    // 如果要传递数据,只能通过Session或URL参数
    // request.setAttribute("msg", "This will be LOST!"); // 无效!
    request.getSession().setAttribute("tempMsg", "This will work via session.");
    // 或者
    response.sendRedirect("/new-location?msg=Hello+via+URL");
}
适用场景
  • 成功提交表单后的跳转(Post-Redirect-Get模式):防止用户刷新页面导致表单重复提交。

  • 用户登录后跳转到目标页面

  • 旧网址迁移到新网址,使用重定向告诉浏览器和搜索引擎新的地址(SEO友好)。

  • 将HTTP请求重定向到HTTPS,提高安全性。

  • 需要将用户引导到外部网站时

二、Session与ServletContext原理

1、cookie

一句话概括:Cookie 是网站为了“记住”你而存储在浏览器里的一小段信息。

它可以看作是你访问某个网站时的 “记忆碎片” 或 “身份证”

一个生动的比喻:咖啡店的会员卡

想象一下你去一家咖啡店:

  1. 第一次光顾:你点了一杯咖啡。店员发现你没有会员卡,于是为你办了一张新卡Set-Cookie),并在卡上记录了一些信息,比如:“顾客:小明,最爱:拿铁,积分:10”。然后把卡交给你保管(存储到浏览器)。

  2. 你把卡收好:你把这张会员卡放在自己的钱包里(浏览器将 Cookie 存储在本地)。

  3. 下次光顾:一周后你再次来到这家店。你一进门,就出示你的会员卡(浏览器自动在请求中带上 Cookie)。店员看到卡后,立刻说:“欢迎回来,小明!老规矩,还是拿铁吗?您目前有10积分。”(服务器读取 Cookie,识别出是你,并恢复了你的偏好和状态)。

  4. 更新信息:你又买了一杯咖啡,积分变成了20。店员更新了你的会员卡信息(服务器发送新的 Cookie 来更新浏览器中的值)。

如果没有这张会员卡(Cookie),店员每次见到你都会像见到一个完全陌生的人,无法提供个性化服务,你每次也需要重新登录、重新设置偏好。

1.1、Cookie 的主要用途

  1. 会话管理(Session Management)

    • 这是最核心的用途。服务器会创建一个唯一的 Session ID 并通过 Cookie 发送给浏览器。浏览器后续每次请求都带上这个 ID,服务器就能找到对应用户的会话数据(如登录状态、购物车内容)。这是大多数网站保持你登录状态的方式。

  2. 个性化(Personalization)

    • 记住用户的偏好设置,如语言、主题、字体大小等。

  3. 跟踪(Tracking)

    • 记录和分析用户的行为。例如,广告商使用第三方 Cookie 来跨站跟踪用户,从而推送精准广告。(注意: 由于隐私问题,现代浏览器正在逐步限制第三方 Cookie)。

1.2、重要的安全与隐私问题

  • 不要存储敏感信息:如密码、信用卡号等不应直接存储在 Cookie 中,因为它们可能在本地被查看或传输中被窃取。

  • HttpOnly 和 Secure 标志:对于像 Session ID 这样的重要 Cookie,务必设置 HttpOnly 和 Secure 属性以增强安全性。

  • 第三方Cookie:由当前网页域名以外的其他域设置的 Cookie,常用于广告追踪。许多浏览器现在默认阻止第三方 Cookie。

  • 隐私法规:像 GDPR(欧盟) 和 CCPA(加州) 这样的法规要求网站在设置非必要的 Cookie 前必须获得用户的明确同意。

2、Session-用户会话

它与 Cookie 紧密相关,但扮演着不同的角色。

一句话概括:Session 是服务器为了跟踪特定用户在整个网站访问期间的状态而创建的一个“档案袋”。

这个档案袋有一个唯一的编号(Session ID),而这个编号通常通过 Cookie 交给用户的浏览器保管。

重温咖啡店比喻:升级版

还记得Cookie的会员卡比喻吗?我们来升级一下,现在Session和Cookie一起工作:

  1. 第一次光顾:你点了一杯咖啡。店员(服务器)发现你没有会员卡。

    • 在店里的后台系统(服务器内存/数据库)中为你创建了一个新档案(创建 Session)。档案号是 #12345,里面记录了“最爱:拿铁,积分:10”。

    • 然后,他给你一张只写有档案号 #12345 的卡片(通过 Set-Cookie 设置一个名为 session_id 的 Cookie,值为 12345)。

  2. 你把卡收好:你把这张只印有号码的卡片放在钱包里(浏览器存储Cookie)。

  3. 下次光顾:一周后你再次来到这家店。

    • 你一进门,就出示你的卡片(浏览器自动在请求中带上 session_id=12345 这个Cookie)。

    • 店员看到卡片上的号码 #12345去后台档案柜里找出对应的档案(服务器用收到的Session ID去查找对应的Session对象)。

    • 店员看到档案后,立刻说:“欢迎回来,小明!老规矩,还是拿铁吗?您目前有10积分。”(服务器使用Session中的数据为你提供个性化服务)。

  4. 更新信息:你又买了一杯咖啡,积分变成了20。店员没有修改你的卡片(Cookie没变),而是更新了后台档案袋里的记录(服务器更新了Session对象中的数据)。

这个比喻的精妙之处在于:

  • Cookie (卡片):只存储一个钥匙(ID),体积小,且由客户保管。

  • Session (后台档案):存储所有重要的、敏感的实际数据(你的偏好、积分、购物车物品),由服务器保管,安全且可存储大量数据。

如果没有Session,所有数据都只能写在会员卡(Cookie)上,卡片容易丢(Cookie丢失)、容易被偷看(不安全)、而且卡片大小有限(Cookie有4KB限制)。

为什么需要 Session?—— HTTP 无状态的问题

HTTP协议本身是“无状态”的。这意味着服务器处理每个请求时,都把它当作一个全新的、独立的请求,它不会记得你之前的任何操作。

这就像得了一种“短期失忆症”。想象一下:

  • 你登录了一个网站,点击“下一页”。

  • 服务器想:“嗯?刚才登录的是你吗?我不记得了,请再登一次。”

  • 你在购物车加了个商品,点击结算。

  • 服务器又想:“购物车?什么购物车?我没给你开过购物车啊。”

这种体验显然是灾难性的。Session 机制就是为了解决HTTP无状态的问题而生的,它是构建交互式Web应用的基石。

2.1、Session常用方法

通用核心概念与方法:无论使用哪种语言,对 Session 的操作都围绕以下几个核心动作

  1. 获取/创建 Session 对象

  2. 向 Session 中存储数据(Set/Write)

  3. 从 Session 中读取数据(Get/Read)

  4. 从 Session 中移除数据(Remove/Delete)

  5. 使 Session 失效(销毁/登出)

  6. 配置与管理 Session(超时时间等)

Java语言示例:在 Java Web 开发中,Session 是通过 HttpServletRequest 对象获取的 HttpSession 对象。

// 1. 获取(或创建)Session对象
// 如果当前请求没有Session,`getSession(true)` 会创建一个新的,`false`则返回null。
HttpSession session = request.getSession();

// 2. 向Session中存储数据
session.setAttribute("userId", 12345);
session.setAttribute("username", "john_doe");
session.setAttribute("shoppingCart", cartObject); // 可以存储对象

// 3. 从Session中读取数据
Integer userId = (Integer) session.getAttribute("userId"); // 需要强制类型转换
String username = (String) session.getAttribute("username");
ShoppingCart cart = (ShoppingCart) session.getAttribute("shoppingCart");

// 如果属性不存在,则返回null
Object value = session.getAttribute("nonExistentKey");

// 4. 从Session中移除特定数据
session.removeAttribute("shoppingCart"); // 移除cart,但session依然存在

// 5. 使整个Session失效(常用用户登出)
session.invalidate(); // 之后此session对象不可再用

// 6. 管理Session
// 设置最大不活动间隔时间(单位:秒)
session.setMaxInactiveInterval(30 * 60); // 30分钟
// 获取Session ID
String sessionId = session.getId();

3、ServletContext

一句话概括ServletContext(Servlet上下文)是Web应用程序的“全局共享空间”或“应用作用域”。 它是一个由Web服务器(如Tomcat)为每个Web应用创建的全局对象,用于在整个Web应用的所有Servlet之间共享数据和资源。

一个生动的比喻:公司的公共公告板

想象一家公司(代表一个Web应用):

  • 每个员工(代表一个Servlet) 都有自己的办公桌(局部变量)和部门共享的文件夹(Servlet之间通过请求传递数据)。

  • 但公司需要一个所有人都能访问的公共空间,比如:

    • 公共公告板:张贴公司通知、全体员工通讯录。

    • 公共资源库:存放公司Logo模板、产品介绍PPT等共享文件。

    • 公司信息手册:记录公司的名称、成立时间等元信息。

这个公共公告板+资源库+信息手册合起来,就是 ServletContext

  • 任何员工都可以去查看公告板的内容或从资源库取文件。

  • 任何经理都可以去更新公告板的内容或在资源库添加新文件。

  • 它的内容是对公司内所有人共享的,与具体的客户(请求)或员工(Servlet)无关。

  • 这个公告板从公司开业(应用启动)到下班(应用关闭)一直存在

3.1、ServletContext 的核心特性

  1. 唯一性一个Web应用有且只有一个ServletContext对象。无论这个应用部署在分布式环境中的多少个服务器节点上,每个节点上的该应用实例都有自己唯一的ServletContext。

  2. 生命周期长:它在Web应用被加载(或服务器启动)时创建,在Web应用被停止或服务器关闭时销毁。它的生命周期覆盖了整个Web应用的生命周期。

  3. 作用域广:它代表的是整个Web应用,因此其中存放的数据可以被该应用中的所有Servlet、JSP、过滤器(Filter)、监听器(Listener)共同访问。

3.2、如何获取 ServletContext?

主要有两种方式:

  1. 通过 ServletConfig 获取(在Servlet内最常用的方式):

public class MyServlet extends HttpServlet {
    protected void doGet(HttpServletRequest request, HttpServletResponse response) {
        // 方式1:通过继承自GenericServlet的getServletContext()方法
        ServletContext context = getServletContext();

        // 方式2:先获取ServletConfig,再通过它获取ServletContext
        ServletContext context = getServletConfig().getServletContext();
    }
}

        2.通过 ServletContextEvent 获取(在监听器中):

public class MyListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        // 从事件对象中获取ServletContext
        ServletContext context = sce.getServletContext();
    }
}

三、Servlet开发应用实践

1、Web应用的中文乱码由来

一句话概括乱码根源中文乱码的本质是:信息的“编码”方和“解码”方使用了不同的“密码本”(字符集)。

比如,一个人用电报码(编码A) 发送了“你好”,但接收的人却用摩斯密码(编码B) 来解读,得到的结果自然是一堆毫无意义的乱码。

核心概念:字符编码

计算机底层只认识0和1。为了让人能看懂的字符(如“中”、“A”、“!”)能被计算机存储和传输,我们需要一套“密码本”来规定每个字符对应哪一串二进制数字。这套“密码本”就是字符编码(Character Encoding)

  • ASCII:早期编码,只包含英文字母、数字和一些符号,没有中文

  • GB2312 / GBK:中国制定的国标编码,兼容ASCII,并包含了大量中文汉字。

  • ISO-8859-1:又名Latin-1,是许多西方服务器的默认编码,同样不支持中文

  • UTF-8Unicode的一种实现方式,这是一种全球统一的编码标准,囊括了世界上几乎所有语言的字符。它是目前Web开发中的绝对主流和最佳实践。

乱码的产生就是当“编码”和“解码”使用的不是同一种编码时,比如:

  • 浏览器用 UTF-8 编码“你好” -> 11100110 10011111 10111101 (E4 BD A011100101 10100101 10111101 (E5 A5 BD)

  • 服务器误用 ISO-8859-1 去解码 -> 将 E4 BD A0 E5 A5 BD 解码成了 “您好” 这样的乱码。

2、Web请求-响应流程

Web交互是一个“浏览器请求 -> 服务器处理 -> 服务器响应 -> 浏览器渲染”的闭环,其中每一步都可能发生编码不一致。下图清晰地展示了这四大关键控制点:

3、web.xml常用配置

我们来系统地梳理一下 web.xml(部署描述符文件)的常用配置。虽然现在基于注解(如 @WebServlet)的配置越来越流行,但理解 web.xml 对于维护老项目和理解Web应用的配置结构仍然至关重要。

web.xml 是Java Web应用的核心配置文件,它位于项目的 WEB-INF/ 目录下,用于定义应用的 servlets、过滤器、监听器、参数等如何被Web服务器(如Tomcat)初始化和交互。

3.1、基础结构与头部信息

每个 web.xml 都以一个标准的XML头开始,并遵循一定的模式定义。

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                             http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
  <!-- 所有配置内容放在这里 -->
  
</web-app>
  • version 和 xmlns:指明了你使用的Servlet规范版本。版本3.1和4.0是目前常见的。版本决定了你可以使用哪些功能。

3.2、常用配置项详解

以下是 web.xml 中最常见和实用的配置项,按功能分类。

1. 全局上下文参数 (<context-param>)

用于定义整个Web应用的初始化参数,所有Servlet都可以通过 ServletContext.getInitParameter() 读取。

<context-param>
    <param-name>jdbcUrl</param-name> <!-- 参数名 -->
    <param-value>jdbc:mysql://localhost:3306/mydb</param-value> <!-- 参数值 -->
</context-param>
<context-param>
    <param-name>uploadPath</param-name>
    <param-value>/var/www/uploads</param-value>
</context-param>

用途:配置数据库连接字符串、文件上传路径、应用名称等全局设置。

2. Servlet配置 (<servlet> & <servlet-mapping>)

这是 web.xml 最核心的功能,用于声明Servlet并将其映射到URL模式。

<!-- 1. 声明一个Servlet,并给它一个内部名称 -->
<servlet>
    <servlet-name>UserServlet</servlet-name> <!-- 内部逻辑名 -->
    <servlet-class>com.example.web.UserServlet</servlet-class> <!-- 完整的类名 -->
    
    <!-- 可选的初始化参数,仅对此Servlet有效 -->
    <init-param>
        <param-name>configFile</param-name>
        <param-value>/WEB-INF/user-config.properties</param-value>
    </init-param>
    
    <!-- 启动加载顺序:数值>=0,越小优先级越高,越先加载 -->
    <load-on-startup>1</load-on-startup>
</servlet>

<!-- 2. 将声明的Servlet映射到一个URL访问模式 -->
<servlet-mapping>
    <servlet-name>UserServlet</servlet-name> <!-- 对应上面的内部名 -->
    <url-pattern>/user/*</url-pattern> <!-- 访问URL:http://host/app/user/xxx -->
    <!-- 也可以配置多个url-pattern -->
    <!-- <url-pattern>/api/user</url-pattern> -->
</servlet-mapping>

<url-pattern> 的几种写法:

  • 精确匹配/user (只匹配 /user)

  • 路径匹配/user/* (匹配 /user/user/add/user/delete/1 等)

  • 扩展名匹配*.do (匹配所有以 .do 结尾的请求,如 addUser.do)

  • 默认Servlet/ (匹配所有其他Servlet不匹配的请求)

3. 过滤器配置 (<filter> & <filter-mapping>)

用于声明过滤器,在请求到达Servlet之前或响应发送给客户端之后执行代码。

<!-- 1. 声明过滤器 -->
<filter>
    <filter-name>EncodingFilter</filter-name>
    <filter-class>com.example.filters.EncodingFilter</filter-class>
    <init-param>
        <param-name>encoding</param-name>
        <param-value>UTF-8</param-value>
    </init-param>
</filter>

<!-- 2. 映射过滤器到URL -->
<filter-mapping>
    <filter-name>EncodingFilter</filter-name>
    <!-- 匹配所有请求 -->
    <url-pattern>/*</url-pattern>
    
    <!-- 也可以通过Servlet名来映射 -->
    <!-- <servlet-name>UserServlet</servlet-name> -->
</filter-mapping>

用途

  • 字符编码过滤(解决中文乱码)

  • 权限验证/认证过滤

  • 日志记录过滤

  • 压缩响应过滤

4. 监听器配置 (<listener>)

用于监听Web应用中的事件,如应用启动、销毁、Session的创建和销毁等。

<listener>
    <listener-class>com.example.listeners.AppContextListener</listener-class>
</listener>
<listener>
    <listener-class>com.example.listeners.SessionListener</listener-class>
</listener>

用途

  • 应用启动时初始化资源(如数据库连接池)

  • 应用关闭时释放资源

  • 监控Session数量

  • 记录用户在线状态

5. 会话(Session)配置 (<session-config>)

用于全局配置Session的超时时间。

<session-config>
    <!-- 会话超时时间,单位:分钟 -->
    <!-- 如果设置为0或负数,表示会话永不过期 -->
    <session-timeout>30</session-timeout> <!-- 30分钟不操作,Session失效 -->
</session-config>

6. 欢迎文件列表 (<welcome-file-list>)

指定当用户访问应用根目录(如 http://localhost:8080/myapp/)时,默认显示的页面。

<welcome-file-list>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.jsp</welcome-file>
    <welcome-file>index.htm</welcome-file>
    <welcome-file>default.html</welcome-file>
</welcome-file-list>

服务器会按列表顺序寻找这些文件,找到第一个存在的文件并返回。

7. 错误页面配置 (<error-page>)

定义当出现特定HTTP错误代码或异常时,显示给用户的自定义友好页面。

<!-- 根据错误代码配置 -->
<error-page>
    <error-code>404</error-code>
    <location>/error/404.html</location>
</error-page>
<error-page>
    <error-code>500</error-code>
    <location>/error/500.html</location>
</error-page>

<!-- 根据异常类型配置 -->
<error-page>
    <exception-type>java.lang.Exception</exception-type>
    <location>/error/global.html</location>
</error-page>
<error-page>
    <exception-type>java.io.IOException</exception-type>
    <location>/error/io.html</location>
</error-page>

8. MIME类型映射 (<mime-mapping>)

为没有常见扩展名的文件指定MIME类型,确保浏览器能正确解析。

<mime-mapping>
    <extension>json</extension>
    <mime-type>application/json</mime-type>
</mime-mapping>
<mime-mapping>
    <extension>mp4</extension>
    <mime-type>video/mp4</mime-type>
</mime-mapping>

4、启动时加载Servlet

一句话概括启动时加载Servlet 是指在Web应用启动阶段(而不是用户第一次访问时),就由服务器(如Tomcat)自动初始化并调用Servlet的 init() 方法。这通常是为了执行一些耗时的、一次性的初始化任务,为后续的用户请求做好准备。

4.1、为什么要启动时加载?

默认情况下,Servlet是 “懒加载” 的:

  • 只有当第一个用户请求到达这个Servlet时,服务器才会实例化它,并调用其 init() 方法。

  • 对于第一个用户来说,体验非常差,因为他需要等待Servlet初始化完成(比如建立数据库连接池、加载大型配置文件)后才能得到响应。

启动时加载解决了这个问题,它的好处包括:

  1. 提升首屏体验:将耗时的初始化工作提前到应用部署阶段,第一个用户访问时就能获得快速响应。

  2. 提前暴露问题:如果初始化过程中有错误(如数据库连不上、配置文件错误),会在启动时立即抛出,方便运维人员及时发现和修复,而不是等到用户访问时才出错。

  3. 初始化全局资源:适合用于初始化那些需要被整个应用共享的资源,如缓存数据、线程池、连接池等。

4.2、如何配置启动时加载?

有两种主要方式:传统 web.xml 配置和 现代注解配置

方法一:在 web.xml 中配置 (<load-on-startup>)

这是最传统、最经典的方式。在Servlet的声明标签内,使用 <load-on-startup> 子元素。

<?xml version="1.0" encoding="UTF-8"?>
<web-app ...>
    <!-- 1. 声明Servlet -->
    <servlet>
        <servlet-name>InitializationServlet</servlet-name>
        <servlet-class>com.example.InitializationServlet</servlet-class>
        
        <!-- 启动加载配置 -->
        <load-on-startup>1</load-on-startup> 
        <!-- 数值是一个非负整数,代表加载的优先级顺序 -->
    </servlet>

    <!-- 2. 映射Servlet(可选,但通常需要) -->
    <servlet-mapping>
        <servlet-name>InitializationServlet</servlet-name>
        <url-pattern>/init</url-pattern> 
        <!-- 即使为了初始化,通常也需要一个URL映射 -->
    </servlet-mapping>
    
    <!-- 另一个Servlet -->
    <servlet>
        <servlet-name>AnotherInitServlet</servlet-name>
        <servlet-class>com.example.AnotherInitServlet</servlet-class>
        <load-on-startup>2</load-on-startup> 
        <!-- 数字更大,表示加载顺序更靠后 -->
    </servlet>
    
</web-app>

<load-on-startup> 的值详解:

  • 值 >= 0:表示启动时加载,数值越小,优先级越高,越先被初始化。

  • 值 < 0 或 不配置:表示采用“懒加载”模式,等第一次请求时才初始化。

  • 示例:上例中,InitializationServlet(值为1)会比 AnotherInitServlet(值为2)先被加载和初始化。

方法二:使用 @WebServlet 注解

在Servlet类上使用 @WebServlet 注解,并设置其 loadOnStartup 属性。

package com.example;

import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;

@WebServlet(
    urlPatterns = "/init", // 配置访问URL
    loadOnStartup = 1 // 配置启动加载顺序,规则同web.xml
)
public class InitializationServlet extends HttpServlet {
    // ... Servlet代码
}

注解方式的优点:

  • 配置简单,直接在代码中完成,无需编辑 web.xml

  • 代码和配置在一起,更直观。

注解方式的缺点:

  • 修改加载顺序需要重新编译代码。

  • 对于非常复杂的配置,还是 web.xml 更强大。

5、Java Web打包与发布

Java Web 项目通常打包成 WAR (Web Application Archive) 文件,然后部署到 Servlet 容器(也称为 Web 容器)中运行,例如 Tomcat、Jetty、Undertow 或应用服务器(如 WildFly, WebLogic, WebSphere)。

整个流程可以概括为:编码 -> 构建 -> 打包 -> 部署 -> 运行

5.1、打包格式:WAR vs. JAR

  1. WAR (Web Application Archive)

    • 目的: 专为 Web 应用程序设计。

    • 结构: 遵循特定的目录结构(包含 WEB-INF/META-INF/, 静态资源等)。

    • 部署: 部署到 Servlet 容器(如 Tomcat)。

    • 特点: 容器提供 Servlet API、JSP 编译等运行时环境,应用本身是一个模块。

  2. JAR (Java Archive)

    • 目的: 通常用于打包普通的 Java 库或应用程序。

    • 在 Web 中的新角色: 随着 Spring Boot 的流行,可执行 JAR 变得非常普遍。这种 JAR 文件内嵌了 Servlet 容器(如 Tomcat),因此可以直接通过 java -jar 命令运行,无需额外安装和配置外部容器。这种方式也称为 “Fat Jar”

总结: 传统方式用 WAR + 外部容器,现代微服务/快速开发风格常用内嵌容器的可执行 JAR。

5.2、传统 WAR 包打包与发布(以 Tomcat 为例)

1. 项目结构(符合 Servlet 标准)

一个标准的 Java Web 项目目录结构如下(通常由 Maven/Gradle 维护):

my-web-app/
├── src/
│   └── main/
│       ├── java/          # Java 源代码 (Servlets, Spring Controllers, etc.)
│       ├── resources/     # 配置文件 (application.properties, XML, 等)
│       └── webapp/        # WEB-INF 和静态文件
│           ├── WEB-INF/
│           │   ├── web.xml  # 部署描述符(可选,现代框架常省略)
│           │   └── classes/ # 编译后的 classes 文件(通常由构建工具自动生成)
│           ├── index.jsp
│           ├── css/
│           ├── js/
│           └── images/
├── target/                # Maven 构建输出目录(由构建工具生成)
│   └── my-web-app.war    # 生成的 WAR 包
├── pom.xml               # Maven 配置文件
└── README.md
2. 使用构建工具打包(Maven / Gradle)

Maven 是最常用的工具。确保你的 pom.xml 中 <packaging> 类型为 war

<!-- pom.xml -->
<project>
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example</groupId>
    <artifactId>my-web-app</artifactId>
    <version>1.0.0</version>
    <packaging>war</packaging> <!-- 关键!指定打包类型为 war -->

    <dependencies>
        <!-- 你的项目依赖(Servlet API, Spring MVC, 等) -->
        <!-- 注意:Servlet API 通常设置为 provided,因为容器会提供 -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>4.0.1</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

    <build>
        <finalName>my-web-app</finalName> <!-- 指定生成的 WAR 包名称 -->
        <plugins>
            <!-- Maven Compiler 插件 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>11</source>
                    <target>11</target>
                </configuration>
            </plugin>
            <!-- 其他插件 -->
        </plugins>
    </build>
</project>

打包命令
在项目根目录(包含 pom.xml 的目录)下执行:

mvn clean package

执行成功后,WAR 包会生成在 target/ 目录下(例如 target/my-web-app.war)。

3. 发布(部署)到 Tomcat

有几种常见的部署方式:

方式一:手动拷贝(最简单)

  1. 关闭正在运行的 Tomcat(可选,支持热部署但建议先关闭)。

  2. 将生成的 my-web-app.war 文件复制到 Tomcat 的 webapps/ 目录下。

  3. 启动 Tomcat(执行 bin/startup.sh 或 bin/startup.bat)。

  4. Tomcat 启动时会自动解压 WAR 包,并加载应用。

  5. 访问应用:http://localhost:8080/my-web-app

方式二:Tomcat Manager Web 界面(可视化)

  1. 配置 Tomcat 的用户权限,编辑 conf/tomcat-users.xml,添加一个具有 manager-gui 和 manager-script 角色的用户。

<tomcat-users>
    <role rolename="manager-gui"/>
    <role rolename="manager-script"/>
    <user username="admin" password="password" roles="manager-gui, manager-script"/>
</tomcat-users>
  1. 重启 Tomcat。

  2. 访问 http://localhost:8080/manager/html,输入用户名和密码。

  3. 在 “WAR file to deploy” 区域,选择你的 WAR 文件并点击 “Deploy”。

方式三:Maven Tomcat 插件(适合开发)
在 pom.xml 中配置插件:

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.tomcat.maven</groupId>
            <artifactId>tomcat7-maven-plugin</artifactId>
            <version>2.2</version>
            <configuration>
                <url>http://localhost:8080/manager/text</url> <!-- Manager 的文本接口 -->
                <username>admin</username>
                <password>password</password>
                <path>/myapp</path> <!-- 应用上下文路径 -->
            </configuration>
        </plugin>
    </plugins>
</build>

部署命令

mvn tomcat7:deploy    # 首次部署
mvn tomcat7:redeploy  # 重新部署
mvn tomcat7:undeploy  # 卸载

5.3、现代方式:使用 Spring Boot 打包可执行 JAR

Spring Boot 极大地简化了部署流程。

1. 项目配置(Maven)

pom.xml 中的 <packaging> 可以是 jar(默认)或 war。对于内嵌容器,我们通常用 jar

关键是要继承 spring-boot-starter-parent 并引入 spring-boot-maven-plugin 插件。

<project>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.0</version> <!-- 使用最新版本 -->
        <relativePath/>
    </parent>

    <groupId>com.example</groupId>
    <artifactId>my-spring-boot-app</artifactId>
    <version>1.0.0</version>
    <packaging>jar</packaging> <!-- 打包成可执行 JAR,这是默认值 -->

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId> <!-- 内嵌了 Tomcat -->
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <!-- 核心插件:用于打包可执行 JAR -->
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
2. 打包命令

同样使用 Maven:

mvn clean package

打包后,在 target/ 目录下会生成两个文件:

  • my-spring-boot-app-1.0.0.jar: 可执行 Fat Jar,包含所有依赖和内嵌容器。

  • my-spring-boot-app-1.0.0.jar.original: 普通的 JAR,不包含依赖。

3. 发布与运行

发布变得极其简单,只需要有 Java 运行环境(JRE)即可。

  1. 运行

java -jar target/my-spring-boot-app-1.0.0.jar
  1. 访问: 应用会直接启动,默认端口是 8080,访问 http://localhost:8080

部署到生产环境

  • 只需将生成的 .jar 文件上传到服务器。

  • 在服务器上执行 nohup java -jar my-spring-boot-app-1.0.0.jar > app.log 2>&1 & 即可在后台运行。

  • 也可以使用 Docker 容器化、系统服务(systemd)或 Kubernetes 等进行更高级的部署和管理。

OVER!!!

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ting_橘子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值