Servlet从原理到实战:手写Java Web请求处理全流程
2026/9/10 9:58:26 网站建设 项目流程

Servlet 这个技术很多朋友一听到就觉得是老古董,尤其是在 Spring Boot 大行其道的今天,好像直接上手 Controller 才是正道。但我在一线踩了这么多年坑,可以很负责任地告诉你:Servlet 是整个 Java Web 的地基,Spring MVC 里的 DispatcherServlet 本质就是一个 Servlet,你用的 Tomcat 就是一个 Servlet 容器。地基如果不稳,后面排查问题是会怀疑人生的。

这篇文章我会带你用最原始、最接近底层的方式,把 Servlet 从创建到运行完完整整跑一遍。不用 Spring Boot,不用任何高级封装,就用手写代码加配置文件的方式,把这里的“为什么”彻底讲透。如果你是刚学完 Java SE、准备进入 Web 开发的小白,这篇文章就是为你准备的;如果你已经用了一段时间 Spring Boot 但对底层一知半解,看完也会有很多“原来如此”的时刻。

1. 内容整体设计与思路拆解

1.1 为什么 Spring Boot 时代还要学习 Servlet

很多初学者会问一个特别现实的问题:我直接用 Spring Boot 写接口不香吗?为什么还要倒退回去学 Servlet?

这个问题我当年也问过。后来当我第一次遇到线程安全导致的线上 Bug、第一次排查请求 404 找到凌晨、第一次被过滤器链执行顺序搞到崩溃时,才明白 Servlet 这套规则恰恰是所有上层封装的基石。Spring Boot 帮你接管了自动配置和服务器嵌入,但它无法替你理解“请求到底是怎么进去的”、“一个请求从到达 Tomcat 到返回给浏览器的完整路径是什么”。如果你不懂 Servlet,你对这些问题的认知就会停留在“调个接口就行”的层面。

用一个生活化的类比来理解:你把房子交给装修公司全包,拎包入住当然爽。但如果某天水管爆了,你连水管阀门在哪、总闸在哪个位置都搞不清楚,只能干瞪眼。Servlet 就是这栋房子的水管系统和电路布局,你把它的基本结构看明白了,以后无论住多豪华的装修房,心里都有底。

从技术演进的角度看,Servlet 规范定义了 Java Web 服务器的标准行为。你写的任何一个 Servlet,本质上就是一个 Java 类,但它继承了HttpServlet并重写了特定方法,从而具备了接客(接收 HTTP 请求)的能力。Tomcat、Jetty 这些容器负责监听端口、解析 HTTP 协议、把请求封装成对象,再调用你写的 Servlet 方法。这就是整条链路的核心。

1.2 一次 HTTP 请求的旅行地图

要真正理解 Servlet,脑子里必须先建立起一张请求旅行地图。我们以访问http://localhost:8080/myapp/hello为例,拆解一下这个过程中到底发生了什么。

首先,浏览器根据域名解析出 IP 和端口,向 Tomcat 发起一个 TCP 连接,紧接着发送基于 HTTP 协议格式的请求数据。Tomcat 作为服务器,在这里扮演了两个角色:一个是网络通信层的角色,负责接收原始字节流;另一个是协议解析器的角色,把字节流解析成结构化数据,包装成HttpServletRequest对象。

这个HttpServletRequest对象里封装了什么?包含请求行(方法、URI、协议版本)、请求头(Header)、请求体(Body)。Tomcat 会根据 URI 中的/myapp匹配到当前部署的应用,再根据/hello去这个应用的映射表里找到对应的 Servlet 类。找到类之后,容器会实例化它,并调用service()方法。这个service()会根据请求方法(GET、POST)分发给doGet()doPost()方法。

你的 Servlet 方法处理完毕之后,会往HttpServletResponse对象里写入响应内容。Tomcat 再把这个对象转换成 HTTP 响应字节流,通过 Socket 回传给浏览器。浏览器拿到之后解析,这就完成了一次完整的请求往返。

这个流程就类似你去餐厅吃饭:门口服务员(Tomcat)接待你,问你吃什么(解析请求),把你的订单传递到后厨(Servlet 映射),后厨做完菜(业务逻辑处理),再由服务员端到你面前(返回响应)。你对顾客屏蔽了后厨的复杂细节,但作为一个经营管理者,你必须清楚后厨每个工位是干嘛的。

2. 核心细节解析与实操要点

2.1 深入 Servlet 的生命周期:从生到死的完整过程

Servlet 的生命周期是整个 Servlet 机制里最核心的概念,也是面试和实际排查问题的高频考点。它一共分为四个阶段:加载与实例化、初始化(init)、服务(service)、销毁(destroy)。

加载与实例化发生在容器启动或第一次请求到达时。当你启动 Tomcat 时,容器会根据配置决定是否要提前加载 Servlet(通过load-on-startup配置控制,数值越小优先级越高)。如果没有提前加载,容器会在第一个请求到达时进行实例化。这里有个关键点:Servlet 是单例多线程的,也就是说在整个生命周期中,只有一个 Servlet 实例存在,多个请求会并发调用这个实例的service()方法。

初始化阶段会调用init(ServletConfig config)方法。这个方法只会执行一次,通常用于加载数据库连接池、读取配置文件等资源初始化工作。我在实际开发中见过不少新手把重量级对象放在构造函数里,这其实是不规范的。构造方法只是简单地创建实例,而init()才是容器给业务方提供的钩子,这个钩子保证了 ServletConfig 已经可用,并且时机在单例化之后、正式对外服务之前。

服务阶段是最繁忙的阶段。每次请求到达,容器都会开启一个线程(由线程池管理),调用 Servlet 的service()方法。service()方法是一个调度中心,它根据 HTTP 请求方法类型,将请求转发给对应的doXxx()方法。比如你重写了doGet(),那 GET 请求就会进入这个方法;如果没有重写,父类HttpServlet的默认实现会返回一个 405 错误。

销毁阶段发生在容器关闭或应用卸载时。destroy()方法用于释放init()中加载的资源,比如关闭连接池。这里有个重要细节:容器保证在调用destroy()之前,不会把任何新的请求分发给这个 Servlet。但如果有正在处理中的请求,容器会等这些请求处理完,才会执行销毁。这就带来一个经验教训:不要在destroy()里做太耗时的操作,否则会拖慢整个应用的停机速度。

从开发者的角度,你只需要关心init()service()相关方法、destroy()这三个阶段。生命周期是容器管理 Servlet 的底层逻辑,理解它,你就不会在成员变量线程安全问题上犯低级失误。

2.2 URL 映射规则:为什么 404 总是找上门

URL 映射是 Servlet 配置中的重灾区。很多新手明明把类写好了,却总是 404,大部分原因就是 URL 映射没配对。Servlet 的 URL 映射有三种规则:精确匹配、路径匹配、扩展名匹配。

精确匹配就是最直接的写死路径,比如/hello。这种匹配方式最简单直接,一个 URL 对应一个 Servlet。路径匹配使用通配符,以/*结尾,比如/api/*会匹配所有以/api/开头的请求。扩展名匹配是以*.开头,比如*.do,匹配所有以.do结尾的请求。

这三者混用时的优先级是:精确匹配最高,其次是路径匹配,最后是扩展名匹配。也就是容器在收到请求时,会先在精确匹配表里找,找不到再去找路径匹配,最后才看扩展名匹配。这个优先级关系我用一个场景来验证:如果你同时配置了/hello的精确匹配和/api/*的路径匹配,访问/api/hello时会走到路径匹配的 Servlet,因为精确匹配只认完全相等的/hello

还有一个极容易混淆的坑://*的区别非常大。/是当前应用的默认 Servlet,它匹配应用中的所有请求,但不会匹配 JSP 页面,因为 JSP 页面在容器里被映射到了*.jsp的 Servlet。而/*是拦截所有请求,包括 JSP。如果你把某个 Servlet 配置成/*,你就把所有 JSP 页面都拦截了,页面会直接报错。

2.3 注解与 web.xml:两条路,殊途同归

Servlet 3.0 之前,所有 Servlet 的映射必须在web.xml中声明。这是标准的 XML 方式,流程非常固化:写一个类继承HttpServlet,然后在web.xml中写<servlet><servlet-mapping>两个标签。

而在 Servlet 3.0 之后,规范新增了注解支持,也就是现在你大概率见到的@WebServlet(urlPatterns = "/hello")。使用注解时,容器在启动时自动扫描注解,完成注册。这在日常开发中确实方便了很多,不用再维护一堆 XML。

但你必须明白 WEB 项目中的一种真实场景:如果你在web.xml中配置了<load-on-startup>结合metadata-complete="false"(默认值),容器会同时扫描注解。如果你设置了metadata-complete="true",容器就不会再扫描注解了。这意味着如果项目里既有 XML 又有注解,很可能出现“我明明写了注解但没生效”的诡异问题。排查思路很简单:看看web.xml的根标签是不是带了metadata-complete="true"

在开发中,注解适合小项目、快速原型;XML 适合需要集中管理映射规则、或者与运维脚本配合的大型传统项目。理解这两种方式的本质,你阅读开源代码时就不会被各种配置绕晕。

3. 实操过程与核心环节实现

3.1 环境准备:版本匹配是第一步

开始写代码之前,先把环境搭好。这里最重要的不是“装好”,而是“版本匹配”。很多新手栽跟头就栽在版本混用上。

JDK 我推荐你用 8 或 11 都行,这俩是目前企业使用最广的版本。Tomcat 的选择非常讲究:如果你用的是 Tomcat 9 及以下,Servlet API 的包名是javax.servlet;如果你用的是 Tomcat 10 及以上,包名已经升级成了jakarta.servlet

这一点绝对是新手最容易踩的巨坑。你从网上找教程,发现别人代码里是import javax.servlet.http.HttpServlet,但自己安装的 Tomcat 10 却找不到这个类,于是怀疑自己配置有问题。实际上是因为 Tomcat 从 10 开始全面拥抱 Jakarta EE 9+,命名空间从javax迁移到jakarta。如果你跟着老教程用 Tomcat 10,请把 import 改成jakarta.servlet.http.HttpServlet

开发工具我建议直接用 IntelliJ IDEA 社区版就够了,操作路径和付费版完全一致。Maven 需要安装,这是 Java 项目的管理和构建工具,相当于前端的 npm 或 Maven 仓库。下面我操作的版本组合是:JDK 11 + Tomcat 9 + Maven 3.8。这套组合兼容性最好,即便你之后转 Spring Boot,也不会遇到版本地狱。

3.2 创建 Maven 项目与 pom.xml 配置

打开 IDEA,新建一个 Maven 项目,不使用模板,直接选“Maven”分类。GroupId 填com.example,ArtifactId 填servlet-demo,版本就用默认的 1.0-SNAPSHOT。

创建完成后,项目结构里会有一个pom.xml,我们先把它改造成适用于 Web 项目的配置。完整内容如下:

<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.example</groupId> <artifactId>servlet-demo</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <properties> <maven.compiler.source>11</maven.compiler.source> <maven.compiler.target>11</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> <scope>provided</scope> </dependency> </dependencies> </project>

注意几个关键点。<packaging>必须设置为war,因为 Web 应用需要打成 war 包部署到 Tomcat,而不是普通 jar。javax.servlet-api<scope>设置为provided很关键,意思是编译时需要这个 API,但运行时由 Tomcat 提供,不需要打入最终的包里。如果你的 pom 里没有加这个依赖,代码里import javax.servlet.*就会直接报红。

3.3 编写第一个 Servlet:HelloServlet

我们写一个最简单的 Servlet。在src/main/java/com/example包下新建一个HelloServlet.java

package com.example; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; @WebServlet("/hello") public class HelloServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("text/html;charset=UTF-8"); PrintWriter out = resp.getWriter(); out.println("<html>"); out.println("<body>"); out.println("<h1>Hello Servlet, 世界!</h1>"); out.println("</body>"); out.println("</html>"); } }

这段代码做了这么几件事:用@WebServlet("/hello")注解声明这个 Servlet 的访问路径是/hello,继承HttpServlet并重写doGet()方法,处理 GET 请求。在方法里,通过resp.setContentType("text/html;charset=UTF-8")设置响应内容类型和编码,然后获取PrintWriter输出流,把 HTML 字符串写回浏览器。

这里有个小细节可以提一嘴:setContentType的 charset 到底能不能省略。如果你不写charset=UTF-8,Tomcat 默认会用 ISO-8859-1 编码你写入的字符,结果就是中文变成乱码。这个坑特别常见,所以每次写响应内容,养成习惯先设置 ContentType。

3.4 配置 IDEA 本地 Tomcat 并启动运行

写完代码后,需要配置一个运行环境。在 IDEA 右上角点开“Add Configuration”,选择“+”,找到“Tomcat Server”下面的“Local”。

点击“Configure”按钮,选择 Tomcat 的安装路径。如果你的 Tomcat 是解压版的,路径就是解压后的根目录,IDEA 会自动识别版本来显示让你确认。

接下来是最关键的部署设置。切到“Deployment”标签,点击“+”,选择“Artifact”。IDEA 会看到你刚刚创建的 Maven 项目,弹窗里有两种选择:servlet-demo:warservlet-demo:war exploded。开发阶段选war exploded,它是解压目录的形式,改代码编译后可以直接热更新,不用重启整个 Tomcat。之后把“Application context”设置为/,这样访问的时候就不用带项目名了,直接是http://localhost:8080/hello

点击运行按钮,Tomcat 启动,控制台刷出日志。看到类似“Server startup in [xxx] milliseconds”的输出,说明容器已经起来了。打开浏览器访问http://localhost:8080/hello,你会在页面上看到一个大大的“Hello Servlet, 世界!”。

3.5 进阶玩法:实现一个带表单处理的登录接口

只输出一个静态页面显然不够“超详细”。我们再加一个LoginServlet,展示如何处理 POST 请求、读取表单参数、处理中文乱码以及做重定向。

首先在webapp目录下新建一个login.html

<!DOCTYPE html> <html lang="zh"> <head> <meta charset="UTF-8"> <title>登录</title> </head> <body> <form action="login" method="post"> 用户名:<input type="text" name="username"/><br/> 密码:<input type="password" name="password"/><br/> <input type="submit" value="登录"/> </form> </body> </html>

然后新建LoginServlet.java

package com.example; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @WebServlet("/login") public class LoginServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("UTF-8"); String username = req.getParameter("username"); String password = req.getParameter("password"); if ("admin".equals(username) && "123456".equals(password)) { resp.sendRedirect("hello"); } else { resp.sendRedirect("login.html"); } } }

doPost()里,第一行req.setCharacterEncoding("UTF-8")是处理 POST 请求中文乱码的关键。HTML 页面使用的是 UTF-8 编码提交数据,如果你不告诉容器用 UTF-8 来解码请求体,容器默认用 ISO-8859-1 解码,中文用户名就会变成一堆乱码。

业务判断部分写死了一个账号密码,正式项目你肯定要去查数据库,但演示交互原理已经足够了。登录成功后用sendRedirect("hello")做重定向,浏览器会重新发起一个 GET 请求到/hello;登录失败就跳回登录页。sendRedirect的本质就是让浏览器重新访问一个新地址,所以地址栏的 URL 会发生变化。

这里跟forward(转发)做一个对比,转发是在服务器内部完成的,浏览器不知道这个过程,地址栏不会变。它们之间的选择在后续学习 Spring MVC 时也会遇到,提前动手跑一遍,体会会更深。

3.6 手动配置一个完整的 web.xml

虽然注解方式很方便,但我还是强烈建议你至少手动配置一次web.xml,这样以后看到老项目不会发怵。在src/main/webapp/WEB-INF目录下新建web.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"> <display-name>servlet-demo</display-name> <servlet> <servlet-name>helloServlet</servlet-name> <servlet-class>com.example.HelloServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>helloServlet</servlet-name> <url-pattern>/hello</url-pattern> </servlet-mapping> </web-app>

配置里的<servlet-class>必须写类的全限定名,不能只写类名。<url-pattern>里的路径就是对外访问的路径。<load-on-startup>1</load-on-startup>表示容器启动时就实例化并初始化这个 Servlet,而不是等第一个请求来了才初始化。如果你配置了注解@WebServlet同时又在这个 web.xml 里配置了相同的类,容器的行为是:XML 配置优先,注解会被忽略,因为容器认为你在 XML 中已经显式声明了管理规则。

4. 常见问题与排查技巧实录

4.1 import 出错:javax.servlet 与 jakarta.servlet 的世纪难题

这是 Tomcat 10 发布之后出现频率最高的编译错误。场景是这样的:你从网上下载了一个老项目,代码里全是import javax.servlet.http.HttpServlet,但你的 Tomcat 是 10.x,编译时一直提示找不到包。

原因我在前面讲过,Tomcat 10 从 Java EE 迁移到了 Jakarta EE 命名空间,包名整体从javax.*改成了jakarta.*。解决办法有几个:第一个最直接,把你的 Tomcat 换成 9.0.x,老项目完全不变;第二个,如果你必须用 Tomcat 10,那就全局替换所有javax.servletjakarta.servlet;第三个,用 IDE 的全局替换功能,一键替换包名,改完之后重新编译,通常就没问题了。

还有一个细节:如果你在 Spring Boot 中使用内嵌 Tomcat,也要注意版本对应关系。Spring Boot 2.x 默认使用 Tomcat 9,所以代码用javax;Spring Boot 3.x 默认使用 Tomcat 10,代码要用jakarta。你在网上看教程时,先确认一下对方用的 Spring Boot 版本,再决定自己代码里该写哪个包。

4.2 404 错误排查:先从路径开始找起

部署启动之后访问http://localhost:8080/hello,结果页面显示 404,这是初学者最常见的打击。排查之前先明确一件事:404 说的是“找不到资源”,问题是这个资源到底叫什么。

先看第一个容易踩的坑:应用上下文路径(context path)。如果你在 IDEA 部署时,Application context 设置的是/servlet-demo,那么访问路径应该是http://localhost:8080/servlet-demo/hello。如果你访问的是http://localhost:8080/hello,就相当于在根路径下找hello这个资源,找不到当然 404。

再看第二个坑:注解里的映射路径。如果你写的是@WebServlet("/HELLO"),实际访问的是/HELLO,大小写敏感。Tomcat 默认不会做大小写归一化转换。

第三个坑是项目没有成功部署。启动日志里如果出现了类似“Deployment of web application archive [xxx.war] has failed”的报错,说明打包或部署环节出了问题,这时候要往前查日志,而不是盯着浏览器地址栏发呆。

我给新手一个统一的排查顺序:先看控制台有没有启动异常,然后看 IDEA 部署面板里的 context path,最后看浏览器地址栏是不是和映射一致。按这个顺序走,80% 的 404 都能解决。

4.3 文件上传报错:feignclient failed to parse multipart servlet request

这个报错是微服务场景下的高频 Bug,我在工作中碰到过很多次,甚至有一些已经上线许久的服务突然开始报错。完整报错信息一般是:

feign.FeignException: [400] during [POST] to [http://xxx-service/upload] ...[400] during [POST]... Caused by: java.io.IOException: The temporary upload location [C:\Users\xxx\AppData\Local\Temp\tomcat.xxxxx.work] is not valid

这个错误表面看是 Feign 客户端调上传接口时失败,但根子出在 Servlet 容器处理 multipart 请求的临时目录上。用 Servlet 处理文件上传时,容器需要把客户端发来的文件先写到服务器的临时目录,再转交给业务代码处理。这个临时目录默认在系统的临时文件目录下,比如 Linux 的/tmp,Windows 的AppData\Local\Temp

为什么之前好好的,突然就报“临时上传位置无效”了呢?最常见的原因是操作系统或运维的定时清理任务把/tmp下旧文件删掉了,而 Tomcat 在你第一次启动时记住的那个临时目录已经不存在了。在 Linux 服务器上,这个问题尤为常见,因为 systemd-tmpfiles 等清理任务默认会清理规定时间之前的文件。

解决这个问题的思路就两个方向:一是修改临时目录指向一个稳定持久的位置,在 Spring Boot 的配置文件中显式设置:

spring.servlet.multipart.location=/data/upload_tmp

同时确保这个目录存在并且应用有写权限。另一个方向是让 Tomcat 在每次启动时强制重新创建临时目录,但如果你用的是内嵌式 Tomcat,这个行为的可控性不如外置配置。这个 Bug 必须记录在案,因为它属于“不踩不会理解,不记录下次还忘”的经典故障。

4.4 中文乱码:请求和响应的双重处理

中文乱码在 Servlet 开发中简直是必修课。先说响应乱码。在doGet()doPost()里,如果你往PrintWriter中写中文,但前面没有setCharacterEncoding("UTF-8"),浏览器收到后用默认编码解析,十有八九是乱码。正确的做法是先设置 ContentType,再获取 Writer:

resp.setContentType("text/html;charset=UTF-8"); PrintWriter out = resp.getWriter();

再说请求乱码。GET 请求的中文参数走的是 URL 地址,默认编码取决于 Tomcat 配置。要正确处理 GET 请求的中文参数,可以在 Tomcat 的conf/server.xml里给 Connector 设置统一 URIEncoding:

<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" URIEncoding="UTF-8" />

而 POST 请求的参数在请求体里,必须用req.setCharacterEncoding("UTF-8")在读取参数之前设置才会生效。注意顺序很重要,如果你先调用getParameter()再去设置编码,那是无效的,因为容器在第一次读取参数时就已经用默认编码解析完了。

4.5 线程安全:千万别在 Servlet 里定义可修改的成员变量

Servlet 是单例的,也就是说只有一个实例对象在服务所有请求。如果你在这个实例里定义了一个非静态的成员变量,那么所有请求线程共享这个变量,这就引发了并发问题。

我举个例子,假设你在 Servlet 里定义了一个private int count = 0,然后在doGet()里执行count++。两个请求同时到达,A 线程读了 count 等于 1,B 线程也读了 count 等于 1,各自加完写回,最终 count 只变成了 2,而不是 3。这就是典型的并发安全漏洞。

解决方案很简单:尽量使用局部变量,局部变量是线程私有的,不存在共享问题。如果确实需要共享状态,就必须加锁或使用线程安全的集合。在实际开发的 Servlet 编程中,最佳实践是只保留不可变对象和无状态方法,就是常说的“无状态 Servlet”。

还有一处容易忽略:不要在init()里启动一些后台线程来轮询共享资源,除非你非常明确这个线程的生命周期和销毁机制。否则应用在热部署或重启时,容易遗留僵尸线程。

最后再分享一个小技巧

根据个人经验,学习 Servlet 时不要只看不练,一定要在本地把项目跑起来,然后亲自去web.xml里改写映射规则做实验。比如故意把<url-pattern>写成/*,看看会发生什么;或者把一个 Servlet 映射到两个不同的路径,看看容器怎么处理。只有亲手踩过这些坑,你才能真正建立起对 Servlet 机制的立体认知。

如果你接下来准备深入学习 Spring MVC,回来看看这一篇,你会发现DispatcherServlet的入口原理和 RequestMappingHandlerMapping 的映射机制,本质上都是在 Servlet 规范之上做了一层扩展。地基打牢了,上层建筑再花哨,你也能一眼看穿它的结构。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询