「為什麼 request.getRequestDispatcher(\"a.jsp\") 能跳轉,但 <a href=\"a.jsp\"> 卻 404?」路徑問題是 Java Web 開發的「隱形殺手」。本文從服務端/客戶端雙視角,系統梳理 /、./、../、contextPath 的語義差異,給出統一最佳實踐。
| 術語 | 含義 | 示例 |
|---|---|---|
| Context Path (上下文路徑) | Web 應用在容器中的路徑前綴 | /myapp (ROOT 應用為空串) |
| Servlet Path | 匹配 @WebServlet/web.xml 的路徑部分 | /user/login |
| Request URI | 完整請求路徑 (含 Context Path) | /myapp/user/login |
| Real Path | 服務器文件系統物理路徑 | C:\tomcat\webapps\myapp\user\login.jsp |
/ 開頭:相對於 Context Path (Web 應用根目錄)。request.getRequestDispatcher(\"/user/a.jsp\") → /myapp/user/a.jsp/ 開頭:相對於 當前請求的 Servlet Path 目錄。/myapp/admin/index.jsp → request.getRequestDispatcher(\"a.jsp\") → /myapp/admin/a.jspresponse.sendRedirect(request.getContextPath() + \"/user/a.jsp\")。<c:url value=\"/user/a.jsp\"/> (JSTL 自動補全 Context Path)。// Servlet
String realPath = getServletContext().getRealPath("/upload"); // → /tomcat/webapps/myapp/upload
// JSP
String realPath = application.getRealPath("/upload");
// Spring Controller
@Autowired ServletContext servletContext;
String realPath = servletContext.getRealPath("/upload");
⚠️ WAR 包部署/爆炸部署差異:getRealPath 可能返回 null (未爆炸部署),**避免依賴物理路徑**,改用 ServletContext.getResourceAsStream() 讀取 classpath/resource 目錄資源。
https://host:port/myapp/page.jsp<a href=\"user/a.jsp\"> → 請求 /myapp/user/a.jsp (相對於頁面目錄)。<a href=\"/user/a.jsp\"> → 請求 /user/a.jsp (相對於**域名根路徑**,丟失 Context Path → 404)。<a href=\"${pageContext.request.contextPath}/user/a.jsp\"> → 正確請求 /myapp/user/a.jsp。<%@ taglib prefix=\"c\" uri=\"https://java.sun.com/jsp/jstl/core\" %>
<a href=\"<c:url value='/user/a.jsp'/>\"">鏈接</a>
<script src=\"<c:url value='/js/app.js'/>\""></script>
<form action=\"<c:url value='/api/login'/>\"" method=\"post\">
<a th:href=\"@{/user/a.jsp}\">
<script th:src=\"@{/js/app.js}\">
<a href=\"${base}/user/a.jsp\"> // base = contextPath
publicPath: '/myapp/' (Webpack/Vite)。location /myapp/ { proxy_pass ...; } 透傳。history + basename: '/myapp' (React Router) / base: '/myapp/' (Vue Router)。| 場景 | 錯誤寫法 | 正確寫法 | 原因 |
|---|---|---|---|
| JSP 引用 CSS/JS | <link href=\"/css/style.css\"> | <c:url value=\"/css/style.css\"/> | 瀏覽器請求到域名根路徑,丟失 Context Path |
| Servlet forward | rd.forward(\"../a.jsp\") | rd.forward(\"/a.jsp\") | 服務端不支持 ../ 相對父目錄 |
| redirect 丟失參數 | resp.sendRedirect(\"a.jsp?id=1\") | resp.sendRedirect(ctx+\"a.jsp?id=1\") | 相對路徑基於當前請求 URL,易出錯 |
| 獲取上傳文件真實路徑 | new File(request.getParameter(\"path\")) | servletContext.getRealPath(\"/upload\") + File.separator + filename | 客戶端路徑對服務端無意義 |
# application.yml
spring:
web:
resources:
static-locations: classpath:/static/,classpath:/public/
mvc:
static-path-pattern: /static/**
https://host:port/myapp/static/js/app.js 自動映射到 resources/static/js/app.js。@{/static/...}。/ = Context Path 根,getRequestDispatcher 用絕對路徑 /xxx。c:url / th:href / publicPath 自動化。getRealPath 不可靠,優先用 getResourceAsStream 讀取 classpath 資源。路徑無小事,統一規範,告別 404。