1. 从零到一:为什么在 Idea2020 里建 Web 项目值得单独拿出来聊
如果你刚接触 Java Web 开发,或者从 Eclipse、MyEclipse 那套老工具迁移过来,第一次在 Idea2020 里点下“New Project”的时候,大概率会愣住。菜单里没有“Web Project”这个选项,你翻遍 Java、Maven、Spring 几个分类,也找不到一个叫“动态 Web 项目”的东西。这不是你操作有问题,而是 Idea 从设计理念上就把这件事拆成了两步:先建一个普通 Java 工程或者 Maven 工程,再通过“添加框架支持”或者手动配置目录结构的方式,把它变成一个能跑 Servlet、能部署到 Tomcat 的 Web 项目。
这个变化背后其实有它的道理。Eclipse 时代的“Dynamic Web Project”本质上是一个被 IDE 深度绑定的工程模型,目录结构、编译输出路径、部署描述符全由 IDE 托管,换一个工具就水土不服。Idea 走的是另一条路,它更倾向于让你用 Maven 或者 Gradle 这种标准化的构建工具来管理项目,IDE 只负责识别和辅助。所以你在 Idea2020 里创建 Web 项目,真正要理解的是“标准 Web 工程目录结构长什么样”以及“Idea 如何识别这个结构并把它交给 Tomcat 运行”。
这篇文章适合几类人看:一是刚学 Java Web、学校还在教 Servlet/JSP 但自己用 Idea 的学生;二是从老 IDE 迁移过来、对 Idea 的工程模型还不熟的老手;三是想搞清楚 Maven Web 工程和普通 Java 工程到底差在哪里的开发者。我会把两种主流方式都讲透——一种是纯手工搭建的普通 Java Web 工程,适合理解底层结构;另一种是基于 Maven 的 Web 工程,适合实际项目开发。两种方式都会给出完整的目录结构、配置参数和运行验证步骤,你跟着做就能跑起来。
2. 先搞清楚 Idea2020 里 Web 项目的两种建法
2.1 普通 Java 工程加框架支持:适合理解原理
这种方式的核心思路是:Idea 本身不区分“Web 项目”和“普通 Java 项目”,它只认目录结构和 Facet。你新建一个 Java 工程,然后通过“Add Framework Support”给它加上 Web Application 这个 Facet,Idea 就会自动帮你生成web目录和WEB-INF/web.xml,并且在工程设置里注册一个 Web Facet。这个 Facet 的作用是告诉 Idea:“这个工程有 Web 资源,部署的时候要把 web 目录下的内容作为根路径,编译输出要放到 WEB-INF/classes 下面。”
我刚开始用 Idea 的时候就是走的这条路,因为它最接近 Eclipse 的思维模式,你能亲眼看到web目录被创建出来,能看到web.xml文件,心里踏实。但这种方式有个明显的短板:依赖管理全靠手动加 jar 包,没有构建工具帮你做依赖传递和版本管理。你从网上下载一个 servlet-api.jar,再下载一个 jsp-api.jar,放到WEB-INF/lib下面,然后手动添加到工程依赖里。项目小的时候还能忍,一旦依赖多起来,jar 包冲突、版本不一致的问题就会把你拖垮。
2.2 Maven Web 工程:实际开发的标准做法
Maven 方式是我现在推荐所有人用的。你新建一个 Maven 工程,在pom.xml里把打包方式设成war,然后按照 Maven 的标准 Web 目录结构去建src/main/webapp和src/main/webapp/WEB-INF/web.xml。Idea2020 会自动识别这个结构,并且在 Maven 面板里出现tomcat7:run或者你配置的其他插件命令。依赖管理交给 Maven,你只需要在pom.xml里写javax.servlet:javax.servlet-api:4.0.1,它就会自动帮你下载并放到正确的位置。
这两种方式没有绝对的好坏,但我建议你至少把第一种方式走一遍,因为面试的时候经常会被问到“Web 工程的目录结构是什么样的”“WEB-INF 目录为什么不能直接访问”“web.xml 的作用是什么”。你亲手搭过一次,这些问题的答案就不是背出来的,而是长在脑子里的。实际做项目的时候再用 Maven 方式,效率高、坑少。
3. 普通 Java 工程变身 Web 项目的完整实操
3.1 新建 Java 工程与添加 Web 框架支持
打开 Idea2020,点File -> New -> Project,选择Java,JDK 选你本地装好的版本,比如 JDK 8 或者 JDK 11。这里有个细节要注意:如果你打算用 Tomcat 9 或者更高版本,JDK 最好选 8 以上;如果用的是 Tomcat 7 或者 8,JDK 8 是最稳的。不要用 JDK 17 去配 Tomcat 8,会报模块访问相关的错误,这个坑我踩过,排查了半天才发现是版本不匹配。
工程建好之后,右键点击工程根目录,选择Add Framework Support。在弹出的列表里勾选Web Application,右边可以勾选Create web.xml。点确定之后,你会看到工程目录下多了一个web文件夹,里面有一个WEB-INF子文件夹,WEB-INF下面有一个web.xml。同时,Idea 会在src目录旁边生成一个web目录的图标,表示它已经被识别为 Web 资源目录。
这个时候你打开File -> Project Structure,在Facets里面能看到一个Web的 Facet,它的Deployment Descriptor指向web/WEB-INF/web.xml,Web Resource Directory指向web目录。在Artifacts里面,Idea 会自动创建一个war exploded的构件,这个构件就是后面部署到 Tomcat 用的。war exploded的意思是“展开的 war 包”,Tomcat 可以直接读取这个目录,不用先打成 war 再解压,开发的时候热部署更快。
3.2 目录结构详解与编译输出配置
一个标准的普通 Java Web 工程目录结构是这样的:
项目根目录/ ├── src/ │ └── com/example/ │ └── HelloServlet.java ├── web/ │ ├── WEB-INF/ │ │ ├── web.xml │ │ └── lib/ │ └── index.jsp └── out/ └── artifacts/ └── 项目名_war_exploded/ ├── WEB-INF/ │ ├── classes/ │ └── lib/ └── index.jspsrc目录放 Java 源代码,web目录放 JSP、HTML、CSS、JS 这些静态资源和WEB-INF配置。编译之后,Java 类文件会被输出到out/artifacts/项目名_war_exploded/WEB-INF/classes下面,web目录下的内容会被复制到out/artifacts/项目名_war_exploded下面。Tomcat 部署的时候,实际上部署的是out/artifacts下面那个展开的目录。
这里有一个很容易搞错的地方:WEB-INF目录下的内容是不能被浏览器直接访问的。你把index.jsp放在web目录下,浏览器可以通过http://localhost:8080/项目名/index.jsp访问;但如果你把它放在web/WEB-INF/index.jsp,浏览器就访问不到。这是 Servlet 规范规定的,WEB-INF是给服务器内部用的,web.xml、类文件、依赖 jar 包都放在这里,保证安全。我见过有新手把 JSP 放到WEB-INF下面然后问为什么 404,就是这个原因。
3.3 配置 Tomcat 运行环境与部署验证
点Run -> Edit Configurations,点左上角的+号,选择Tomcat Server -> Local。在Server标签页里,Application server选择你本地 Tomcat 的安装目录。如果 Idea 没有自动识别,点Configure手动指定。HTTP port默认是 8080,如果被占用了就改成 8081 或者别的。JRE选你工程用的那个 JDK。
切到Deployment标签页,点+号,选择Artifact,把那个war exploded加进来。Application context可以设成/或者/demo,这个决定了你访问时的 URL 前缀。设成/的话,访问路径就是http://localhost:8080/index.jsp;设成/demo的话,就是http://localhost:8080/demo/index.jsp。我一般习惯设成/,少打几个字。
点确定之后,在工具栏上就能看到 Tomcat 的启动按钮。点一下绿色三角形,Idea 会编译工程、复制资源、启动 Tomcat。启动日志里如果看到Artifact 项目名:war exploded: Deployed successfully,就说明部署成功了。打开浏览器访问http://localhost:8080/index.jsp,如果能看到页面,整个流程就走通了。
注意:如果启动时报
ClassNotFoundException: javax.servlet.ServletException,说明 servlet-api.jar 没有加到工程依赖里。你需要把 Tomcat 安装目录下lib/servlet-api.jar和lib/jsp-api.jar复制到web/WEB-INF/lib下面,然后在Project Structure -> Modules -> Dependencies里把它们加进去,scope 设成Provided。设成Provided的意思是“编译和测试的时候用,打包的时候不打进去”,因为 Tomcat 自己会提供这两个 jar 包,打进去反而会冲突。
4. Maven Web 工程:实际项目中的标准姿势
4.1 用 Maven 原型快速生成 Web 工程骨架
Idea2020 新建 Maven 工程的时候,可以勾选Create from archetype,然后选择org.apache.maven.archetypes:maven-archetype-webapp。这个原型会帮你生成一个标准的 Maven Web 工程骨架,包括src/main/webapp目录和src/main/webapp/WEB-INF/web.xml文件,pom.xml里的packaging也会自动设成war。
但我要提醒你一点:这个原型生成的web.xml版本比较老,通常是 2.3 或者 2.4 的。如果你要用 Servlet 4.0 的新特性,比如@WebServlet注解,需要把web.xml的 schema 声明改成 4.0 的。不改也能用注解,但web.xml里的配置和注解混用的时候,老版本的 schema 可能会让 Idea 报校验错误。我一般会直接把web.xml替换成 4.0 的模板,或者干脆把web.xml删掉,纯用注解配置,这样更清爽。
如果你不想用原型,也可以手动建 Maven 工程,然后自己建src/main/webapp/WEB-INF目录和web.xml文件。手动建的好处是你完全清楚每个目录是怎么来的,不会有一堆用不上的模板文件。我现在的习惯是手动建,因为原型生成的东西我还要删一遍,不如直接来。
4.2 pom.xml 关键配置与依赖管理
Maven Web 工程的核心就在pom.xml里。下面是一个最小可用的配置:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.example</groupId> <artifactId>demo-web</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> <scope>provided</scope> </dependency> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>javax.servlet.jsp-api</artifactId> <version>2.3.3</version> <scope>provided</scope> </dependency> </dependencies> <build> <finalName>demo-web</finalName> <plugins> <plugin> <groupId>org.apache.tomcat.maven</groupId> <artifactId>tomcat7-maven-plugin</artifactId> <version>2.2</version> <configuration> <port>8080</port> <path>/</path> </configuration> </plugin> </plugins> </build> </project>这里有几个关键点要解释。packaging设成war是必须的,Maven 看到war才知道这是一个 Web 工程,打包的时候会按照 Web 工程的结构去处理。javax.servlet-api的scope设成provided,意思是编译的时候需要这个 jar,但打包的时候不打进去,因为 Tomcat 运行时会提供。如果你设成默认的compile,打出来的 war 包里会包含servlet-api.jar,部署到 Tomcat 的时候会和 Tomcat 自带的版本冲突,报ClassCastException或者NoSuchMethodError。
tomcat7-maven-plugin是一个内嵌的 Tomcat 插件,让你不用装 Tomcat 就能直接跑 Web 项目。在 Idea 右边的 Maven 面板里,展开Plugins -> tomcat7,双击tomcat7:run就能启动。这个插件的好处是启动快、配置简单,适合开发阶段快速验证。但它毕竟是 Tomcat 7 的版本,如果你要用 Servlet 4.0 的特性,还是得用外部的 Tomcat 9 或者 10。
4.3 标准 Maven Web 目录结构与资源过滤
Maven Web 工程的标准目录结构是这样的:
项目根目录/ ├── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── com/example/ │ │ │ └── HelloServlet.java │ │ ├── resources/ │ │ │ └── application.properties │ │ └── webapp/ │ │ ├── WEB-INF/ │ │ │ └── web.xml │ │ └── index.jsp │ └── test/ │ └── java/ ├── pom.xml └── target/ └── demo-web/ ├── WEB-INF/ │ ├── classes/ │ └── lib/ └── index.jspsrc/main/java放 Java 源代码,src/main/resources放配置文件,src/main/webapp放 Web 资源。编译打包之后,java目录下的类文件会输出到target/demo-web/WEB-INF/classes,resources目录下的文件也会复制到classes下面,webapp目录下的内容会复制到target/demo-web下面。target/demo-web就是最终 war 包的展开形式。
这里有一个资源过滤的细节:如果你在src/main/resources下面放了application.properties,Maven 默认会把它复制到classes目录下。但如果你在pom.xml里配置了<resources>标签做过滤,比如把${db.url}这样的占位符替换成实际值,就要注意过滤规则不要误伤二进制文件。我一般会在resources里只放文本配置文件,二进制文件放到webapp下面,避免过滤出问题。
4.4 在 Idea2020 中配置 Maven Tomcat 插件运行
在 Idea2020 里跑 Maven Tomcat 插件有两种方式。第一种是在 Maven 面板里直接双击tomcat7:run,这种方式最简单,但每次都要手动点。第二种是配置一个 Maven 运行配置:点Run -> Edit Configurations,点+号,选择Maven,在Command line里输入tomcat7:run,Working directory选你的工程根目录。这样你就可以在工具栏上直接点运行按钮启动,和跑普通 Java 程序一样方便。
启动之后,控制台会输出 Tomcat 的启动日志,看到Starting ProtocolHandler ["http-bio-8080"]就说明启动成功了。访问http://localhost:8080/就能看到你的index.jsp页面。如果你改了 JSP 文件,刷新浏览器就能看到变化,不需要重启。但如果你改了 Java 代码,需要重新编译,tomcat7:run默认不会自动重新加载类,你需要停掉再启动,或者配置热部署。我一般改 Java 代码的时候就手动重启一下,反正也就几秒钟的事。
提示:
tomcat7:run默认使用的是src/main/webapp作为 Web 资源目录,编译输出在target/classes。如果你发现 JSP 页面能访问但 Servlet 报 404,先检查web.xml里的url-pattern配置,再检查 Servlet 类是否在target/classes下面。有时候 Idea 的 Maven 面板没有自动刷新,需要点一下刷新按钮重新导入依赖。
5. 两种方式的核心差异与选型建议
5.1 依赖管理:手动 jar 包 vs Maven 坐标
普通 Java Web 工程加框架支持的方式,依赖管理全靠手动。你需要自己去下载servlet-api.jar、jsp-api.jar、jstl.jar、数据库驱动 jar 包,然后放到WEB-INF/lib下面,再在Project Structure里一个个加到模块依赖里。这个过程在项目初期还能接受,但一旦依赖超过十个,你就会陷入“这个 jar 是哪个版本的”“为什么这个类找不到”“为什么运行时报方法不存在”的泥潭。
Maven 方式把依赖管理抽象成了坐标。你只需要在pom.xml里写groupId、artifactId、version,Maven 就会自动从中央仓库下载对应的 jar 包,并且处理依赖传递。比如你引入spring-webmvc,Maven 会自动把spring-core、spring-beans、spring-context这些传递依赖也下载下来。版本冲突的时候,Maven 还有一套仲裁机制,最近的版本优先。这些能力在手动管理的方式下是完全不具备的。
我个人的经验是:学习阶段可以用手动方式建一两个小项目,把 Web 工程的目录结构和部署流程搞清楚;一旦进入实际开发,立刻切换到 Maven 方式,不要犹豫。手动管理依赖的时间成本太高了,而且容易出错。
5.2 部署方式:Idea 构件 vs 内嵌 Tomcat 插件
普通 Java Web 工程在 Idea 里的部署方式是配置Artifact,把war exploded加到 Tomcat 运行配置里。这种方式的好处是你可以用外部 Tomcat,版本自己选,配置自己调,和真实生产环境更接近。缺点是每次改完代码都要重新编译、重新部署,虽然 Idea 有热部署功能,但有时候会失效,需要手动重启。
Maven Tomcat 插件的方式是把 Tomcat 内嵌到 Maven 生命周期里,tomcat7:run直接启动一个内嵌的 Tomcat 实例。这种方式启动快、配置简单,适合开发阶段快速迭代。但它的 Tomcat 版本是固定的(tomcat7-maven-plugin 就是 Tomcat 7),如果你要用新版本的 Servlet 规范,就得换tomcat9-maven-plugin或者用cargo-maven2-plugin来管理外部 Tomcat。
实际项目中,我一般是这样搭配的:开发阶段用tomcat7:run快速启动,因为大部分项目用 Servlet 3.1 就够了;需要测试新版本特性或者模拟生产环境的时候,用外部 Tomcat 加war exploded部署。两种方式切换成本不高,但能覆盖不同的场景。
5.3 适用场景对照表
| 对比维度 | 普通 Java 工程加框架支持 | Maven Web 工程 |
|---|---|---|
| 依赖管理 | 手动下载 jar 包,手动添加依赖 | Maven 坐标自动下载,自动处理传递依赖 |
| 目录结构 | 手动创建web/WEB-INF | 标准src/main/webapp/WEB-INF |
| 打包方式 | Idea 构件自动打包 | mvn package生成 war 包 |
| Tomcat 配置 | 外部 Tomcat,Idea 运行配置 | 内嵌插件或外部 Tomcat |
| 适合场景 | 学习原理、小型 Demo | 实际项目开发、团队协作 |
| 学习曲线 | 低,但后期维护成本高 | 前期需要学 Maven,后期效率高 |
这张表不是绝对的,只是一个参考。我见过有人用普通 Java 工程加框架支持的方式做完整项目的,也见过有人用 Maven 但依赖管理一团糟的。工具是死的,人是活的,关键是理解每种方式背后的原理和取舍。
6. 踩坑实录:新手最容易翻车的六个地方
6.1 web.xml 版本与 Servlet 注解冲突
Idea2020 的Add Framework Support生成的web.xml默认是 4.0 的 schema,这个没问题。但如果你用 Maven 原型生成,web.xml可能是 2.3 的,schema 声明里没有version属性,或者version是 2.3。这种情况下,你用@WebServlet("/hello")注解配置 Servlet,Tomcat 可能识别不到,因为 2.3 版本的web.xml不支持注解扫描。
解决办法很简单:把web.xml的 schema 声明改成 4.0 的,或者直接把web.xml删掉。Servlet 3.0 以后,web.xml就是可选的了,你可以完全用注解来配置 Servlet、Filter、Listener。我现在的习惯是,如果项目里没有必须用web.xml配置的东西(比如某些第三方框架的初始化参数),我就直接删掉web.xml,纯用注解,少一个文件少一个出错的地方。
6.2 Artifact 未配置导致 404
这是新手最常见的问题:Tomcat 启动成功了,日志里也没有报错,但浏览器访问就是 404。你打开Run -> Edit Configurations -> Deployment,发现里面是空的,或者只有一个Artifact但没有正确关联到你的工程。这种情况通常是因为你在Project Structure -> Artifacts里没有创建war exploded构件,或者创建了但没有加到 Tomcat 运行配置的 Deployment 里。
解决步骤:先打开File -> Project Structure -> Artifacts,确认有一个项目名:war exploded的构件,它的Output directory指向out/artifacts/项目名_war_exploded。如果没有,点+号,选Web Application: Exploded -> From Modules,选择你的模块。然后回到Run -> Edit Configurations -> Deployment,点+号,选Artifact,把刚才那个war exploded加进来。Application context设成/或者你想要的路径。重新启动 Tomcat,应该就能访问了。
6.3 依赖 scope 设错导致 ClassNotFound
Maven 工程里,servlet-api和jsp-api的scope必须设成provided。如果你设成了默认的compile,打出来的 war 包里会包含这两个 jar,部署到 Tomcat 的时候,Tomcat 的类加载器会先加载自己lib目录下的servlet-api.jar,然后你的WEB-INF/lib下面也有一个,两个版本的类冲突,就会报ClassCastException或者NoSuchMethodError。
反过来,如果你把scope设成了provided,但在 Idea 里直接跑tomcat7:run,插件会使用自己内嵌的 Tomcat 类库,不会用你provided的依赖,所以能正常运行。但如果你用外部 Tomcat 部署,Tomcat 自己会提供servlet-api,所以也没问题。这个provided的语义就是“编译时需要,运行时由容器提供”,理解了这个,就不会设错了。
6.4 端口占用与 Tomcat 启动失败
Tomcat 启动的时候如果报Address already in use: JVM_Bind,说明 8080 端口被占用了。可能是你之前启动的 Tomcat 没有完全关闭,也可能是别的程序占用了 8080。在 Windows 上可以用netstat -ano | findstr 8080找到占用端口的进程 ID,然后在任务管理器里结束它。在 Mac 或者 Linux 上用lsof -i:8080找到进程,然后kill -9掉。
如果不想杀进程,也可以改 Tomcat 的端口。在 Idea 的 Tomcat 运行配置里,Server标签页的HTTP port改成 8081 或者别的。如果用tomcat7-maven-plugin,在pom.xml的<configuration>里把<port>改成 8081。改完端口之后,访问 URL 也要相应改成http://localhost:8081/。
6.5 JSP 页面中文乱码
JSP 页面中文乱码是另一个高频问题。你写了一个 JSP,里面包含中文,浏览器打开全是问号或者乱码。原因通常是 JSP 文件的编码和浏览器解析的编码不一致。解决办法是在 JSP 文件的第一行加上<%@ page contentType="text/html;charset=UTF-8" language="java" %>,同时在 HTML 的<head>里加上<meta charset="UTF-8">。这样 JSP 编译的时候用 UTF-8,浏览器解析的时候也用 UTF-8,就不会乱码了。
如果是 Servlet 输出中文乱码,需要在response.getWriter()之前设置response.setContentType("text/html;charset=UTF-8")或者response.setCharacterEncoding("UTF-8")。这两个的区别是:setContentType会同时设置响应的 MIME 类型和字符编码,setCharacterEncoding只设置字符编码。我一般用setContentType,一步到位。
6.6 Maven 依赖下载慢或失败
Maven 默认从中央仓库下载依赖,国内访问有时候会很慢,甚至超时失败。解决办法是配置国内镜像仓库。在 Maven 安装目录的conf/settings.xml里,找到<mirrors>标签,添加一个镜像:
<mirror> <id>aliyunmaven</id> <mirrorOf>*</mirrorOf> <name>阿里云公共仓库</name> <url>https://maven.aliyun.com/repository/public</url> </mirror>配置之后,Maven 会从这个镜像仓库下载依赖,速度会快很多。如果你用的是 Idea 自带的 Maven,可以在File -> Settings -> Build, Execution, Deployment -> Build Tools -> Maven里,把User settings file指向你修改过的settings.xml。改完之后,在 Maven 面板里点刷新按钮,重新导入依赖。
注意:镜像仓库的
mirrorOf设成*表示所有仓库请求都走这个镜像。如果你有公司内部的私服,需要把mirrorOf设成*,!内部仓库ID,排除内部仓库,否则内部依赖会下载失败。
7. 从建项目到跑起来:一个最小可运行示例
7.1 创建 Servlet 并配置映射
不管你用哪种方式建工程,最终都要写一个 Servlet 来验证。在src/main/java/com/example下面新建一个HelloServlet.java:
package com.example; 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, Idea2020 Web Project!</h1>"); } }@WebServlet("/hello")这个注解的作用是把/hello这个 URL 映射到这个 Servlet。浏览器访问http://localhost:8080/hello的时候,Tomcat 会调用这个 Servlet 的doGet方法,把<h1>Hello, Idea2020 Web Project!</h1>写到响应里。
如果你用的是普通 Java Web 工程,没有用 Maven,@WebServlet注解需要 Servlet 3.0 以上的web.xml才支持。如果你用的是 2.3 的web.xml,注解不会生效,你需要在web.xml里手动配置:
<servlet> <servlet-name>HelloServlet</servlet-name> <servlet-class>com.example.HelloServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>HelloServlet</servlet-name> <url-pattern>/hello</url-pattern> </servlet-mapping>两种方式效果一样,注解更简洁,web.xml更集中。我一般用注解,因为改起来方便,不用在 Java 文件和 XML 文件之间来回切换。
7.2 启动验证与日志排查
启动 Tomcat 之后,控制台会输出一系列日志。你需要关注几个关键点:Deploying web application archive或者Deployed successfully表示部署成功;Starting ProtocolHandler表示 HTTP 连接器启动成功;Server startup in XXX ms表示启动完成。如果看到Error deploying或者Exception,就要根据错误信息去排查。
访问http://localhost:8080/hello,如果看到<h1>Hello, Idea2020 Web Project!</h1>,说明整个流程走通了。如果 404,检查@WebServlet的路径和访问路径是否一致;如果 500,检查控制台有没有异常堆栈,通常是代码写错了或者依赖缺失;如果连接被拒绝,检查 Tomcat 是否真的启动了,端口是否正确。
我一般会在doGet方法里加一行System.out.println("HelloServlet doGet called"),这样在控制台能看到请求是否到达了 Servlet。如果控制台没有输出,说明请求没有匹配到 Servlet,问题出在 URL 映射上;如果有输出但浏览器显示异常,问题出在响应输出上。这个简单的打印语句在排查问题时非常有用。
7.3 热部署配置与开发效率提升
Idea2020 支持热部署,改完 JSP 或者 HTML 之后,刷新浏览器就能看到变化,不需要重启 Tomcat。但改 Java 代码之后,默认需要重新编译和重启。你可以通过以下配置提升热部署的效率:
在 Tomcat 运行配置的Server标签页里,On 'Update' action选择Update classes and resources,On frame deactivation也选择Update classes and resources。这样当你切到浏览器再切回 Idea 的时候,Idea 会自动编译改动的类并更新到 Tomcat 的部署目录里。对于方法体内部的修改,Tomcat 通常能直接加载新类,不需要重启;但如果改了方法签名或者加了新类,还是需要重启。
tomcat7-maven-plugin也支持热部署,但配置稍微麻烦一点。你需要在pom.xml的插件配置里加上<contextReloadable>true</contextReloadable>,然后启动的时候用tomcat7:run-war而不是tomcat7:run。run-war会先打成 war 包再运行,支持热部署;run是直接跑展开的目录,不支持热部署。我一般开发阶段用run,因为启动快;需要热部署的时候用run-war,但启动会慢一点。
8. 工程化扩展:从 Demo 到可维护的项目结构
8.1 分层包结构与职责划分
一个能跑通的 Demo 和一个可维护的项目之间,差的是包结构和分层。我建议在src/main/java下面按职责分包:
com.example/ ├── controller/ # Servlet 控制器,处理请求和响应 ├── service/ # 业务逻辑层 ├── dao/ # 数据访问层 ├── entity/ # 实体类 └── util/ # 工具类controller包放 Servlet,负责接收请求参数、调用 Service、返回响应。service包放业务逻辑,比如用户注册、订单处理。dao包放数据库操作,比如增删改查。entity包放实体类,和数据库表对应。util包放工具类,比如数据库连接、日期处理。
这种分层的好处是职责清晰,改数据库操作不会影响业务逻辑,改业务逻辑不会影响控制器。刚开始可能觉得麻烦,一个简单的功能要写三个类,但项目稍微大一点,分层的好处就体现出来了。我见过太多把所有代码都写在 Servlet 里的项目,后期维护的时候改一个功能要通读几百行代码,痛苦不堪。
8.2 配置文件管理与环境隔离
实际项目中,数据库连接、日志配置、第三方接口地址这些信息不应该硬编码在 Java 代码里,而是放在配置文件中。Maven 工程的标准做法是在src/main/resources下面放application.properties或者application.yml,然后在代码里用Properties类或者ResourceBundle读取。
环境隔离是指开发环境、测试环境、生产环境用不同的配置。Maven 的profile功能可以做到这一点。在pom.xml里定义多个profile,每个profile对应一套配置文件的路径或者属性值。打包的时候用mvn package -P prod指定生产环境的profile,Maven 会把对应的配置文件打进 war 包里。
我一般会在src/main/resources下面建application-dev.properties、application-test.properties、application-prod.properties三个文件,然后在pom.xml里配置profile来决定打包时用哪个。这样开发的时候用开发配置,部署到服务器的时候用生产配置,不会搞混。
8.3 日志框架集成与调试技巧
System.out.println 在 Demo 里用用还行,实际项目中一定要用日志框架。最常用的是 Logback 加 SLF4J。在pom.xml里加依赖:
<dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.3</version> </dependency>然后在src/main/resources下面放一个logback.xml,配置日志级别、输出格式、输出位置。日志级别一般设成DEBUG用于开发,INFO用于生产。输出格式里加上时间、线程、类名、日志内容,方便排查问题。
调试技巧方面,Idea 的断点调试功能非常强大。你可以在 Servlet 的doGet方法里打个断点,然后以 Debug 模式启动 Tomcat,浏览器访问的时候就会停在断点处。你可以查看变量值、调用栈、表达式求值。这个功能在排查复杂问题时比打印日志高效得多。我一般在本地开发的时候都用 Debug 模式启动,随时可以打断点。
9. 我个人在实际操作中的体会
从 Eclipse 迁移到 Idea 的那段时间,我在创建 Web 项目这件事上卡了整整两天。Eclipse 里点几下就能建好的 Dynamic Web Project,在 Idea 里怎么都找不到对应的选项。后来才明白,Idea 不是没有这个功能,而是把它拆成了“建工程”和“加框架支持”两步。理解了这个设计理念之后,后面的事情就顺了。
我现在建 Web 项目的习惯是:直接用 Maven 工程,手动建src/main/webapp/WEB-INF目录,pom.xml里配好servlet-api和tomcat7-maven-plugin,然后写一个 HelloServlet 验证环境。整个过程五分钟以内搞定,不需要翻任何文档。这个流程我重复了上百次,每个步骤都清楚为什么要这么做,出了问题也知道去哪里找原因。
最后分享一个小技巧:如果你经常需要建 Web 项目,可以把配置好的pom.xml和目录结构保存成一个模板,下次直接复制粘贴,改一下artifactId和groupId就能用。Idea 也支持自定义工程模板,在File -> New -> Project的时候可以选择你保存的模板。这个技巧能帮你省下不少重复劳动的时间。