你收到
404 文件未找到
错误的原因是,作为
href
属性值给出的 CSS 路径缺少
上下文路径。
HTTP 请求 URL 包含以下部分:
http://[host]:[port][request-path]?[query-string]
请求路径由以下元素进一步组成:
上下文路径:由斜杠(/)和servlet web应用程序的上下文根连接而成。例如:http://host[:port]/context-root[/url-pattern]
Servlet路径:对应于激活此请求的组件别名的路径部分。此路径以斜杠(/)开头。
路径信息:请求路径中不是上下文路径或servlet路径的部分。
在这里阅读更多信息。
解决方案
有几种解决方案可供选择,以下是其中一些:
1) 使用JSTL中的<c:url>
标签
在我的Java Web应用程序中,当定义CSS/JavaScript/图像和其他静态资源的路径时,我通常使用JSTL中的<c:url>
标签。这样做可以确保这些资源始终相对于应用程序上下文(上下文路径)引用。
如果你说,你的CSS位于WebContent文件夹内,则应该可以这样做:
<link type="text/css" rel="stylesheet" href="<c:url value="/globalCSS.css" />" />
它能够工作的原因在于“JavaServer Pages™ Standard Tag Library” 1.2 版本
specification 第7.5章中有解释(重点在于我):
7.5 <c:url>
构建一个应用了适当重写规则的 URL。
...
URL 必须是以协议开头的绝对 URL(例如,“http:// server/context/page.jsp”),或者是由 JSP 1.2 在 JSP.2.2.1 “相对 URL 规范” 中定义的相对 URL。因此,实现必须在以斜杠开头的 URL 前面添加上下文路径(例如,“/page2.jsp”),以便客户端浏览器可以正确地解释这些 URL。
注意
在您的JSP中不要忘记使用Taglib指令,以便能够引用JSTL标签。此外,请参见示例JSP页面此处。
2) 使用JSP表达式语言和内置对象
另一种解决方案是使用表达式语言(EL)添加应用程序上下文:
<link type="text/css" rel="stylesheet" href="${pageContext.request.contextPath}/globalCSS.css" />
在这里,我们从request对象中检索出上下文路径。为了访问请求对象,我们使用了pageContext隐式对象。
3) 使用JSTL中的<c:set>
标签
免责声明
这个解决方案的想法来自这里。
为了比解决方案2中更紧凑地访问上下文路径,您可以首先使用JSTL <c:set>
标签,它设置EL变量或EL变量的属性的值在任何JSP范围(页面、请求、会话或应用程序)中供以后访问。
<c:set var="root" value="${pageContext.request.contextPath}"/>
...
<link type="text/css" rel="stylesheet" href="${root}/globalCSS.css" />
重要提示
默认情况下,为了以这种方式设置变量,包含此set标记的JSP必须至少访问一次(包括在使用scope属性将值设置在application范围内时,如<c:set var="foo" value="bar" scope="application" />
),在使用这个新变量之前。例如,您可能有多个需要此变量的JSP文件。因此,您必须要么a)同时在application范围内设置持有上下文路径的新变量,并首先访问此JSP,然后再在其他JSP文件中使用此变量,或者b)在每个需要访问它的JSP文件中设置此持有上下文路径的变量。
4) 使用ServletContextListener
使访问上下文路径更加简洁的更有效方法是设置一个变量,将其保存在应用程序范围内,并使用Listener将其存储在应用程序范围中。这种解决方案类似于解决方案3,但好处在于现在保存上下文路径的变量在Web应用程序开始时就设置好了,并且可以在整个应用程序范围内使用,无需额外的步骤。
我们需要一个实现ServletContextListener接口的类。以下是这样一个类的示例:
package com.example.listener;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
@WebListener
public class AppContextListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent event) {
ServletContext sc = event.getServletContext();
sc.setAttribute("ctx", sc.getContextPath());
}
@Override
public void contextDestroyed(ServletContextEvent event) {}
}
现在在JSP中,我们可以使用EL访问这个全局变量:
<link type="text/css" rel="stylesheet" href="${ctx}/globalCSS.css" />
注意
@WebListener注解可用于Servlet 3.0及以上版本。如果您使用支持较旧Servlet规范的Servlet容器或应用服务器,则需删除@WebServlet注解,并在部署描述符(web.xml)中配置监听器。以下是适用于支持最大Servlet版本2.5的容器的web.xml文件示例(为简洁起见省略了其他配置):
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">
...
<listener>
<listener-class>com.example.listener.AppContextListener</listener-class>
</listener>
...
</webapp>
5) 使用脚本片段
如用户 @gavenkoa 建议 的那样,您也可以像这样使用 脚本片段:
<%= request.getContextPath() %>
对于这样一件小事,可能是可以的,只要注意通常不建议在JSP中使用脚本片段。
结论
我个人更喜欢第一种解决方案(在我的以前的项目中大部分时间都使用它)或第二种,因为它们最清晰、直观和明确(在我看来)。但你可以选择适合你自己的。
其他想法
您可以将Web应用程序部署为默认应用程序(即在默认根上下文中),因此可以无需指定上下文路径访问它。有关更多信息,请阅读“更新”部分此处。