我的 WebContent/jsps
文件夹中的 JSP 文件中有一个 HTML 表单。我在 src
文件夹中的默认包中有一个 servlet 类 servlet.java
。在我的 web.xml
它被映射为 /servlet
。
我在 HTML 表单的 action
属性中尝试了几个 URL:
<form action="/servlet">
<form action="/servlet.java">
<form action="/src/servlet.java">
<form action="../servlet.java">
但这些都不起作用。它们都在 Tomcat 6/7/8 中不断返回 HTTP 404 错误,如下所示:
HTTP 状态 404 — /servlet
_说明_:请求的资源 (/servlet) 不可用。
或者在 Tomcat 8.5⁄9 中如下所示:
HTTP 状态 404 - 未找到
_消息_:/servlet
_描述_:源服务器没有找到目标资源的当前表示或不愿意透露存在的表示
或者在 Tomcat 10 中如下所示:
HTTP 状态 404 - 未找到
_类型_:状态报告
_消息_:请求的资源 (/servlet) 不可用
_描述_:源服务器没有找到目标资源的当前表示或不愿意透露存在的表示
为什么它不起作用?
原文由 pongahead 发布,翻译遵循 CC BY-SA 4.0 许可协议
介绍
这可能有很多原因,这些原因在以下部分中进行了细分:
package
url-pattern
@WebServlet
仅适用于 Servlet 3.0 或更新版本javax.servlet.*
在 Servlet 5.0 或更新版本中不再工作*.class
文件存在于构建的 WAR 中将 servlet 类放在
package
首先,将 servlet 类放在 Java
package
中。您应该 始终 将可公开重用的 Java 类放在一个包中,否则它们对于包中的类是不可见的,例如服务器本身。这样您就可以消除潜在的环境特定问题。 Packageless servlets 仅在特定的 Tomcat+JDK 组合中工作,永远不要依赖它。在“普通”IDE 项目的情况下,类需要放置在“Java Sources”文件夹内的包结构中, _而不是_“Web Content”文件夹内,后者用于 JSP 等 Web 文件。下面是在 Navigator 视图中看到的默认 Eclipse 动态 Web 项目 的文件夹结构示例(“Java Sources”文件夹在此类项目中默认由
src
文件夹表示):对于 Maven 项目,该类需要放在其包结构中
main/java
而 不是main/resources
, 这是针对非类文件的,绝对 也不 是main/webapp
,这是用于网络文件。下面是在 Eclipse 的 导航器 视图中看到的默认 Maven webapp 项目的文件夹结构示例:请注意,
/jsps
子文件夹并非绝对必要。您甚至可以不使用它并将 JSP 文件直接放在 webcontent/webapp 根目录中,但我只是从您的问题中接管了它。在
url-pattern
servlet URL 被指定为 servlet 映射的“URL 模式”。根据定义,它绝对不是 servlet 类的类名/文件名。 URL 模式将被指定为
@WebServlet
注释的值。如果你想支持像
/servlet/foo/bar
这样的路径参数,那么使用/servlet/*
的 URL 模式。另见 Servlet 和路径参数,如 /xyz/{value}/test,如何在 web.xml 中映射?请注意,使用
/*
或/
的 Servlet URL 模式试图拥有“前端控制器”被认为是一种不好的做法。因此,不要滥用这些 URL 模式来尝试捕获所有 URL。有关深入的解释,另请参阅 Difference between / and /* in servlet mapping url pattern 。@WebServlet
仅适用于 Servlet 3.0 或更新版本为了使用
@WebServlet
,您只需要确保您的web.xml
文件(如果有的话)(自 Servlet 3.0 以来它是可选的)被声明为符合 Servlet 3.0+ 版本 ,因此 不 符合例如 2.5 版本或更低版本。它绝对也不应该有任何<!DOCTYPE>
行。下面是一个完整的 Servlet 6.0 兼容版本(匹配 Tomcat 10.1+、WildFly 27+(预览版)、GlassFish/Payara 7+ 等):下面是一个 Servlet 5.0 兼容的(匹配 Tomcat 10.0.x、WildFly 22+(预览版)、GlassFish/Payara 6+ 等)。
下面是一个 Servlet 4.0 兼容的(匹配 Tomcat 9+、WildFly 11+、GlassFish/Payara 5+ 等)。
或者,如果您还没有使用 Servlet 3.0+(例如 Tomcat 6 或更早版本),则删除
@WebServlet
注释。并在
web.xml
中注册 servlet,如下所示:因此请注意,您不应同时使用这两种方式。使用基于注释的配置或基于 XML 的配置。当你同时拥有两者时,基于 XML 的配置将覆盖基于注释的配置。
javax.servlet.*
在 Servlet 5.0 或更新版本中不再工作从 Jakarta EE 9 / Servlet 5.0(Tomcat 10、TomEE 9、WildFly 22 Preview、GlassFish 6、Payara 6、Liberty 22 等)开始,
javax.*
包已重命名为jakarta.*
包裹。换句话说,请绝对确保您不会随意将不同服务器的 JAR 文件放入您的 WAR 项目中,例如 tomcat-servlet-api-9.xxjar 仅仅是为了获得
javax.*
包编译。这只会带来麻烦。完全删除它并编辑您的 servlet 类的导入到
如果您使用的是 Maven,您可以在这个答案中找到 Tomcat 10+、Tomcat 9-、JEE 9+ 和 JEE 8- 的正确
pom.xml
声明示例: How to properly configure Jakarta EE libraries in Tomcat 的 Maven pom.xml? 另一种方法是将服务器降级到旧版本,例如从 Tomcat 10 降级到 Tomcat 9 或更早版本,但这显然不是推荐的方法。确保已编译的
*.class
文件存在于构建的 WAR 中如果您使用的是 Eclipse 和/或 Maven 等构建工具,那么您需要绝对确保已编译的 servlet 类文件驻留在其包结构中,位于生成的 WAR 文件的
/WEB-INF/classes
文件夹中。在package com.example; public class YourServlet
的情况下,它必须位于/WEB-INF/classes/com/example/YourServlet.class
中。否则你将面临@WebServlet
404 错误,或者<servlet>
HTTP 500 错误,如下所示:并在服务器日志中找到
java.lang.ClassNotFoundException: com.example.YourServlet
,然后是java.lang.NoClassDefFoundError: com.example.YourServlet
,然后依次是jakarta.servlet.ServletException: Error instantiating servlet class com.example.YourServlet
。验证 servlet 是否正确编译并放置在类路径中的一种简单方法是让构建工具生成一个 WAR 文件(例如,右键单击项目,在 Eclipse 中 _导出 > WAR 文件_),然后使用 ZIP 工具检查其内容。如果 servlet 类在
/WEB-INF/classes
中丢失,或者如果导出导致错误,则项目配置错误或一些 IDE/项目配置默认值被错误地恢复(例如, 项目 > 自动构建 已被禁用蚀)。您还需要确保项目图标没有指示构建错误的红叉。您可以在“ _问题_”视图( “窗口”>“显示视图”>“其他…” )中找到确切的错误。通常错误信息是很好的 Googlable。如果您不知道,最好从头开始,不要触及任何 IDE/项目配置默认值。如果您使用的是 Eclipse,可以在 如何在我的 Eclipse 项目中导入 javax.servlet / jakarta.servlet API? 中找到说明。
在没有任何 JSP/HTML 页面的情况下单独测试 servlet
假设服务器运行在
localhost:8080
上,并且 WAR 成功部署在/contextname
的上下文路径(默认为 IDE 项目名称,区分大小写!),以及 servlet尚未初始化失败(读取服务器日志以获取任何部署/servlet 成功/失败消息以及实际上下文路径和 servlet 映射),然后 URL 模式为/servlet
的 servlet 可在http://localhost:8080/contextname/servlet
。您可以直接在浏览器的地址栏中输入它来单独测试它。如果它的
doGet()
被正确覆盖和实现,那么您将在浏览器中看到它的输出。或者,如果您没有任何doGet()
或者它错误地调用了super.doGet()
,则会显示“ HTTP 405:此 URL 不支持 HTTP 方法 GET ”错误(仍然比 404 好,因为 405 是实际找到 servlet 本身的证据)。重写
service()
是一种不好的做法,除非你正在重新发明一个 MVC 框架——如果你刚开始使用 servlet 并且对当前问题中描述的问题一无所知,这是不太可能的;)请参阅还有 设计模式基于 Web 的应用程序。无论如何,如果 servlet 在单独测试时已经返回 404,那么尝试使用 HTML 表单是完全没有意义的。因此,从逻辑上讲,在有关来自 servlet 的 404 错误的问题中包含任何 HTML 表单也是完全没有意义的。
使用域相对 URL 从 HTML 引用 servlet
一旦您验证了 servlet 在单独调用时工作正常,您就可以继续使用 HTML。至于 HTML 表单的具体问题,
<form action>
值需要是有效的 URL。这同样适用于<a href>
,<img src>
,<script src>
等。您需要了解绝对/相对 URL 的工作原理。您知道,URL 是您可以在网络浏览器的地址栏中输入/查看的网址。如果您将相对 URL 指定为表单操作,即没有http://
方案,那么它将变成相对于您在网络浏览器地址栏中看到的 当前 URL。因此,它与许多初学者似乎认为的服务器 WAR 文件夹结构中的 JSP/HTML 文件位置绝对无关。因此,假设带有 HTML 表单的 JSP 页面由
http://localhost:8080/contextname/jsps/page.jsp
打开(因此 不是file://...
),并且您需要提交到位于http://localhost:8080/contextname/servlet
的 servlet---
, here are several cases (note that you can here safely substitute<form action>
with<a href>
,<img src>
,<script src>
, etc):前导斜杠
/
使 URL 相对于域,因此表单将提交到但这可能会导致 404,因为它处于错误的上下文中。
这使得 URL 相对于当前 URL 的当前文件夹,因此表单将提交到
但这可能会导致 404,因为它位于错误的文件夹中。
这将向上移动一个文件夹(就像在本地磁盘文件系统路径中一样!),因此表单将提交给
这个必须工作!
这将产生
因此,它将始终提交到正确的 URL。
在 HTML 属性中使用直引号
You need to make absolutely sure you’re using straight quotes in HTML attributes like
action="..."
oraction='...'
and thus not curly quotes likeaction=”...”
oraction=’...’
。 HTML 不支持大引号,它们只会成为值的一部分。从博客中复制粘贴代码片段时要小心!一些博客引擎,尤其是 Wordpress,默认使用所谓的“智能引号”,因此也会以这种方式破坏代码片段中的引号。另一方面,不要复制粘贴代码,而是尝试自己简单地键入代码。通过你的大脑和手指实际获取代码的另一个好处是,从长远来看,它会让你更好地记住和理解代码,也会让你成为更好的开发人员。也可以看看:
HTTP Status 404 错误的其他情况: