Servlet应用基于Maven启动嵌入式Tomcat报错排查:命令行运行JAR包出现NoClassDefFoundError问题解决
1. 为什么会出现NoClassDefFoundError?
你遇到的错误核心原因很明确:你用maven-jar-plugin生成的Jar只包含了自己项目的代码,没有把依赖的Tomcat、Servlet相关类打包进去。当你用java -jar运行时,JVM找不到这些第三方依赖类,所以抛出了ClassNotFoundException。
解决方法:打包包含所有依赖的可执行Jar
推荐用maven-shade-plugin替换或配合原有的插件,它会把所有依赖类合并到一个Jar里(也就是常说的「胖Jar」)。修改pom.xml的<build><plugins>部分:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>3.2.4</version> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <mainClass>p.Main</mainClass> </transformer> </transformers> </configuration> </execution> </executions> </plugin>
重新执行mvn clean install后,生成的Jar就会包含所有依赖,再运行java -jar target/AssignmentAppWeb-0.0.1-SNAPSHOT.jar就能正常启动了。
另外,你已经配置了appassembler-maven-plugin,这个插件会生成包含依赖和启动脚本的目录结构——你可以进入target/bin目录,直接运行对应的启动脚本(Windows是.bat,Linux/macOS是.sh),也能解决依赖问题,不需要手动处理Jar的依赖加载。
2. 代码注册Servlet vs web.xml,哪个更合适?
在你这种嵌入式Tomcat的场景下,代码注册Servlet绝对是更合适的选择,原因如下:
- 灵活性更高:可以根据运行时条件动态注册Servlet、调整URL映射,不需要修改配置文件重启。
- 贴合嵌入式场景设计:嵌入式Tomcat的核心就是用代码配置整个容器,摆脱传统WAR包和
web.xml的束缚。 - 更直观:代码即配置,不需要在多个文件间切换维护,逻辑更集中。
如果是传统WAR包部署到独立Tomcat的场景,web.xml或者@WebServlet注解是常用方式,但你的需求是命令行启动的轻量化应用,代码注册是最优解。
3. 如何正确启动嵌入式Tomcat?
你的Main类代码其实已经是正确的嵌入式Tomcat启动流程了,调整完依赖打包后,只需要注意几个细节:
tomcat.setBaseDir("temp")指定的临时目录如果不存在,Tomcat会自动创建,用来存放容器运行时的临时文件,无需手动创建。- 你的Servlet注册逻辑完全正确:创建Servlet实例 → 注册到Tomcat容器 → 绑定URL映射,这个流程符合嵌入式Tomcat的使用规范。
- 最后
tomcat.getServer().await()会让主线程保持阻塞,确保Tomcat持续运行,这一步不能省略,否则程序会启动后立刻退出。
验证启动成功
启动后访问http://localhost:8080/go,如果能看到Have a Great Day!的页面,就说明嵌入式Tomcat和Servlet都正常工作了。
内容的提问来源于stack exchange,提问作者Wrapper

