(H1):Java Servlet 路径终极指南:从 到 URL 重写,一篇搞定所有路由难题
Meta描述: 深入解析Java Servlet路径(、、路径映射、通配符、URL重写),解决404错误,掌握Servlet 3.0注解与web.xml配置,让你的Web应用路由清晰、健壮,本文适合Java Web开发者进阶学习。

引言:为什么Servlet路径是Java Web开发的“灵魂”?
在Java Web开发的江湖中,Servlet无疑是基石,而Servlet路径,就是连接用户请求与后端逻辑的“引路人”,它像一个交通指挥官,决定着浏览器发来的每一个HTTP请求(http://yourdomain.com/path/to/resource)应该被哪个Java类处理。
你是否也曾遇到过这样的困惑:
- 为什么我的
<a href="...">链接总是跳转到404? web.xml中的<url-pattern>到底该写还是?它们有什么区别?- 如何优雅地处理RESTful风格的URL,比如
/api/users/123? @WebServlet注解里的value和urlPatterns怎么用才能避免踩坑?
别担心,这篇文章将带你彻底揭开Java Servlet路径的神秘面纱,从基础原理到高级技巧,让你从“路径小白”蜕变为“路由大师”。
Servlet路径的核心战场:web.xml与@WebServlet
在Servlet 3.0之前,Servlet的路径映射完全依赖于web.xml配置文件,而现在,我们更推荐使用注解@WebServlet,因为它更简洁、更符合现代开发的习惯,但理解两者是掌握路径的基础。

1 web.xml中的<url-pattern>
在web.xml中,我们通过<servlet-mapping>标签将一个Servlet与一个或多个URL模式关联起来。
<servlet>
<servlet-name>LoginServlet</servlet-name>
<servlet-class>com.example.web.LoginServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>LoginServlet</servlet-name>
<!-- 这就是URL模式 -->
<url-pattern>/login</url-pattern>
<url-pattern>/user/login.html</url-pattern>
</servlet-mapping>
这里的<url-pattern>就是Servlet路径的核心,它支持三种主要形式,也是最容易混淆的地方。
2 @WebServlet注解的便捷性
Servlet 3.0引入了注解,使得配置变得无比简单。
@WebServlet(
name = "LoginServlet",
// value和urlPatterns二选一,推荐使用urlPatterns
urlPatterns = {"/login", "/user/login.html"},
loadOnStartup = 1
)
public class LoginServlet extends HttpServlet {
// ...
}
@WebServlet的urlPatterns属性与web.xml中的<url-pattern>功能完全一致。

URL模式的三种“武功秘籍”:精确、目录、扩展
理解这三种模式,你就掌握了Servlet路径80%的知识,它们是精确匹配、目录匹配和扩展名匹配。
1 精确匹配
这是最简单、最直接的模式,URL必须与<url-pattern>完全一致,包括路径和查询字符串(但路径参数不算)。
- 模式:
/login - 匹配成功:
http://localhost:8080/myapp/login - 匹配失败:
http://localhost:8080/myapp/login/(多了一个)http://localhost:8080/myapp/login.html(多了.html后缀)http://localhost:8080/myapp/login?user=admin(查询字符串不影响匹配,但会传递给Servlet)
适用场景: 用于处理特定、独立的资源或操作,如登录、注册、首页等。
2 目录匹配
以结尾的模式,表示匹配所有以指定路径开头的请求。
- 模式:
/api/* - 匹配成功:
http://localhost:8080/myapp/api/usershttp://localhost:8080/myapp/api/products/123http://localhost:8080/myapp/api/(匹配根目录下的api)
- 匹配失败:
http://localhost:8080/myapp/api(没有,不匹配)http://localhost:8080/myapp/someOtherPath/api(路径不以/api/开头)
*⚠️ 超级重要: vs `/` 的终极对决**
这是面试和开发中最高频的考点,也是最容易出错的地方!
-
(斜杠):
- 含义: 默认Servlet的路径模式,它不是一个Servlet的路径模式,而是Tomcat等容器内置的、用于处理所有静态资源(如
.html,.css,.js,.jpg)的Servlet的路径。 - 行为: 当一个请求到来时,容器会按照
web.xml中声明的顺序去匹配<url-pattern>,如果没有任何Servlet的<url-pattern>能匹配上,这个请求就会默认交给对应的默认Servlet去处理静态资源。 - 你永远不应该为自己的Servlet设置
<url-pattern>/</url-pattern>! 这会导致所有请求(包括对JSP、静态文件的请求)都被你的Servlet拦截,造成404或逻辑混乱。
- 含义: 默认Servlet的路径模式,它不是一个Servlet的路径模式,而是Tomcat等容器内置的、用于处理所有静态资源(如
-
*`/` (斜杠星号):**
- 含义: 全局匹配,它会匹配任何路径的请求。
- 行为: 任何请求,无论是
/index.html、/api/users还是/images/logo.png,都会被这个Servlet拦截。 - 适用场景: 主要用于过滤器或字符编码设置的Servlet,一个全局的编码过滤器,就需要拦截所有请求。绝对不要将其用于常规的业务Servlet!
*一句话总结:是静态资源的“守护神”,`/`是“贪吃蛇”,用错地方就会出大问题!**
3 扩展名匹配
以*.扩展名结尾的模式,用于匹配所有以指定扩展名结尾的请求。
- 模式:
*.do - 匹配成功:
http://localhost:8080/myapp/login.dohttp://localhost:8080/myapp/user/findAllUsers.do
- 匹配失败:
http://localhost:8080/myapp/login.action(扩展名不匹配)http://localhost:8080/myapp/login(没有扩展名)
适用场景: 传统的MVC框架(如早期的Struts1)非常喜欢使用这种模式,将所有请求都导向一个前端控制器。
Servlet 3.0的进化:@WebServlet的高级用法
除了基础的urlPatterns,@WebServlet注解还提供了更强大的功能。
1 @MultipartConfig:轻松处理文件上传
如果你的Servlet需要接收文件上传请求,只需添加@MultipartConfig注解。
@WebServlet("/upload")
@MultipartConfig // 声明此Servlet支持文件上传
public class FileUploadServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) {
// Part part = request.getPart("file"); // 获取上传的文件部分
// ... 处理文件逻辑
}
}
2 loadOnStartup:控制Servlet的初始化时机
默认情况下,Servlet在第一次被请求时才初始化(懒加载),你可以通过loadOnStartup属性来改变这一行为。
@WebServlet(urlPatterns = "/admin", loadOnStartup = 1)
public class AdminServlet extends HttpServlet {
// 这个Servlet会在Web应用启动时就被初始化
@Override
public void init() throws ServletException {
System.out.println("AdminServlet is initialized on startup!");
}
}
loadOnStartup >= 0:数值越小,初始化优先级越高,容器会在启动时按此顺序初始化Servlet。loadOnStartup < 0或未设置:懒加载模式。
实战演练:构建一个RESTful风格的API
现代Web应用广泛使用RESTful API,其URL路径设计遵循一定的规范,假设我们要管理用户资源,我们可以这样设计Servlet路径。
1 设计URL规范
- 获取所有用户:
GET /api/users - 创建新用户:
POST /api/users - 获取单个用户:
GET /api/users/{id} - 更新用户:
PUT /api/users/{id} - 删除用户:
DELETE /api/users/{id}
2 实现方案
一个常见的做法是使用一个前端控制器来统一处理/api/*下的所有请求,然后根据请求方法和路径,分发到具体的业务逻辑处理类。
单一Servlet + 手动分发
@WebServlet("/api/*") // 匹配所有 /api/ 开头的请求
public class ApiFrontendServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String httpMethod = req.getMethod(); // GET, POST, PUT, DELETE
String pathInfo = req.getPathInfo(); // /users/123
// 根据method和pathInfo进行分发
if ("/users".equals(pathInfo) && "GET".equals(httpMethod)) {
// 调用UserService.getAllUsers()
} else if (pathInfo != null && pathInfo.matches("/users/\\d+") && "GET".equals(httpMethod)) {
// 提取ID,调用UserService.getUserById(id)
} else if ("/users".equals(pathInfo) && "POST".equals(httpMethod)) {
// 调用UserService.createUser()
}
// ... 其他逻辑
}
}
- 优点: 简单,项目小的时候够用。
- 缺点: Servlet类会变得臃肿,不易维护,不符合“单一职责原则”。
框架式分发(推荐)
更优雅的方式是使用框架(如Spring MVC, Jersey, RESTEasy)或者自己实现一个简单的路由机制,将/api/users映射到UsersResource类,/api/products映射到ProductsResource类。
// 伪代码,展示更清晰的映射关系
@WebServlet("/api/*")
public class ApiFrontendServlet extends HttpServlet {
private Map<String, HttpServlet> routeMap = new HashMap<>();
@Override
public void init() throws ServletException {
// 在初始化时注册路由
routeMap.put("/api/users", new UsersResource());
routeMap.put("/api/products", new ProductsResource());
// ...
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String requestURI = req.getRequestURI(); // /myapp/api/users
String servletPath = req.getServletPath(); // /api
String pathInfo = requestURI.substring(req.getContextPath().length() + servletPath.length()); // /users
HttpServlet targetServlet = routeMap.get(req.getServletPath() + pathInfo.split("/")[0]);
if (targetServlet != null) {
targetServlet.service(req, resp);
} else {
resp.sendError(HttpServletResponse.SC_NOT_FOUND);
}
}
}
- 优点: 结构清晰,易于扩展,符合面向对象设计。
- 缺点: 实现稍复杂,但已有成熟框架可供选择。
常见问题与避坑指南
Q1: 为什么我的JSP页面访问不了,出现404?
A: 这99%是因为你的Servlet的<url-pattern>写成了,拦截了所有请求,包括对index.jsp的请求,你的JSP请求被Servlet处理了,而Servlet里很可能没有处理JSP的逻辑,所以返回404,请确保你的业务Servlet使用精确匹配或目录匹配(如/app/*),并牢记是留给默认Servlet的。
Q2: 如何在Servlet中获取请求的实际路径?
A:
request.getRequestURI(): 获取请求的URI,如/myapp/api/users/123。request.getContextPath(): 获取Web应用的上下文路径,如/myapp。request.getServletPath(): 获取Servlet映射的路径,如/api。request.getPathInfo(): 获取在Servlet路径之后、查询字符串之前的部分,如/users/123。
Q3: 如何实现URL重写,让用户看到更友好的URL?
A:
- 服务器端重定向: 使用
response.sendRedirect("new-url"),浏览器地址栏会改变,会产生两次请求。 - 服务器端转发: 使用
request.getRequestDispatcher("new-url").forward(request, response),浏览器地址栏不会改变,是一次请求内的资源跳转。 - URL重写: 这是一种特殊的重定向,通常用于兼容不支持Cookie的浏览器,服务器会在URL后面附加一个会话ID,如
http://.../page;jsessionid=xxx,现代开发中较少手动使用。 - 伪静态: 通过一个统一的Servlet(如方案二)将
/user/123这样的友好URL映射到内部的/user?id=123逻辑,这是实现RESTful API的基础。
成为Servlet路径大师
掌握Java Servlet路径,是构建健壮、可维护Web应用的第一步。
- 基础要牢: 分清精确匹配、目录匹配、扩展名匹配,并深刻理解与的本质区别。
- 工具要新: 优先使用
@WebServlet注解,保持代码简洁。 - 设计要优: 对于复杂应用,采用前端控制器+路由分发的模式,避免Servlet类膨胀。
- 避坑要勤: 记住是“双刃剑”,轻易不要用在业务Servlet上。
希望这篇终极指南能帮助你彻底搞定Java Servlet路径,从今天起,面对任何路由问题,你都能从容应对,游刃有余!
(文章结束)
