Appearance
SpringMVC教程 - 2 第一个SpringMVC程序
版本要求:
- JDK 17
- Maven 3.6+
开发工具 IntelliJ IDEA,没什么好说的。
下面就来实现一个 HelloWorld 的程序,通过浏览器发起请求到我们的服务器程序,服务器响应请求,并返回数据给浏览器。
因为现在一般都是前后端分离的项目,所以这里我们会使用浏览器发起一个请求到我们的服务器程序,服务器程序返回 JSON 格式的数据(什么是JSON)给浏览器。在课程后面的章节,再介绍使用模板引擎,解析并生成HTML页面返回给前端。
2.1 准备工作
在开始编写我们的第一个 SpringMVC 程序之前,需要先准备一个能够运行 Java Web 应用的服务器环境。最常用的 Web 容器就是 Apache Tomcat。
Tomcat 是由 Apache 软件基金会开发的一个开源 Java Web 容器(Web Container),它能够解析和执行:
- Servlet 程序
- JSP 页面(Java Server Pages)
SpringMVC 底层依赖 Servlet 机制运行,因此必须在 Tomcat 或类似的 Servlet 容器中才能启动。
简单来说:你写的 SpringMVC 应用程序,本质上是一组 Servlet;而 Tomcat 就是让这些 Servlet “运行起来” 的地方。
1 下载Tomcat
直接访问 Tomcat 官网下载即可,官网地址:Tomcat官网 >> https://tomcat.apache.org/
然后下载 Tomcat10 版本以上,这里我下载 Tomcat11。

下载完成,解压到一个目录备用。
2.2 HelloWorld实现
1 创建项目
创建一个 Maven 项目,没什么好说的,轻车熟路。

- 我这个IDEA不是特别新,新的话,可以创建一个Java 的Maven项目,作为开始。后面熟练了,可以直接创建一个 Web 项目。
2 引入依赖
在项目的 pom.xml 中添加相关的依赖。
xml
<?xml version="1.0" encoding="UTF-8"?>
<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.foooor</groupId>
<artifactId>hello-springmvc</artifactId>
<version>1.0-SNAPSHOT</version>
<!-- 打包为 war 包 -->
<packaging>war</packaging>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- Servlet API -->
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>6.1.0</version>
<!-- Tomcat容器会提供,这里主要是写代码时需要引入,否则会报错 -->
<scope>provided</scope>
</dependency>
<!-- Spring MVC 核心依赖,会包含 Spring 核心依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>6.2.11</version>
</dependency>
<!-- Jackson 用于 JSON 序列化 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.20.0</version>
</dependency>
<!-- 日志实现,Logback -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.5.19</version>
</dependency>
<!-- Lombok 可以生成 getter/setter 等方法 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.42</version>
</dependency>
</dependencies>
<build>
<plugins>
<!-- 打包配置 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.4.0</version>
<configuration>
<warName>hello-springmvc</warName> <!-- 可选:自定义 WAR 文件名 -->
</configuration>
</plugin>
</plugins>
</build>
</project>- 在上面的代码中添加了 SpringMVC 的核心依赖、Servlet 的依赖、日志、JSON依赖;
- 添加了 Lombok 依赖,通过注解会可以在编译期间自动帮我们生成类的 getter 和 setter,也可以帮我们注入日志对象(基于 SLF4J API),减少样板代码。Lombok 的使用可以参考 Lombok传送门 。
- 添加打包的依赖,可以通过 Maven 将项目打包成 war 包。
添加完成,右键 pom.xml 文件,Maven -> Reload project ,下载一下依赖。
一开始 Java Web 的开发规范 Java EE 由 Sun 公司建立,主要包括 Servlet、JSP、JPA、EJB、JAX-RS 等规范,用于企业级 Web / 企业应用开发。但是后来 Sun 被 Oracle 收购了,接手 Java 和 Java EE 的管理。2017 年,Oracle 将 Java EE 规范移交给 Eclipse Foundation,由开源社区接手,但是Oracle 拥有
Java商标,Eclipse 不能再用Java EE这个名字,为了符合开源许可和规范改进,把包名从javax.*改成jakarta.*。Spring5.x 及之前版本都是基于
javax.servlet的 API。直到 Spring6,才开始支持jakarta.servlet包(对应 Jakarta EE 9+)。所以 Spring6 需要引入jakarta.servlet-api依赖。所以如果是 Spring5 或更早版本,就必须使用javax.servlet-api依赖。另外,因为旧版本 Tomcat(如 Tomcat 9、8)只实现javax.servlet.*接口,无法识别jakarta.servlet.*,所以只能使用 Tomcat10+ 的版本来部署使用jakarta.servlet.*依赖的项目,同理 Tomcat 10+ 的 Servlet API 不再包含javax.servlet.*类,也就不能部署使用javax.servlet.*类的项目。
3 添加Web支持
在 src/main 下创建一个文件夹 webapp ,然后右键项目模块,Open Module Setting ,打开 Project Structure 。
然后按照下面的操作,添加项目发布描述文件:

操作完成,会在 webapp/WEB-INF 添加一个 web.xml 文件。如果没生成,手动添加也可以,也就是添加 src/main/webapp/WEB-INF/web.xml 文件,但是也要像上面这样指定 web.xml 路径。
XML 中的内容在下一个步骤有。
4 配置DispatcherServlet
在SpringMVC中,所有的请求都会到达 DispatcherServlet,并由 DispatcherServlet 分发到指定的 Controller,所以需要在 web.xml 中配置 DispatcherServlet 。
编辑 web.xml 如下:
xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
version="5.0">
<!-- DispatcherServlet 配置 -->
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
<!-- 和上面的 servlet-name 对应 -->
<servlet-name>springmvc</servlet-name>
<!-- 拦截所有除了 JSP 页面的请求,如果是/*表示拦截所有请求 -->
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>- 在
web.xml中,添加DispatcherServlet,并添加 servlet 映射,将路径/映射到DispatcherServlet,这样所有的请求都会到达DispatcherServlet,由DispatcherServlet来处理。
5 创建返回对象
我们是要将返回的数据使用 JSON 格式返回,为了数据的统一,我们也定一个统一的格式,例如所有返回的数据都以如下格式返回:
json
{
"code": 200,
"message": "success",
"data": {
// 数据
}
}不同的接口返回的数据都遵循上面的格式,不同接口的数据只是作为 data 部分返回。
所以这里我们创建一个统一的响应对象,将数据使用这个对象封装并返回,由SpringMVC帮我们自动转换为JSON格式。
我这里创建包,并创建一个 Result 类,与上面的格式对应,如下:
java
package com.foooor.hellospringmvc.common;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data // Lombok 自动生成 getter/setter 等方法
@NoArgsConstructor // 生成无参构造
@AllArgsConstructor // Lombok 自动生成全参构造方法
public class Result {
private int code;
private String message;
private Object data;
public static Result success() {
return new Result(200, "success", null);
}
public static Result success(Object data) {
return new Result(200, "success", data);
}
public static Result error(int code, String message) {
return new Result(code, message, null);
}
}- 类中包含了三个属性,返回码、信息和数据;
- 为了便于操作,还添加了几个方法。
6 创建Controller
创建包,并在包下创建一个 IndexController.java (类名自定义),内容如下:
java
package com.foooor.hellospringmvc.controller;
import com.foooor.hellospringmvc.common.Result;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class IndexController {
@GetMapping("/")
@ResponseBody
public Result index() {
return Result.success("Hello For技术栈!");
}
@GetMapping("/hello")
@ResponseBody
public Result hello() {
return Result.success("Hello World!");
}
}- 就是一个普通的类,需要添加
@Controller注解,标识这是一个 Controller。 - 添加了两个方法(方法名随意),并在方法上添加
@ResponseBody注解,作用是告诉 SpringMVC 将结果通过合适的转换器,转换为 JSON 字符串返回。 @GetMapping("/")表示匹配的是项目的根目录/,@GetMapping("/hello")表示匹配/hello请求,另外需要使用Get方法请求,才能访问。后面通过浏览器地址栏进行访问,就是使用Get方法来请求的。
7 配置SpringMVC文件
首先在项目的 webapp/WEB-INF 目录下创建 springmvc-servlet.xml 配置文件,这个名称是和 web.xml 中的 Servlet 的名称 springmvc (<servlet-name>springmvc</servlet-name>)是对应的,也就是 名称-servlet.xml 这个格式。
然后编写内容如下:
xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!-- 启用注解扫描 -->
<context:component-scan base-package="com.foooor.hellospringmvc" />
<!-- 启用 Spring MVC 注解支持 -->
<mvc:annotation-driven />
</beans>- 在其中开启了组件扫描,并启用了 MVC注解处理器 ,这样可是使用 Spring 和 SpringMVC 的注解来进行开发。
8 添加日志配置
添加一下日志配置,后面在项目中可以更方便的打印日志,这里使用 logback 日志框架实现。
在项目的 resources 目录下,创建 logback.xml,配置如下:
xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- 定义日志文件存放路径,这里指定放在 Tomcat 根目录下的 logs 文件夹 -->
<property name="LOG_PATH" value="${catalina.base}/logs" />
<!-- 定义日志文件路径 -->
<property name="LOG_FILE" value="${LOG_PATH}/hello-springmvc.log" />
<!-- 定义控制台输出 -->
<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>
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- 定义文件输出 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_FILE}</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 按天生成新日志文件 -->
<fileNamePattern>${LOG_PATH}/app.%d{yyyy-MM-dd}.log</fileNamePattern>
<!-- 保留 30 天的日志 -->
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- 还可以单独指定某些包下的日志级别 -->
<logger name="com.foooor.hellospringmvc" level="DEBUG" additivity="false">
<appender-ref ref="CONSOLE" />
<appender-ref ref="FILE" />
</logger>
<!-- root 日志级别 -->
<root level="INFO">
<appender-ref ref="CONSOLE" />
<appender-ref ref="FILE" />
</root>
</configuration>9 运行
首先需要进行一下发布配置,点击一下 Edit Configurations...

打开后,添加一个 Tomcat Server --> Local ,如下:

然后点击 Configure 按钮:

配置一下 Tomcat 目录:

然后选择第二个标签页,Deployment 配置一下要发布的项目:

修改一下项目的访问路径,这里可以改为 / ,那么后面可以通过浏览器使用 http://localhost:8080/ 来访问项目,如果配置为 /abc ,那么就需要使用 http://localhost:8080/abc 来访问项目:

此时第一个标签页显示如下,点击 OK ,完成配置。

然后点击运行按钮运行(或调试模式运行):

看一下控制台启动是否正常,有没有报错。
10 测试
打开浏览器,访问 http://localhost:8080/ 就可以访问项目了。

可以看到数据以 JSON 的格式返回。
这里 http://localhost:8080/ 访问的就是项目的根目录 / ,那么就会被 IndexController 的 @GetMapping("/") 匹配处理,也就是被 index() 方法接收到请求:
java
@GetMapping("/") // 匹配根目录/
@ResponseBody
public Result index() {
return Result.success("Hello For技术栈!");
}方法返回的是 Result.success("Hello For技术栈!"),最终转换为 JSON,如上。
2.3 代码解析
1 代码执行的流程
首先来说说,从浏览器发起请求,到返回数据整个过程大概的执行流程:
在应用启动时,会扫描所有带
@Controller/@RequestMapping注解的 bean,将 Controller 和其中的方法等信息封装为HandlerMethod对象,HandlerMethod对象中封装了调用方法所需的全部信息,例如持有 Controller 实例和方法元数据,建立path和HandlerMethod对象的映射表(HashMap 形式),这个映射表由RequestMappingHandlerMapping管理。用户通过浏览器发起请求,并到达服务器;
请求会被前端控制器
DispatcherServlet接收;DispatcherServlet会调用RequestMappingHandlerMapping,根据请求 URL、HTTP 方法等信息,找到匹配的HandlerMethod。并根据HandlerMethod找到合适的HandlerAdapter,因为不同类型的HandlerMethod(如普通 Controller、注解 Controller)需要不同的HandlerAdapter来执行,在注解驱动的 SpringMVC 中,会使用RequestMappingHandlerAdapter来执行HandlerMethod;通过
HandlerMethod封装的信息,利用反射机制来调用 Controller 中对应的方法;得到方法返回值以后,会通过返回值类型选择不同的处理方式。如果方法或类上有
@ResponseBody或@RestController注解(后面再讲),则直接进入响应体输出流程。此时HandlerAdapter会委托给RequestResponseBodyMethodProcessor,再由HttpMessageConverter(如MappingJackson2HttpMessageConverter)将 Java 对象序列化为 JSON。如果方法或类上没有@ResponseBody或@RestController注解,返回值会以视图名进行解析,进入视图解析流程;序列化后的 JSON(或视图渲染结果)被写入
HttpServletResponse的输出流,返回给浏览器,完成一次完整的请求处理过程。
画一下执行的流程图:

我们现在没有用到视图,这个后面再讲。
另外,我们的项目一般采用分层设计,Controller -> Service -> Dao,结合 MVC 模式,Service 和 Dao 层是属于 MVC 中的 M 层的细化。
2.4 DispatcherServlet配置
上面的 springmvc-servlet.xml 配置文件名称,是和 web.xml 中的 Servlet 的名称 springmvc 是对应的,也就是 名称-servlet.xml 这个格式。
我们还可以指定该文件的位置和名称,例如:
xml
<!-- DispatcherServlet 配置 -->
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/springmvc-web.xml</param-value>
</init-param>
<!-- 表示在应用启动时加载Servlet,数字越小,加载优先级越高 -->
<load-on-startup>1</load-on-startup>
</servlet>- 上面通过
contextConfigLocation指定了配置文件的路径。 - 还通过
load-on-startup指定了DispatcherServlet在项目启动的时候就加载,这样在第一次访问的时候,请求能快一些,如果不配置,DispatcherServlet 会在第一次请求的时候创建,那么第一次请求的响应就慢一些,体验稍差。
2.5 访问说明
1 项目路径
上面在配置项目访问路径的时候,配置的是 / ,那么我们可以使用 http://localhost:8080/ 来访问项目。
如果项目路径配置的是 hello-springmmvc ,那么我们需要使用 http://localhost:8080/hello-springmvc/ 来访问到项目。
localhost 表示的是访问当前的主机。
2 Tomcat端口号
Tomcat 默认监听的是 8080 端口,在 Tomcat 目录下的 conf/server.xml 中可以修改配置:
xml
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />当网络请求访问 8080 端口时,就会将数据发送给 Tomcat。
为什么我访问百度 http://www.baidu.com 没有端口号呢,这是因为使用的是 80 端口,http 默认访问的是 80 端口,所以不写就代表访问的是 80 端口。而对于 https,默认访问的是 443 端口,所以下面两种方式是等价的:
http://www.baidu.com等价于http://www.baidu.com:80https://www.baidu.com等价于https://www.baidu.com:443
2.6 打包并发布项目
上面已经完成了 HelloWorld 项目的编写,可以处理网络请求并返回数据了,下面可以将项目打包发布了。
1 打包
在项目的 Maven 窗口,双击 package 进行打包,打包完成,会在 target 文件夹下生成项目的 war 包。

2 发布
打完 war 包,就可以将项目发布了,我们可以在服务器上部署 Tomcat,然后将 war 包放在 Tomcat 目录下的 webapps 目录下。
当然我们现在没有服务器,所以就直接放在自己本机的 Tomcat 目录下的 webapps 目录下。(可以将 webapps 目录下的其他所有文件删掉)
然后修改一下 Tomcat 的配置,修改 conf/server.xml 文件中的 <Host> 标签下的内容,添加 <Context> 配置:
xml
<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true">
<!-- 添加配置 -->
<Context path="" docBase="hello-springmvc" reloadable="true"> </Context>
<!-- 其他配置 -->
</Host>然后打开 Tomcat 下的 bin 目录,如果是 windows 系统,可以运行 startup.bat 文件启动 Tomcat(需要将 IDEA 中运行的 Tomcat 程序停掉),如果是 Linux 系统,可以在终端进入 bin 目录,然后运行 ./startup.sh 启动 Tomcat。
Tomcat 启动后,会自动解压 webapps 目录下的 war 包,然后就可以通过浏览器来访问项目了。
要停止 Tomcat ,可以运行 shutdown.bat 或 shutdown.sh 。
2.7 关于日志记录
上面的项目中,我们使用 logback 日志框架记录日志,并对日志进行了配置,将日志输出到了日志文件:
xml
<!-- 定义日志文件存放路径,这里指定放在 Tomcat 根目录下的 logs 文件夹 -->
<property name="LOG_PATH" value="${catalina.base}/logs" />
<!-- 定义日志文件路径 -->
<property name="LOG_FILE" value="${LOG_PATH}/hello-springmvc.log" />上面是指定输出到 Tomcat 目录下的 logs 文件夹中,日志名称为 hello-springmvc.log 。
如果是打包发布项目的话,就可以看到在 Tomcat 目录下的 logs 文件夹中确实有日志文件生成。
但是如果是使用 IDEA 启动 Tomcat,你会发现并没有在 Tomcat 目录下生成日志、也看不到 webapps 目录下存在项目相关的文件。这是因为启动在 Run/Debug Configurations 里的 Tomcat Server时,Tomcat 的运行方式与手动启动的独立 Tomcat 完全不同,IDEA 在项目下生成一个临时的、隔离的 Tomcat 实例,通过启动日志可以发现,这样的日志:
bash
信息 [main] org.apache.catalina.startup.VersionLoggerListener.log 命令行参数: -Dcatalina.base=/xxxxx/xxxxx/xxxxx/JetBrains/IntelliJIdea2022.2/tomcat/a5f749ef-1275-4577-829d-2a667f5c5412这个是真实的目录,如果进入到这个目录,就能看到其下有一个 logs 文件夹,可以找到输出的日志。