IDEA创建Spring MVC项目全流程:从Maven配置到Tomcat部署

1. 从零到一:为什么需要一个Spring MVC项目?

如果你刚接触Java Web开发,或者从其他IDE(比如Eclipse)转过来,可能会觉得在IDEA里新建一个项目是件挺简单的事。但说实话,我见过太多新手,包括一些有经验的开发者,在第一步就踩了坑。他们要么创建了一个“假”的Spring MVC项目,结构混乱,依赖缺失;要么配置了半天,Tomcat一启动就报404。这背后的原因,往往不是技术有多难,而是对“项目”这个概念的理解,以及IDEA这个工具的工作逻辑没摸透。

所以,这篇内容我们不只讲“点击哪里”,更要讲清楚“为什么这么点”。Spring MVC是一个经典的、基于Servlet的Web框架,它的核心是DispatcherServlet(前端控制器)。一个标准的Spring MVC项目,本质上是一个配置了特定Servlet和Spring容器的Web应用。在IDEA里创建它,意味着我们需要一个符合Servlet规范的Web项目结构,并正确引入Spring MVC的核心依赖,最后配置好DispatcherServlet。听起来步骤不少,但IDEA通过项目模板(Project Template)和模块(Module)的概念,把这些步骤封装了起来。我们的任务,就是理解并正确使用这些封装,而不是被它们迷惑。

接下来,我会带你完整走一遍流程,从项目类型选择、依赖管理、目录结构解读,到最终的运行和调试。我会重点解释每个选择背后的考量,以及那些官方文档不会写的、容易出错的细节。比如,为什么我推荐用Maven而不是Gradle作为起步?web.xml和注解配置该怎么选?lib目录下的jar包到底从哪来?这些看似基础的问题,恰恰是项目能否顺利跑起来的关键。

2. 项目创建的十字路口:Maven、Gradle与项目模板的抉择

打开IDEA,点击“New Project”,你会面临第一个关键选择:项目类型。这里常见的选项有Maven、Gradle,以及老式的“Java Enterprise”(它可能会引导你使用应用服务器提供的模板)。对于Spring MVC新手,我强烈建议选择Maven作为构建工具。原因有三:第一,Maven的pom.xml配置文件结构清晰,依赖声明一目了然,是学习依赖管理的最佳教材;第二,网络上的Spring MVC教程和解决方案,绝大多数基于Maven,遇到问题更容易搜索到答案;第三,IDEA对Maven的支持已经非常成熟和稳定,能减少很多工具层面的干扰。

注意:虽然Gradle更现代、构建速度更快,但其基于Groovy或Kotlin DSL的构建脚本对新手来说学习曲线更陡。先掌握Maven,以后再迁移到Gradle会容易得多。

在Maven项目中,IDEA通常会提供一个“archetype”(原型)列表。这里不要选择任何Spring相关的archetype,比如spring-boot-starter。我们要创建的是传统的、非Spring Boot的Spring MVC项目,所以直接使用最基础的maven-archetype-webapp即可。这个原型会生成一个最基础的Web应用骨架,包含标准的src/main/webapp目录和web.xml文件,这正是我们需要的起点。

如果找不到这个原型,或者你想从绝对空白开始,也可以直接创建一个“Maven”项目,不选择任何原型。创建完成后,手动创建Web应用所需的目录结构。但使用maven-archetype-webapp能省去不少手动配置的麻烦。

关键步骤与参数解析:

  1. GroupId & ArtifactId: 这是Maven坐标。GroupId通常用公司或组织域名的反写(如com.example),ArtifactId是项目名(如springmvc-demo)。这会影响你的包名和最终生成的jar/war文件名。
  2. Version: 保持默认的1.0-SNAPSHOT即可。
  3. 项目位置: 选择一个干净的目录。避免路径中包含中文或特殊字符,这是所有Java项目的通用准则,可以避免很多潜在的编码和路径解析问题。

点击“Finish”后,IDEA会开始创建项目并下载Maven wrapper(如果勾选了相关选项)。首次创建可能会慢一些,因为需要从远程仓库下载Maven核心插件。

3. 核心依赖注入:pom.xml的精准配置艺术

项目创建好后,打开根目录下的pom.xml文件。这是整个项目的“心脏”,所有依赖和构建配置都在这里。一个典型的、用于学习目的的Spring MVC项目,需要以下核心依赖:

<properties> <spring.version>5.3.23</spring.version> <!-- 建议选择一个稳定的5.3.x版本 --> <servlet-api.version>4.0.1</servlet-api.version> <junit.version>5.9.1</junit.version> </properties> <dependencies> <!-- Spring MVC 核心 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency> <!-- Servlet API (provided scope,因为Tomcat会提供) --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>${servlet-api.version}</version> <scope>provided</scope> </dependency> <!-- JSP API (provided scope) --> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>javax.servlet.jsp-api</artifactId> <version>2.3.3</version> <scope>provided</scope> </dependency> <!-- JSTL 标签库,用于在JSP中简化逻辑 --> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <!-- 日志门面,Spring默认使用commons-logging,这里用SLF4J+Logback更佳 --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.36</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.11</version> </dependency> <!-- 单元测试 --> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <version>${junit.version}</version> <scope>test</scope> </dependency> <!-- 为了让Spring TestContext能运行,需要此依赖 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>${spring.version}</version> <scope>test</scope> </dependency> </dependencies>

为什么这么配置?

  • 版本管理:使用<properties>统一管理版本号,便于后续升级。Spring 5.3.x是一个长期支持且非常稳定的分支,适合学习。
  • spring-webmvc:这个依赖是核心,它本身会传递性引入spring-context,spring-aop,spring-beans,spring-core,spring-web等所有必要的Spring模块。你不需要单独引入它们。
  • provided作用域:Servlet和JSP的API,在项目运行时由Tomcat这类Servlet容器提供。标记为provided意味着Maven只在编译和测试时使用这些依赖,打包成WAR时不会包含进去,避免与容器中的库冲突。
  • JSTL:虽然现代项目可能更多使用Thymeleaf等模板引擎,但JSP+JSTL仍是理解Spring MVC视图层最直接的方式。它允许你在JSP中使用类似<c:forEach>的标签,避免在页面中编写大量的Java脚本片段。
  • 日志:Spring内部使用commons-logging作为日志门面,但它会自动适配当前类路径上的具体日志实现(如Logback)。我们直接配置好SLF4J和Logback,就能看到Spring内部的详细日志,对于调试至关重要。

保存pom.xml后,IDEA右上角通常会弹出提示,问你是否要导入更改(Enable Auto-Import)。务必点击“Enable Auto-Import”,这样IDEA会在后台自动下载这些依赖库。你可以在右侧边栏的“Maven”工具窗口中查看下载进度和依赖树。

4. 目录结构的重塑:从Maven标准到可运行的Web应用

使用maven-archetype-webapp生成的项目,目录结构可能不完全符合我们的习惯,尤其是源代码目录。我们需要手动调整和完善。

标准且推荐的目录结构如下:

springmvc-demo (项目根目录) ├── pom.xml ├── src │ ├── main │ │ ├── java <-- 手动创建,存放Java源代码 │ │ │ └── com │ │ │ └── example │ │ │ └── controller │ │ │ └── HelloController.java │ │ ├── resources <-- 手动创建,存放配置文件 │ │ │ ├── spring │ │ │ │ └── spring-mvc.xml │ │ │ └── logback.xml │ │ └── webapp <-- 原型已创建,Web应用根目录 │ │ ├── WEB-INF │ │ │ ├── web.xml <-- 部署描述符 │ │ │ └── views <-- 手动创建,存放JSP视图 │ │ │ └── hello.jsp │ │ └── index.jsp │ └── test <-- 测试代码目录 │ ├── java │ └── resources

手动创建与配置要点:

  1. src/main下右键,新建目录(Directory),分别创建javaresources目录。
  2. 关键一步:将这两个新建的目录标记为源代码根和资源根。右键点击src/main/java目录 -> “Mark Directory as” -> “Sources Root”。同样,右键点击src/main/resources-> “Mark Directory as” -> “Resources Root”。这样IDEA才会识别这些目录下的文件并进行编译和资源处理。
  3. webapp/WEB-INF下创建views目录,用于存放我们的JSP文件。将视图放在WEB-INF下是一种安全实践,因为WEB-INF下的文件不能直接被客户端浏览器访问,必须通过控制器(Controller)转发,这符合MVC模式。

5. 配置文件的交响曲:web.xml与Spring MVC配置详解

接下来是配置的核心部分,涉及两个主要文件:web.xml和Spring的配置文件(如spring-mvc.xml)。这里我们采用基于web.xml的配置结合注解驱动的方式,这是理解Spring MVC工作原理最清晰的方式。

5.1 配置web.xml:前端控制器的入口

web.xml是Servlet规范的部署描述符,我们需要在这里声明和配置Spring MVC的核心——DispatcherServlet

<?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"> <!-- 1. 配置Spring的上下文监听器,用于加载根应用上下文(非Web层Bean,如Service, Dao) --> <!-- 本例为简化,将所有配置都放在DispatcherServlet中,故可省略ContextLoaderListener --> <!-- <listener>...</listener> --> <!-- 2. 配置字符编码过滤器,解决POST请求中文乱码问题 --> <filter> <filter-name>characterEncodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> <init-param> <param-name>forceEncoding</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>characterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- 3. 配置Spring MVC的核心控制器:DispatcherServlet --> <servlet> <servlet-name>dispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!-- 指定Spring MVC配置文件的位置和名称 --> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring/spring-mvc.xml</param-value> </init-param> <!-- 设置容器启动时立即加载此Servlet,而不是第一次请求时 --> <load-on-startup>1</load-on-startup> </servlet> <!-- 4. 将DispatcherServlet映射到所有请求(/) --> <servlet-mapping> <servlet-name>dispatcherServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>

关键解析:

  • CharacterEncodingFilter:这是一个非常实用且容易被忽略的过滤器。它确保了请求(request)和响应(response)的编码都设置为UTF-8,从根本上杜绝了中文乱码问题。forceEncoding参数确保即使请求头已指定编码,也强制使用我们设置的UTF-8。
  • DispatcherServletload-on-startup=1让它在Web容器(Tomcat)启动时就初始化,并加载其对应的Spring IoC容器(WebApplicationContext)。contextConfigLocation参数指向我们即将创建的Spring MVC配置文件。如果不指定,默认会去/WEB-INF/下找名为<servlet-name>-servlet.xml的文件(即dispatcherServlet-servlet.xml)。
  • <url-pattern>/</url-pattern>:这个映射非常关键。它表示DispatcherServlet将处理所有到达应用的请求(除了像.jsp这样的由Servlet容器默认处理的请求)。注意,这里不是/*/*会匹配包括.jsp在内的所有路径,可能导致一些意外行为。使用/是Spring MVC的推荐做法。

5.2 配置spring-mvc.xml:Spring MVC的大脑

src/main/resources/spring/目录下创建spring-mvc.xml

<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd"> <!-- 1. 自动扫描指定包下的组件(@Controller, @Service等),并注册为Bean --> <context:component-scan base-package="com.example.controller"/> <!-- 2. 开启注解驱动,替代传统的处理器映射器和适配器配置 --> <!-- 它会自动注册RequestMappingHandlerMapping、RequestMappingHandlerAdapter等 --> <mvc:annotation-driven/> <!-- 3. 配置静态资源处理。将对/css/**, /js/**, /images/**的请求映射到对应目录 --> <!-- 不经过DispatcherServlet,由容器默认Servlet处理,提升性能 --> <mvc:resources mapping="/static/**" location="/static/"/> <!-- 4. 配置视图解析器(ViewResolver) --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <!-- 前缀:视图文件所在的目录 --> <property name="prefix" value="/WEB-INF/views/"/> <!-- 后缀:视图文件的扩展名 --> <property name="suffix" value=".jsp"/> </bean> <!-- 5. 配置文件上传解析器(如果需要的话) --> <!-- <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">...</bean> --> </beans>

为什么这么配置?

  • <context:component-scan>:这是Spring“约定优于配置”理念的体现。它让Spring自动去com.example.controller包及其子包下扫描带有@Controller@Service@Repository@Component等注解的类,并将它们实例化、管理起来。无需在XML中手动声明每一个Bean。
  • <mvc:annotation-driven>:这是Spring MVC注解驱动模式的核心开关。打开它,Spring MVC就会自动配置好处理@RequestMapping@RequestBody@ResponseBody等注解所必需的组件。没有它,你的@Controller注解将不起作用。
  • <mvc:resources>:这是一个性能优化点。对于CSS、JavaScript、图片等静态资源,不应该经过复杂的Spring MVC调度,而应该由Web容器直接返回。此配置告诉DispatcherServlet:“凡是匹配/static/**路径的请求,直接去/static/目录下找文件,我不处理了。”
  • InternalResourceViewResolver:视图解析器。当控制器方法返回一个逻辑视图名(如"hello")时,解析器会将其解析为具体的物理视图路径(/WEB-INF/views/hello.jsp)。这样控制器就无需关心视图的具体位置和类型。

6. 编写第一个控制器与视图:让Hello World跑起来

现在,骨架和神经都已就位,是时候添加血肉了。我们来创建一个最简单的控制器和视图。

6.1 创建控制器HelloController.java

src/main/java/com/example/controller包下创建类HelloController.java

package com.example.controller; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; @Controller // 标记这是一个Spring MVC控制器 public class HelloController { // 处理GET请求,路径为 /hello @GetMapping("/hello") public String sayHello(@RequestParam(value = "name", required = false, defaultValue = "World") String name, Model model) { // 向模型中添加数据,键为“userName”,值为传入的name参数 model.addAttribute("userName", name); // 返回逻辑视图名,视图解析器会将其解析为 /WEB-INF/views/hello.jsp return "hello"; } }

代码解读:

  • @Controller:声明这是一个控制器,会被<context:component-scan>扫描到。
  • @GetMapping(“/hello”):一个组合注解,等价于@RequestMapping(value = “/hello”, method = RequestMethod.GET)。它将该方法映射到处理GET /hello请求。
  • @RequestParam:用于获取请求参数。value=“name”指定参数名,required=false表示非必传,defaultValue提供默认值。
  • Model:一个接口,用于控制器向视图传递数据。这里我们添加了一个属性userName
  • 返回值“hello”:这是一个逻辑视图名,由InternalResourceViewResolver解析为/WEB-INF/views/hello.jsp

6.2 创建视图hello.jsp

src/main/webapp/WEB-INF/views/目录下创建hello.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <html> <head> <title>Spring MVC Demo</title> </head> <body> <h1>Hello, <c:out value="${userName}"/>!</h1> <p>This is your first Spring MVC page.</p> </body> </html>

视图要点:

  • <%@ page ... %>:设置页面编码为UTF-8。
  • <%@ taglib ... %>:引入JSTL核心标签库,这样我们才能使用<c:out>等标签。
  • ${userName}:这是EL表达式(Expression Language),用于从请求属性、会话属性等作用域中获取数据。它对应控制器中model.addAttribute(“userName”, name)设置的属性。
  • <c:out value=“${userName}”/>:使用JSTL标签输出内容。<c:out>默认会对内容进行XML/HTML转义,是一种防止XSS攻击的好习惯。

7. 配置与运行:Tomcat集成与项目部署

代码写完了,怎么运行?我们需要一个Servlet容器,这里以Tomcat为例。

7.1 在IDEA中配置Tomcat

  1. 点击IDEA右上角的“Add Configuration...”。
  2. 点击“+”号,选择“Tomcat Server” -> “Local”。
  3. 在“Server”标签页,点击“Configure...”指定你的Tomcat安装目录(需要提前下载Tomcat 9.x或更高版本)。
  4. 在“Deployment”标签页,点击“+” -> “Artifact”,选择你的项目生成的War包(通常名为springmvc-demo:war exploded)。务必选择带exploded的版本,这代表“展开的War”,支持热部署,修改代码和JSP后无需重启Tomcat即可生效(仅限部分更新)。
  5. 在“Application context”处,可以设置访问路径,例如设置为/demo,那么应用访问地址就是http://localhost:8080/demo。如果留空或设置为/,则直接访问http://localhost:8080/

7.2 启动与访问

  1. 点击绿色的运行或调试按钮,IDEA会启动Tomcat并部署你的应用。
  2. 观察控制台日志,如果没有报错,看到类似“Initializing Spring DispatcherServlet ‘dispatcherServlet’”和“Completed initialization in XXX ms”的日志,说明Spring MVC上下文已成功启动。
  3. 打开浏览器,访问http://localhost:8080/[你的应用上下文]/hello。例如,如果你设置了上下文为/demo,就访问http://localhost:8080/demo/hello
  4. 你应该能看到页面显示“Hello, World!”。尝试在URL后加上参数:http://localhost:8080/demo/hello?name=Spring,页面会显示“Hello, Spring!”。

8. 深度排错与进阶配置指南

项目跑起来了,但真正的挑战往往在后面。这里分享几个我踩过的坑和对应的解决方案。

8.1 常见启动失败问题排查

  • 问题:启动Tomcat时报ClassNotFoundExceptionNoClassDefFoundError,通常是javax.servlet相关的类。
    • 原因与解决:检查pom.xml中Servlet API的依赖是否设置了<scope>provided</scope>。如果没有,Maven会将其打包进WAR,可能与Tomcat自带的Servlet库冲突。确保作用域正确。
  • 问题:访问URL报404错误,但Tomcat启动日志正常。
    • 排查链:
      1. 检查应用上下文路径:确认浏览器访问的URL中的路径与IDEA中Tomcat配置的“Application context”一致。
      2. 检查控制器映射:确认@GetMapping(“/hello”)中的路径是否正确,是否包含了不必要的上下文。
      3. 检查web.xml中的<url-pattern>确认是/而不是/*
      4. 检查视图解析器前缀后缀:确认InternalResourceViewResolver配置的prefixsuffix能正确拼接出JSP文件的物理路径。可以尝试在控制器方法中直接返回完整的JSP路径如“/WEB-INF/views/hello.jsp”来绕过视图解析器,测试是否是解析器配置问题。
      5. 查看Tomcat日志:IDEA的Run或Debug控制台会输出Tomcat的访问日志,查看是否有对应的请求记录和可能的错误信息。

8.2 日志配置与查看清晰的日志是调试的生命线。我们在pom.xml中引入了Logback,现在在src/main/resources下创建logback.xml来配置它。

<?xml version="1.0" encoding="UTF-8"?> <configuration> <!-- 控制台输出 --> <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern> </encoder> </appender> <!-- 将Spring框架的日志级别设为INFO或WARN,避免过多DEBUG日志 --> <logger name="org.springframework" level="INFO"/> <!-- 将我们自己的应用包设为DEBUG级别,便于调试 --> <logger name="com.example" level="DEBUG"/> <root level="INFO"> <appender-ref ref="CONSOLE" /> </root> </configuration>

启动项目时,你会在控制台看到格式化的日志输出。通过调整不同包的日志级别,可以精准定位问题。

8.3 彻底告别JSP:转向Thymeleaf模板引擎虽然JSP是入门的好选择,但它在现代Spring Boot项目中已不是首选。Thymeleaf语法更自然(原生支持HTML),不依赖Servlet容器,功能也更强大。迁移到Thymeleaf非常简单:

  1. 修改pom.xml,添加Thymeleaf依赖:
    <dependency> <groupId>org.thymeleaf</groupId> <artifactId>thymeleaf-spring5</artifactId> <version>3.1.1.RELEASE</version> </dependency>
  2. 修改spring-mvc.xml,替换视图解析器:
    <bean id="templateResolver" class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver"> <property name="prefix" value="/WEB-INF/views/"/> <property name="suffix" value=".html"/> <property name="templateMode" value="HTML"/> <property name="characterEncoding" value="UTF-8"/> <property name="cacheable" value="false"/> <!-- 开发时设为false,生产环境设为true --> </bean> <bean id="templateEngine" class="org.thymeleaf.spring5.SpringTemplateEngine"> <property name="templateResolver" ref="templateResolver"/> </bean> <bean class="org.thymeleaf.spring5.view.ThymeleafViewResolver"> <property name="templateEngine" ref="templateEngine"/> <property name="characterEncoding" value="UTF-8"/> </bean>
  3. hello.jsp重命名为hello.html,并使用Thymeleaf语法:
    <!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Spring MVC Demo</title> </head> <body> <h1>Hello, <span th:text="${userName}">World</span>!</h1> <p>This is your first Spring MVC page with Thymeleaf.</p> </body> </html>
    控制器代码完全无需修改。Thymeleaf的th:text属性会动态替换标签体内的文本。

8.4 关于web.xml与纯注解配置我们上面使用了web.xml。但Servlet 3.0+规范支持用Java代码(实现WebApplicationInitializer接口)完全替代web.xml。Spring提供了便捷的抽象类AbstractAnnotationConfigDispatcherServletInitializer。使用纯注解配置更简洁,也是Spring Boot的默认方式。但对于初学者,从web.xml开始能更直观地理解Servlet和Filter的配置流程,之后再学习纯注解配置会更容易理解其背后的原理。