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能省去不少手动配置的麻烦。关键步骤与参数解析GroupId ArtifactId: 这是Maven坐标。GroupId通常用公司或组织域名的反写如com.exampleArtifactId是项目名如springmvc-demo。这会影响你的包名和最终生成的jar/war文件名。Version: 保持默认的1.0-SNAPSHOT即可。项目位置: 选择一个干净的目录。避免路径中包含中文或特殊字符这是所有Java项目的通用准则可以避免很多潜在的编码和路径解析问题。点击“Finish”后IDEA会开始创建项目并下载Maven wrapper如果勾选了相关选项。首次创建可能会慢一些因为需要从远程仓库下载Maven核心插件。3. 核心依赖注入pom.xml的精准配置艺术项目创建好后打开根目录下的pom.xml文件。这是整个项目的“心脏”所有依赖和构建配置都在这里。一个典型的、用于学习目的的Spring MVC项目需要以下核心依赖properties spring.version5.3.23/spring.version !-- 建议选择一个稳定的5.3.x版本 -- servlet-api.version4.0.1/servlet-api.version junit.version5.9.1/junit.version /properties dependencies !-- Spring MVC 核心 -- dependency groupIdorg.springframework/groupId artifactIdspring-webmvc/artifactId version${spring.version}/version /dependency !-- Servlet API (provided scope因为Tomcat会提供) -- dependency groupIdjavax.servlet/groupId artifactIdjavax.servlet-api/artifactId version${servlet-api.version}/version scopeprovided/scope /dependency !-- JSP API (provided scope) -- dependency groupIdjavax.servlet.jsp/groupId artifactIdjavax.servlet.jsp-api/artifactId version2.3.3/version scopeprovided/scope /dependency !-- JSTL 标签库用于在JSP中简化逻辑 -- dependency groupIdjavax.servlet/groupId artifactIdjstl/artifactId version1.2/version /dependency !-- 日志门面Spring默认使用commons-logging这里用SLF4JLogback更佳 -- dependency groupIdorg.slf4j/groupId artifactIdslf4j-api/artifactId version1.7.36/version /dependency dependency groupIdch.qos.logback/groupId artifactIdlogback-classic/artifactId version1.2.11/version /dependency !-- 单元测试 -- dependency groupIdorg.junit.jupiter/groupId artifactIdjunit-jupiter/artifactId version${junit.version}/version scopetest/scope /dependency !-- 为了让Spring TestContext能运行需要此依赖 -- dependency groupIdorg.springframework/groupId artifactIdspring-test/artifactId version${spring.version}/version scopetest/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等模板引擎但JSPJSTL仍是理解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手动创建与配置要点在src/main下右键新建目录Directory分别创建java和resources目录。关键一步将这两个新建的目录标记为源代码根和资源根。右键点击src/main/java目录 - “Mark Directory as” - “Sources Root”。同样右键点击src/main/resources- “Mark Directory as” - “Resources Root”。这样IDEA才会识别这些目录下的文件并进行编译和资源处理。在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 version1.0 encodingUTF-8? web-app xmlnshttp://xmlns.jcp.org/xml/ns/javaee xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd version4.0 !-- 1. 配置Spring的上下文监听器用于加载根应用上下文非Web层Bean如Service, Dao -- !-- 本例为简化将所有配置都放在DispatcherServlet中故可省略ContextLoaderListener -- !-- listener.../listener -- !-- 2. 配置字符编码过滤器解决POST请求中文乱码问题 -- filter filter-namecharacterEncodingFilter/filter-name filter-classorg.springframework.web.filter.CharacterEncodingFilter/filter-class init-param param-nameencoding/param-name param-valueUTF-8/param-value /init-param init-param param-nameforceEncoding/param-name param-valuetrue/param-value /init-param /filter filter-mapping filter-namecharacterEncodingFilter/filter-name url-pattern/*/url-pattern /filter-mapping !-- 3. 配置Spring MVC的核心控制器DispatcherServlet -- servlet servlet-namedispatcherServlet/servlet-name servlet-classorg.springframework.web.servlet.DispatcherServlet/servlet-class !-- 指定Spring MVC配置文件的位置和名称 -- init-param param-namecontextConfigLocation/param-name param-valueclasspath:spring/spring-mvc.xml/param-value /init-param !-- 设置容器启动时立即加载此Servlet而不是第一次请求时 -- load-on-startup1/load-on-startup /servlet !-- 4. 将DispatcherServlet映射到所有请求/ -- servlet-mapping servlet-namedispatcherServlet/servlet-name url-pattern//url-pattern /servlet-mapping /web-app关键解析CharacterEncodingFilter这是一个非常实用且容易被忽略的过滤器。它确保了请求request和响应response的编码都设置为UTF-8从根本上杜绝了中文乱码问题。forceEncoding参数确保即使请求头已指定编码也强制使用我们设置的UTF-8。DispatcherServletload-on-startup1让它在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.xmlSpring MVC的大脑在src/main/resources/spring/目录下创建spring-mvc.xml。?xml version1.0 encodingUTF-8? beans xmlnshttp://www.springframework.org/schema/beans xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xmlns:contexthttp://www.springframework.org/schema/context xmlns:mvchttp://www.springframework.org/schema/mvc xsi:schemaLocationhttp://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-packagecom.example.controller/ !-- 2. 开启注解驱动替代传统的处理器映射器和适配器配置 -- !-- 它会自动注册RequestMappingHandlerMapping、RequestMappingHandlerAdapter等 -- mvc:annotation-driven/ !-- 3. 配置静态资源处理。将对/css/**, /js/**, /images/**的请求映射到对应目录 -- !-- 不经过DispatcherServlet由容器默认Servlet处理提升性能 -- mvc:resources mapping/static/** location/static// !-- 4. 配置视图解析器ViewResolver -- bean classorg.springframework.web.servlet.view.InternalResourceViewResolver !-- 前缀视图文件所在的目录 -- property nameprefix value/WEB-INF/views// !-- 后缀视图文件的扩展名 -- property namesuffix value.jsp/ /bean !-- 5. 配置文件上传解析器如果需要的话 -- !-- bean idmultipartResolver classorg.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”指定参数名requiredfalse表示非必传defaultValue提供默认值。Model一个接口用于控制器向视图传递数据。这里我们添加了一个属性userName。返回值“hello”这是一个逻辑视图名由InternalResourceViewResolver解析为/WEB-INF/views/hello.jsp。6.2 创建视图hello.jsp在src/main/webapp/WEB-INF/views/目录下创建hello.jsp。% page contentTypetext/html;charsetUTF-8 languagejava % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % html head titleSpring MVC Demo/title /head body h1Hello, c:out value${userName}/!/h1 pThis 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点击IDEA右上角的“Add Configuration...”。点击“”号选择“Tomcat Server” - “Local”。在“Server”标签页点击“Configure...”指定你的Tomcat安装目录需要提前下载Tomcat 9.x或更高版本。在“Deployment”标签页点击“” - “Artifact”选择你的项目生成的War包通常名为springmvc-demo:war exploded。务必选择带exploded的版本这代表“展开的War”支持热部署修改代码和JSP后无需重启Tomcat即可生效仅限部分更新。在“Application context”处可以设置访问路径例如设置为/demo那么应用访问地址就是http://localhost:8080/demo。如果留空或设置为/则直接访问http://localhost:8080/。7.2 启动与访问点击绿色的运行或调试按钮IDEA会启动Tomcat并部署你的应用。观察控制台日志如果没有报错看到类似“Initializing Spring DispatcherServlet ‘dispatcherServlet’”和“Completed initialization in XXX ms”的日志说明Spring MVC上下文已成功启动。打开浏览器访问http://localhost:8080/[你的应用上下文]/hello。例如如果你设置了上下文为/demo就访问http://localhost:8080/demo/hello。你应该能看到页面显示“Hello, World!”。尝试在URL后加上参数http://localhost:8080/demo/hello?nameSpring页面会显示“Hello, Spring!”。8. 深度排错与进阶配置指南项目跑起来了但真正的挑战往往在后面。这里分享几个我踩过的坑和对应的解决方案。8.1 常见启动失败问题排查问题启动Tomcat时报ClassNotFoundException或NoClassDefFoundError通常是javax.servlet相关的类。原因与解决检查pom.xml中Servlet API的依赖是否设置了scopeprovided/scope。如果没有Maven会将其打包进WAR可能与Tomcat自带的Servlet库冲突。确保作用域正确。问题访问URL报404错误但Tomcat启动日志正常。排查链检查应用上下文路径确认浏览器访问的URL中的路径与IDEA中Tomcat配置的“Application context”一致。检查控制器映射确认GetMapping(“/hello”)中的路径是否正确是否包含了不必要的上下文。检查web.xml中的url-pattern确认是/而不是/*。检查视图解析器前缀后缀确认InternalResourceViewResolver配置的prefix和suffix能正确拼接出JSP文件的物理路径。可以尝试在控制器方法中直接返回完整的JSP路径如“/WEB-INF/views/hello.jsp”来绕过视图解析器测试是否是解析器配置问题。查看Tomcat日志IDEA的Run或Debug控制台会输出Tomcat的访问日志查看是否有对应的请求记录和可能的错误信息。8.2 日志配置与查看清晰的日志是调试的生命线。我们在pom.xml中引入了Logback现在在src/main/resources下创建logback.xml来配置它。?xml version1.0 encodingUTF-8? configuration !-- 控制台输出 -- appender nameCONSOLE classch.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 nameorg.springframework levelINFO/ !-- 将我们自己的应用包设为DEBUG级别便于调试 -- logger namecom.example levelDEBUG/ root levelINFO appender-ref refCONSOLE / /root /configuration启动项目时你会在控制台看到格式化的日志输出。通过调整不同包的日志级别可以精准定位问题。8.3 彻底告别JSP转向Thymeleaf模板引擎虽然JSP是入门的好选择但它在现代Spring Boot项目中已不是首选。Thymeleaf语法更自然原生支持HTML不依赖Servlet容器功能也更强大。迁移到Thymeleaf非常简单修改pom.xml添加Thymeleaf依赖dependency groupIdorg.thymeleaf/groupId artifactIdthymeleaf-spring5/artifactId version3.1.1.RELEASE/version /dependency修改spring-mvc.xml替换视图解析器bean idtemplateResolver classorg.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver property nameprefix value/WEB-INF/views// property namesuffix value.html/ property nametemplateMode valueHTML/ property namecharacterEncoding valueUTF-8/ property namecacheable valuefalse/ !-- 开发时设为false生产环境设为true -- /bean bean idtemplateEngine classorg.thymeleaf.spring5.SpringTemplateEngine property nametemplateResolver reftemplateResolver/ /bean bean classorg.thymeleaf.spring5.view.ThymeleafViewResolver property nametemplateEngine reftemplateEngine/ property namecharacterEncoding valueUTF-8/ /bean将hello.jsp重命名为hello.html并使用Thymeleaf语法!DOCTYPE html html xmlns:thhttp://www.thymeleaf.org head meta charsetUTF-8 titleSpring MVC Demo/title /head body h1Hello, span th:text${userName}World/span!/h1 pThis 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的配置流程之后再学习纯注解配置会更容易理解其背后的原理。