Skip to content

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 代码执行的流程

首先来说说,从浏览器发起请求,到返回数据整个过程大概的执行流程:

  1. 在应用启动时,会扫描所有带 @Controller / @RequestMapping 注解的 bean,将 Controller 和其中的方法等信息封装为 HandlerMethod 对象, HandlerMethod 对象中封装了调用方法所需的全部信息,例如持有 Controller 实例和方法元数据,建立 pathHandlerMethod 对象的映射表(HashMap 形式),这个映射表由 RequestMappingHandlerMapping 管理。

  2. 用户通过浏览器发起请求,并到达服务器;

  3. 请求会被前端控制器 DispatcherServlet 接收;

  4. DispatcherServlet 会调用 RequestMappingHandlerMapping ,根据请求 URL、HTTP 方法等信息,找到匹配的 HandlerMethod。并根据 HandlerMethod 找到合适的 HandlerAdapter ,因为不同类型的 HandlerMethod(如普通 Controller、注解 Controller)需要不同的 HandlerAdapter 来执行,在注解驱动的 SpringMVC 中,会使用 RequestMappingHandlerAdapter 来执行 HandlerMethod

  5. 通过 HandlerMethod 封装的信息,利用反射机制来调用 Controller 中对应的方法;

  6. 得到方法返回值以后,会通过返回值类型选择不同的处理方式。如果方法或类上有 @ResponseBody@RestController 注解(后面再讲),则直接进入响应体输出流程。此时 HandlerAdapter 会委托给 RequestResponseBodyMethodProcessor,再由 HttpMessageConverter(如 MappingJackson2HttpMessageConverter)将 Java 对象序列化为 JSON。如果方法或类上没有 @ResponseBody@RestController 注解,返回值会以视图名进行解析,进入视图解析流程;

  7. 序列化后的 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:80
  • https://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.batshutdown.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 文件夹,可以找到输出的日志。