很多初学JavaWeb的朋友在IDEA里把代码写完了一运行却满脸问号明明代码看着没问题数据库也建好了Tomcat也启动了就是报错、白屏、连不上数据库。这种挫败感我太熟悉了。本篇文章我就用最直白的方式把“创建JavaWeb项目、部署到Tomcat、连接数据库”这条完整链路从头到尾捋一遍包括那些文档里不写、老师不细讲、但实际操作中一定会踩的坑。这篇文章适合完全没有Web项目经验、第一次在IDEA里接触Servlet和JSP的同学也适合那些之前只写过JavaSE控制台程序、想转到Web开发的朋友。我尽量把每一步都讲透不跳步骤不做假设确保你照着操作就能跑通。1. 环境准备与版本避坑1.1 核心工具与推荐版本搭配在开始创建项目之前先把环境装好。这里我直接给出经过大量实操验证的版本组合能帮你避开很多莫名其妙的兼容性问题。工具推荐版本说明JDK1.8 或 11JavaWeb老项目多用JDK8新项目用11也完全没问题Tomcat8.5.x 或 9.0.x这两个版本对Servlet规范支持成熟适合绝大多数教学项目IDEA2021版及以上社区版和专业版均可下文会说明差异MySQL5.7 或 8.05.7稳定8.0要注意驱动版本匹配这个搭配有什么讲究JDK版本不要盲目追新因为Tomcat版本和IDE对JDK有支持上限。比如Tomcat 8.5默认支持到JDK 11如果你装了JDK 17却用了老版本Tomcat启动时直接报版本错误。我见过太多同学在这上面折腾一下午。1.2 环境变量配置与验证装好JDK之后配置环境变量是第一个容易出问题的地方。新建系统变量变量名是JAVA_HOME变量值填你的JDK安装路径比如C:\Program Files\Java\jdk1.8.0_202。然后编辑Path变量在最后面追加%JAVA_HOME%\bin。打开命令行工具输入以下命令验证java -version能看到版本信息就说明JDK环境没问题。很多同学在这一步会犯一个低级错误装完JDK忘了配置JAVA_HOME或者Path里配的是JRE路径结果IDEA里能跑但命令行一敲就报“不是内部或外部命令”。打包部署到服务器的时候命令行是检验环境是否正常的最后防线这个坑越早踩越好。Tomcat不需要配置环境变量就能用IDEA里集成部署不需要CATALINA_HOME但建议还是配一下方便日后直接用命令行启动CATALINA_HOME D:\apache-tomcat-8.5.88 Path 追加 %CATALINA_HOME%\bin1.3 数据库准备与初始化脚本MySQL安装完成后需要准备一个测试数据库。这里我建议直接在命令行或Navicat里执行下面这段SQL创建库和一张简单的用户表CREATE DATABASE IF NOT EXISTS web_demo DEFAULT CHARACTER SET utf8mb4; USE web_demo; CREATE TABLE t_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL, password VARCHAR(50) NOT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; INSERT INTO t_user (username, password) VALUES (admin, 123456); INSERT INTO t_user (username, password) VALUES (test, 654321);建表时特别注意字符集。很多老教程还在用utf8但MySQL 8.0里utf8mb4才是完整的中文支持方案你要是存Emoji或者生僻字utf8直接报错或者存储乱码。这条经验从新手阶段就养成习惯后面能省太多事。数据库账户建议单独建一个专用账号不要直接用root虽然本地学习无所谓但这能帮你提前适应生产环境的账号管理规范CREATE USER demolocalhost IDENTIFIED BY demo123456; GRANT ALL PRIVILEGES ON web_demo.* TO demolocalhost; FLUSH PRIVILEGES;2. 项目创建与目录结构解析2.1 IDEA中创建Web项目的两条路径使用IDEA创建JavaWeb项目目前有两条主流路径。路径一原生方式适合老版本和教学打开IDEA选择新建项目左侧选Java在Additional Libraries and Frameworks里勾选Web Application然后勾选Create web.xml。这种方式生成的是传统JavaWeb项目结构清晰web.xml掌控全局适合理解底层原理。路径二Maven方式强烈推荐新项目使用选择新建项目左侧选Maven勾选Create from archetype选择maven-archetype-webapp骨架。这个骨架会帮你生成标准的Web项目目录结构而且后续管理jar包极其方便。用Maven创建的项目pom.xml就是你的依赖总管家。从我个人的实操体验来看建议新手直接上Maven方式。虽然多一层Maven的学习成本但现代JavaWeb开发早就脱离不了Maven的依赖管理早学早受益。老式的手工导jar包到WEB-INF/lib那种方式遇到版本冲突时能把你逼疯。2.2 标准目录结构与注意事项无论用哪种方式创建项目最终目录结构长这样src main java -- 存放Servlet类、JavaBean、工具类等源码 resources -- 存放配置文件、Mapper映射文件等资源 webapp -- Web根目录 WEB-INF -- 受保护的目录外部无法直接通过URL访问 index.jsp pom.xml -- Maven项目核心配置文件这里有一个新手必然迷惑的点为什么WEB-INF目录下的文件浏览器访问不到因为Web容器有安全机制WEB-INF下的文件和META-INF一样是禁止通过URL直接访问的。你所有的JSP页面如果放WEB-INF里就只能通过Servlet转发来访问这是一种安全设计思路。而静态资源如CSS、JS、图片放webapp根目录下就行。2.3 Maven坐标配置与依赖引入使用Maven方式创建项目后编辑pom.xml把下面这段依赖加进去properties project.build.sourceEncodingUTF-8/project.build.sourceEncoding maven.compiler.source1.8/maven.compiler.source maven.compiler.target1.8/maven.compiler.target /properties dependencies !-- Servlet API -- dependency groupIdjavax.servlet/groupId artifactIdjavax.servlet-api/artifactId version4.0.1/version scopeprovided/scope /dependency !-- JSP API -- dependency groupIdjavax.servlet.jsp/groupId artifactIdjavax.servlet.jsp-api/artifactId version2.3.3/version scopeprovided/scope /dependency !-- JSTL标签库 -- dependency groupIdjavax.servlet/groupId artifactIdjstl/artifactId version1.2/version /dependency !-- MySQL驱动 -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency /dependencies注意看Servlet和JSP的依赖scope是provided这个细节很多同学不知道。意思是这个jar包在编译和测试阶段需要但最终打包时不用打进去因为Tomcat容器本身已经内置了Servlet API和JSP API。你要是写了compile打出来的war包会带上这些jar包运气不好就会和Tomcat自带的冲突出现NoSuchMethodError这类奇怪异常。MySQL驱动版本一定要和MySQL版本对应8.0.x的数据库就用8.0.x的驱动别用5.1的老驱动硬连会报Public Key Retrieval is not allowed这种让人摸不着头脑的错误。3. 连接数据库的核心技术与详细配置3.1 驱动加载与JDBC连接的关键代码我先写一个最基础的JDBC工具类保证你能先把库连上后面再讲怎么优化package com.demo.util; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/web_demo?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8mb4; private static final String USERNAME demo; private static final String PASSWORD demo123456; static { try { Class.forName(com.mysql.cj.jdbc.Driver); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USERNAME, PASSWORD); } }这里解读几个关键点都是新手极易栽跟头的地方。URL参数里的serverTimezoneAsia/Shanghai不是可选项。MySQL 8.0默认时区是UTC如果你不指定服务器时区Java和MySQL之间会产生8小时的时间差。查询出来的时间和你插入的时间对不上排查半天发现原来是时区问题这个坑我在带新人时遇到过太多次。useSSLfalse是为了避免SSL握手的警告和潜在连接失败本地开发环境完全不需要SSL加密。驱动类名com.mysql.cj.jdbc.Driver也是MySQL 8.0的专属写法老版本驱动是com.mysql.jdbc.Driver。用错类名会报ClassNotFoundException这种错误在搜索引擎里一搜一大堆但很少有人直接告诉你是驱动版本和类名的对应关系。如果你用的驱动是5.x版本类名就用老的那个8.x版本就用新的。本质上是因为驱动包的路径结构发生了变化。3.2 通过Servlet完成一次真实查询光有工具类还不够得在一个Servlet里真正查一次数据项目跑起来才能看到效果。我写一个最简单的Servlet查询所有用户并打印到页面package com.demo.servlet; import com.demo.util.DBUtil; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; WebServlet(/user/list) public class UserListServlet extends HttpServlet { Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType(text/html;charsetutf-8); PrintWriter out response.getWriter(); String sql SELECT id, username, create_time FROM t_user; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql); ResultSet rs ps.executeQuery()) { out.println(htmlbody); out.println(h2用户列表/h2); out.println(table border1); out.println(trthID/thth用户名/thth创建时间/th/tr); while (rs.next()) { int id rs.getInt(id); String username rs.getString(username); String createTime rs.getString(create_time); out.println(trtd id /tdtd username /tdtd createTime /td/tr); } out.println(/table); out.println(/body/html); } catch (SQLException e) { response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); out.println(数据库操作失败 e.getMessage()); e.printStackTrace(); } } }用Java 7引入的try-with-resources语法Connection、PreparedStatement、ResultSet三个资源会按顺序自动关闭。这里有一个反射性的问题有些人喜欢在finally块里逐个关资源但是关闭顺序搞反了ResultSet先被关然后触发了后续方法的调用反而报错。try-with-resources语法直接从语言层面规避了这个问题代码也简洁不少。3.3 为什么我不直接推荐JSP里写Java代码很多老教程会直接在JSP页面里写数据库连接和查询代码看起来确实“方便”——文件建好代码一写运行就直接在浏览器看到效果。但从实际项目维护的角度来说这种方式是灾难的开始。JSP本质上是视图层职责是展示数据把数据库查询硬塞进去业务逻辑和页面渲染彻底耦合在一起。改个页面样式还好要是改查询逻辑你得在整个HTML里翻Java代码改完还不一定能快速定位到是哪一段。我的建议是Servlet负责接收请求和调用数据访问逻辑JSP只负责把结果展示出来。哪怕你只是学习阶段也尽量按照“ServletJavaBeanJSP”这种MVC思想来组织代码。早期养成好习惯后面接触SpringMVC会顺畅太多。那问题来了Servlet里查询完数据之后怎么把数据交给JSP展示request.setAttribute(userList, userList); request.getRequestDispatcher(/WEB-INF/jsp/userList.jsp).forward(request, response);Servlet用setAttribute把数据塞进request对象然后转发到JSP页面。JSP里再用request.getAttribute(userList)把数据拿出来或者结合JSTL标签库更优雅地遍历。转发和重定向的区别这里不展开但记住一句话转发时request对象是同一个重定向会创建新的request。这个区别在传值的时候至关重要。4. Tomcat部署的完整流程与服务器配置解析4.1 IDEA中集成Tomcat的配置步骤项目写完了运行环境还没配。在IDEA里配置Tomcat同样有几个细节值得注意。点击顶部工具栏的运行配置下拉框选择Edit Configurations。点加号选择Tomcat Server然后选Local。在Server选项卡里点击Configure按钮选择你的Tomcat安装目录。这里有个关键细节Application Server选完Tomcat之后会看到下方有个Open Browser的选项要填浏览器访问的URL。很多同学在这里填成http://localhost:8080/结果运行起来浏览器打开的是Tomcat默认页面觉得自己项目没部署成功。实际上这里应该填你的项目上下文路径比如http://localhost:8080/web_demo_war_exploded/。部署相关的内容在Deployment选项卡里。点击加号选Artifact再选web_demo:war exploded。这个带有exploded字样的就是解压模式的部署方式。IDEA会把项目编译输出放到Tomcat的webapps目录之外的一个临时位置Tomcat以目录形式加载这个Web应用。这种方式的好处是支持热部署——修改了Java代码或JSP页面只要IDEA里点击更新按钮就能立即生效不用每次都重启Tomcat。Application context这个输入框就是你项目的访问根路径。默认会显示/web_demo_war_exploded太长太丑我一般直接改成/或者/web_demo。改成/的意思是直接通过根路径访问即http://localhost:8080/index.jsp。根据你的需求和习惯来本地调试怎么方便怎么来。4.2 war包方式部署到独立Tomcat在IDEA里调试开发阶段用解压模式非常方便但如果你要把项目部署到一台独立的服务器上就需要打war包。在Maven工具窗口里双击package命令Maven就会按pom.xml的配置打出一个war包。这个war包是Web应用的完整压缩包里面包含了编译后的class文件、JSP页面、WEB-INF目录下的web.xml和依赖jar包。war包所在的路径通常是项目名/target/目录下。拿到war包之后的部署就简单了把war包复制到Tomcat的webapps目录下然后启动Tomcat。Tomcat启动时会自动解压war包形成对应的应用目录。如果你的war包叫web_demo.war启动后访问路径就是http://服务器IP:8080/web_demo/。关于war包有一点需要额外提醒war包的正确性是建立在pom.xml配置正确的基础上的。如果你用了provided作用域的依赖它们不会被打进war包这是好事因为Tomcat自带。如果你的MySQL驱动是用compile作用域引用的driven会进war包这也是合理的。但要是你把一些本来Tomcat就有的包以compile方式打进去了部署后轻则启动日志有警告重则类冲突直接启动失败。4.3 通过配置文件直接修改数据库连接信息在开发阶段数据库连接信息直接写在Java源码里是没问题的图个方便。但是项目一旦要部署到别的机器上比如换了一台服务器的MySQL或者数据库密码要修改你就得改编译后的class文件里的字符串然后重新打包重新部署非常麻烦。更合理的做法是把数据库连接信息放到配置文件里。JavaWeb项目最常见的做法是把配置放到src/main/resources目录下比如创建一个db.properties文件jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/web_demo?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8mb4 jdbc.usernamedemo jdbc.passworddemo123456然后在工具类里用Properties类加载这个文件private static Properties props new Properties(); static { try (InputStream in DBUtil.class.getClassLoader().getResourceAsStream(db.properties)) { props.load(in); Class.forName(props.getProperty(jdbc.driver)); } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection( props.getProperty(jdbc.url), props.getProperty(jdbc.username), props.getProperty(jdbc.password) ); }这种方式的好处是显而易见的。连接信息集中在单独的文件里修改时不用触碰Java代码。getResourceAsStream(db.properties)是从classpath根目录查找资源所以配置文件放在src/main/resources下编译后会进classes目录项目部署后也在classpath下能被正确加载。4.4 Tomcat数据源配置的可选方案上面的方式已经足够应付绝大多数学习项目。再往前一步生产环境还有一套更“正统”的做法让Tomcat管理数据库连接池项目通过JNDI从容器获取连接。在Tomcat的conf/context.xml或者项目的META-INF/context.xml里配置Context Resource namejdbc/web_demo authContainer typejavax.sql.DataSource maxTotal20 maxIdle10 maxWaitMillis10000 usernamedemo passworddemo123456 driverClassNamecom.mysql.cj.jdbc.Driver urljdbc:mysql://localhost:3306/web_demo?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8mb4/ /Context然后在代码中通过Context上下文获取数据源Context initContext new InitialContext(); Context envContext (Context) initContext.lookup(java:/comp/env); DataSource ds (DataSource) envContext.lookup(jdbc/web_demo); Connection conn ds.getConnection();这个方案的核心价值在于连接池。每次DriverManager.getConnection()都是新建一个物理连接操作频繁时既慢又耗资源连接池复用连接性能好得多。但JNDI查找方式写起来确实麻烦而且idea在调试阶段使用Tomcat的数据源配置偶尔会出幺蛾子本地调试时直接用JDBC连接更省心。5. 常见问题与完整排查思路5.1 启动时报端口占用错误已经在使用中的端口你会看到这样的报错Port 8080 required by Tomcat v9.0 Server at localhost is already in use.导致这个错误的原因通常是上次运行Tomcat没有正常关闭进程还残留在后台。Windows系统打开任务管理器找到Java进程然后结束进程或者直接在命令行里执行netstat -ano | findstr 8080通过这条命令找到占用8080端口的PID然后taskkill /PID 这里填PID号 /F把这个进程杀掉再重新启动。也有可能是其它软件占用了8080端口那就需要修改Tomcat的端口配置。在conf/server.xml里找到Connector节点把port8080改成别的数字。5.2 404错误与访问路径的排查启动成功但返回404是最折磨人的问题。出现404通常有三种原因。第一访问路径写错了。Servlet的WebServlet(/user/list)注解里写的路径和浏览器里的访问路径必须完全一致区分大小写、包含上下文路径。假设项目上下文路径是/web_demoServlet映射是/user/list访问路径就是http://localhost:8080/web_demo/user/list。第二项目没部署成功。检查IDEA的运行配置里Deployment选项卡有没有正确的ArtifactContext有没有填写。Tomcat启动日志会明确告诉你在部署哪个应用、应用是否启动成功养成看日志的习惯比什么都重要。第三web.xml版本问题。如果你是用原生方式创建项目web.xml头部的schema版本要跟你的Tomcat匹配。Tomcat 9使用Servlet 4.0规范web.xml头部对应应该是xsi:schemaLocationhttp://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd如果写的是老版本2.5部分新特性可能不会被正确识别。5.3 数据库连接失败的五种典型报错数据库连接问题是最常见的拦路虎我把典型报错整理成一个速查表方便你对照定位。报错信息核心原因解决方案ClassNotFoundException: com.mysql.cj.jdbc.Driver驱动jar没引入或类名写错pom.xml中加入MySQL驱动依赖确认驱动类名与版本匹配Public Key Retrieval is not allowedMySQL 8.0 SSL连接配置问题URL参数加上allowPublicKeyRetrievaltrueCommunications link failure网络不通或端口未开放检查MySQL服务是否启动确认端口3306可访问Access denied for user用户名密码错误或权限不足核对账号密码检查GRANT授权是否正确The server time zone value is unrecognized时区配置缺失URL参数加上serverTimezoneAsia/Shanghai有个很隐蔽的问题需要单独提醒一下MySQL 8.0默认使用了caching_sha2_password认证插件某些老版本的驱动不支持这个插件导致连接失败。解决方法是升级驱动到8.0版本或者执行SQL把用户的认证方式改回mysql_native_passwordALTER USER demolocalhost IDENTIFIED WITH mysql_native_password BY demo123456; FLUSH PRIVILEGES;我自己更推荐顺手就升级驱动毕竟老认证方式迟早会被淘汰提前适应新方式没坏处。5.4 JSP页面中文乱码问题的根源与解法中文乱码问题虽然不直接影响功能但看着着实心烦。乱码的根源是字符编码在某个环节不统一。页面展示乱码时检查JSP页面头部有没有设置% page contentTypetext/html; charsetUTF-8 pageEncodingUTF-8 %HTML头部也应该有对应的meta标签meta charsetUTF-8请求参数乱码时如果是POST请求Servlet里要在读取参数之前设置编码request.setCharacterEncoding(UTF-8);如果是GET请求修改Tomcat的conf/server.xml里的Connector节点加上URIEncodingUTF-8属性。数据库读写乱码时连接URL里的characterEncodingutf8mb4和数据库本身字符集必须保持一致。字符集问题就是一条链数据库连接URL、数据库库表字符集、Java代码的编码、JSP页面编码、浏览器的编码显示任何一个环节断了中文就变天书。5.5 热部署失效与手动更新的处理使用idea解压模式部署后修改了Java代码有时候直接运行不会立即生效。原因在于对于Java类文件的修改IDEA的默认配置不会自动热加载。JSP文件修改通常刷新页面就能看到效果Java类修改就需要手动更新。IDEA里右上角运行窗口左侧有一个Update按钮点击后选择Update classes and resources。如果是新写的Servlet类大概率需要完全重启Tomcat才能被识别。这个问题源于JVM本身的限制——类结构变化无法被热加载Java语言层面目前也没有完美的解决方案。6. 开发阶段提效的六个细节工具链跑通之后真正影响日常开发效率的往往是一些小技巧。这里整理几个我觉得最有用的。第一善用live template。在IDEA里输入psfi就能快速生成public static final int输入sout生成System.out.println()输入fori生成标准for循环。但这些属于基础操作我想强调Servlet开发中更实用的一个定义一个sout的试错模板没有多大意义真正有用的是构造函数、getter/setter这些高频代码块。第二配置IDEA的自动编译。打开设置找到Build, Execution, Deployment下的Compiler勾上Build project automatically。这样你在JSP里改了代码IDEA会在后台自动编译部署后刷新页面就能看到效果。第三把JSP页面放在WEB-INF之下。这样浏览器通过URL就无法直接访问页面必须经过Servlet的转发。类似这样request.getRequestDispatcher(/WEB-INF/jsp/login.jsp).forward(request, response);好处是页面访问逻辑受到Servlet控制想直接绕过去访问不存在的页面文件不可能。第四数据库操作建议都用PreparedStatement而不是Statement。PreparedStatement预编译SQL防止SQL注入更安全而且处理参数时不需要手动拼接字符串代码清晰度也更高。PreparedStatement ps conn.prepareStatement(SELECT * FROM t_user WHERE username ?); ps.setString(1, username);明眼人一下就能看出来?占位符的方式比拼接字符串优雅、安全太多。第五异常处理不要只打印到控制台。把异常信息写入日志文件排查问题时才能追溯现场。入门阶段用e.printStackTrace()没有任何问题但日记等级、时间、类名这些信息是无法记录的。后面接触到log4j或者slf4j时你会感激早早就养成看日志的习惯。第六项目里的路径尽量用绝对路径或者基于request.getContextPath()的路径。JSP里要引用CSS或者图片时写成${pageContext.request.contextPath}/css/style.css而不要写css/style.css。因为后者是相对路径如果当前页面URL层级变化相对路径就会指向错误位置。用上下文路径拼出来的绝对URL无论页面嵌套多深层都能正确加载资源。7. 从单个Servlet到分层设计的初步思考运行环境配置好、第一个查询页面展示出来你已经迈过了JavaWeb最基础但也最关键的一道坎。有些同学跑通之后就开始对着代码微调、纠结页面样式其实可以稍微停下来想一想代码本身的组织结构。我上面写的例子把Servlet和数据库访问逻辑都放在一个类里这是最原始的形态方便理解。但真实项目中绝对不会这样写。层次化设计是逃离“能跑就行”的舒适区之后的第一课。最基本的划分是三层层次职责对应上面例子中的类Servlet层接收请求、参数校验、控制转发跳转UserListServletService层处理业务逻辑、事务边界UserServiceDAO层数据访问、SQL操作UserDao业务复杂起来之后Servlet里只做取参数和跳转业务规则写在Service里SQL全部封装在DAO里。Service调DAOServlet调Service层次关系单向依赖。这样改SQL不影响Servlet逻辑改页面展示不影响数据处理。代码结构清晰了排查问题时定位范围也就缩小了。这套分层思路过渡到后面学SpringMVC和MyBatis时你会有一种“原来都是同一件事”的豁然开朗感。框架只是把这种模式简化、统一、规范化了而已。8. 最后说几句实在话回头看整条链路创建项目、配置Tomcat、连MySQL、部署上线每一步单独拎出来都不算难但环环相扣时任何一环出问题都会卡住你半天甚至一天。我在带新人的时候经常说这段路摔过的跤、踩过的坑都是值得的因为只有亲手排过这些错误你才真正理解了JavaWeb的底层运作方式后面上框架才能心里有底。个人经验跑通第一个项目之后你可以做三个方向的练习来巩固这套知识一是把JSP页面改成纯JSTL标签遍历数据彻底告别在页面里写Java代码二是自己写一个注册功能把表单提交、Servlet接收、参数校验、数据库插入串在一起完整走一遍三是试试用Maven的package命令打一次war包部署到本地独立Tomcat里跑一遍体会一下从开发环境到真实部署环境的差异。这些练习做完JavaWeb的基础你就彻底打扎实了。下一步无论是学习Spring和SpringBoot还是去了解更前沿的微服务架构都不会再被这些底层配置绊住手脚。有问题多看看Tomcat的日志输出那是整个运行过程里最诚实的记录者。