Servlet返回"HTTP状态404 请求的资源(/servlet)不可用"。

118
我在我的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">

但是这些方法都不起作用。它们都会返回一个HTTP 404错误,就像在Tomcat 6/7/8中所示:
HTTP状态404 - /servlet 描述:所请求的资源(/servlet)不可用。
或者在Tomcat 8.5/9中如下所示:
HTTP状态404 - 未找到 消息:/servlet 描述:源服务器未找到目标资源的当前表示,或者不愿透露该资源的存在。
或者在Tomcat 10中如下所示:
HTTP状态404 - 未找到 类型:状态报告 消息:所请求的资源(/servlet)不可用 描述:源服务器未找到目标资源的当前表示,或者不愿透露该资源的存在。
为什么不起作用呢?
19个回答

165

介绍

这可能有很多原因,可以在以下部分中进行细分:

  • 将Servlet类放入package
  • url-pattern中设置Servlet URL
  • @WebServlet仅适用于Servlet 3.0或更新版本
  • javax.servlet.*在Servlet 5.0或更新版本中不再适用
  • 确保编译的*.class文件存在于构建的WAR中
  • 单独测试Servlet,不包含任何JSP/HTML页面
  • 使用相对于域的URL来引用HTML中的Servlet
  • 在HTML属性中使用直引号

将Servlet类放入package

首先,将servlet类放置在Java的package中。你应该总是将可公开重用的Java类放在一个package中,否则它们对于位于package中的类是不可见的,比如服务器本身的源代码。这样可以消除潜在的环境特定问题。没有package的servlet只能在特定的Tomcat+JDK组合中工作,不能依赖它。如果你不知道选择哪个package,可以从com.example开始。
对于“普通”IDE项目,该类需要放置在“Java Sources”文件夹中的package结构中,而不是放在“Web Content”文件夹中,后者用于存放Web文件,如JSP。下面是一个默认的Eclipse动态Web项目的文件夹结构示例,可以在Navigator视图中看到(默认情况下,“Java Sources”文件夹由src文件夹表示):
EclipseProjectName
 |-- src
 |    `-- com
 |         `-- example
 |              `-- YourServlet.java
 |-- WebContent
 |    |-- WEB-INF
 |    |    `-- web.xml
 |    `-- jsps
 |         `-- page.jsp
 :

在Maven项目中,类需要放置在其包结构内的main/java目录下,而不是main/resources目录下,这是用于非类文件的。绝对不要放在main/webapp目录下,这是用于Web文件的。下面是一个默认的Maven Web应用项目的文件夹结构示例,可以在Eclipse的Navigator视图中看到:
MavenProjectName
 |-- src
 |    `-- main
 |         |-- java
 |         |    `-- com
 |         |         `-- example
 |         |              `-- YourServlet.java
 |         |-- resources
 |         `-- webapp
 |              |-- WEB-INF
 |              |    `-- web.xml
 |              `-- jsps
 |                   `-- page.jsp
 :

请注意,/jsps子文件夹并不是必需的。你甚至可以不用它,直接将JSP文件放在webcontent/webapp根目录下,但我只是从你的问题中接手过来。

url-pattern中设置servlet URL

Servlet URL被指定为servlet映射的"URL模式"。它绝对不是servlet类的类名/文件名。URL模式应该作为@WebServlet注解的值来指定。

package com.example; // Use a package!

import jakarta.servlet.annotation.WebServlet; // or javax.*
import jakarta.servlet.http.HttpServlet; // or javax.*

@WebServlet("/servlet") // This is the URL of the servlet.
public class YourServlet extends HttpServlet { // Must be public and extend HttpServlet.
    // ...
}

如果你想支持路径参数,比如 /servlet/foo/bar,那么请使用 /servlet/* 作为URL模式。详见 Servlet和路径参数如何在web.xml中映射?
请注意,在尝试使用Servlet URL模式 /*/ 来实现"前端控制器"时,这被认为是一种不良实践。因此,请不要滥用这些URL模式来尝试捕获所有URL。有关详细解释,请参阅 servlet映射URL模式中/和/*的区别@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+等匹配):
<?xml version="1.0" encoding="UTF-8"?>
<web-app 
    xmlns="https://jakarta.ee/xml/ns/jakartaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
    version="6.0"
>
    <!-- Config here. -->
</web-app>

以下是一个兼容Servlet 5.0的示例(适用于Tomcat 10.0+,WildFly 22+(预览版),GlassFish/Payara 6+等)。
<?xml version="1.0" encoding="UTF-8"?>
<web-app 
    xmlns="https://jakarta.ee/xml/ns/jakartaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
    version="5.0"
>
    <!-- Config here. -->
</web-app>

以下是一个兼容Servlet 4.0的示例(适用于Tomcat 9+,WildFly 11+,GlassFish/Payara 5+等)。
<?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"
>
    <!-- Config here. -->
</web-app>

或者,如果您还没有使用Servlet 3.0+(例如Tomcat 6或更早版本),那么请删除@WebServlet注解(并确保您还删除了所有错误的JAR文件或Maven依赖项,这些错误使您能够成功编译代码)。
package com.example;

import javax.servlet.http.HttpServlet;

public class YourServlet extends HttpServlet {
    // ...
}

web.xml中注册servlet,像这样:
<servlet>
    <servlet-name>yourServlet</servlet-name>
    <servlet-class>com.example.YourServlet</servlet-class> <!-- Including the package thus -->
</servlet>
<servlet-mapping>
    <servlet-name>yourServlet</servlet-name>
    <url-pattern>/servlet</url-pattern>  <!-- This is the URL of the servlet. -->
</servlet-mapping>

请注意,您不应同时使用两种方式。请使用基于注解的配置或基于XML的配置。当两者都存在时,基于XML的配置将覆盖基于注解的配置。

javax.servlet.*在Servlet 5.0或更新版本中不再适用

自从Jakarta EE 9 / Servlet 5.0(Tomcat 10,TomEE 9,WildFly 22预览版,GlassFish 6,Payara 6,Liberty 22等)以来,javax.*包已被重命名为jakarta.*包。
换句话说,请务必确保您不会随机将不同服务器的JAR文件放入您的WAR项目中,例如tomcat-servlet-api.jar,仅仅为了使javax.*包能够编译。这只会带来麻烦。请将它们全部删除,并编辑您的servlet类的导入语句。
import javax.servlet.*;
import javax.servlet.annotation.*;
import javax.servlet.http.*;

import jakarta.servlet.*;
import jakarta.servlet.annotation.*;
import jakarta.servlet.http.*;

如果您使用的是Maven,您可以在此答案中找到适用于Tomcat 10+、Tomcat 9-、JEE 9+和JEE 8-的正确的pom.xml声明示例:如何在Maven pom.xml中正确配置Jakarta EE库以供Tomcat使用? 另一种选择是将服务器降级到旧版本,例如从Tomcat 10降级到Tomcat 9或更早版本,但这显然不是推荐的方法。
如果您使用的是Spring而不是Jakarta EE,Spring 6和Spring Boot 3是首个针对Servlet 5.0的版本,因此也是首个使用jakarta.*包的版本。旧版本仍使用javax.*包。请相应地调整您的导入。
你必须确保在你的依赖项中没有任何冲突的库(无论是pom.xml还是/WEB-INF/lib中的物理文件),这样才能正确地编译使用javax.servlet.*。在面向Servlet 5.0+的项目中,使用javax.servlet.*必须会导致编译错误(所以,要么是Jakarta EE 9+,要么是Spring 6+,要么是Spring Boot 3+)。
确保编译后的*.class文件存在于构建的WAR文件中
如果你使用Eclipse和/或Maven等构建工具,那么你必须确保编译后的servlet类文件位于生成的WAR文件的/WEB-INF/classes文件夹中的包结构中。例如,对于package com.example; public class YourServlet,它必须位于/WEB-INF/classes/com/example/YourServlet.class。否则,如果使用@WebServlet,你将面临404错误,如果使用,你将面临以下HTTP 500错误:
HTTP状态500 实例化servlet类com.example.YourServlet时出错
在服务器日志中找到一个`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工具检查其内容。如果在`/WEB-INF/classes`中缺少Servlet类,或者导出过程中出现错误,则项目配置错误或者某些IDE/项目配置默认值已被错误地恢复(例如,在Eclipse中禁用了“项目 > 自动构建”)。
你还需要确保项目图标上没有红色的叉号,表示构建错误。你可以在“问题”视图中找到具体的错误(窗口 > 显示视图 > 其他...)。通常错误信息是可以通过谷歌搜索解决的。如果你完全不知道怎么解决,最好的办法是从头开始重新启动,不要修改任何IDE/项目配置的默认值。如果你使用的是Eclipse,你可以在如何在我的Eclipse项目中导入javax.servlet/jakarta.servlet API?中找到说明。

在没有任何JSP/HTML页面的情况下单独测试servlet

假设服务器运行在localhost:8080上,并且WAR成功部署在上下文路径/contextname上(默认为IDE项目名称或Maven构建工件文件名,区分大小写!),并且servlet没有初始化失败(查看服务器日志以获取任何部署/ servlet成功/失败消息和实际的上下文路径和servlet映射),那么URL模式为/servlet的servlet可以在http://localhost:8080/contextname/servlet上访问。
你可以直接在浏览器的地址栏中输入来单独测试它。如果它的doGet()被正确地重写和实现,那么你将在浏览器中看到它的输出。或者如果你没有任何doGet(),或者如果它错误地调用了super.doGet(),那么将显示一个“HTTP 405: HTTP method GET is not supported by this URL”错误(这比404错误要好,因为405错误证明了servlet本身实际上是存在的)。
重写service()是一种不好的做法,除非你正在重新发明一个MVC框架——如果你刚开始学习servlet并对当前问题的描述一无所知,那么这种情况是非常不可能的。另请参阅Design Patterns web based applications
请注意,当单独测试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,这里有几种情况(请注意,您可以在这里安全地将<form action>替换为<a href><img src><script src>等):
<form action="/servlet"> 前导斜杠/使URL相对于域名,因此表单将提交到
  http://localhost:8080/servlet
但这很可能导致404错误,因为它在错误的上下文中。
  • Form action提交到一个没有前导斜杠的URL。

      <form action="servlet">
    
    这使得URL相对于当前URL的当前文件夹,因此表单将提交到
      http://localhost:8080/contextname/jsps/servlet
    
    但这很可能导致404错误,因为它在错误的文件夹中。
  • Form action提交到一个向上移动一个文件夹的URL。

      <form action="../servlet">
    
    这将向上移动一个文件夹(就像在本地磁盘文件系统路径中一样!),因此表单将提交到
      http://localhost:8080/contextname/servlet
    
    这个一定会起作用!
  • 然而,规范的方法是使URL相对于域名,这样当您将JSP文件移动到另一个文件夹时,您不需要再次修复URL。

      <form action="${pageContext.request.contextPath}/servlet">
    
    这将生成
      <form action="/contextname/servlet">
    
    这样将始终提交到正确的URL。
  • 在HTML属性中使用直引号

    你必须确保在HTML属性中使用直引号,例如action="..."action='...',而不是弯引号,如action=”...”action=’...’。HTML不支持弯引号,它们将成为值的一部分。在从博客复制粘贴代码片段时要小心!一些博客引擎,尤其是WordPress,默认使用所谓的“智能引号”,这也会破坏代码片段中的引号。另一方面,尝试自己输入代码而不是复制粘贴代码。通过大脑和手指实际编写代码的额外优势是,它将使你在长期记住和理解代码,并使你成为一个更好的开发者。

    另请参阅:

    HTTP状态404错误的其他情况:


    1
    使用Glassfish的Web应用程序版本为“3.1”,当我在web.xml中有映射和注释时,我可以单独测试我的Servlet。但是,由于我有最新版本,我删除了映射并保留了注释,但是现在我会收到404错误? - SallyRothroat
    1
    如果您在Web应用程序本身中包含Servlet 2.5或更早版本的库,而不是依赖于目标运行时自己提供Servlet库,则可能会发生这种情况。 - BalusC
    关于 javax.servlet.* 不再起作用spring-boot;可能很难确定要使用什么,因此您需要查看规格:2.x 需要 Tomcat 9 javax3.x Tomcat 10 Jakarta - slindenau
    1
    非常感谢您提供这么详细的答案。我之前使用jar命令错误地创建了war文件。当我直接在eclipse中创建war文件后,我的问题得到了解决。 - undefined

    5

    场景1:在tomcat已经运行时,您意外地从命令行重新部署。

    简短回答:停止Tomcat,删除目标文件夹,mvn打包,然后重新部署。


    场景2:request.getRequestDispatcher("MIS_SPELLED_FILE_NAME.jsp")

    简短回答:检查文件名拼写,确保大小写正确。


    场景3:类未找到异常 (将答案放在这里的原因是:问题#17982240) (java.lang.ClassNotFoundException for servlet in tomcat with eclipse) (被标记为重复并引导我来这里)

    简短回答 #3.1:web.xml中servlet-class标签的包路径错误。

    简短回答 #3.2:java文件的import语句错误。


    以下是场景1的更多细节:


    1:停止Tomcat

    • 选项1:通过终端中的CTRL+C。
    • 选项2:(终端关闭时tomcat仍在运行)
    • ------------ 2.1:按下:Windows+R --> 输入:“services.msc
    • ------------ 2.2:在列表的名称列中找到“Apache Tomcat #.# Tomcat#”。
    • ------------ 2.3:右键单击 --> “停止

    2:删除“target”文件夹。 (mvn clean在这里无法帮助您)

    3:mvn打包

    4:YOUR_DEPLOYMENT_COMMAND_HERE

    (我的:java -jar target/dependency/webapp-runner.jar --port 5190 target/*.war)

    完整背景故事:


    意外地打开了一个新的git-bash窗口, 尝试通过以下方式部署heroku项目的.war文件:

    java -jar target/dependency/webapp-runner.jar --port 5190 target/*.war

    在部署失败后,我意识到我有两个git-bash窗口打开, 并且没有使用CTLRC来停止以前的部署

    我遇到了:

    HTTP状态404-未找到状态报告

    消息/if-student-test.jsp

    描述源服务器没有找到当前表示形式 目标资源或不愿透露一个 存在。

    Apache Tomcat/8.5.31

    以下是场景3的更多细节:


    场景 3.1: 在您的 web.xml 文件中,servlet-class 包路径错误。 它应该与您 Java Servlet 类顶部的 package 声明匹配。

    文件:my_stuff/MyClass.java

       package my_stuff;
    

    File: PRJ_ROOT/src/main/webapp/WEB-INF/web.xml

       <servlet-class>
       my_stuff.MyClass
       </servlet-class>
    

    场景 3.2:

    你在 myClass.java 文件的顶部放置了错误的 "package" 声明。

    例如:

    文件位于 "/my_stuff" 文件夹中。

    你错误地写成:

    package com.my_stuff
    

    这很棘手,因为:
    1. Maven构建(mvn package)不会报告任何错误。
    2. web.xml中的servlet-class行可以拥有正确的包路径。例如:
    <servlet-class>
    my_stuff.MyClass
    </servlet-class>
    

    使用的技术栈: Notepad++ + GitBash + Maven + Heroku Web App Runner + Tomcat9 + Windows10:


    你的AppName.war文件以及解压后的文件夹名称与你期望的名称不匹配,例如当你的war文件版本号为AppName-1.0-SNAPSHOT.war时,你尝试访问的路径为/AppName/。 - jla

    2
    如果您使用的是IntelliJ,以下是我解决问题的方法:
    转到Tomcat配置: enter image description here 配置 > 部署选项卡 enter image description here 向下滚动并将“/”添加到应用程序上下文下拉列表中 enter image description here

    2

    请检查你是否按照Web.xml中指定的URL映射输入了正确的内容。

    例如:

    在Web.xml中,你的Servlet声明可能为:

    <servlet>
            <servlet-name>ControllerA</servlet-name>
            <servlet-class>PackageName.ControllerA</servlet-class>
    </servlet>
    
    <servlet-mapping>
            <servlet-name>ControllerA</servlet-name>
            <url-pattern>/theController</url-pattern>
    </servlet-mapping>
    

    这段代码的作用是将<url-pattern>/theController</url-pattern>设置为前端(例如表单)通过URL调用servlet时使用的名称。因此,当您在前端引用servlet时,为了确保请求发送到servlet “ControllerA”,应该从表单中引用指定的URL模式“ theController”。
    <form action="theController" method="POST">
    </form>
    

    0

    我也遇到了同样的问题。尝试了所有方法,但都没有帮助。最终我通过在 XML 文件的开头和结尾添加元素标签来解决了这个问题。以下是我的 XML 文件,仅供参考。

    <?xml version="1.0" encoding="UTF-8"?>
    
    <element>
    
    <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_3_1.xsd"
             version="3.1">
         
    <servlet> 
        <servlet-name>InsertServlet</servlet-name> 
        <servlet-class>com.worklog.InsertServlet</servlet-class> 
    </servlet> 
    <servlet-mapping> 
        <servlet-name>InsertServlet</servlet-name> 
        <url-pattern>/insert</url-pattern> 
    </servlet-mapping>
    
    </web-app>
    
    </element>
    

    0

    我曾经遇到过同样的问题。我正在开发一个基于MVC的REST API,其中没有明确的HTML配置或文件。该API使用Swagger来生成用户界面。当我引入Swagger版本"3.0.0"时,问题就开始了。我回退到Swagger "2.9.2",这解决了我的问题。

    <!-- Swagger -->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.9.2</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
        </dependency>
    

    0
    如果您想要在不使用“表单”和“提交”按钮的情况下使用JavaScript打开一个Servlet,那么以下是代码:
    var button = document.getElementById("<<button-id>>");
    button.addEventListener("click", function() {
      window.location.href= "<<full-servlet-path>>" (eg. http://localhost:8086/xyz/servlet)
    });
    

    关键字:

    1)button-id:您在html/jsp文件中为按钮指定的“id”标签。

    2)full-servlet-path:仅运行servlet时在浏览器中显示的路径。


    0
    首先,以管理员身份运行您的IDE。之后,右键单击项目文件夹 -> 项目 Facets 并确保 Java 版本设置正确。在我的电脑上(例如 1.8)。现在应该可以工作了。
    不要仅仅使用 cmd 启动服务器,例如 Wildfly。它必须在 IDE 中启动,然后访问您的本地主机 URL。例如:http://localhost:8080/HelloWorldServlet/HelloWorld

    0
    我使用的解决方法是(如果您正在使用Maven):右键单击您的项目,选择Maven -> 更新项目。这可能会导致一些其他与JDK和其他库(在我的情况下是MySQL连接器)有关的错误,但是一旦您解决了它们,您原来的问题应该就解决了!

    0
    我的问题是我的方法缺少@RequestBody注释。添加了该注释后,我不再收到404异常。

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接