介绍
这可能有很多原因,可以在以下部分中进行细分:
- 将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;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
@WebServlet("/servlet")
public class YourServlet extends 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"
>
</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"
>
</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"
>
</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>
</servlet>
<servlet-mapping>
<servlet-name>yourServlet</servlet-name>
<url-pattern>/servlet</url-pattern>
</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>
等):
javax.servlet.* 不再起作用
和spring-boot
;可能很难确定要使用什么,因此您需要查看规格:2.x 需要 Tomcat 9 javax 和 3.x Tomcat 10 Jakarta。 - slindenau