2023版IDEA创建JavaWeb项目全流程:从环境配置到Servlet+JSP+MySQL实战
2026/9/8 20:16:40 网站建设 项目流程

我整理这份JavaWeb笔记的时候,正好有个学弟问我“2023版IDEA到底怎么创建JavaWeb项目”,他说网上教程东一榔头西一棒子,跟着做总是卡壳。我想了想,干脆把从环境搭建到第一个程序跑通的完整过程写下来,顺便把Servlet、JSP、MySQL连接这些核心知识点串一遍。这份笔记适合刚学完JavaSE、准备进入Web开发阶段的同学,也适合那些被各种碎片教程折磨得晕头转向的自学者。你不需要有很深的功底,只要Java基础语法过关,能看懂简单的SQL,就能跟着一步步把JavaWeb项目跑起来。

我先把丑话说在前面:JavaWeb这块内容,网上资料多但杂,很多教程还停留在Eclipse时代,用的还是老版IDEA界面。这篇笔记我以2023版IntelliJ IDEA为例,结合黑马程序员那套非常经典的课程体系来写,但我会把思路讲透,让你换个IDE、换个版本也能自己搞定,这才是真正重要的能力。

1. JavaWeb到底是什么,以及为什么你绕不开它

1.1 从静态页面到动态交互:一段必要的认知铺垫

先说个我在教学中反复强调的观点:JavaWeb不是一门“新语言”,而是一整套用Java解决Web开发问题的技术组合。很多初学者被“JavaWeb”这四个字吓住,觉得它是一座大山,但实际上拆开看,它就是Java语言在网络编程、数据库操作、前端交互这些场景下的综合应用。

理解JavaWeb,最好的切入点是先搞清楚它解决了什么问题。你打开一个网页,看到的可能是静态的HTML页面,这种页面写死了内容,十个人访问看到的东西一模一样。但如果你打开购物网站,每个人看到的购物车、推荐列表、订单状态都不同,这就是动态页面。动态页面意味着服务器端需要根据不同的请求,去查数据库、做计算、拼HTML,最后把结果返回给浏览器。JavaWeb干的就是这件事。

从技术栈来看,JavaWeb是一个典型的“全家桶”组合:

  • 前端部分:HTML、CSS、JavaScript,负责页面展示和用户交互
  • 后端部分:Servlet、JSP(现在JSP已经逐渐被前端模板或前后端分离替代,但学习阶段还是建议了解)、Filter、Listener
  • 服务器部分:Tomcat(最常用的Servlet容器)
  • 数据库部分:MySQL + JDBC(或者MyBatis等框架)
  • 工程化部分:Maven(项目构建和依赖管理工具,新版IDEA创建项目时核心依赖它)

这套组合就是你后面学习Spring Boot、Spring Cloud这些企业级框架的基础。我经常跟学生打比方:JavaWeb是摆地摊,Spring Boot是开店。地摊虽然简陋,但进货、摆摊、吆喝、收钱的全流程你都亲手做过,后面开店只是把这套流程标准化、规模化而已。如果直接上来学Spring Boot,你会发现很多东西都是“黑盒”,出了问题完全不知道怎么排查。

1.2 为什么Tomcat是绕不开的那道坎

在JavaWeb的世界里,Tomcat的地位相当于“网页的容器”。你写好的Servlet类,本质上是一个Java类,但它不能独立运行,必须放到Tomcat这个容器里,由Tomcat负责接收浏览器的请求、创建Servlet实例、调用Servlet方法、把结果返回给浏览器。

这里我想多聊几句Tomcat的工作原理,因为这对你理解后续的很多操作都有帮助。Tomcat本身是用Java写的,所以运行它要求你的机器上已经装好JDK。当你通过IDEA把一个Web项目部署到Tomcat上时,实际上做的是三件事:把项目的Web目录打成war包或直接发布到Tomcat的webapps目录、启动Tomcat进程、监听8080端口。

我见过太多初学者卡在这几个环节:要么是JDK环境变量没配好,Tomcat启动报错;要么是8080端口被其他程序占用了,页面一直打不开;要么是项目部署路径配置错了,访问的时候404。这些都不是什么高深的问题,但确实很磨人。这篇笔记里我会把每个坑都踩一遍给你看,你就不会再怕了。

2. 环境准备:把2023版IDEA配到“开箱即用”的状态

2.1 JDK安装与配置:最基础也最容易出错的一步

我一直认为,JDK的安装和配置是JavaWeb开发的第一道关卡,也是很多初学者的“分手关”。明明代码写得没问题,环境一配就是一上午,心态直接崩掉。其实这里面的逻辑很简单,你只要理解了三个关键词,就不会再配错:JAVA_HOME、PATH、CLASSPATH。

JAVA_HOME是告诉系统“JDK装在哪儿”的指针;PATH是让你在命令行里直接输入java、javac就能找到可执行文件;CLASSPATH是告诉JVM“类库在哪儿找”。对于现代JDK(8及以上),CLASSPATH其实不需要手动配置了,JVM会自动查找,所以初学者只需要配好JAVA_HOME和PATH就足够了。

具体操作是这样的:去官网(这里要特别注意,别随便在第三方网站下载JDK,最好去Oracle官网或者国内大厂的镜像站)下载JDK,推荐JDK 8或者JDK 11,这两个版本最成熟稳定,企业用的多,教程也多,对初学者最友好。下载的是一个安装包,双击安装,记住安装路径,比如C:\Program Files\Java\jdk-11.0.18

安装完成后,打开系统环境变量设置(Win10/11:右键“此电脑” -> “属性” -> “高级系统设置” -> “环境变量”),新建一个系统变量,变量名填JAVA_HOME,变量值填你的JDK安装路径。然后在Path变量中新增一行:%JAVA_HOME%\bin

注意:配置完环境变量后,必须重新打开命令行窗口才能生效。检查是否配置成功,在命令行输入java -version,如果出现版本信息就说明搞定了。我见过有人配置完后不重开终端,一直报“不是内部或外部命令”,其实就是一个很低级的疏忽。

2.2 Tomcat的下载与IDEA集成绑定

接下来是Tomcat。Tomcat的版本要和JDK版本匹配,这一点很多人会忽略。比如JDK 8对应Tomcat 9.x,JDK 11对应Tomcat 10.x(不过Tomcat 10把包名从javax.servlet改成了jakarta.servlet,和很多老教程对不上,所以新手我建议直接用Tomcat 9配合JDK 8,最丝滑)。

去Apache官网下载Tomcat 9的zip压缩包,解压到一个没有中文和空格的路径下,比如D:\apache-tomcat-9.0.83。解压后目录结构大致是:bin(启动和关闭脚本)、conf(配置文件,核心是server.xml)、lib(Tomcat运行所需的jar包)、webapps(部署应用的地方)、logs(日志文件)。

我建议你先不急着用IDEA,直接进bin目录双击startup.bat启动一次试试。启动成功后,浏览器访问http://localhost:8080,能看到Tomcat的默认首页,就说明环境没问题。这里有个小细节:启动窗口如果一闪而过,多半是JAVA_HOME没配好,双击startup.bat的时候系统找不着Java环境。这时候去logs目录看日志,比瞎猜效率高得多。

IDEA集成Tomcat就更简单了。打开IDEA,进入Settings -> Build, Execution, Deployment -> Application Servers,点加号选Tomcat Server,把Tomcat的安装路径指过去,IDEA会自动识别版本。这一步做完,你的开发环境就算“通电”了。

2.3 为什么我不建议你现在就去研究Maven

新版IDEA创建JavaWeb项目时,经常会遇到Maven的问题。Maven是一个项目管理和构建工具,它的核心作用是帮你管理第三方jar包依赖,不用再手动把jar包一个个下载下来扔到lib目录里。

我看到很多初学者在环境准备阶段就被Maven劝退了,其实这完全没必要。创建项目如果选择Maven骨架,IDEA会自动帮你生成一个pom.xml文件,你只需要在这里面声明需要什么库(比如MySQL驱动、JSTL标签库),Maven就会自动下载并关联到项目里。这个过程第一次会比较慢,因为要下载很多插件和依赖,是正常现象,不要以为卡死了。

如果你用的是2023版IDEA,创建项目时选Maven Archetype,然后选择org.apache.maven.archetypes:maven-archetype-webapp这个骨架,IDEA就会帮你生成一个带web.xml的标准Web项目结构。我有必要提醒你:IDEA内置的Maven版本可能和你系统里装的不一致,但没关系,IDEA自带的Maven就能用,初学者完全没必要单独装一个Maven,徒增烦恼。

3. 创建你的第一个JavaWeb项目:手把手实操

3.1 从新建项目到目录结构:看清每一层的作用

现在进入正题,我用2023版IDEA(如果你用的版本稍有不同,界面文字可能有点变化,但逻辑是一样的)带你走一遍创建流程。

第一步,打开IDEA,点击New Project。左侧选择Maven,勾选Create from archetype,在列表里找到org.apache.maven.archetypes:maven-archetype-webapp。这一步很关键,这个骨架会生成一个标准的Web项目结构,省去你手动创建一堆目录的麻烦。

第二步,填写GroupIdArtifactIdGroupId一般填公司域名倒写,比如com.exampleArtifactId填项目名,比如javaweb-demo。这俩概念不用背,你就理解成给项目起个唯一身份证就行。填完后设置项目的存储路径,点Finish,IDEA就开始自动创建项目并下载Maven依赖了。

等右下角进度条跑完,你会看到这样一个目录结构:

javaweb-demo ├── pom.xml ├── src │ └── main │ ├── resources │ └── webapp │ ├── WEB-INF │ │ └── web.xml │ └── index.jsp

项目根目录下的pom.xml是Maven的配置文件;src/main/java(骨架可能不会自动创建,需要手动建一个)放你的Java源码;src/main/resources放配置文件;src/main/webapp是Web根目录,放JSP、HTML、CSS、JS等前端资源和WEB-INF目录。

WEB-INF目录下的web.xml是Web项目最核心的配置文件,在JavaEE规范中叫“部署描述符”。早期版本的Servlet需要在web.xml里配置映射关系,Servlet 3.0之后虽然可以用注解代替,但web.xml依然存在,它是Web容器识别你的项目合法性的标志。

注意:WEB-INF目录下的文件浏览器是访问不到的,这是Servlet容器的一种安全机制。你的JSP页面如果放在WEB-INF里,就必须通过服务器端跳转来访问,适合放一些需要权限控制的页面。平时练习的时候JSP直接放webapp根目录下就行。

3.2 配置Tomcat并部署项目:这一步通了,整个项目就活了一半

项目创建好了还是“死”的,得把它部署到Tomcat里跑起来才知道行不行。

点击IDEA顶部工具栏右侧的Add Configuration(一个下拉框,写着“Add Configuration...”),然后点左上角加号,找到Tomcat Server -> Local。在Server选项卡里,如果前面已经把IDEA和Tomcat绑定好了,这里应用服务器会自动关联;Deployment选项卡里,点加号选Artifact,把项目添加进去。Application context可以改成/(访问时直接http://localhost:8080即可,不需要加路径),或者保留默认的/javaweb-demo_war_exploded(访问时需要带上一段长路径)。

这里我想展开聊一下warwar exploded的区别,因为几乎每个新手都会困惑。war是打包好的压缩包,部署的时候Tomcat会解压它再运行;war exploded是“展开”的(不是打包的)目录结构,开发时用这个模式最方便,因为修改代码后IDEA可以快速把变更的文件同步到Tomcat的部署目录,支持热部署,不用每次改一行代码就重启服务器。

配置好之后,点右上角的绿色三角形运行按钮。IDEA会启动Tomcat,在浏览器里自动打开一个页面(如果你没设置默认浏览器,需要自己手动访问http://localhost:8080/index.jsp)。看到"Hello World!"的JSP页面,恭喜你,你的第一个JavaWeb项目已经跑起来了。

3.3 热部署这个小功能,能帮你节省大量无谓的等待时间

很多人在开发时有个坏习惯:改一行代码,重启一次Tomcat。改一个前端页面还好,如果改了Java代码,重启Tomcat可能要等好几秒甚至十几秒,时间一长,整个人的心气儿就没了。

IDEA实际上提供了“热部署”功能,就在Tomcat配置的Server选项卡里。On Update actionOn frame deactivation这两个选项,前者是“手动更新时干什么”,后者是“IDEA窗口失去焦点(比如你切回浏览器)时干什么”。我通常把两者都设为Update classes and resources,这样修改Java代码后按Ctrl+F10,或者直接从IDEA切到浏览器,变更就会被自动同步,不需要重启Tomcat。

不过这个功能有一个限制:它只能更新方法内部的内容。如果你改了方法的签名(比如加参数、改返回值类型)、新增了类、或者改了web.xml配置,热部署就不管用了,必须重启。这个道理也不难理解:方法内部的字节码可以动态替换,但类的结构变了,JVM需要重新加载类,这必须重启容器才能完成。

4. Servlet:JavaWeb的“心脏”,你必须亲手写一遍

4.1 从生活场景理解Servlet的生命周期

假如你现在已经在Tomcat上部署了项目,也看到了JSP页面,但这还不算真正进入JavaWeb开发,因为你还没亲手写过Servlet。Servlet是JavaWeb最核心的组件,没有之一,理解了Servlet,你后面学Spring MVC就轻松很多。

Servlet本质上是一个Java类,它是怎么被Tomcat“接客”的呢?我们可以用一个生活化的类比:Tomcat就像一个酒店的前台,Servlet就是酒店里的服务员。一个客人(浏览器请求)来了,前台(Tomcat)会叫一个服务员(Servlet实例)来接待。服务员不是客人来了才现招的,而是酒店里早就备好的——Servlet在第一次被访问或者容器启动的时候就已经创建好了,后面所有客人都共用这一个服务员。

这个类比可以帮助你理解Servlet的生命周期。Servlet的完整生命周期由三个方法定义:

  • init():Servlet被创建时调用,只调用一次。就像服务员入职时的培训,只做一次,后面直接上岗。
  • service():每次请求都会调用,是所有请求的入口。在这里根据请求的HTTP方法(GET、POST等),决定调用doGet还是doPost。
  • destroy():Servlet被销毁时调用,只调用一次。比如Tomcat关闭的时候,给服务员发“离职通知”。

很多人不理解“Servlet单实例多线程”这个概念。默认情况下,Tomcat只会为一个Servlet创建一个实例对象,但会有多个线程同时调用这个实例的service()方法。这意味着你的Servlet存在线程安全问题——如果在Servlet里定义了成员变量并对其进行修改,多个线程同时访问时就会产生数据混乱。

实战建议:不要在Servlet里使用成员变量来存储请求相关的数据,一律使用局部变量,或者把数据存在request、session域对象里。这个问题在面试里也是高频考点,每次问必倒一片。

4.2 手写第一个Servlet并完成请求转发与重定向

现在我们来写第一个Servlet。在src/main/java下建包com.example.servlet,新建一个类HelloServlet.java

package com.example.servlet; 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; @WebServlet("/hello") public class HelloServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("text/html;charset=UTF-8"); resp.getWriter().write("<h1>Hello JavaWeb!</h1>"); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req, resp); } }

注意第12行的@WebServlet("/hello"),这是Servlet 3.0引入的注解式配置,作用和在web.xml里写<servlet>标签完全一样,都是把/hello这个URL路径和这个Servlet类绑定起来。如果你用的是Tomcat 9及以下版本,用javax.servlet包名,要是换成Tomcat 10,就变成了jakarta.servlet,写代码时导入的包名对应不上就会编译报错。

启动项目,浏览器访问http://localhost:8080/hello,应该能看到大标题"Hello JavaWeb!"。如果看到404,多半是URL路径对不上,检查一下注解里的路径和访问地址是否一致,这是最常见的问题。

接下来聊两个在开发中天天用到的知识点:请求转发和重定向。

**请求转发(forward)**是服务器内部的跳转,浏览器地址栏不会变化,整个过程只发起一次请求。代码写法是req.getRequestDispatcher("/index.jsp").forward(req, resp)。这种行为像是部门没处理完,把活给另一个同事,客户(浏览器)完全不知道换了人。

**重定向(redirect)**是服务器告诉浏览器“你别找我了,去这个新地址”,浏览器会重新发起一次新的请求。代码写法是resp.sendRedirect("/index.jsp"),地址栏会变成新地址,整个过程有两次请求。

我当年学这两个概念时也迷糊过,后来找到一个记忆方法:转发是“代办”,重定向是“转告”。你要办事,转发是办事员帮你直接搞定,你不用动;重定向是办事员告诉你“你走错门了,去隔壁”,你得自己再跑去隔壁。在实际开发里,表单提交后保存数据、操作成功要跳转页面,一般用重定向,这样可以避免刷新页面时表单重复提交;在Servlet里查询出数据后要带着数据去渲染一个JSP页面,一般用转发

4.3 请求域、会话域和应用域:数据到底该放哪?

Servlet里经常需要在一个请求的不同组件之间传递数据,或者在一个用户的多次请求之间保持状态。这种数据的“暂存地”称为域对象,JavaWeb中有三个最常用的域:

  • request域:一次请求之内有效,请求转发时也能带过去。适合放查询结果、表单参数等一次性的数据。
  • session域:同一个用户(浏览器)的多次请求之间有效,默认30分钟过期。适合放登录状态、购物车等会话级数据。
  • application域 / ServletContext:整个应用只有一份,所有用户共享。适合放全局配置、访问计数等数据。

我见过一个典型的初学者错误:把用户登录信息放request里,但用户刷新一下页面就丢了,于是觉得“JavaWeb真难”。其实不是难,是你没选对域。登录信息是跨请求、同会话的,放到session里才对。这三个域总结成一句话:能用request就不要用session,能用session就不要用application。域开得越大,数据生命周期越长,内存占用越高,出现混乱和安全隐患的概率也越大。

5. JSP与数据交互:让Web页面“活”起来

5.1 创建你的第一个动态JSP页面

JavaWeb传统模式中,JSP负责展示数据。JSP的全称是Jakarta Server Pages(以前叫JavaServer Pages),本质上是一个“能写Java代码的HTML页面”。Tomcat会把JSP翻译成一个Servlet(对,它本身就是一个Servlet),然后执行后生成HTML返回给浏览器。

src/main/webapp下新建一个login.jsp,我来演示怎么把Java代码嵌进页面:

<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>登录页</title> </head> <body> <% String message = (String) request.getAttribute("message"); if (message != null) { %> <p style="color:red;"><%= message %></p> <% } %> <form action="/login" method="post"> 用户名:<input type="text" name="username"><br> 密码:<input type="password" name="password"><br> <input type="submit" value="登录"> </form> </body> </html>

注意开头的<%@ page %>指令,它定义了这个JSP页面的属性,比如字符集、语言、是否允许使用session等。<% %>里面写的是Java脚本片段(scriptlet),<%= %>则是输出表达式,相当于Java里的out.print()

这里我要由衷地建议你:JSP里的Java代码越少越好。早期的项目会在JSP里写大量业务逻辑,结果页面和Java代码纠缠在一起,改个业务逻辑要在HTML标签里翻半天Java代码,维护起来简直要命。规范的做法是:Servlet负责接收请求、调用业务逻辑、获取结果,然后把结果存到request里,再转发到JSP,JSP里只负责用EL表达式或JSTL标签把数据显示出来。

5.2 Servlet + JSP + JDBC:完成一个完整的登录功能

现在到了这篇文章的重头戏:用Servlet + JSP + JDBC + MySQL做一个小而完整的登录功能。这个例子虽然没有用到框架,但麻雀虽小五脏俱全,你能看清一条数据从前端表单到后端处理再到数据库的完整链路。

首先准备MySQL数据库。你已经建好了javaweb_demo库,执行下面的SQL建表:

CREATE TABLE `user` ( `id` int NOT NULL AUTO_INCREMENT, `username` varchar(50) NOT NULL, `password` varchar(50) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; INSERT INTO `user` (username, password) VALUES ('admin', '123456');

然后写一个工具类DBUtils来拿数据库连接:

package com.example.util; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class DBUtils { private static final String URL = "jdbc:mysql://localhost:3306/javaweb_demo?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=UTF-8"; private static final String USERNAME = "root"; private static final String PASSWORD = "你自己的密码"; 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); } }

这里有两个容易踩的坑:一是使用MySQL 8.x以上版本,驱动类是com.mysql.cj.jdbc.Driver(注意中间多了cj),老教程里写的com.mysql.jdbc.Driver会报ClassNotFoundException;二是连接URL里必须加上serverTimezone=Asia/Shanghai,否则会报时区错误,这也是很多新手第一次连数据库时最常见的报错。

接着在pom.xml里添加MySQL驱动依赖:

<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.33</version> </dependency>

然后写登录的Servlet和Servlet里的逻辑:

package com.example.servlet; import com.example.util.DBUtils; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.*; import java.io.IOException; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; @WebServlet("/login") public class LoginServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String username = req.getParameter("username"); String password = req.getParameter("password"); try (Connection conn = DBUtils.getConnection(); PreparedStatement ps = conn.prepareStatement( "SELECT * FROM user WHERE username = ? AND password = ?")) { ps.setString(1, username); ps.setString(2, password); try (ResultSet rs = ps.executeQuery()) { if (rs.next()) { HttpSession session = req.getSession(); session.setAttribute("username", username); resp.sendRedirect("/success.jsp"); } else { req.setAttribute("message", "用户名或密码错误"); req.getRequestDispatcher("/login.jsp").forward(req, resp); } } } catch (SQLException e) { e.printStackTrace(); req.setAttribute("message", "系统异常,请稍后再试"); req.getRequestDispatcher("/login.jsp").forward(req, resp); } } }

这个例子虽然简单,但包含了几个重要的开发规范:

第一,使用PreparedStatement而不是Statement。用?占位符替代拼接字符串,可以有效防止SQL注入攻击。如果你没见过SQL注入,想象一下用户在密码框里输入' OR '1'='1,如果用字符串拼接,SQL就变成了SELECT * FROM user WHERE username = 'admin' AND password = '' OR '1'='1',后面永远为真,直接就能登录成功。PreparedStatement用预编译+参数绑定,从根源上杜绝了这个问题。

第二,用try-with-resources自动关闭资源。Java 7之后,实现了AutoCloseable接口的资源(Connection、PreparedStatement、ResultSet)放在try的括号里声明,代码块结束时会自动调用close()方法。这是防止连接泄漏的最有效手段。很多人初学JDBC时习惯手动close,一忙起来就忘了关ResultSet或PreparedStatement,埋下连接数耗尽的隐患。

第三,密码不能明文存储在数据库。这个Demo为了简洁用了明文密码,真实项目中必须用MD5加盐或BCrypt等算法对密码加密存储,否则数据库一旦泄露,用户密码全部暴露。这一点我在教学时一定会强调,哪怕Demo再简陋,安全意识要从第一天养成。

5.3 JSP里的EL表达式和JSTL:数据展示的正确姿势

JSP脚本(<% %>)虽然能干活,但页面里嵌了大量Java代码会非常丑陋。规范的JavaWeb开发应该用EL表达式(Expression Language)和JSTL标签库来替代脚本。

EL表达式的语法是${key},可以非常简洁地取出域对象中的数据。比如在Servlet里存了req.setAttribute("message", "用户名或密码错误"),JSP里直接用${message}就能取出来,比写<% String message = (String)request.getAttribute("message"); %>舒服太多了。

EL表达式还可以访问JavaBean的属性,比如${user.username}等价于调用了user对象的getUsername()方法,这背后是JavaBean规范在起作用。

再配合JSTL标签库,可以在页面里做循环和条件判断,而完全不需要写Java脚本。先把依赖加到pom.xml

<dependency> <groupId>jstl</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency>

然后JSP页面顶部引入标签库:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

循环输出数据就可以这样写:

<c:forEach items="${userList}" var="user"> <tr> <td>${user.id}</td> <td>${user.username}</td> </tr> </c:forEach>

整个页面看不到一行<% %>,既干净又清晰。我自己的体会是,学JSP阶段就把EL和JSTL用熟,价值非常大,因为这套思维(在模板里循环、条件判断、读取值)和后面学Spring Boot时用的Thymeleaf、FreeMarker非常相似,可以说是“一次学习,终身受用”。

6. 完整案例:一个支持增删改查的用户管理模块

6.1 从登录到列表展示,把前面的知识点串成一个闭环

登录功能做完,你可能还是觉得零散。为了把Servlet、JSP、JDBC、Request转发、重定向这些知识串成一个真正有业务逻辑的闭环,我建议你动手做一个“用户管理模块”,这个模块在企业后台里是最常见的:管理员登录后能看到用户列表,可以新增用户、删除用户、编辑用户信息。

我先不贴全部代码,而是把设计思路和流程讲清楚,你跟着这个思路去自己实现一遍,效果会比直接抄代码好一百倍。

  • 登录:用户提交表单,Servlet验证账号密码,成功后把用户信息存session,重定向到列表页。
  • 查询列表:列表页发出请求,Servlet查询数据库用户表,把结果set到request里,转发给list.jsp渲染成表格。
  • 新增用户:填写表单提交到Servlet,Servlet校验参数后执行INSERT操作,完了重定向到列表页。这里用重定向是为了防止浏览器刷新时重复提交,这是必须养成的习惯。
  • 删除用户:列表页每行一个“删除”按钮,URL带上用户id,Servlet拿到id执行DELETE操作,完成后重定向回列表页。
  • 编辑用户:先根据id查询该用户数据,set到request里,转发到编辑页面;表单提交后执行UPDATE操作,再重定向回列表页。

你有没有发现,这里面的流程有一个高度重复的模式:

  1. 接收请求,获取参数
  2. 调用数据库操作
  3. 根据结果存数据或跳转
  4. 转发或重定向

这就是很多框架(尤其是Spring MVC)底层的“请求处理模型”。你在JavaWeb阶段把这个流程练得滚瓜烂熟,到后面学框架时,你会惊喜地发现框架只是把这个流程的“重复部分”给封装了,而你早就知道每一步内部发生了什么。反过来,如果跳过了这个阶段,直接学Spring Boot,你会连“为什么Controller里返回一个字符串就能跳到页面”“为什么加个注解就能操作数据库”这些基本问题都想不明白。

6.2 接口设计原则:把公共代码抽出来,别复制粘贴

做这个模块的时候,我强烈建议你做到“不复制粘贴代码”。比如DBUtils里获取连接的方法,你已经在多个Servlet里重复用了。但还有一个更值得抽取的地方:增删改查的通用CRUD代码

public class BaseServlet extends HttpServlet { protected void write(HttpServletResponse resp, String data) throws IOException { resp.setContentType("text/html;charset=UTF-8"); resp.getWriter().write(data); } protected User parseUser(HttpServletRequest req) { String username = req.getParameter("username"); String password = req.getParameter("password"); User user = new User(); user.setUsername(username); user.setPassword(password); return user; } }

然后把LoginServlet改成继承BaseServlet,公共逻辑一下子清爽了很多。这一步不是什么高端技巧,但它是面向对象思想“封装、继承”在真实项目里最朴素的应用。很多同学学JavaSE时觉得“继承”这个特性没什么用,等你看多了这种重复代码抽取的过程,就会明白为什么Java设计者要把继承放到语言核心的位置上。

6.3 数据查询的进阶要点:分页其实没那么神秘

如果你照着上面的思路做完了用户管理模块,你会有一种“不过如此”的感觉。但一旦用户表里的数据超过一百条,你就会发现列表页一口气全展示出来非常笨重,这时候就需要分页了。

分页的原理并不复杂,核心就两件事:一是在SQL层面用LIMIT offset, size(MySQL的写法)控制查多少条,二是在页面上展示“上一页”“下一页”的链接。这里的关键是计算offset:假设每页显示5条数据,当前是第3页,那offset = (3-1) * 5 = 10,也就是从第11条开始取5条。

核心SQL写出来是这样的:

SELECT * FROM user LIMIT #{offset}, #{pageSize}

查询总条数再用一条:

SELECT COUNT(*) FROM user

然后用“总条数 / 每页条数”算出总页数,把当前页、总页数、每页大小都set到request里,在JSP页面里用JSTL的<c:forEach>输出页码链接。整个过程不需要任何框架,纯Servlet + JDBC就能搞定。

我记得第一次给项目加分页功能时,SQL怎么连表查询、怎么算总数,折腾了整整一下午,后来才发现其实就是一个LIMIT加一个COUNT的事。分页这种“小学算术级别”的逻辑,一旦你想清楚,就能举一反三理解很多框架里的分页插件(比如PageHelper)不过是帮你自动拼了这条SQL而已。

7. 项目运行中的常见问题与排查技巧

7.1 这些高频报错的解法,我建议你收藏

JavaWeb开发中遇到的报错来来回回就那么几类,我把最高频的整理成表格,按“报错现象 -> 原因 -> 解法”三步来排查:

报错现象常见原因解决办法
启动Tomcat时端口被占用之前服务没关,或其他程序占用8080找到占用进程并结束,或修改conf/server.xml里的端口号
访问项目返回404URL路径写错,或者应用上下文路径不对检查@WebServlet路径和访问地址;检查IDEA Deployment里的Application context
访问页面报500,控制台提示ClassNotFoundExceptionMaven依赖没有下载完整或没有引入检查pom.xml对应依赖,Maven -> Reload Project
JDBC连接数据库报中文乱码连接URL没有指定编码,或数据库表本身不是utf8URL里加characterEncoding=UTF-8,创建库时指定utf8mb4
Tomcat启动后黑板报“找不到JDK”环境变量JAVA_HOME未配置或配置错误重新配置JAVA_HOME,并确认路径里没有多出来的空格或引号
JSP页面里的标签全部显示成文本页面头部忘了加<%@ taglib %>指令在JSP开头加上对应的taglib引用

这里我再多说一句关于404和500的区别。404是“资源不存在”,服务器想找你要的那个页面或Servlet,没找到;500是“服务器内部错误”,找到了程序,但程序执行时抛异常了。很多人一看到500就慌,其实500反而是好事,因为控制台会打印详细的异常堆栈信息,你顺着堆栈把BUG揪出来反而是可查的。最让人头大的是404,因为404通常意味着你要访问的路径和实际注册的路径对不上,需要你把URL、注解、配置文件挨个检查。

7.2 自测与排障心法:别迷信“重启试试”

“重启大法”在JavaWeb开发中确实有效,但我不建议你无脑重启。遇到问题首先要做的事是看控制台日志。IDEA的控制台会打出Tomcat启动日志、项目运行日志、堆栈异常信息,这是你排查问题的第一手证据。

有一个排查思路很笨但极度有效:二分法。当页面报错的时候,你先把login.jsp改成纯静态HTML(去掉所有JSP标签)访问,如果能打开,说明Tomcat部署没问题,问题出在JSP标签或Servlet;再把Servlet里所有数据库操作注释掉,写一个假数据返回,如果能通,说明问题在SQL或数据库配置。一层一层把“代码”和“环境”剥开,问题范围就缩小了。

再分享一个经验值:如果JSP页面改了但是浏览器显示的还是旧内容,大概率是浏览器缓存。开发阶段按Ctrl+F5强制刷新,或者把浏览器设置为禁用缓存,能省掉很多莫名其妙的“灵异事件”。

8. 写在最后:JavaWeb只是开始,但这步走稳了后面才快

如果你坚持看到了这里,并且亲手把登录功能和用户管理模块都跑通了,说明你已经迈过了JavaWeb最关键的入门门槛。从Servlet生命周期到请求转发和重定向,从JDBC连接到JSP数据渲染,你已经亲手打通了一条完整的Web数据处理链路。

我在带学生和带新人的过程中越来越确认一件事:JavaWeb阶段真不是为了让你用它去做项目,而是为了让你看清Web开发的底层逻辑。你踩过的每一个坑——端口被占用、时区报错、中文乱码、404和500——都是值得的,因为这些坑背后隐藏的是网络协议、编码规范、资源管理、安全防御这些永远不会过时的基础。

接下来你可以顺着两条线继续走:一条是向前端延伸,学一学JavaScript、Ajax,理解前后端交互的更多细节;另一条是向后端框架延伸,开始接触Maven的高级用法、MyBatis、Spring、Spring MVC,最后进入Spring Boot。等你用上Spring Boot的那一刻,你会感谢现在这个亲手写过Servlet、亲手配过Tomcat、亲手敲过PreparedStatement的自己,因为你不是在用框架,而是在真正理解框架。

最后送你一个我个人坚持了很多年的小习惯:遇到报错,先读日志再动手,先复现再修复,先搜资料再问人。这套方法论比任何具体的技术知识点都值钱,它能陪你走完整个程序员生涯。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询