SpringMVC 基础

214 阅读22分钟

SpringMVC简介

MVC是什么

MVC是一种软件架构的思想,将软件按照模型、视图、控制器来划分。

  • M:Model模型层,指工程中的 JavaBean,作用是处理数据
  • V:View视图层,指工程中的 html、jsp等页面,作用是与用户进行交互、展示数据
  • C:Controller控制层,指工程中的 servlet,作用是接收请求和响应浏览器

JavaBean分为两类:

  • 一类称为实体类 Bean,专门存储业务数据,如 Student、User等
  • 一类称为业务处理 Bean,指 Service、Dao对象,专门处理业务逻辑和数据访问

MVC的工作流程:

用户通过视图层发送请求到服务器,在服务器中请求被 Controller接收,Controller调用相应的 Model层处理请求,处理完毕将结果返回到 Controller,Controller再根据请求处理的结果找到相应的 View视图,渲染数据后最终响应给浏览器。

SpringMVC是什么

SpringMVC是 Spring的一个后续产品,是 Spring的一个子项目。 SpringMVC是 Spring为表述层开发提供的一套完备的解决方案。在表述层框架历经 Strust等诸多产品的更迭之后,目前业界普遍选择了 SpringMVC作为 JavaEE项目表述层开发的首选方案。

三层架构:

  • 表述层:表示前端页面和后端 Servlet
  • 业务逻辑层:Service层,主要是对业务的逻辑处理
  • 数据访问层:Dao层,对数据的增删改查

三层架构处理流程:

前端页面 -发送请求-> 后端 Servlet接收 -调用 Service方法-> Service类 -根据不同的方法调用 Dao-> Dao类 -增删改查数据-> Service类 -处理数据-> Servlet -获取数据并跳转页面-> 前端页面

SpringMVC的特点

  • Spring家族原生产品,与 IOC容器等基础设施无缝对接
  • 基于原生的 Servlet,通过了功能强大的前端控制器 DispatcherServlet,对请求和响应统一处理
  • 表述层各细分领域需要解决的问题全方位覆盖
  • 代码清新简洁,大幅提升开发效率
  • 内部组件化程度高,可插拔式组件即插即用,根据需求配置相应的组件即可
  • 性能卓越,尤其适合现代大型、超大型互联网项目

HelloWord(搭建环境)

创建 maven工程

Module -> Maven -> next -> GroupId -> Module name -> Finish

  • 添加 web模块
    • file -> Project Structure -> Modules -> 选择添加 web模块的项目 -> + -> 选择对应的目录添加 web.xml
  • 打包方式:war
    • <packaging>war</packaging>:包含 web模块需要打包方式为 war包
  • 引入依赖
<dependencies>
    <!-- SpringMVC -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.3.1</version>
    </dependency>
    // ...
</dependencies>

配置 maven工程

注册 SpringMVC的前端控制器 DispatcherServlet

默认配置方式:

此配置作用下,SpringMVC的配置文件默认位于 WEB-INF下,默认名为 -servlet.xml,例如,以下配置所对应 SpringMVC的配置文件位于 WEB-INF下,文件名为 SpringMVC-servlet.xml

  • <servlet-name> 配置 SpringMVC的前端控制器,对请求进行统一处理
  • /,表示浏览器发送的请求可以是 /login、.html、.css,但不会匹配 .jsp请求路径的相求
  • /*,表示所有请求
<?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">
    <!--
        配置 SpringMVC的前端控制器,
        对浏览器发送的请求进行统一处理
    -->
    <servlet>
        <servlet-name>SpringMVC</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>SpringMVC</servlet-name>
        <!--
            /,表示浏览器发送的请求可以是 /login、.html、.css,
            但不会匹配 .jsp请求路径的相求

            /*,表示所有请求
        -->
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

扩展配置方式:(推荐)

可通过 init-param标签设置 SpringMVC配置文件的位置和名称,通过 load-on-startup标签设置 SpringMVC前端控制器 DispatcherServlet的初始化时间

  • 通过初始化参数指定 SpringMVC配置文件的位置和名称
  • 通过此标签将前端控制器 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">
    <!--
        配置 SpringMVC的前端控制器,
        对浏览器发送的请求进行统一处理
    -->
    <servlet>
        <servlet-name>SpringMVC</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!--
            通过初始化参数指定 SpringMVC配置文件的位置和名称
        -->
        <init-param>
            <!--contextConfigLocation为固定值-->
            <param-name>contextConfigLocation</param-name>
            <!--
                使用 classpath: 表示从类路径查找配置文件,
                如,maven工程中的 src/main/resources
            -->
            <param-value>classpath:springMVC.xml</param-value>
        </init-param>
        <!--
            Servlet:初始化是在第一次访问时才初始化,
            作为框架的核心组件,在启动过程中有大量的初始化操作要做,
            而这些操作如果放在第一次请求时才执行会严重影响访问速度,
            因此需要通过此标签将前端控制器 DispatcherServlet的初始化时间提前到服务器启动时
        -->
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>SpringMVC</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

创建请求控制器

由于前端控制器对浏览器发送的请求进行了统一的处理,但是具体的请求有不同的处理过程,因此需要创建具体请求的类,即请求控制器。 请求控制器中每一个请求的方法称为控制器方法。 因为 SpringMVC的控制器由一个 POJO(普通的 Java类)担任,因此需要通过 @Controller注解将其标识为一个控制层组件,交给 Spring的 IoC容器管理,此时 SpringMVC才能够识别控制器的存在。

  • HelloController.java
package com.atjava.mvc.controller;

/**
 * @author lv
 * @create 2022-01-27 21:30
 */
import org.springframework.stereotype.Controller;
/**
 * 将此类作为 SpringIoC容器中的一个组件时,才是一个控制器,
 * 将一个类变为 IoC容器中的一个组件:
 * 1. 使用 Bean标签配置
 * 2. 注解(@Component@Controller@Repository@Service) + 扫描
 */
@Controller
public class HelloController {
  // ...
}
  • 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:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd
                           http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

    <!--配置扫描组件-->
    <context:component-scan base-package="com.atjava.mvc.controller"></context:component-scan>
</beans>

创建 SpringMVC的配置文件

  • 配置 Thymeleaf视图解析器
  • 处理静态资源
  • 开启mvc注解驱动
<!-- 配置Thymeleaf视图解析器 -->
<bean id="viewResolver" class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
    <!--
        order:配置视图解析器的优先级,存在多个视图解析器时配置当前解析器的优先级
    -->
    <property name="order" value="1"/>
    <property name="characterEncoding" value="UTF-8"/>
    <property name="templateEngine">
        <bean class="org.thymeleaf.spring5.SpringTemplateEngine">
            <property name="templateResolver">
                <bean class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver">
                    <!--
                        以下属性是设置解析当前视图的策略
                    -->
                    <!-- 视图前缀 -->
                    <property name="prefix" value="/WEB-INF/templates/"/>

                    <!-- 视图后缀 -->
                    <property name="suffix" value=".html"/>
                    <property name="templateMode" value="HTML5"/>
                    <property name="characterEncoding" value="UTF-8" />
                </bean>
            </property>
        </bean>
    </property>
</bean>

<!-- 
   处理静态资源,例如html、js、css、jpg
  若只设置该标签,则只能访问静态资源,其他请求则无法访问
  此时必须设置<mvc:annotation-driven/>解决问题
 -->
<mvc:default-servlet-handler/>

<!-- 开启mvc注解驱动 -->
<mvc:annotation-driven>
    <mvc:message-converters>
        <!-- 处理响应中文内容乱码 -->
        <bean class="org.springframework.http.converter.StringHttpMessageConverter">
            <property name="defaultCharset" value="UTF-8" />
            <property name="supportedMediaTypes">
                <list>
                    <value>text/html</value>
                    <value>application/json</value>
                </list>
            </property>
        </bean>
    </mvc:message-converters>
</mvc:annotation-driven>

测试 HelloWorld

  • 配置 Tomca:Run/Debug Configurations -> Tomcat Server -> Local -> Name、Deployment...

实现对首页的访问:

  • 在请求控制器中创建处理请求的方法
    1. @RequestMapping(value = "/")将当前请求和控制器方法创建映射关系
package com.atjava.mvc.controller;
/**
 * @author lv
 * @create 2022-01-27 21:30
 */
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
/**
 * 将此类作为 SpringIoC容器中的一个组件时,才是一个控制器,
 * 将一个类变为 IoC容器中的一个组件:
 * 1. 使用 Bean标签配置
 * 2. 注解(@Component@Controller@Repository@Service) + 扫描
 */
@Controller
public class HelloController {
    // http://localhost:8080/springMVC-demo1/
    // "/" -> /WEB-INF/templates/index.html

    // 控制器中的方法才是真正处理请求的方法
    @RequestMapping(value = "/") // 将当前的请求和控制器方法创建映射关系
    /**
     * .RequestMappingHandlerMapping
     * - Mapped to com.atjava.mvc.controller.HelloController#index()
     */
    public String index() {
        // 返回要跳转的视图名称 index.html
        return "index";
    }
}

通过超链接跳转到指定页面:

  • 在主页面 index.html中设置超链接
  1. "/",以斜线开头的路径称为绝对路径,分为浏览器解析和服务器解析两种
  2. 超链接中的绝对路径就是浏览器解析:localhost:8080/就是绝对路径的上下文路径
  3. 可以使用 Thmeleaf语法来灵活获取 Tomcat服务器设置的上下文路径,@{/...}
    • <a th:href="@{/target}">访问目标页面 target.html</a>
  • index.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<!--
    xmlns:th="http://www.thymeleaf.org" 表示 Thymeleaf的命名空间,用于使用 Thymeleaf的语法
-->
<head>
    <meta charset="UTF-8">
    <title>Index</title>
</head>
<body>
    <h1>首页</h1>
    <p>http://localhost:8080/springMVC-demo1/</p>
    <!--
        "/",以斜线开头的路径称为绝对路径,分为浏览器解析和服务器解析两种,
        超链接中的绝对路径就是浏览器解析:localhost:8080/就是绝对路径的上下文路径。

        此 demo的上下文路径是创建 Tomcat时设置的:/springMVC-demo1。
        可以使用 Thmeleaf语法来灵活获取上下文路径,@{/...},
        当发现其中内容是绝对路径时,th会自动添加上下文路径
    -->
    <a th:href="@{/target}">访问目标页面 target.html</a>
</body>
</html>
  • HelloController.java
package com.atjava.mvc.controller;

/**
 * @author lv
 * @create 2022-01-27 21:30
 */

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * 将此类作为 SpringIoC容器中的一个组件时,才是一个控制器,
 * 将一个类变为 IoC容器中的一个组件:
 * 1. 使用 Bean标签配置
 * 2. 注解(@Component@Controller@Repository@Service) + 扫描
 */
@Controller
public class HelloController {
    // http://localhost:8080/springMVC-demo1/
    // "/" -> /WEB-INF/templates/index.html

    // 控制器中的方法才是真正处理请求的方法
    @RequestMapping(value = "/") // 将当前的请求和控制器方法创建映射关系
    /**
     * org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
     * - Mapped to com.atjava.mvc.controller.HelloController#index()
     */
    public String index() {
        // 返回要跳转的视图名称 index.html
        return "index";
    }
    @RequestMapping("/target")
    public String target() {
        return "target";
    }
}
请求流程总结:
  1. 获取请求
  2. 找到请求控制器
  3. 通过 @RequestMapping(请求映射)匹配请求与对应的处理方法
  4. 处理方法的返回值被视图解析器解析,加上前后缀就表示请求要跳转的页面

总结:

工程的搭建步骤:

  1. 创建 Maven工程,添加 web模块、添加依赖
  2. 配置 web.xml、配置前端控制器
  3. 创建请求控制器、方法,配置 SpringMVC的配置文件
  4. 扫描组件、配置视图解析器
1. 创建 webapp并注册前端控制器:DispatcherServlet
2. 创建控制器类 @Controller + springMVC.xml配置文件扫描
3. 在 springMVC.xml中配置视图解析器 Thymeleaf

请求处理流程:

  1. 浏览器发送请求,若请求地址符合前端控制器 SpringMVC的 url-pattern,该请求会被前端控制器 DispatcherServlet处理
  2. 前端控制器读取 SpringMVC的核心配置文件,通过扫描组件找到控制器(标有 @Controller注解的类)
  3. 将请求地址和控制器中标有 @RequestMapping(value值)注解的方法进行匹配
  4. 匹配成功的方法会返回一个字符串类型的视图名称,此视图名称会被视图解析器解析,添加前后缀组成视图的路径
  5. 通过 Thymeleaf对视图进行渲染,最终转发到视图所对应的页面

@RequestMapping注解

@RequestMapping注解的功能

从注解名称上我们可以看到,@RequestMapping注解的作用就是将请求和处理请求的控制器方法关联起来,建立映射关系。

SpringMVC接收到指定的请求,就会来找到在映射关系中对应的控制方法来处理这个请求。

@RequestMapping注解的位置

  • @RequestMapping标识一个类(主要用于区分模块):设置映射请求的请求路径的初始信息
  • @RequestMapping标识一个方法:设置映射请求的请求路径的具体信息
package com.atjava.mvc.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * @author lv
 * @create 2022-02-07 23:28
 */
@Controller
@RequestMapping("/test")
public class RequestMappingController {
    // 此时请求映射所映射的请求路径为:/test/testRequestMapping
    @RequestMapping("/testRequestMapping")
    public String testRequestMapping() {
        return "success";
    }
}

@RequestMapping注解的 value属性

  • @RequestMapping注解的 value属性通过请求的请求地址匹配请求映射
  • @RequestMapping注解的 value属性可以是一个字符串类型的数组,表示该请求映射能够匹配多个请求地址所对应的请求
  • @RequestMapping注解的 value属性必须设置,至少通过请求地址匹配请求映射
<h1>index</h1>
<a th:href="@{/test/testRequestMapping}">测试 RequestMapping注解的位置</a><br/>
<a th:href="@{/test/test1}">测试 RequestMapping注解的 value属性 /test1</a><br/>
@Controller
@RequestMapping("/test")
public class RequestMappingController {
    // 此时请求映射所映射的请求路径为:/test/testRequestMapping
    @RequestMapping(
            value = {"/testRequestMapping", "/test1"}
    )
    public String testRequestMapping() {

        return "test";
    }
}

@RequestMapping注解的 method属性

  • @RequestMapping注解的 method属性通过请求地址的请求方式(get、post)匹配请求映射
  • @RequestMapping注解的 method属性是一个 RequestMethod类型的数组,表示该请求映射能够匹配多种请求方式的请求
  • 若当前请求的请求地址满足请求映射的 value属性,但是请求方式不满足 method属性,则浏览器报错 HTTP Status 405 Request method 'POST' not supported
  • method属性值如下(枚举):
  1. RequestMethod.GET,
  2. RequestMethod.POST
  3. RequestMethod.DELETE
  4. RequestMethod.PUT

对于处理指定请求方式的控制器方法,SpringMVC提供了 @RequestMapping的派生注解:

  1. 处理 get请求的映射 -> @GetMapping
  2. 处理 post请求的映射 -> @PostMapping
  3. 处理 delete请求的映射 -> @DeleteMapping
  4. 处理 put请求的映射 -> @PutMapping

常用的请求方式有 get、post、delete、put

  1. 目前浏览器只支持 get、post,若在 form表单提交时,为 method设置了其它请求方式的字符串(put、delete),则按照默认的请求方式 get处理
  2. 若要发送 put、delete请求,则需要通过 spring提供的过滤器 HiddenHttpMethodFilter,在 Restfull部分会讲到
@Controller
//@RequestMapping("/test")
public class RequestMappingController {
    // 此时请求映射所映射的请求路径为:/test/testRequestMapping
    @RequestMapping(
            value = {"/testRequestMapping", "/test1"},
            method = {RequestMethod.GET}
            //method = {RequestMethod.GET, RequestMethod.POST}
    )
    // HTTP Status 405 - Request method 'POST' not supported
    public String testRequestMapping() {

        return "test";
    }

    @GetMapping("/testGetMapping")
    public String testGetMapping() {
        return "test";
    }
}
<form th:action="@{/test/test1}" method="post">
    <!--<button type="submit">test method</button>-->
    <input type="submit" value="test mothod" />
</form>
<a th:href="@{/testGetMapping}">testGetMapping</a>

注意:

  1. @RequestMapping必须要有 value属性,之后才会去匹配其它属性

@RequestMapping注解的 params属性(了解)

  • @ReqquestMapping注解的 params属性通过请求的请求参数匹配请求映射
  • @RequestMapping注解的 params属性是一个字符串类型的数组,可以通过四种表达式设置请求参数和请求映射的匹配关系
  1. param:要求请求映射所匹配的请求必须携带 param请求参数
  2. !param:要求请求映射所匹配的请求必须不能携带 param请求参数
  3. param=value:要求请求映射所匹配的请求必须携带 param请求参数且 param=value
  4. param!=value:要求请求映射所匹配的请求必须携带 param请求参数但 param!=value
  • 请求参数必须同时满足 params属性中的值的所有要求
  • 若当前请求满足 @RequestMapping注解的 value和 method属性,但是不满足params属性,此时页面报错400
@RequestMapping(
        value = {"/testParamsAndHeaders"},
        params = {"username", "password!=123456"}
)
public String testParamsAndHeaders() {
    return "test";
}
@PostMapping(
        value = {"/testParamsAndHeaders2"},
        params = {"username", "password!=123456"}
)
public String testParamsAndHeaders2() {
    return "test";
}
<!--
使用括号(...)或问号?进行传参:
-->
<a th:href="@{/testParamsAndHeaders(username='admin', password=1234567)}">testParams</a><br/>
<a th:href="@{/testParamsAndHeaders?username=admin&password=1234567}">testParams?</a><br/>
<form th:action="@{/testParamsAndHeaders2}" method="post">
    <input name="username" value="admin" /><br />
    <input name="password" value="123456" /><br />
    <input type="submit" value="testPostParams" />
</form>

@RequestMapping注解的 headers属性(了解)

  • @RequestMapping注解的 headers属性通过请求的请求头信息匹配请求映射
  • @RequestMapping注解的 headers属性是一个字符串类型的数组,可以通过四种表达式设置请求头信息和请求映射的匹配关系
  1. header:要求请求映射所匹配的请求必须携带 header请求头信息
  2. !header:要求请求映射所匹配的请求必须不能携带 header请求头信息
  3. header=value:要求请求映射所匹配的请求必须携带 header请求头信息且 header=value
  4. header!=value:要求请求映射所匹配的请求必须携带 header请求头信息且 header!=value
  • 若当前请求满足 @RequestMapping注解的 value和 method属性,但是不满足 headers属性,此时页面显示 404错误,即资源未找到
    @GetMapping(
            value = {"/testParamsAndHeaders3"},
            headers = {"Host=localhost:8080"}
    )
    public String testParamsAndHeaders3() {
        return "test";
    }
<a th:href="@{/testParamsAndHeaders3}">testHeaders</a><br />

SpringMVC支持 ant(模糊匹配)风格的请求路径

  • ?:表示任意的单个字符,此字符不能为空
  • *:表示任意的 0个或多个字符
  • **:表示任意的一层或多层目录,且 **的周围不能出现其它字符 注意:在使用 **时,只能使用 /**/xxx的方式
// ?
@RequestMapping("/a?a/testAnt")
public String testAnt() {
    return "test";
}

// *
@RequestMapping("/a*a/testAnt")
public String testAnt2() {
    return "test";
}

// **
@RequestMapping("/**/testAnt")
public String testAnt3() {
    return "test";
}
<a th:href="@{/a1a/testAnt}">testAnt1,?:表示任意的单个字符</a><br />
<a th:href="@{/aaa/testAnt}">testAnt2,?:表示任意的单个字符</a><br />
<a th:href="@{/aa/testAnt}">testAnt3,?:表示任意的单个字符</a><br />

<a th:href="@{/aa/testAnt}">testAnt4,*:表示任意的 0个或多个字符</a><br />
<a th:href="@{/a1a/testAnt}">testAnt5,*:表示任意的 0个或多个字符</a><br />
<a th:href="@{/a12a/testAnt}">testAnt6,*:表示任意的 0个或多个字符</a><br />

<a th:href="@{/testAnt}">testAnt7,**:表示任意的一层或多层目录</a><br />
<a th:href="@{/a/testAnt}">testAnt8,**:表示任意的一层或多层目录</a><br />
<a th:href="@{/a/a/a/testAnt}">testAnt9,**:表示任意的一层或多层目录</a><br />

SpringMVC支持路径中的占位符(重点)

  • 原始方式:/deleteUser?id=1&xxx=xxx
  • rest方式:/deleteUser/1
  • @PathVariable(xxx):获取请求地址中占位符所代表的值

SpringMVC路径中的占位符常用于 RESTful风格中,当请求路径中将某些数据通过路径的方式传输到服务器中,就可以在相应的 @RequestMapping注解的 value属性中通过占位符 {xxx}表示传输的数据,再通过 @PathVariable注解,将占位符所表示的数据赋值给控制器方法的形参

注意:匹配路径中有占位符时,则实际请求地址中就必须有占位符这一层路径的数据

@RequestMapping("/test/{id}/{username}")
public String testTab(@PathVariable("id")Integer id, @PathVariable("username")String username) {
    // http://localhost:8080/springMVC-demo2/test/1/lver
    System.out.println("id: " + id + ", username: " + username); // id: 1, username: lver
    return "test";
}
<a th:href="@{/test/1/lver}">@RequestMapping支持路径中的占位符{xxx}</a><br />

SpringMVC获取请求参数(重要)

通过 ServletAPI获取

将 HttpServletRequest作为控制器方法的形参,此时 HttpServletRequest类型的参数表示封装了当前请求的请求报文的对象。

@RequestMapping("/testServletAPI")
// http://localhost:8080/springMVC-demo2/testServletAPI?id=123&username=admin
public String testServletAPI(HttpServletRequest request) {
    System.out.println(request.toString());
    Integer id = Integer.parseInt(request.getParameter("id"));
    String username = request.getParameter("username");
    System.out.println("id: " + id + ", username: " + username); // id: 123, username: admin

    return "test";
}
<h2>测试请求参数 params</h2>
<a th:href="@{/testServletAPI(id=123, username='admin')}">测试 ServletAPI</a>

通过控制器方法的形参获取请求参数

在控制器方法的形参位置,设置和请求参数同名的形参,当浏览器发送请求,匹配到请求映射时,在 DispatcherServlet中就会将请求参数赋值给相应的形参。

  • 控制器方法的形参名必须与请求参数的参数名保持一致
  • 对于同名的请求参数,可以通过:
    1. 字符串参数获取,属性值使用逗号分隔
    2. 字符串数组获取
@RequestMapping("/testParams")
public String testParams(int id, String username) {
    System.out.println("id: " + id + ", username: " + username);
    return "test";
}

/**
 * 对于同名的请求参数,可以通过:
 * 1. 字符串获取
 * 2. 字符串数组获取
 * @param username
 * @param hobby
 * @return
 */
@GetMapping("/testFormParams")
public String testFormParams(String username, String hobby) {
    System.out.println("username: " + username + ", hobby: " + hobby);
    // username: lver, hobby: 123,456,789
    return "test";
}
@PostMapping("/testFormParams")
public String testFormParams2(String username, String[] hobby) {
    System.out.println("username: " + username + ", hobbyArr: " + Arrays.asList(hobby));
    // username: lver, hobbyArr: [123, 456, 789]
    return "test";
}
<a th:href="@{/testParams(id=123, username='admin')}">测试 控制器的形参</a><br />

<form th:action="@{/testFormParams}" method="post">
<!--<form th:action="@{/testFormParams}" method="get">-->
    <label>
        name: <input name="username" value="lver" />
    </label><br />
    <label>
        hobby:<br />
        <input type="checkbox" name="hobby" value="123" />123<br />
        <input type="checkbox" name="hobby" value="456" />456<br />
        <input type="checkbox" name="hobby" value="789" />789<br />
    </label>
    <input type="submit" value="submit" />
</form>

@RequestParam

  • @RequestParam是将请求参数和控制器方法的形参创建映射关系

@RequestParam注解一共有三个属性:

  1. value:指定为形参赋值的请求参数的参数名
  2. required:设置是否必须传输此请求参数,默认值 true(若设置为true时,则当前请求必须传输value所指定的请求参数,若没有传输该请求参数,且没有设置defaultValue属性,则页面报错400:Required String parameter 'xxx' is not present;若设置为false,则当前请求不是必须传输value所指定的请求参数,若没有传输,则注解所标识的形参的值为null)
  3. defaultValue:不管 required属性值为 true或 false,当 value所指定的请求参数没有传输或传输的值为空字符串时,则使用默认值为形参赋值
@PostMapping("/testFormParams")
public String testFormParams2(
        @RequestParam(value = "user_name", required = false, defaultValue = "xxxName") String username,
        String[] hobby
) {
    System.out.println("username: " + username + ", hobbyArr: " + Arrays.asList(hobby));
    // username: lver, hobbyArr: [123, 456, 789]
    return "test";
}
<form th:action="@{/testFormParams}" method="post">
    <label>
        name: <input name="user_name" value="lver" />
    </label><br />
    <label>
        hobby:<br />
        <input type="checkbox" name="hobby" value="123" />123<br />
        <input type="checkbox" name="hobby" value="456" />456<br />
        <input type="checkbox" name="hobby" value="789" />789<br />
    </label>
    <input type="submit" value="submit" />
</form>

@RequestHeader

  • 如果想要获取请求中请求头的信息,那必须使用 @RequestHeader
  • @RequestHeader是将请求头信息和控制器方法的形参创建映射关系

@RequestHeader注解一共有三个属性(用法同 @RequestParam):

  1. value
  2. required
  3. defaultValue
@PostMapping("/testFormParams")
public String testFormParams2(
        @RequestParam(value = "user_name", required = false, defaultValue = "xxxName") String username,
        String[] hobby,
        @RequestHeader(value = "Host", required = true, defaultValue = "localhost:8080") String host
) {
    System.out.println("username: " + username + ", hobbyArr: " + Arrays.asList(hobby));
    // username: lver, hobbyArr: [123, 456, 789]

    System.out.println("host: " + host);
    // host: localhost:8080
    return "test";
}
<form th:action="@{/testFormParams}" method="post">
    <label>
        name: <input name="user_name" value="lver" />
    </label><br />
    <label>
        hobby:<br />
        <input type="checkbox" name="hobby" value="123" />123<br />
        <input type="checkbox" name="hobby" value="456" />456<br />
        <input type="checkbox" name="hobby" value="789" />789<br />
    </label>
    <input type="submit" value="submit" />
</form>

@CookieValue

  • @CookieValue是将 cookie数据和控制器方法的形参创建映射关系

@CookieValue注解一共有三个属性(用法同 @RequestParam):

  1. value
  2. required
  3. defaultValue
@PostMapping("/testFormParams")
public String testFormParams2(
        @RequestParam(value = "user_name", required = false, defaultValue = "xxxName") String username,
        String[] hobby,
        @RequestHeader(value = "Host", required = true, defaultValue = "localhost:8080") String host,
        @RequestHeader(value = "Origin", required = true, defaultValue = "localhost:8080") String origin,
        @CookieValue(value = "JSESSIONID") String JSESSIONID
) {
    System.out.println("username: " + username + ", hobbyArr: " + Arrays.asList(hobby));
    // username: lver, hobbyArr: [123, 456, 789]

    System.out.println("host: " + host + ", Origin: " + origin);
    // host: localhost:8080, Origin: http://localhost:8080

    System.out.println("JSESSIONID: " + JSESSIONID);
    // JSESSIONID: 3B956BC0E844CA910D8742A1B06D68B6
    return "test";
}
<form th:action="@{/testFormParams}" method="post">
    <label>
        name: <input name="user_name" value="lver" />
    </label><br />
    <label>
        hobby:<br />
        <input type="checkbox" name="hobby" value="123" />123<br />
        <input type="checkbox" name="hobby" value="456" />456<br />
        <input type="checkbox" name="hobby" value="789" />789<br />
    </label>
    <input type="submit" value="submit" />
</form>

通过 POJO获取请求参数

可以通过控制器方法的形参位置设置一个实体类类型的形参,此时若浏览器传输的请求参数的参数名和实体类中的属性名一致,那么请求参数就会为此属性赋值。

  • 类属性名必须与请求参数名一致
@PostMapping("/testPOJO")
public String testFormParams3(User user) {
    System.out.println("User: " + user);
    // User: User{id=null, username='lver', password='123', gender='female', age=456, email='784683810@qq.com', hobby=[123, 456]}
    return "test";
}
<form th:action="@{/testPOJO}" method="post">
    <label>
        name: <input name="username" value="lver" />
    </label><br />
    <label>
        password: <input name="password" value="" />
    </label><br />
    <label>
        gender:
        <input type="radio" name="gender" value="male" /><input type="radio" name="gender" value="female" /></label><br />
    <label>
        age: <input type="text" name="age" value="" />
    </label><br />
    <label>
        email: <input name="email" value="" />
    </label><br />
    <label>
        hobby:<br />
        <input type="checkbox" name="hobby" value="123" />123<br />
        <input type="checkbox" name="hobby" value="456" />456<br />
        <input type="checkbox" name="hobby" value="789" />789<br />
    </label><br />
    <input type="submit" value="使用 POJO实体类参数接收请求参数" />
</form>
package com.atjava.mvc.bean;

import java.util.Arrays;

/**
 * @author lv
 * @create 2022-02-15 23:14
 */
public class User {
    private Integer id;
    private String username;
    private String password;
    private String gender;
    private Integer age;
    private String email;
    private String[] hobby;

    public User() {
    }

    public User(Integer id, String username, String password, String gender, Integer age, String email, String[] hobby) {
        this.id = id;
        this.username = username;
        this.password = password;
        this.gender = gender;
        this.age = age;
        this.email = email;
        this.hobby = hobby;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String[] getHobby() {
        return hobby;
    }

    public void setHobby(String[] hobby) {
        this.hobby = hobby;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + ''' +
                ", password='" + password + ''' +
                ", gender='" + gender + ''' +
                ", age=" + age +
                ", email='" + email + ''' +
                ", hobby=" + Arrays.toString(hobby) +
                '}';
    }
}

解决获取请求参数的乱码问题

解决获取请求参数的乱码问题,可以使用 SpringMVC提供的编码过滤器 CharacterEncodingFilter,但是必须在 web.xml中进行注册。

SprigMVC中处理编码的过滤器一定要配置到其它过滤器之前,否则无效。

  • get请求方式的乱码是可以在 Tomcat的 server.xml配置文件中一次性解决的
    • <Connector URLEncoding="UTF-8" />
  • 其它请求方式的乱码解决,使用 SpringMVC提供的编码过滤器 CharacterEncodingFilter
  1. web.xml
<!--配置 SpringMVC的编码过滤器-->
<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>forceResponseEncoding</param-name>
        <param-value>true</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>CharacterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
@PostMapping("/testPOJO")
public String testFormParams3(User user) {
    System.out.println("User: " + user);
    // User: User{id=null, username='六lver', password='123456', gender='male', age=12, email='aweiweier192@163.com', hobby=[123, 456]}
    return "test";
}

域对象共享数据

  • request一次请求
  • Session浏览器开启到关闭的过程(只与浏览器开启关闭有关)
    1. 钝化:服务器关闭,浏览器没有关闭,Session中的数据序列化到磁盘上
    2. 活化:服务器关闭后又开启,浏览器一直没关闭,服务器会将钝化到磁盘中的 Session数据重新读取到 Session中
  • Application(ServletContext)服务器的开启到关闭的过程

使用 ServletAPI向 request域对象共享数据

request设域数据操作:

  • request.setAttribute("testRequestScope", "Hello, requestScope");设置共享数据
  • request.getAttribute("scopeName"); 获取共享数据
  • request.removeAttribute("scopeName"); 删除共享数据

页面获取域数据的方式:

  • 语法:th:xxx="${xxx}"
  1. request域中的数据,直接写键名
  2. Session域中的数据,使用 session.xxx的方式
  3. Application域中的数据,使用 application.xxx的方式
// 使用 servletAPI向 request域对象共享数据
@RequestMapping("/testRequestByServletAPI")
public String testRequestByServletAPI(HttpServletRequest request) {
    request.setAttribute("testRequestScope", "Hello, requestScope"); // 设置共享数据
    // request.getAttribute(); 获取共享数据
    // request.removeAttribute(); 删除共享数据
    return "test";
}
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h2>test</h2>
<!--
1.request域中的数据,直接写键名
2.Session域中的数据,使用 session.xxx的方式
3.Application域中的数据,使用 application.xxx的方式
-->
<p th:text="${testRequestScope}"></p>
</body>
</html>

使用 SpringMVC的 ModelAndView(重点)向 request域对象共享数据

  • 在控制器方法中创建 ModelAndView对象
  1. addObject(xxx),向域中设置共享数据
  2. setViewName(),设置视图,实现页面跳转
  • 控制器方法返回值必须是 ModelAndView
@RequestMapping("/testModelAndView")
public ModelAndView testModelAndView() {
    /**
     * ModelAndView又 Model和 View的功能
     * Model主要用于向 请求域中共享数据
     * View主要用于设置视图,实现页面跳转
     */
    ModelAndView mv = new ModelAndView();
    // 向域中设置共享数据
    mv.addObject("testModelAndView", "Hello, ModelAndView");
    // 设置视图,实现页面跳转
    mv.setViewName("test");
    return mv;
}
<a th:href="@{/testModelAndView}">使用 ModelAndView向 request域对象共享数据</a>

<p th:text="${testModelAndView}"></p>

使用 Model向 request域对象共享数据

  • 控制器参数是 Model类
    • addAttribute(),向 request域中添加共享数据
  • 控制器返回值是 String类型
@RequestMapping("/testModel")
public String testModel(Model model) {
    model.addAttribute("testModel", "Hello, testModel");
    return "test";
}
<a th:href="@{/testModel}">使用 Model向 request域对象共享数据</a><br />

<p th:text="${testModel}"></p>

使用 Map向 request域对象共享数据

  • 将 Map作为控制器的参数使用
  • 向 Map中存放的数据,就是向 request域对象共享的数据
@RequestMapping("/testMap")
public String testMap(Map<String, Object> map) {
    map.put("testMap", "Hello, testMap");
    return "test";
}
<a th:href="@{/testMap}">使用 Map向 request域对象共享数据</a><br />

<p th:text="${testMap}"></p>

使用 ModelMap向 request域对象共享数据

  • 与 Model类似
@RequestMapping("/testModelMap")
public String testModelMap(ModelMap mmp) {
    mmp.addAttribute("testModelMap", "Hello, testModelMap");
    return "test";
}
<a th:href="@{/testModelMap}">使用 ModelMap向 request域对象共享数据</a><br />

<p th:text="${testModelMap}"></p>

Model、ModelMap、Map的关系

Model、ModelMap、Map类型的参数其本质上都是 BindingAwareModelMap类型的。

public interface Model {}
public class ModelMap extends LinkedHashMap<String, Object> {}
public class ExtendedModelMap extends ModelMap implements Model {}
public class BindingAwareModelMap extends ExtendedModelMap {}

向 Session域中共享数据

  • 使用 ServletAPI中的 HttpSession作为控制器的参数,向 Session域中共享数据
  • html页面获取 session域中的数据 ${session.xxx}
@RequestMapping("/testSession")
public String testSession(HttpSession httpSession) {
    httpSession.setAttribute("testSession", "Hello, session");
    return "test";
}
<a th:href="@{/testSession}">使用 HttpSession向 session域中共享数据</a>

<p th:text="${session.testSession}"></p>

向 application域共享数据

  • 使用 HttpSession获取 ServletContext实例
  • html页面获取 application域中的数据 ${application.xxx}
@RequestMapping("/testApplication")
public String testApplication(HttpSession httpSession) {
    // ServletContext表示整个应用
    ServletContext servletContext = httpSession.getServletContext();
    servletContext.setAttribute("testApplication", "Hello, Application");
    return "test";
}
<a th:href="@{/testApplication}">使用 HttpSession向 application域中共享数据</a><br />

<p th:text="${application.testApplication}"></p>

ServletContext、HttpSession、HttpServletRequest

  1. ServletContext:范围最大,应用程序级别的,整个应用程序都能访问;
  2. HttpSession–次之,会话级别的,在当前的浏览器中都能訪问[不论是在同一浏览器开多少窗体,都能够访问],可是换个浏览器就不行了,就必须又一次创建session;
  3. HttpServletRequest–范围最小,请求级别,请求结束,变量的作用域也结束【也就是仅仅是一次访问,访问结束,这个也结束】。
    @RequestMapping("/testServlet")
    public ResponseBean testServlet(HttpServletRequest request, HttpServletResponse response) {
        // 1
        ServletContext sc = request.getServletContext();
        sc.setAttribute("sc_name", "sc_value");
        // 2
        HttpSession session = request.getSession();
        session.setAttribute("session_name", "session_value");
        // 3
        request.setAttribute("request_name", "request_value");

        String sc_value = (String) sc.getAttribute("sc_name");
        String session_value = (String) session.getAttribute("session_name");
        String request_value = (String) request.getAttribute("request_name");

        log.debug(sc_value + ":" + session_value + ":" + request_value);

        // request.getRequestDispatcher("MyServlet2").forward(request, response);

        return ResponseBean.buildSuccessResponse();
    }

SpringMVC的视图

SpringMVC中的视图是 View接口,视图的作用是渲染数据,将模型 Model中的数据展示给用户。

SpringMVC视图的种类很多,默认有转发视图(InternalResourceView)和重定向视图(RedirectView)。

当工程中引入 jstl的依赖,转发视图会自动转换为 JstlView。

若使用的视图技术为 Thymeleaf,在 SpringMVC的配置文件中配置了 Thymeleaf的视图解析器,由此视图解析器解析之后所得到的是 ThymeleafView。

ThymeleafView

当控制器方法中所设置的视图名称没有任何前缀时,此时的视图名称会被 SpringMVC配置文件中所配置的视图解析器解析,视图名称拼接视图前缀和视图后缀所得到的最终路径,会通过转发的方式实现跳转。

  • 当返回的视图名称没有任何前缀时,此时创建的就是 ThymeleafView视图
@RequestMapping("/testThymeleafView")
public String testThymeleafView() {
    return "test"; // ThymeleafView
}

InternalResourceView 转发视图

  • 转发发生在服务器内部,不能跨域只能访问服务器内部的资源 SpringMVC中默认的转发视图是 InternalRourceView。

SpringMVC中创建转发视图的情况:

当控制器方法中所设置的视图名称以 forward:为前缀时,创建 InternalResourceView视图,此时的视图名称不会被 SpringMVC配置文件中所配置的视图解析器解析,而是会将前缀 forward:去掉,剩余部分作为最终路径通过转发的方式实现跳转;如 "forward:/viewName"。

  • forward:/xxx,其中 xxx必须是一个控制器方法对应的请求地址
@RequestMapping("/testForward")
public String testForward() {
    return "forward:/testThymeleafView";
}
<a th:href="@{/testForward}">视图 InternalResourceView</a>

RedirectView 重定向视图

  • 重定向是浏览器发起两次请求,通过浏览器可以访问任何资源,故可以跨域

SpringMVC中默认的重定向视图是 RedirectView。

当控制器方法中设置的视图名称为 redirect:为前缀时,创建 RedirectView视图,此时的视图名称不会被 SpringMVC配置文件中所配置的视图解析器解析,而是会将前缀 redirect:去掉,剩余部分作为最终路径通过重定向的方式实现跳转;如 "redirect:/xxx"。

  • redirect:/xxx,redirect: 后有 /时 xxx是其它控制器方法的请求地址
  • redirect:http://xxx123,redirect:后没有 /时,跳转到其它服务器
@RequestMapping("/testRedirect")
public String testRedirect() {
    return "redirect:/testThymeleafView";
}
@RequestMapping("/testRedirectBaidu")
public String testRedirectBaidu() {
    return "redirect:http://www.baidu.com";
    // https://fanyi.youdao.com/
}
<a th:href="@{/testRedirect}">视图 RedirectView</a><br />
<a th:href="@{/testRedirectBaidu}">重定向 百度</a><br />

视图控制器 view-controller

当控制器方法中,仅仅用来实现页面跳转,即只需要设置视图名称时,可以将处理器方法使用 view-controller标签来表示。

  • <mvc:view-controller path="xxx" view-name="xxx" />
  1. path: 设置处理的请求地址
  2. view-name: 设置请求地址所对应的视图名称
  • <mvc:annotation-driven /> 开启 mvc的注解驱动
  • 注: 当 SpringMVC中设置任何一个 view-controller时,所有的控制器将全部失效, 此时需在 SpringMVC核心配置文件中设置开启 mvc注解驱动的标签 <mvc:annotation-driven />
<!--视图控制器:view-controller -->
<!--
    path: 设置处理的请求地址
    view-name: 设置请求地址所对应的视图名称

    注: 当 SpringMVC中设置任何一个 view-controller时,所有的控制器将全部失效,
    此时需在 SpringMVC核心配置文件中设置开启 mvc注解驱动的标签 <mvc:annotation-driven />
-->
<mvc:view-controller path="/" view-name="index"></mvc:view-controller>
<!--开启 mvc的注解驱动-->
<mvc:annotation-driven />