SpringMVC学习笔记---带你快速入门和复习

一、初识SpringMVC

1.1、什么是SpringMVC

1.1.1、什么是MVC

MVC是一种软件架构模式(是一种软件架构设计思想,不止Java开发中用到,其它语言也需要用到),它将应用分为三块:

  • M:Model(模型)(就是数据!)
  • V:View(视图)
  • C:Controller(控制器)

应用为什么要被分为三块,优点是什么?

  • 低耦合,扩展能力增强
  • 代码复用性增强
  • 代码可维护性增强
  • 高内聚,让程序员更加专注业务的开发

MVC将应用分为三块,每一块各司其职,都有自己专注的事情要做,他们属于分工协作,互相配合:

  • Model:负责业务处理及数据的收集。
  • View:负责数据的展示
  • Controller:负责调度。它是一个调度中心,它来决定什么时候调用Model来处理业务,什么时候调用View视图来展示数据。

MVC架构模式如下所示:
在这里插入图片描述
MVC架构模式的描述:前端浏览器发送请求给web服务器,web服务器中的Controller接收到用户的请求,Controller负责将前端提交的数据进行封装,然后Controller调用Model来处理业务,当Model处理完业务后会返回处理之后的数据给Controller,Controller再调用View来完成数据的展示,最终将结果响应给浏览器,浏览器进行渲染展示页面。

面试题:什么是三层模型,并说一说MVC架构模式与三层模型的区别?
三层模型(其实就是对model层的细分):
在这里插入图片描述
MVC架构模式:
在这里插入图片描述
MVC 和三层模型都采用了分层结构来设计应用程序,都是降低耦合度,提高扩展力,提高组件复用性。区别在于:他们的关注点不同,三层模型更加关注业务逻辑组件(model层)的划分。MVC架构模式关注的是整个应用程序的层次关系和分离思想。现代的开发方式大部分都是MVC架构模式结合三层模型一起用。

1.1.2、SpringMVC概述

SpringMVC是一个实现了MVC架构模式的Web框架,底层基于Servlet实现。
SpringMVC已经将MVC架构模式实现了,因此只要我们是基于SpringMVC框架写代码,编写的程序就是符合MVC架构模式的。(MVC的架子搭好了,我们只需要添添补补
Spring框架中有一个子项目叫做Spring Web,Spring Web子项目当中包含很多模块,例如:

  • Spring MVC
  • Spring WebFlux
  • Spring Web Services
  • Spring Web Flow
  • Spring WebSocket
  • Spring Web Services Client

可见 SpringMVC是Spring Web子项目当中的一个模块。因此也可以说SpringMVC是Spring框架的一部分。
所以学习SpringMVC框架之前要先学习Spring框架中的IoC和AOP等内容。
另外,使用SpringMVC框架的时候同样也可以使用IoC和AOP。

1.1.3、SpringMVC帮我们做了什么

SpringMVC框架帮我们做了什么,与纯粹的Servlet开发有什么区别?

  1. 入口控制:SpringMVC框架通过DispatcherServle(前端控制器)t作为入口控制器,负责接收请求和分发请求。而在Servlet开发中,需要自己编写Servlet程序,并在web.xml中进行配置,才能接受和处理请求。
  2. 在SpringMVC中,表单提交时可以自动将表单数据绑定到相应的JavaBean对象中,只需要在控制器方法的参数列表中声明该JavaBean对象即可,无需手动获取和赋值表单数据。而在纯粹的Servlet开发中,这些都是需要自己手动完成的。
  3. IoC容器:SpringMVC框架通过IoC容器管理对象,只需要在配置文件中进行相应的配置即可获取实例对象,而在Servlet开发中需要手动创建对象实例。
  4. 统一处理请求:SpringMVC框架提供了拦截器、异常处理器等统一处理请求的机制,并且可以灵活地配置这些处理器。而在Servlet开发中,需要自行编写过滤器、异常处理器等,增加了代码的复杂度和开发难度。
  5. 视图解析:SpringMVC框架提供了多种视图模板,如JSP、Freemarker、Velocity等,并且支持国际化、主题等特性。而在Servlet开发中需要手动处理视图层,增加了代码的复杂度。

总之,与Servlet开发相比,SpringMVC框架可以帮我们节省很多时间和精力,减少代码的复杂度,更加专注于业务开发。同时,也提供了更多的功能和扩展性,可以更好地满足企业级应用的开发需求。

1.1.4、SpringMVC框架的特点

  1. 轻量级:相对于其他Web框架,Spring MVC框架比较小巧轻便。(只有几个几百KB左右的Jar包文件)
  2. 模块化:请求处理过程被分成多个模块,以模块化的方式进行处理。
    1. 控制器模块:Controller
    2. 业务逻辑模块:Model
    3. 视图模块:View
  3. 依赖注入:Spring MVC框架利用Spring框架的依赖注入功能实现对象的管理,实现松散耦合。
  4. 易于扩展:提供了很多口子,允许开发者根据需要插入自己的代码,以扩展实现应用程序的特殊需求。
    1. Spring MVC框架允许开发人员通过自定义模块和组件来扩展和增强框架的功能。
    2. Spring MVC框架与其他Spring框架及第三方框架集成得非常紧密,这使得开发人员可以非常方便地集成其他框架,以获得更好的功能。
  5. 易于测试:支持单元测试框架,提高代码质量和可维护性。 (对SpringMVC中的Controller测试时,不需要依靠Web服务器。)
  6. 自动化配置:提供自动化配置,减少配置细节。
    1. Spring MVC框架基于约定大于配置的原则,对常用的配置约定进行自动化配置。
  7. 灵活性:Spring MVC框架支持多种视图技术,如JSP、FreeMarker、Thymeleaf、FreeMarker等,针对不同的视图配置不同的视图解析器即可。

1.2、第一个SpringMVC程序

1.2.1、创建一个maven模块

将pom.xml文件中的打包方式修改为war,并添加依赖

<?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.atguigu</groupId>
    <artifactId>springmvc-001</artifactId>
    <version>1.0-SNAPSHOT</version>
    <!--打包方式-->
    <packaging>war</packaging>

    <!--依赖-->
    <dependencies>
        <!-- Spring MVC依赖 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>6.1.4</version>
        </dependency>
        <!--日志框架Logback依赖-->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.5.3</version>
        </dependency>
        <!--Servlet依赖-->
        <dependency>
            <groupId>jakarta.servlet</groupId>
            <artifactId>jakarta.servlet-api</artifactId>
            <version>6.0.0</version>
            <scope>provided</scope>
        </dependency>
        <!--Spring6Thymeleaf整合依赖-->
        <dependency>
            <groupId>org.thymeleaf</groupId>
            <artifactId>thymeleaf-spring6</artifactId>
            <version>3.1.2.RELEASE</version>
        </dependency>
    </dependencies>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

</project>

1.2.2、添加web支持

在main目录下创建一个webapp目录
在这里插入图片描述
添加web.xml配置文件
在这里插入图片描述注意 web.xml 文件的位置:E:\Spring MVC\code\springmvc\springmvc-001*src\main\webapp\WEB-INF\web.xml*
注意版本选择:6.0(根据Tomcat版本定)
在这里插入图片描述
添加web支持后的目录结构:
在这里插入图片描述

1.2.3、配置web.xml文件

Spring MVC是一个web框架,在javaweb中谁来负责接收请求,处理请求,以及响应呢?当然是Servlet。在SpringMVC框架中已经为我们写好了一个Servlet,它的名字叫做:DispatcherServlet,我们称其为前端控制器。既然是Servlet,那么它就需要在web.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_6_0.xsd"
         version="6.0">

    <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <!--
            / 表示:除了xxx.jsp结尾的请求路径之外所有的请求路径
            /* 表示:所有的请求路径
            如果是xxx.jsp 请求路径,那么就走自己jsp对应的Servlet,不走SpringMVC的前端控制器
        -->
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

DispatcherServlet是SpringMVC框架为我们提供的最核心的类,它是整个SpringMVC框架的前端控制器,负责接收HTTP请求、将请求路由到处理程序、处理响应信息,最终将响应返回给客户端。DispatcherServlet是Web应用程序的主要入口点之一,它的职责包括:

  1. 接收客户端的HTTP请求:DispatcherServlet监听来自Web浏览器的HTTP请求,然后根据请求的URL将请求数据解析为Request对象。
  2. 处理请求的URL:DispatcherServlet将请求的URL(Uniform Resource Locator)与处理程序进行匹配,确定要调用哪个控制器(Controller)来处理此请求。
  3. 调用相应的控制器:DispatcherServlet将请求发送给找到的控制器处理,控制器将执行业务逻辑,然后返回一个模型对象(Model)。
  4. 渲染视图:DispatcherServlet将调用视图引擎,将模型对象呈现为用户可以查看的HTML页面。
  5. 返回响应给客户端:DispatcherServlet将为用户生成的响应发送回浏览器,响应可以包括表单、JSON、XML、HTML以及其它类型的数据。

1.2.4、编写控制器FirstController

DispatcherServlet接收到请求之后,会根据请求路径分发到对应的Controller,Controller来负责处理请求的核心业务。在SpringMVC框架中Controller是一个普通的Java类(一个普通的POJO类,不需要继承任何类或实现任何接口),需要注意的是:POJO类要纳入IoC容器来管理,POJO类的生命周期由Spring来管理,因此要使用注解标注:

package com.powernode.springmvc.controller;
import org.springframework.stereotype.Controller;
@Controller
public class FirstController {
}

1.2.5、配置springmvc-servlet.xml文件

SpringMVC框架有它自己的配置文件,该配置文件的名字默认为:-servlet.xml(这里我们是springmvc-servlet.xml),默认存放的位置是WEB-INF 目录下:
在这里插入图片描述

<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"
       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">

    <!--Spring MVC框架的配置文件-->

    <!--组件扫描-->
    <context:component-scan base-package="com.powernode.springmvc.controller"></context:component-scan>

    <!--配置视图解析器-->
    <bean id="thymeleafViewResolver" class="org.thymeleaf.spring6.view.ThymeleafViewResolver">
        <!--作用于视图渲染的过程中,可以设置视图渲染后输出时采用的编码字符集-->
        <property name="characterEncoding" value="UTF-8"/>
        <!--如果配置多个视图解析器,它来决定优先使用哪个视图解析器,它的值越小优先级越高-->
        <property name="order" value="1"/>
        <!--ThymeleafViewResolver 渲染模板时,会使用该模板引擎来解析、编译和渲染模板-->
        <property name="templateEngine">
            <bean class="org.thymeleaf.spring6.SpringTemplateEngine">
                <!--用于指定 Thymeleaf 模板引擎使用的模板解析器。模板解析器负责根据模板位置、模板资源名称、文件编码等信息,加载模板并对其进行解析-->
                <property name="templateResolver">
                    <bean class="org.thymeleaf.spring6.templateresolver.SpringResourceTemplateResolver">
                        <!--设置模板文件的位置(前缀)-->
                        <property name="prefix" value="/WEB-INF/templates/"/>
                        <!--设置模板文件后缀(后缀),Thymeleaf文件扩展名不一定是html,也可以是其他,例如txt,大部分都是html-->
                        <property name="suffix" value=".html"/>
                        <!--设置模板类型,例如:HTML,TEXT,JAVASCRIPT,CSS-->
                        <property name="templateMode" value="HTML"/>
                        <!--用于模板文件在读取和解析过程中采用的编码字符集-->
                        <property name="characterEncoding" value="UTF-8"/>
                    </bean>
                </property>
            </bean>
        </property>
    </bean>
</beans>

以上配置主要两项:

  • 第一项:组件扫描。spring扫描这个包中的类,将这个包中的类实例化并纳入IoC容器的管理。
  • 第二项:视图解析器。视图解析器(View Resolver)的作用主要是将Controller方法返回的逻辑视图名称解析成实际的视图对象。视图解析器将解析出的视图对象返回给DispatcherServlet,并最终由DispatcherServlet将该视图对象转化为响应结果,呈现给用户。

注意:如果采用了其它视图,请配置对应的视图解析器,例如:

  • JSP的视图解析器:InternalResourceViewResolver
  • FreeMarker视图解析器:FreeMarkerViewResolver
  • Velocity视图解析器:VelocityViewResolver

1.2.6、提供视图

在WEB-INF目录下新建templates目录,在templates目录中新建html文件(如上图所示),例如:first.html,并提供以下代码:

<!doctype html>
<!--指定 th 命名空间,让 Thymeleaf 标准表达式可以被解析和执行-->
<!--th不是固定的,可以指定其它的命名空间,只不过大部分情况下用th-->
<!--表示程序中出现的 th 开头的后面代码都是 Thymeleaf语法,需要被 Thymeleaf识别-->
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
        <title>First Spring MVC</title>
    </head>
    <body>
        <h1>First Spring MVC!</h1>
    </body>
</html>

对于每一个Thymeleaf文件来说 xmlns:th="http://www.thymeleaf.org" 是必须要写的,为了方便后续开发,可以将其添加到html模板文件中:
在这里插入图片描述

1.2.7、控制器FirstController处理请求返回逻辑视图名称

package com.powernode.springmvc.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class FirstController {

    //请求映射
    //这个方法是一个实例方法
    //这个方法目前返回一个String字符串
    //返回值代表的是一个逻辑视图名称
    //test为请求路径,hehe方法名称可随意
    @RequestMapping(value = "/test")
    public String hehe(){
        //返回一个逻辑视图名称(这个必须与请求的视图名称一致,后续需要进行拼接使用)
        return "first";
    }
}

1.2.8、测试

第一步:配置Tomcat服务器
在这里插入图片描述
在这里插入图片描述
第二步:部署web模块到Tomcat服务器
在这里插入图片描述
第三步:启动Tomcat服务器。如果在控制台输出的信息有中文乱码,请修改tomcat服务器配置文件:apache-tomcat-10.1.19\conf\logging.properties(全部改成GBK)
在这里插入图片描述
第四步:打开浏览器,在浏览器地址栏上输入地址:http://localhost:8080/springmvc/test
在这里插入图片描述

1.2.9、执行流程总结

  1. 浏览器发送请求:http://localhost:8080/springmvc/test
  2. SpringMVC的前端控制器DispatcherServlet接收到请求
  3. DispatcherServlet根据请求路径 /test 映射到 FirstController#hehe(),调用该方法
  4. FirstController#hehe() 处理请求
  5. FirstController#hehe() 返回逻辑视图名称 first 给视图解析器
  6. 视图解析器找到 /WEB-INF/templates/first.html 文件,并进行解析,生成视图解析对象返回给前端控制器DispatcherServlet
  7. 前端控制器DispatcherServlet响应结果到浏览器。

1.2.10、一个Controller可以编写多个方法

一个Controller可以提供多个方法,每个方法通常是处理对应的请求,例如:

@Controller
public class FirstController {

    @RequestMapping(value = "/")
    public String index(){
        //返回一个逻辑视图名称
        return "index";
    }

    //请求映射
    //这个方法是一个实例方法
    //这个方法目前返回一个String字符串
    //返回值代表的是一个逻辑视图名称
    @RequestMapping(value = "/test")
    public String hehe(){
        //返回一个逻辑视图名称
        return "first";
    }

    @RequestMapping(value = "/heihei")
    public String heihei(){
        //返回一个逻辑视图名称
        return "other";
    }
}

添加相应的html文件在templates文件夹中,如index.html:

<!doctype html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <title>First Spring MVC</title>
    </head>
    <body>
        <h1>Index!</h1>
        <!--自动获取项目名-->
        <a th:href="@{/test}">First</a>
        <!--自动获取项目名 通过th-->
        <a th:href="@{/heihei}">Other</a>
    </body>
</html>

运行结果:
可点击进行页面跳转!
在这里插入图片描述

1.3、第二个SpringMVC程序

根据1.2进行文件的创建以及配置

1.3.1、配置web.xml文件

重点:SpringMVC配置文件的名字和路径是可以手动设置的,如下:

<?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">
    <!--配置前端控制器-->
    <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!--手动设置springmvc配置文件的路径及名字-->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:springmvc.xml</param-value>
        </init-param>
        <!--为了提高用户的第一次访问效率,建议在web服务器启动时初始化前端控制器-->
        <load-on-startup>0</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

通过来设置SpringMVC配置文件的路径和名字。在DispatcherServlet的init方法执行时设置的。
0建议加上,这样可以提高用户第一次访问的效率。表示在web服务器启动时初始化DispatcherServlet。对比第一次要快很多,是一个性能优化的方法
因此,可以在根目录的resources目录下创建springmvc的配置文件。

1.3.2、编写IndexController

package com.powernode.springmvc.controller;

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

@Controller
public class IndexController {
    @RequestMapping(value="/index")
    public String toIndex(){
        System.out.println("正在处理请求....");
        // 返回逻辑视图名称(决定跳转到哪个页面)
        return "index";
    }
}

在resources目录下配置springmvc.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"
       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">
    <!--组件扫描-->
    <context:component-scan base-package="com.powernode.springmvc.controller"/>
    <!--视图解析器-->
    <bean id="thymeleafViewResolver" class="org.thymeleaf.spring6.view.ThymeleafViewResolver">
        <!--作用于视图渲染的过程中,可以设置视图渲染后输出时采用的编码字符集-->
        <property name="characterEncoding" value="UTF-8"/>
        <!--如果配置多个视图解析器,它来决定优先使用哪个视图解析器,它的值越小优先级越高-->
        <property name="order" value="1"/>
        <!--ThymeleafViewResolver 渲染模板时,会使用该模板引擎来解析、编译和渲染模板-->
        <property name="templateEngine">
            <bean class="org.thymeleaf.spring6.SpringTemplateEngine">
                <!--用于指定 Thymeleaf 模板引擎使用的模板解析器。模板解析器负责根据模板位置、模板资源名称、文件编码等信息,加载模板并对其进行解析-->
                <property name="templateResolver">
                    <bean class="org.thymeleaf.spring6.templateresolver.SpringResourceTemplateResolver">
                        <!--设置模板文件的位置(前缀)-->
                        <property name="prefix" value="/WEB-INF/templates/"/>
                        <!--设置模板文件后缀(后缀),Thymeleaf文件扩展名不一定是html,也可以是其他,例如txt,大部分都是html-->
                        <property name="suffix" value=".html"/>
                        <!--设置模板类型,例如:HTML,TEXT,JAVASCRIPT,CSS-->
                        <property name="templateMode" value="HTML"/>
                        <!--用于模板文件在读取和解析过程中采用的编码字符集-->
                        <property name="characterEncoding" value="UTF-8"/>
                    </bean>
                </property>
            </bean>
        </property>
    </bean>
</beans>

1.3.3、提供视图

在这里插入图片描述
注意!!!
尽管后缀是html,但它并不是html文件,它仍然是thymeleaf模版字符串,只有通过thymleaf模版引擎进行解析才能转成浏览器看得懂的html代码。

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>index page</title>
</head>
<body>
<h1>index page</h1>
</body>
</html>

1.3.4、测试

部署到web服务器,启动web服务器,打开浏览器,在地址栏上输入:http://localhost:8080/springmvc/index
在这里插入图片描述

二、RequestMapping注解

2.1、RequestMapping的作用

@RequestMapping 注解是 Spring MVC 框架中的一个控制器映射注解,用于将请求映射到相应的处理方法上。具体来说,它可以将指定 URL 的请求绑定到一个特定的方法或类上,从而实现对请求的处理和响应。

2.2、RequestMapping的出现位置

在这里插入图片描述
通过RequestMapping的源码可以看到RequestMapping注解只能出现在类上或者方法上。

2.3、类上与方法上结合使用

在同一个web应用中,不可以有两个完全一样的RequestMapping。测试一下:假设两个RequestMapping,其中一个是展示用户详细信息,另一个是展示商品详细信息。提供两个Controller,一个是UserController,另一个是ProductController。如下

@Controller
public class UserController {
    @RequestMapping("/detail")
    public String toDetail(){
        return "detail";
    }
}
@Controller
public class ProductController {
    @RequestMapping("/detail")
    public String toDetail(){
        return "detail";
    }
}

以上两个Controller的RequestMapping相同,都是"/detail",启动服务器看出现问题:异常发生了,异常信息如下

org.springframework.beans.factory.BeanCreationException: 
Error creating bean with name 'org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping': 
Ambiguous mapping. Cannot map 'userController' method 
com.powernode.springmvc.controller.UserController#toDetail()
to { [/detail]}: There is already 'productController' bean method
com.powernode.springmvc.controller.ProductController#toDetail() mapped.

以上异常信息大致的意思是:不明确的映射。无法映射UserController中的toDetail()方法,因为已经在ProductController中映射过了!!!!
通过测试得知,在同一个webapp中,RequestMapping必须具有唯一性。怎么解决以上问题?两种解决方案:

  • 第一种方案:将方法上RequestMapping的映射路径修改的不一样。
  • 第二种方案:在类上添加RequestMapping的映射路径,以类上的RequestMapping作为命名空间,来加以区分两个不同的映射。

第一种方案
都改成这种形式

@RequestMapping("/user/detail")
public String toDetail(){
    return "/user/detail";
}

第二种方案
在类上添加前缀,那么这个类的所有RequestMapping中的路径都会加上该前缀。如下等价于:/user/detail

@Controller
@RequestMapping("/user")
public class UserController {
    @RequestMapping("/detail")
    public String toDetail(){
        return "/user/detail";
    }
}

2.4、RequestMapping注解的value属性

2.4.1、value属性的使用

value属性是该注解最核心的属性,value属性填写的是请求路径,也就是说通过该请求路径与对应的控制器的方法绑定在一起。另外通过源码可以看到value属性是一个字符串数组:
value的别名是path,path的别名是value。
在这里插入图片描述
既然是数组,就表示可以提供多个路径,也就是说,在SpringMVC中,多个不同的请求路径可以映射同一个控制器的同一个方法:
编写新的控制器:
在IndexController类中加入一个测试方法

@RequestMapping(value = {"/testValue1", "/testValue2"})
    public String testValue(){
        return "testValue";
    }

提供testValue视图,在templates文件夹中添加:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>测试RequestMapping注解的value属性</title>
</head>
<body>
<h1>测试成功</h1>
</body>
</html>

在index.html中增加测试链接:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>首页</title>
</head>
<body>
<h1>index page</h1>
<a th:href="@{/user/detail}">用户详情</a><br>
<a th:href="@{/product/detail}">商品详情</a><br>
<a th:href="@{/testValue1}">测试1</a><br>
<a th:href="@{/testValue2}">测试2</a><br>
</body>
</html>

运行结果:
均成功跳转!
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.4.2、Ant风格的value

value是可以用来匹配路径的,路径支持模糊匹配,我们把这种模糊匹配称之为Ant风格。关于路径中的通配符包括:

  • ?,代表任意一个字符(不能为空,除/ ?等特殊字符)
  • *,代表0到N个任意字符(除/ ?等特殊字符)
  • **,代表0到N个任意字符,并且路径中可以出现路径分隔符 /

注意:** 通配符在使用时,左右不能出现字符,只能是 /
创建一个测试方法:

@RequestMapping("/x?z/testValueAnt")
    public String testValueAnt(){
        return "test";
    }

创建test.html视图用于测试:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Test</title>
</head>
<body>
<h1>测试的通用页面!!!</h1>
</body>
</html>

测试结果:
在这里插入图片描述

    @RequestMapping("/x*z/testValueAnt")
    public String testValueAnt(){
        return "test";
    }

测试结果:
在这里插入图片描述
Spring5及以下可以这样用

 @RequestMapping("/**/testValueAnt")
    public String testValueAnt(){
        return "test";
    }

Spring只能用,在路径末尾:

 @RequestMapping("/testValueAnt/**")
    public String testValueAnt(){
        return "test";
    }

Spring6运行结果:
在这里插入图片描述

2.4.3、value中的占位符(重点)

到目前为止,我们的请求路径是这样的格式:uri?name1=value1&name2=value2&name3=value3
其实除了这种方式,还有另外一种格式的请求路径,格式为:uri/value1/value2/value3,我们将这样的请求路径叫做 RESTful 风格的请求路径。
RESTful风格的请求路径在现代的开发中使用较多。
测试方法:

//这里就映射了一个RESTFul风格的URL
    @RequestMapping(value = "/login/{username}/{password}")
    public String testRESTFulURL(
            @PathVariable("username")//绑定
            String username,
            @PathVariable("password")
            String password){
        System.out.println("用户名:" + username + ",密码:" + password);
        return "test";
    }

运行结果:
在这里插入图片描述
在这里插入图片描述

2.5、RequestMapping注解的method属性

2.5.1、method属性的作用

在Servlet当中,如果后端要求前端必须发送一个post请求,后端可以通过重写doPost方法来实现。后端要求前端必须发送一个get请求,后端可以通过重写doGet方法来实现。当重写的方法是doPost时,前端就必须发送post请求,当重写doGet方法时,前端就必须发送get请求。如果前端发送请求的方式和后端的处理方式不一致时,会出现405错误。

HTTP状态码405,这种机制的作用是:限制客户端的请求方式,以保证服务器中数据的安全。

假设后端程序要处理的请求是一个登录请求,为了保证登录时的用户名和密码不被显示到浏览器的地址栏上,后端程序有义务要求前端必须发送一个post请求,如果前端发送get请求,则应该拒绝。

那么在SpringMVC框架中应该如何实现这种机制呢?可以使用RequestMapping注解的method属性来实现。

通过RequestMapping源码可以看到,method属性也是一个数组(允许有多种请求方式):
在这里插入图片描述
数组中的每个元素是 RequestMethod,而RequestMethod是一个枚举类型的数据:
在这里插入图片描述
因此如果要求前端发送POST请求,该注解应该这样用:

//当前端发送的请求路径是 /user/login ,并且发送的请求方式是以POST方式请求的,则可以正常映射
    //当前端发送的请求路径不是 /user/login,请求方式是POST,不会映射到这个方法上。
    //当前端发送的请求路径是 /user/login,请求方式不是POST,不会映射到这个方法上。
    @RequestMapping(value = "/user/login", method = RequestMethod.POST)
    public String userLogin(){
        return "test";
    }

在index.html增加表单进行测试:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>首页</title>
</head>
<body>
<h1>index page</h1>
<a th:href="@{/user/detail}">用户详情</a><br>
<a th:href="@{/product/detail}">商品详情</a><br>
<a th:href="@{/testValue1}">测试1</a><br>
<a th:href="@{/testValue2}">测试2</a><br>

<!--发送POST请求-->
<form th:action="@{/user/login}" method="post">
    用户名:<input type="text" name="username"><br>
    密码:<input type="password" name="password"><br>
    <input type="submit" value="登录">
</form>
</body>
</html>

测试,进入首页,并输入信息:
在这里插入图片描述
提交成功:
在这里插入图片描述

2.5.2、衍生Mapping

@PostMapping 注解代替的是:@RequestMapping(value=“”, method=RequestMethod.POST)
@GetMapping 注解代替的是:@RequestMapping(value=“”, method=RequestMethod.GET)
PutMapping:要求前端必须发送put请求
DeleteMapping:要求前端必须发送delete请求
PatchMapping:要求前端必须发送patch请求

 @PostMapping("/user/login")
    public String userLogin(){
        return "test";
    }

运行结果与上方一致。

2.5.3、web的请求方式

前端向服务器发送请求的方式包括哪些?共9种,前5种常用,后面作为了解:

  • GET:获取资源,只允许读取数据,不影响数据的状态和功能。使用 URL 中传递参数或者在 HTTP 请求的头部使用参数,服务器返回请求的资源。
  • POST:向服务器提交资源,可能还会改变数据的状态和功能。通过表单等方式提交请求体,服务器接收请求体后,进行数据处理。
  • PUT:更新资源,用于更新指定的资源上所有可编辑内容。通过请求体发送需要被更新的全部内容,服务器接收数据后,将被更新的资源进行替换或修改。
  • DELETE:删除资源,用于删除指定的资源。将要被删除的资源标识符放在 URL 中或请求体中。
  • HEAD:请求服务器返回资源的头部,与 GET 命令类似,但是所有返回的信息都是头部信息,不能包含数据体。主要用于资源检测和缓存控制。
  • PATCH:部分更改请求。当被请求的资源是可被更改的资源时,请求服务器对该资源进行部分更新,即每次更新一部分。
  • OPTIONS:请求获得服务器支持的请求方法类型,以及支持的请求头标志。“OPTIONS *”则返回支持全部方法类型的服务器标志。
  • TRACE:服务器响应输出客户端的 HTTP 请求,主要用于调试和测试。
  • CONNECT:建立网络连接,通常用于加密 SSL/TLS 连接。

注意:

  1. 使用超链接以及原生的form表单只能提交get和post请求,put、delete、head请求可以使用发送ajax请求的方式来实现。
  2. 使用超链接发送的是get请求
  3. 使用form表单,如果没有设置method,发送get请求
  4. 使用form表单,设置method=“get”,发送get请求
  5. 使用form表单,设置method=“post”,发送post请求
  6. 使用form表单,设置method=“put/delete/head”,发送get请求。(针对这种情况,可以测试一下)

2.5.4、GET和POST的区别

HTTP请求协议之GET请求:
在这里插入图片描述
HTTP请求协议之POST请求:
在这里插入图片描述
区别是什么?

  1. get请求发送数据的时候,数据会挂在URI的后面,并且在URI后面添加一个“?”,"?"后面是数据。这样会导致发送的数据回显在浏览器的地址栏上。

http://localhost:8080/springmvc/login?username=zhangsan&userpwd=1111

  1. post请求发送数据的时候,在请求体当中发送。不会回显到浏览器的地址栏上。也就是说post发送的数据,在浏览器地址栏上看不到。
  2. get请求只能发送普通的字符串。并且发送的字符串长度有限制,不同的浏览器限制不同。这个没有明确的规范。get请求无法发送大数据量。
  3. post请求可以发送任何类型的数据,包括普通字符串,流媒体等信息:视频、声音、图片。post请求可以发送大数据量,理论上没有长度限制。
  4. get请求在W3C中是这样说的:get请求比较适合从服务器端获取数据。
  5. post请求在W3C中是这样说的:post请求比较适合向服务器端传送数据。
  6. get请求是安全的。因为在正确使用get请求的前提下,get请求只是为了从服务器上获取数据,不会对服务器数据进行修改。
  7. post请求是危险的。因为post请求是修改服务器端的资源。
  8. get请求支持缓存。 也就是说当第二次发送get请求时,会走浏览器上次的缓存结果,不再真正的请求服务器。(有时需要避免,怎么避免:在get请求路径后添加时间戳)
  9. post请求不支持缓存。每一次发送post请求都会真正的走服务器。

怎么选择
11. 如果你是想从服务器上获取资源,建议使用GET请求,如果你这个请求是为了向服务器提交数据,建议使用POST请求。
12. 大部分的form表单提交,都是post方式,因为form表单中要填写大量的数据,这些数据是收集用户的信息,一般是需要传给服务器,服务器将这些数据保存/修改等。
13. 如果表单中有敏感信息,建议使用post请求,因为get请求会回显敏感信息到浏览器地址栏上。(例如:密码信息)
14. 做文件上传,一定是post请求。要传的数据不是普通文本。
15. 其他情况大部分都是使用get请求。

2.6、RequestMapping注解的params属性

2.6.1、params属性的理解

params属性用来设置通过请求参数来映射请求。
对于RequestMapping注解来说:

  • value属性是一个数组,只要满足数组中的任意一个路径,就能映射成功
  • method属性也是一个数组,只要满足数组中任意一个请求方式,就能映射成功。
  • params属性也是一个数组,不过要求请求参数必须和params数组中要求的所有参数完全一致后,才能映射成功。
    在这里插入图片描述

2.6.2、params属性的4种用法

@RequestMapping(value=“/login”, params={“username”, “password”}) 表示:请求参数中必须包含 username 和 password,才能与当前标注的方法进行映射。
@RequestMapping(value=“/login”, params={“!username”, “password”}) 表示:请求参数中不能包含username参数,但必须包含password参数,才能与当前标注的方法进行映射。
@RequestMapping(value=“/login”, params={“username=admin”, “password”}) 表示:请求参数中必须包含username参数,并且参数的值必须是admin,另外也必须包含password参数,才能与当前标注的方法进行映射。
@RequestMapping(value=“/login”, params={“username!=admin”, “password”}) 表示:请求参数中必须包含username参数,但参数的值不能是admin,另外也必须包含password参数,才能与当前标注的方法进行映射。

注意:如果前端提交的参数,和后端要求的请求参数不一致,则出现400错误!!!
HTTP状态码400的原因:请求参数格式不正确而导致的。

2.6.3、测试params属性

在IndexController类中添加方法:

@RequestMapping(value = "/testParams", params = {"username", "password"})
    public String testParams(){
        return "test";
    }

在index.html增添测试:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>首页</title>
</head>
<body>
<h1>index page</h1>
<a th:href="@{/user/detail}">用户详情</a><br>
<a th:href="@{/product/detail}">商品详情</a><br>
<a th:href="@{/testValue1}">测试1</a><br>
<a th:href="@{/testValue2}">测试2</a><br>

<!--发送POST请求-->
<form th:action="@{/user/login}" method="post">
    用户名:<input type="text" name="username"><br>
    密码:<input type="password" name="password"><br>
    <input type="submit" value="登录">
</form>

<!--测试RequestMapping注解的params属性-->
<!--发送请求 /testParams,并且携带参数 username password-->
<a th:href="@{/testParams?username=admin&password=1234}">测试RequestMapping注解的params属性</a>
</body>
</html>

结果:
html请求的格式必须与params要求的一致才能成功!!!
在这里插入图片描述
在这里插入图片描述

2.7、RequestMapping注解的headers属性

2.7.1、认识headers属性

headers和params原理相同,用法也相同。
当前端提交的请求头信息和后端要求的请求头信息一致时,才能映射成功。
请求头信息怎么查看?在chrome浏览器中,F12打开控制台,找到Network,可以查看具体的请求协议和响应协议。在请求协议中可以看到请求头信息,例如:
在这里插入图片描述

2.7.2、headers属性的4种用法

@RequestMapping(value=“/login”, headers={“Referer”, “Host”}) 表示:请求头信息中必须包含Referer和Host,才能与当前标注的方法进行映射。
@RequestMapping(value=“/login”, headers={“Referer”, “!Host”}) 表示:请求头信息中必须包含Referer,但不包含Host,才能与当前标注的方法进行映射。
@RequestMapping(value=“/login”, headers={“Referer=http://localhost:8080/springmvc/”, “Host”}) 表示:请求头信息中必须包含Referer和Host,并且Referer的值必须是http://localhost:8080/springmvc/,才能与当前标注的方法进行映射。
@RequestMapping(value=“/login”, headers={“Referer!=http://localhost:8080/springmvc/”, “Host”}) 表示:请求头信息中必须包含Referer和Host,并且Referer的值不是http://localhost:8080/springmvc/,才能与当前标注的方法进行映射。

注意:如果前端提交的请求头信息,和后端要求的请求头信息不一致,则出现404错误!!!

三、获取请求数据(必须要精通)

假设有这样一个请求:http://localhost:8080/springmvc/register?name=zhangsan&password=123&email=zhangsan@powernode.com
在SpringMVC中应该如何获取请求提交的数据呢?
在SpringMVC中又应该如何获取请求头信息呢?
在SpringMVC中又应该如何获取客户端提交的Cookie数据呢?

3.1、准备

创建一个新的模块并添加依赖:

<?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.atguigu</groupId>
    <artifactId>springmvc-004</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <dependencies>
        <!--springmvc依赖-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>6.1.4</version>
        </dependency>
        <!--logback依赖-->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.5.3</version>
        </dependency>
        <!--servlet依赖-->
        <dependency>
            <groupId>jakarta.servlet</groupId>
            <artifactId>jakarta.servlet-api</artifactId>
            <version>6.0.0</version>
            <scope>provided</scope>
        </dependency>
        <!--thymeleaf和spring6整合的依赖-->
        <dependency>
            <groupId>org.thymeleaf</groupId>
            <artifactId>thymeleaf-spring6</artifactId>
            <version>3.1.2.RELEASE</version>
        </dependency>
    </dependencies>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
</project>

3.1.1、添加web支持

在这里插入图片描述

3.1.2、编写web.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_6_0.xsd"
         version="6.0">

    <!--前端控制器-->
    <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!--通过初始化参数来指定springmvc配置文件的路径和名字。-->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:springmvc.xml</param-value>
        </init-param>
        <!--在服务器启动的时候初始化DispatcherServlet,提高第一次访问的效率-->
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

3.1.3、创建UserController

@Controller
public class UserController {

    @RequestMapping("/")
    public String toRegister(){
        //返回一个逻辑视图
        return "register";
    }
}

3.1.4、编写springmvc.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"
       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">

    <!--组件扫描-->
    <context:component-scan base-package="com.powernode.springmvc.controller"/>

    <!--视图解析器-->
    <bean id="thymeleafViewResolver" class="org.thymeleaf.spring6.view.ThymeleafViewResolver">
        <!--作用于视图渲染的过程中,可以设置视图渲染后输出时采用的编码字符集-->
        <property name="characterEncoding" value="UTF-8"/>
        <!--如果配置多个视图解析器,它来决定优先使用哪个视图解析器,它的值越小优先级越高-->
        <property name="order" value="1"/>
        <!--ThymeleafViewResolver 渲染模板时,会使用该模板引擎来解析、编译和渲染模板-->
        <property name="templateEngine">
            <bean class="org.thymeleaf.spring6.SpringTemplateEngine">
                <!--用于指定 Thymeleaf 模板引擎使用的模板解析器。模板解析器负责根据模板位置、模板资源名称、文件编码等信息,加载模板并对其进行解析-->
                <property name="templateResolver">
                    <bean class="org.thymeleaf.spring6.templateresolver.SpringResourceTemplateResolver">
                        <!--设置模板文件的位置(前缀)-->
                        <property name="prefix" value="/WEB-INF/templates/"/>
                        <!--设置模板文件后缀(后缀),Thymeleaf文件扩展名不一定是html,也可以是其他,例如txt,大部分都是html-->
                        <property name="suffix" value=".html"/>
                        <!--设置模板类型,例如:HTML,TEXT,JAVASCRIPT,CSS-->
                        <property name="templateMode" value="HTML"/>
                        <!--用于模板文件在读取和解析过程中采用的编码字符集-->
                        <property name="characterEncoding" value="UTF-8"/>
                    </bean>
                </property>
            </bean>
        </property>
    </bean>
</beans>

编写register.html文件(在webapp下创建templates文件,在该文件夹下创建):

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
  <meta charset="UTF-8">
  <title>用户注册</title>
</head>
<body>
<!--注册页面-->
<form action="" method="post">
  用户名:<input type="text" name="username"><br>
  密码:<input type="password" name="password"><br>
  性别:
  男 <input type="radio" name="sex" value="1"><input type="radio" name="sex" value="0">
  <br>
  爱好:
  抽烟 <input type="checkbox" name="hobby" value="smoke">
  喝酒 <input type="checkbox" name="hobby" value="drink">
  烫头 <input type="checkbox" name="hobby" value="tt">
  <br>
  简介:<textarea rows="10" cols="60" name="intro"></textarea><br>
  <input type="submit" value="注册">
</form>
<hr>
</body>
</html>

3.1.5、部署并测试

在这里插入图片描述
在这里插入图片描述
运行,输入数据并提交,可以看到数据提交成功:
在这里插入图片描述

3.2、使用原生的Servlet API进行获取

原生的Servlet API指的是:HttpServletRequest,在SpringMVC当中,一个Controller类中的方法参数上如果有HttpServletRequest,SpringMVC会自动将**当前请求对象**传递给这个参数。
创建一个test.html用于测试:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Test</title>
</head>
<body>
<h1>测试的通用页面!!!</h1>
</body>
</html>

创建一个测试方法:

@PostMapping("/user/reg")
    public String register(HttpServletRequest request){

        //获取请求提交的数据
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        String sex = request.getParameter("sex");
        String[] hobbies = request.getParameterValues("hobby");
        String intro = request.getParameter("intro");
        System.out.println(username + "," + password + "," + sex + "," + Arrays.toString(hobbies) + "," + intro);

        return "test";
    }

测试:
输入信息提交成功:
在这里插入图片描述
后端获取数据成功:
在这里插入图片描述

这样通过Servlet原生的API获取到提交的数据。但是这种方式不建议使用,因为方法的参数依赖Servlet原生API,Controller的测试将不能单独测试,必须依赖WEB服务器才能测试。

3.3、使用RequestParam注解标注

3.3.1、RequestParam注解的基本使用

RequestParam注解作用:将请求参数与方法上的形参映射。

@PostMapping("/user/reg")
    public String register(
            @RequestParam(value = "username") //这个值是前端提交过来的名字,保持一致
            String username,
            @RequestParam(value = "password")
            String password,
            @RequestParam(value = "sex")
            Integer sex,
            @RequestParam(value = "hobby")
            String[] hobby,
            @RequestParam(value = "intro")
            String intro){

        return "test";
    }

测试:
提交数据成功:
在这里插入图片描述
接收数据成功:
在这里插入图片描述

3.3.2、RequestParam注解的required属性

在这里插入图片描述
required属性用来设置该方法参数是否为必须的。默认情况下,这个参数为 true,表示方法参数是必需的。如果请求中缺少对应的参数,则会抛出异常。可以将其设置为false,false表示不是必须的,如果请求中缺少对应的参数,则方法的参数为null。
在这里插入图片描述
添加了一个 age 形参,没有指定 required 属性时,默认是true,表示必需的,但前端表单中没有年龄age,则报错:
在这里插入图片描述
错误信息告诉我们:参数age是必需的。没有提供这个请求参数,HTTP状态码 400

如果将 required 属性设置为 false。则该参数则不是必须的,如果请求参数仍然未提供时:
在这里插入图片描述
那么输出:age=null
通过测试得知,如果一个参数被设置为不是必需的,当没有提交对应的请求参数时,形参默认值null。当然,如果请求参数中提供了age,则age为真实提交的数据。

3.3.3.、RequestParam注解的defaultValue属性

defaultValue属性用来设置形参的默认值,当没有提供对应的请求参数或者请求参数的值是空字符串""的时候,方法的形参会采用默认值。

3.4、依靠控制器方法上的形参名来接收

@RequestParam 这个注解是可以省略的,如果方法形参的名字和提交数据时的name相同,则 @RequestParam 可以省略。

但有一个前提:如果你采用的是Spring6+版本,你需要在pom.xml文件中指定编译参数’-parameter’,配置如下:

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.12.1</version>
            <configuration>
                <source>21</source>
                <target>21</target>
                <compilerArgs>
                    <arg>-parameters</arg>
                </compilerArgs>
            </configuration>
        </plugin>
    </plugins>
</build>

注意:如果你使用的是Spring5的版本,以上的配置是不需要的。
本人JDK17报错,应该需要JDK21:
在这里插入图片描述
下面是视频演示:
在这里插入图片描述

3.5、使用POJO类/JavaBean接收请求参数(最常用的)

当提交的数据非常多时,方法的形参个数会非常多,这不是很好的设计。在SpringMVC中也可以使用POJO类/JavaBean来接收请求参数(底层原理:反射机制,假设前端发送的参数名是username,会通过反射机制去寻找pojo类中的setUsername这个方法,通过这个方法去给pojo类中的username属性进行赋值,实际上只需要保持setXxx()方法 与 前端的参数名xxx保持一致即可!)。不过有一个非常重要的要求:POJO类的属性名必须和请求参数的参数名保持一致。提供以下的JavaBean:
(自己补充无参构造、有参构造、get方法、set方法、toString重写)

public class User {
    private Long id;
    private String username;
    private String password;
    private String sex;
    private String[] hobby;
    private String intro;
    private Integer age;
}

方法:

    @PostMapping("/user/reg")
    public String register(User user){
        System.out.println(user);
        return "test";
    }

运行测试:
发送数据成功!
在这里插入图片描述
接收数据成功:
在这里插入图片描述

3.6、RequestHeader注解

该注解的作用是:将请求头信息映射到方法的形参上。 和RequestParam注解功能相似,RequestParam注解的作用:将请求参数映射到方法的形参上。当然,对于RequestHeader注解来说,也有三个属性:value、required、defaultValue,和RequestParam一样。
测试方法:

 @PostMapping("/user/reg")
    public String register(User user,
                           @RequestHeader(value = "Referer", required = false, defaultValue = "")
                           String referer){
        System.out.println(user);
        System.out.println(referer);
        return "test";
    }

测试结果:
前端发送请求成功!
在这里插入图片描述
在这里插入图片描述
后端接收信息成功!
在这里插入图片描述

3.7、CookieValue注解

该注解的作用:将请求提交的Cookie数据映射到方法形参上,同样是有三个属性:value、required、defaultValue。
前端页面中编写发送cookie的代码:

<script type="text/javascript">
    function sendCookie(){
        document.cookie = "id=123456789; expires=Thu, 18 Dec 2025 12:00:00 UTC; path=/";
        document.location = "/springmvc/user/reg";
    }
</script>
<button onclick="sendCookie()">向服务器端发送Cookie</button>

后端测试方法:

public String register(User user,
                           @RequestHeader(value = "Referer", required = false, defaultValue = "")
                           String referer,
                           @CookieValue(value = "id", required = false,defaultValue = "")
                           String id){
        System.out.println(user);
        System.out.println(referer);
        System.out.println("客户端提交cookie的id值:" + id);
        return "test";
    }

测试:
点击发送cookie按钮
后端成功接收数据:
在这里插入图片描述

3.8、请求的中文乱码问题

Tomcat10以上没有post和get乱码问题,Tomcat已经默认改了!如果你出现中文乱码问题,参考以下链接(p41):
【springmvc教程,SpringMVC从零到精通,老杜springmvc,动力节点springmvc,spring】 https://www.bilibili.com/video/BV1sC411L76f/?p=41&share_source=copy_web&vd_source=4d877b7310d01a59f27364f1080e3382

四、三个域对象

4.1、Servlet中的三个域对象

请求域:request
会话域:session
应用域:application
三个域都有以下三个方法:

// 向域中存储数据
void setAttribute(String name, Object obj);

// 从域中读取数据
Object getAttribute(String name);

// 删除域中的数据
void removeAttribute(String name);

主要是通过:setAttribute + getAttribute方法来完成在域中数据的传递和共享。

4.1.1、request

接口名:HttpServletRequest
简称:request
request对象代表了一次请求。一次请求一个request。
使用请求域的业务场景:在A资源中通过转发的方式跳转到B资源,因为是转发,因此从A到B是一次请求,如果想让A资源和B资源共享同一个数据,可以将数据存储到request域中。
如果你想在同一个请求当中共享数据,那么使用请求域。

4.1.2、session

接口名:HttpSession
简称:session
session对象代表了一次会话。从打开浏览器开始访问,到最终浏览器关闭,这是一次完整的会话。每个会话session对象都对应一个JSESSIONID,而JSESSIONID生成后以cookie的方式存储在浏览器客户端。浏览器关闭,JSESSIONID失效,会话结束。
希望在多次请求之间共享同一个数据,可以使用会话域。
使用会话域的业务场景:

  1. 在A资源中通过重定向的方式跳转到B资源,因为是重定向,因此从A到B是两次请求,如果想让A资源和B资源共享同一个数据,可以将数据存储到session域中。
  2. 登录成功后保存用户的登录状态。

4.1.3、application

接口名:ServletContext
简称:application
application对象代表了整个web应用,服务器启动时创建,服务器关闭时销毁。对于一个web应用来说,application对象只有一个。
使用应用域的业务场景:记录网站的在线人数。

4.2、request域对象

在SpringMVC中,在request域中共享数据有以下几种方式:

  1. 使用原生Servlet API方式。
  2. 使用Model接口。
  3. 使用Map接口。
  4. 使用ModelMap类。
  5. 使用ModelAndView类。

4.2.1、使用原生Servlet API方式

在Controller的方法上使用HttpServletRequest:

 @RequestMapping("/testServletAPI")
    public String testServletAPI(HttpServletRequest request){
        // 将共享的数据存储到request域当中
        request.setAttribute("testRequestScope", "在SpringMVC当中使用原生Servlet API完成request域数据共享");
        // 跳转视图,在视图页面将request域中的数据取出来,这样就完成了,Controller和View在同一个请求当中两个组件之间数据的共享

        //这个跳转默认是转发的方式(转发是一次请求)
        //这个返回是一个逻辑视图名称,通过视图解析器解析,变成物理视图名称  /WEB-INF/templates/test.html
        return "test";
    }

页面:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Test</title>
</head>
<body>
<div th:text="${testRequestScope}"></div>
</body>
</html>

index控制器:

@RequestMapping("/")
    public String index(){
        return "index";
    }

index页面:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>测试三个域对象</title>
</head>
<body>
<h1>测试三个域对象</h1>
<a th:href="@{/testServletAPI}">测试在SpringMVC当中使用原生Servlet API完成request域数据共享</a>
<hr>
</body>
</html>

测试:
在这里插入图片描述
在这里插入图片描述用SpringMVC框架,不建议使用原生Servlet API,依赖tomcat,request是由tomcat创建好并穿进去的,不能进行单元测试!

4.2.2、使用Model接口

@RequestMapping("/testModel")
    public String testModel(Model model){
        // 向request域当中绑定数据
        model.addAttribute("testRequestScope", "在SpringMVC当中使用Model完成request域数据共享");
        //转发
        return "test";
    }

在index.html增加一条测试:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>测试三个域对象</title>
</head>
<body>
<h1>测试三个域对象</h1>
<a th:href="@{/testServletAPI}">测试在SpringMVC当中使用原生Servlet API完成request域数据共享</a>
<br>
<a th:href="@{/testModel}">测试在SpringMVC当中使用Model完成request域数据共享</a>
<hr>
</body>
</html>

测试:
在这里插入图片描述
在这里插入图片描述

4.2.3、使用Map接口

@RequestMapping("/testMap")
    public String testMap(Map<String, Object> map){
        // 向request域当中存储数据
        map.put("testRequestScope", "在SpringMVC当中使用Map完成request域数据共享");
        return "test";
    }

index.html增加一条测试:

<a th:href="@{/testMap}">测试在SpringMVC当中使用Map完成request域数据共享</a>

测试:
在这里插入图片描述
在这里插入图片描述

4.2.4、使用ModelMap类

@RequestMapping("/testModelMap")
    public String testModelMap(ModelMap modelMap){
        // 向request域当中存储数据
        modelMap.addAttribute("testRequestScope", "在SpringMVC当中使用ModelMap类完成request域数据共享");
        return "test";
    }

在index.html增加一条测试数据;

<a th:href="@{/testModelMap}">测试在SpringMVC当中使用ModelMap类完成request域数据共享</a>

测试:
在这里插入图片描述
在这里插入图片描述

Model、Map、ModelMap的关系
可以在以上Model、Map、ModelMap的测试程序中将其输出,看看输出什么:
在这里插入图片描述
看不出来什么区别,从输出结果上可以看到都是一样的。
可以将其运行时类名输出:
在这里插入图片描述
通过输出结果可以看出,无论是Model、Map还是ModelMap,底层实例化的对象都是:BindingAwareModelMap。
可以查看BindingAwareModelMap的继承结构:
在这里插入图片描述
通过继承结构可以看出:BindingAwareModelMap继承了ModelMap,而ModelMap又实现了Map接口。
在这里插入图片描述
可以看出ModelMap又实现了Model接口。因此表面上是采用了不同方式,底层本质上是相同的。
SpringMVC之所以提供了这些方式,目的就是方便程序员的使用,提供了多样化的方式。

4.2.5、使用ModelAndView类

在SpringMVC框架中为了更好的体现MVC架构模式,提供了一个类:ModelAndView。这个类的实例封装了Model和View。也就是说这个类既封装业务处理之后的数据,也体现了跳转到哪个视图。使用它也可以完成request域数据共享。

@RequestMapping("/testModelAndView")
    public ModelAndView testModelAndView(){
        // 创建“模型与视图对象”
        ModelAndView modelAndView = new ModelAndView();
        // 绑定数据
        modelAndView.addObject("testRequestScope", "在SpringMVC中使用ModelAndView实现request域数据共享");
        // 绑定视图
        modelAndView.setViewName("test");
        // 返回
        return modelAndView;
    }

测试:
在这里插入图片描述
在这里插入图片描述
这种方式需要注意的是:

  1. 方法的返回值类型不是String,而是ModelAndView对象。
  2. ModelAndView不是出现在方法的参数位置,而是在方法体中new的。
  3. 需要调用addObject向域中存储数据。
  4. 需要调用setViewName设置视图的名字。

ModelAndView源码分析
以上通过了五种方式完成了request域数据共享,包括:原生Servlet API,Model、Map、ModelMap、ModelAndView其中后四种:Model、Map、ModelMap、ModelAndView。这四种方式在底层DispatcherServlet调用Controller之后,返回的对象都是ModelAndView给了DispatcherServlet。

当请求路径不是JSP的时候,都会走前端控制器DispatcherServlet。
DispatcherServlet有一个核心方法,doDispatch(),这个方法用来通过请求路径找到对应的 处理器方法(即写的Controller类的方法,如testMap) 然后调用 处理器方法,处理器方法返回一个逻辑视图名称(也可能会返回一个ModelAndView对象),底层会将逻辑视图名称转换成View对象,然后将View对象结合Model对象,封装成一个ModelAndView对象,然后将该对象返回给DispatcherServlet类。
在这里插入图片描述
在这里插入图片描述

4.3、session域对象

在SpringMVC中使用session域共享数据,实现方式有多种,其中比较常见的两种方式:

  1. 使用原生Servlet API
  2. 使用SessionAttributes注解

4.3.1、使用原生Servlet API

@Controller
public class SessionScopeTestController {

    @RequestMapping("/testSessionServletAPI")
    public String testServletAPI(HttpSession session){
        // 处理核心业务。。。
        // 将数据存储到session中
        session.setAttribute("testSessionScope","在SpringMVC当中使用原生Servlet API完成session域数据共享");

        //返回逻辑名称(转发)
        return "test";
    }

}

视图页面:

<div th:text="${session.testSessionScope}"></div>

超链接:

<h2>测试session域对象</h2>
<a th:href="@{/testSessionServletAPI}">测试在SpringMVC当中使用原生Servlet API完成session域数据共享</a><br>

测试:
在这里插入图片描述
在这里插入图片描述

4.3.2、使用SessionAttributes注解

使用SessionAttributes注解标注Controller:

@Controller
@SessionAttributes(value = {"x", "y"}) //标注x和y都是存放到session域而不是request域
public class SessionScopeTestController {
	@RequestMapping("/testSessionAttributes")
    public String testSessionAttributes(ModelMap modelMap){
        //处理业务
        //将数据存储到session域当中
        modelMap.addAttribute("x","张三");
        modelMap.addAttribute("y","李四");
        return "test";
    }
}

test视图:

<div th:text="${session.x}"></div>
<div th:text="${session.y}"></div>

index.html:

<a th:href="@{/testSessionAttributes}">测试在SpringMVC当中使用SessionAttributes注解完成session域数据共享</a><br>

运行:
在这里插入图片描述
在这里插入图片描述

4.4、application域对象

在SpringMVC实现application域数据共享,最常见的方案就是直接使用Servlet API了:

@Controller
public class ApplicationScopeTestController {

    @RequestMapping("/testApplicationScope")
    public String testApplicationScope(HttpServletRequest request){

        // 获取ServletContext对象
        ServletContext application = request.getServletContext();

        // 向应用域中存储数据
        application.setAttribute("applicationScope", "在SpringMVC中使用Servlet API实现application域数据共享");

        return "test";
    }
}

test视图:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Test</title>
</head>
<body>
<div th:text="${testRequestScope}"></div>
<div th:text="${session.testSessionScope}"></div>
<div th:text="${session.x}"></div>
<div th:text="${session.y}"></div>

<div th:text="${application.applicationScope}"></div>
</body>
</html>

index.html:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>测试三个域对象</title>
</head>
<body>
<h1>测试三个域对象</h1>
<a th:href="@{/testServletAPI}">测试在SpringMVC当中使用原生Servlet API完成request域数据共享</a>
<br>
<a th:href="@{/testModel}">测试在SpringMVC当中使用Model完成request域数据共享</a>
<br>
<a th:href="@{/testMap}">测试在SpringMVC当中使用Map完成request域数据共享</a>
<br>
<a th:href="@{/testModelMap}">测试在SpringMVC当中使用ModelMap类完成request域数据共享</a>
<br>
<a th:href="@{/testModelAndView}">测试在SpringMVC当中使用testModelAndView完成request域数据共享</a>
<br>
<h2>测试session域对象</h2>
<a th:href="@{/testSessionServletAPI}">测试在SpringMVC当中使用原生Servlet API完成session域数据共享</a><br>
<a th:href="@{/testSessionAttributes}">测试在SpringMVC当中使用SessionAttributes注解完成session域数据共享</a><br>
<hr>
<h2>测试application域对象</h2>
<a th:href="@{/testApplicationScope}">测试在SpringMVC当中使用原生Servlet API完成application域数据共享</a><br>
</body>
</html>

运行:
在这里插入图片描述
在这里插入图片描述

五、视图view

5.1、SpringMVC中视图的实现原理

5.1.1、Spring MVC视图支持可配置

在Spring MVC中,视图View是支持定制的,例如之前在 springmvc.xml 文件中进行了如下的配置:

<!--视图解析器-->
<bean id="thymeleafViewResolver" class="org.thymeleaf.spring6.view.ThymeleafViewResolver">
    <!--作用于视图渲染的过程中,可以设置视图渲染后输出时采用的编码字符集-->
    <property name="characterEncoding" value="UTF-8"/>
    <!--如果配置多个视图解析器,它来决定优先使用哪个视图解析器,它的值越小优先级越高-->
    <property name="order" value="1"/>
    <!--当 ThymeleafViewResolver 渲染模板时,会使用该模板引擎来解析、编译和渲染模板-->
    <property name="templateEngine">
        <bean class="org.thymeleaf.spring6.SpringTemplateEngine">
            <!--用于指定 Thymeleaf 模板引擎使用的模板解析器。模板解析器负责根据模板位置、模板资源名称、文件编码等信息,加载模板并对其进行解析-->
            <property name="templateResolver">
                <bean class="org.thymeleaf.spring6.templateresolver.SpringResourceTemplateResolver">
                    <!--设置模板文件的位置(前缀)-->
                    <property name="prefix" value="/WEB-INF/templates/"/>
                    <!--设置模板文件后缀(后缀),Thymeleaf文件扩展名不一定是html,也可以是其他,例如txt,大部分都是html-->
                    <property name="suffix" value=".html"/>
                    <!--设置模板类型,例如:HTML,TEXT,JAVASCRIPT,CSS等-->
                    <property name="templateMode" value="HTML"/>
                    <!--用于模板文件在读取和解析过程中采用的编码字符集-->
                    <property name="characterEncoding" value="UTF-8"/>
                </bean>
            </property>
        </bean>
    </property>
</bean>

以上的配置表明当前SpringMVC框架使用的视图View是Thymeleaf的。
如果需要换成其他的视图View,修改以上的配置即可。这样就可以非常轻松的完成视图View的扩展。
这种设计是完全符合OCP开闭原则的。视图View和框架是解耦合的,耦合度低扩展能力强。视图View可以通过配置文件进行灵活切换。

5.1.2、Spring MVC支持的常见视图

Spring MVC支持的常见视图包括(前三个使用较多):

  1. InternalResourceView:内部资源视图(Spring MVC框架内置的,专门为JSP模板语法准备的,也是为转发准备的)
  2. RedirectView:重定向视图(Spring MVC框架内置的,用来完成重定向效果)
  3. ThymeleafView:Thymeleaf视图(第三方的,为Thymeleaf模板语法准备的)
  4. FreeMarkerView:FreeMarker视图(第三方的,为FreeMarker模板语法准备的)
  5. VelocityView:Velocity视图(第三方的,为Velocity模板语法准备的)
  6. PDFView:PDF视图(第三方的,专门用来生成pdf文件视图)
  7. ExcelView:Excel视图(第三方的,专门用来生成excel文件视图)

5.1.3、实现视图机制的核心接口

实现视图的核心类与接口包括:

  • DispatcherServlet类(前端控制器):负责接收前端的请求(/login),根据请求路径找到对应的处理器方法(UserController#login()),执行处理器方法(执行UserController#login()),并且最终返回ModelAndView对象。往下就是处理视图。
    核心方法:
    根据请求路径调用映射的处理器方法,处理器方法执行结束之后,返回逻辑视图名称。返回逻辑视图名称之后,DispatcherServlet会将 逻辑视图名称ViewName + Model封装为ModelAndView对象。
    在这里插入图片描述
    在这里插入图片描述
  • ViewResolver接口(视图解析器):这个接口将 逻辑视图名称 转换为 物理视图名称,并最终返回一个View接口对象。
    核心方法:
    这个方法的作用是将 逻辑视图名称 转换成 物理视图名称,并且最终返回视图对象View。
    在这里插入图片描述
  • View接口(视图):这个接口负责将模版语法的字符串转换成html代码,并且将html代码响应给浏览器(渲染)。
    核心方法:
    渲染页面(将模版字符串转换成html代码响应到浏览器)
    在这里插入图片描述
  • ViewResolverRegistry(视图解析器注册器):负责在web容器(Tomcat)启动的时候,完成视图解析器的注册。如果有多个视图解析器,会将视图解析器对象按照order的配置放入List集合。

总结:

  • 实现视图的核心类和接口包括:ViewResolverRegistry、DispatcherServlet、ViewResolver、View
  • 如果你想定制自己的视图组件:
    • 编写类实现ViewResolver接口,实现resolveViewName方法,在该方法中完成**逻辑视图名**转换为**物理视图名**,并返回View对象。
    • 编写类实现View接口,实现render方法,在该方法中将模板语言转换成HTML代码,并将HTML代码响应到浏览器。
  • 如果Spring MVC框架中使用Thymeleaf作为视图技术。那么相关的类包括:
    • ThymeleafView
    • ThymeleafViewResolver

5.1.4、实现视图机制的原理描述

假设我们SpringMVC中使用了Thymeleaf作为视图。
第一步:浏览器发送请求给web服务器
第二步:Spring MVC中的DispatcherServlet接收到请求
第三步:DispatcherServlet根据请求路径分发到对应的Controller
第四步:DispatcherServlet调用Controller的方法
第五步:Controller的方法处理业务并返回一个逻辑视图名给DispatcherServlet
第六步:DispatcherServlet调用ThymeleafViewResolver的resolveViewName方法,将逻辑视图名转换为物理视图名,并创建ThymeleafView对象返回给DispatcherServlet
第七步:DispatcherServlet再调用ThymeleafView的render方法,render方法将模板语言转换为HTML代码,响应给浏览器,完成最终的渲染。

假设我们SpringMVC中使用了JSP作为视图。
第一步:浏览器发送请求给web服务器
第二步:Spring MVC中的DispatcherServlet接收到请求
第三步:DispatcherServlet根据请求路径分发到对应的Controller
第四步:DispatcherServlet调用Controller的方法
第五步:Controller的方法处理业务并返回一个逻辑视图名给DispatcherServlet
第六步:DispatcherServlet调用InternalResourceViewResolverresolveViewName方法,将逻辑视图名转换为物理视图名,并创建InternalResourceView对象返回给DispatcherServlet
第七步:DispatcherServlet再调用InternalResourceViewrender方法,render方法将模板语言转换为HTML代码,响应给浏览器,完成最终的渲染。

5.2、转发与重定向

5.2.1、回顾转发和重定向区别

  1. 转发是一次请求。因此浏览器地址栏上的地址不会发生变化。
  2. 重定向是两次请求。因此浏览器地址栏上的地址会发生变化。
  3. 转发的代码实现:request.getRequestDispatcher(“/index”).forward(request, response);
  4. 重定向的代码实现:response.sendRedirect(“/webapproot/index”);
  5. 转发是服务器内部资源跳转,由服务器来控制。不可实现跨域访问。
  6. 重定向可以完成内部资源的跳转,也可以完成跨域跳转。
  7. 转发的方式可以访问WEB-INF目录下受保护的资源。
  8. 重定向相当于浏览器重新发送了一次请求,在浏览器直接发送的请求是无法访问WEB-INF目录下受保护的资源的。
  9. 转发原理:
    1. 假设发送了 /a 请求,执行了 AServlet
    2. 在AServlet 中通过request.getRequestDispatcher("/b").forward(request,response);转发到BServlet
    3. 从AServlet跳转到BServlet是服务器内部来控制的。对于浏览器而言,浏览器只发送了一个 /a 请求。
  10. 重定向原理:
    1. 假设发送了 /a 请求,执行了 AServlet
    2. 在AServlet 中通过response.sendRedirect("/webapproot/b")重定向到BServlet
    3. 此时服务器会将请求路径/webapproot/b响应给浏览器
    4. 浏览器会自发的再次发送/webapproot/b请求来访问BServlet
    5. 因此对于重定向来说,发送了两次请求,一次是 /webapproot/a,另一次是/webapproot/b

以上所描述的是使用原生Servlet API来完成转发和重定向

5.2.2、forward

在Spring MVC中默认就是转发的方式,之前所写的程序,都是转发的方式。只不过都是转发到Thymeleaf的模板文件xxx.html上。

在Spring MVC中如何转发到另一个Controller上呢?可以使用Spring MVC的forward
代码实现如下:

@Controller
public class ForwardController {

    @RequestMapping("/a")
    public String toA(){
        //采用SpringMVC的转发方式跳转到 /b
        //转发的时候 格式有特殊要求 return "forward:下一个资源的路径"
        //转发到/b 这是一次请求,底层创建的视图对象是InternalResourceView对象。
        //这不是逻辑视图名称
        return "forward:/b";
    }

    @RequestMapping("/b")
    public String toB(){
        return "b";
    }
}

在这里插入图片描述
思考:既然会创建InternalResourceView,应该会对应一个视图解析器呀(InternalResourceViewResolver)?但是我在springmvc.xml文件中只配置了ThymeleafViewResolver,并没有配置InternalResourceViewResolver呀?这是为什么?
这是因为**forward:**** 后面的不是****逻辑视图名**,而是一个**请求路径**。因此转发是不需要视图解析器的。
另外,转发使用的是InternalResourceView,也说明了转发是内部资源的跳转。(Internal是内部的意思,Resource是资源的意思。)

5.2.3、redirect

redirect是专门完成重定向效果的。和forward语法类似,只需要将之前的 return "forward:/b"修改为 return "redirect:/b"即可。
在这里插入图片描述

总结:
转发: return “forward:/b” 底层创建的是InternalResourceView对象。
return “a” 底层创建的是ThymeleafView对象。
重定向:return “redirect:/b” 底层创建的是RedirectView对象。

注意:从springmvc应用重定向到springmvc2应用(跨域),语法是:

@RequestMapping("/a")
public String a(){
    return "redirect:http://localhost:8080/springmvc2/b";
}

5.2.4、mvc:view-controller

<mvc:view-controller> 配置用于将某个请求映射到特定的视图上,即指定某一个 URL 请求到一个视图资源的映射,使得这个视图资源可以被访问。它相当于是一个独立的处理程序,不需要编写任何 Controller,只需要指定 URL 和对应的视图名称就可以了。
一般情况下,<mvc:view-controller> 配置可以替代一些没有业务逻辑的 Controller,例如首页、错误页面等。当用户访问配置的 URL 时,框架将直接匹配到对应的视图,而无需再经过其他控制器的处理。

<mvc:view-controller> 配置的格式如下:

<mvc:view-controller path="/如何访问该页面" view-name="对应的逻辑视图名称" />

我这里用的(我这里有一个test.html的测试页面):

<mvc:view-controller path="/test" view-name="test"></mvc:view-controller>

我的index.html:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>首页</title>
</head>
<body>
<h1>SpringMVC视图实现原理</h1>
<hr>
<a th:href="@{/a}">A页面</a><br>
<a th:href="@{/b}">B页面</a><br>
<a th:href="@{/test}">Test页面</a>
</body>
</html>

使用该配置必须增加<mvc:annotation-driven/>,因为mvc:view-controller会让所有的注解失效,因此需要重新开启注解:
在这里插入图片描述
测试结果:
在这里插入图片描述
在这里插入图片描述

5.2.5、mvc:annotation-driven

在SpringMVC中,如果在springmvc.xml文件中配置了 <mvc:view-controller>,就需要同时在springmvc.xml文件中添加如下配置:

<mvc:annotation-driven/>

该配置的作用是:启用Spring MVC的注解。
如果没有以上的配置,Controller就无法访问到。访问之前的Controller会发生 404 问题。

5.3、访问静态资源

一个项目可能会包含大量的静态资源,比如:css、js、images等。
由于我们DispatcherServlet的url-pattern配置的是“/”,之前我们说过,这个"/"代表的是除jsp请求之外的所有请求,也就是说访问应用中的静态资源,也会走DispatcherServlet,这会导致404错误,无法访问静态资源,如何解决,两种方案:

  • 使用默认 Servlet 处理静态资源
  • 使用 mvc:resources 标签配置静态资源处理

这两种方式都可以。自行选择。

5.3.1、使用默认Servlet处理静态资源

首先需要在springmvc.xml文件中添加以下配置,开启 默认Servlet处理静态资源 功能:

<!-- 开启注解驱动 -->
<mvc:annotation-driven />
<!--开启默认Servlet处理-->
<mvc:default-servlet-handler/>

然后在web.xml文件中指定什么样的路径走其他Servlet:

<servlet>
    <servlet-name>default</servlet-name>
    <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
    <init-param>
        <param-name>debug</param-name>
        <param-value>0</param-value>
    </init-param>
    <init-param>
        <param-name>listings</param-name>
        <param-value>false</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>default</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

以上配置url-pattern使用的也是"/",和DispatcherServlet一样。表示的含义是:同一个请求路径,先走DispatcherServlet,如果找不到则走默认的Servlet。
默认的 Servlet 类中的代码已经由 Tomcat 服务器提供了实现,一般不需要开发者自己编写。在上面的示例中,我们指定了 org.apache.catalina.servlets.DefaultServlet,则 Tomcat 服务器会自动将请求转发给该类处理。在处理时,该类会根据请求的 URL 去查询 Web 应用的静态资源(如 HTML、CSS、JavaScript 和图片等),并将其返回给用户。
以上在web.xml文件中的配置我们也可以省略了,因为在Tomcat服务器中已经为我们提前配置好了,在CATALINA_HOME/conf/web.xml文件中(默认下不开启),如下:
在这里插入图片描述
因此我们只需要在springmvc.xml文件中启用这个默认的Servlet即可:<mvc:default-servlet-handler>
项目中添加静态资源进行测试:
在这里插入图片描述
运行:
在这里插入图片描述

5.3.2、使用 mvc:resources 标签配置静态资源

访问静态资源,也可以在springmvc.xml文件中添加如下的配置:

<!-- 开启注解驱动 -->
<mvc:annotation-driven />

<!-- 配置静态资源处理 -->
<mvc:resources mapping="/static/**" location="/static/" />

表示凡是请求路径是"/static/“开始的,都会去”/static/"目录下找该资源。
注意:要想使用 <mvc:resources> 配置,必须开启注解驱动 <mvc:annotation-driven />
运行:
在这里插入图片描述

六、RESTFul编程风格

6.1、RESTFul是什么

RESTFul是WEB服务接口的一种设计风格。
RESTFul定义了一组约束条件和规范,可以让WEB服务接口更加简洁、易于理解、易于扩展、安全可靠。

RESTFul对一个WEB服务接口都规定了哪些东西?

  • 对请求的URL格式有约束和规范
  • 对HTTP的请求方式有约束和规范
  • 对请求和响应的数据格式有约束和规范
  • 对HTTP状态码有约束和规范
  • 等 …

REST对请求方式的约束是这样的:

  • 查询必须发送GET请求
  • 新增必须发送POST请求
  • 修改必须发送PUT请求
  • 删除必须发送DELETE请求

REST对URL的约束是这样的:

  • 传统的URL:get请求,/springmvc/getUserById?id=1

  • REST风格的URL:get请求,/springmvc/user/1

  • 传统的URL:get请求,/springmvc/deleteUserById?id=1

  • REST风格的URL:delete请求, /springmvc/user/1

RESTFul对URL的约束和规范的核心是:通过采用**不同的请求方式****+ ****URL**来确定WEB服务中的资源。

RESTful 的英文全称是 Representational State Transfer(表述性状态转移)。简称REST。
表述性(Representational)是:URI + 请求方式。
状态(State)是:服务器端的数据。
转移(Transfer)是:变化。
表述性状态转移是指:通过 URI + 请求方式 来控制服务器端数据的变化。

6.1.1、RESTFul风格与传统方式对比

传统的 URL 与 RESTful URL 的区别是传统的 URL 是基于方法名进行资源访问和操作,而 RESTful URL 是基于资源的结构和状态进行操作的。下面是一张表格,展示两者之间的具体区别:

传统的 URLRESTful URL
GET /getUserById?id=1GET /user/1
GET /getAllUserGET /user
POST /addUserPOST /user
POST /modifyUserPUT /user
GET /deleteUserById?id=1DELETE /user/1

从上表中可以看出,传统的URL是基于动作的,而 RESTful URL 是基于资源和状态的,因此 RESTful URL 更加清晰和易于理解,这也是 REST 架构风格被广泛使用的主要原因之一。

6.1.2、RESTFul方式演示查询

RESTFul规范中规定,如果要查询数据,需要发送GET请求。

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>首页</title>
</head>
<body>
<h1>测试RESTFul编程风格</h1>
<hr>
<!--RESTful风格,查询用户列表-->
<a th:href="@{/user}">查看用户列表</a><br>

<!--RESTful风格,根据id查询用户信息-->
<a th:href="@{/user/110}">查询id=110的这个用户信息</a><br>
</body>
</html>
 @RequestMapping(value = "/user", method = RequestMethod.GET)
    public String getAll(){
        System.out.println("正在查询所有用户信息!");
        return "ok";
    }

    @RequestMapping(value = "/user/{id}", method = RequestMethod.GET)
    public String getById(@PathVariable("id") String id){
        System.out.println("正在根据用户id查用户信息,id:" + id);
        return "ok";
    }

运行结果:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

6.1.3、RESTFul方式演示增加(POST /api/user)

RESTFul规范中规定,如果要进行保存操作,需要发送POST请求。

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>首页</title>
</head>
<body>
<h1>测试RESTFul编程风格</h1>
<hr>
<!--RESTful风格,查询用户列表-->
<a th:href="@{/user}">查看用户列表</a><br>

<!--RESTful风格,根据id查询用户信息-->
<a th:href="@{/user/110}">查询id=110的这个用户信息</a><br>

<!--RESTful风格,新增用户信息。新增必须发送POST风格-->
<form th:action="@{/user}" method="post">
    用户名:<input type="text" name="username"><br>
    密码:<input type="password" name="password"><br>
    年龄:<input type="text" name="age"><br>
    <input type="submit" value="保存">
</form>

</body>
</html>
 @RequestMapping(value = "/user", method = RequestMethod.POST)
    public String save(User user){
        System.out.println("正在保存用户信息!");
        System.out.println(user);
        return "ok";
    }

记得新建一个User类,属性包括username、password、age,设置set,get,构造器,重写toString。
运行结果:
在这里插入图片描述在这里插入图片描述

6.1.4、RESTFul方式演示修改

RESTFul规范中规定,如果要进行保存操作,需要发送PUT请求。
如何发送PUT请求?
第一步:首先你必须是一个POST请求。
第二步:在发送POST请求的时候,提交这样的数据:**_method=PUT**
第三步:在web.xml文件配置SpringMVC提供的过滤器:HiddenHttpMethodFilter

index.html:


<!--RESTful风格,修改用户信息。首先发送POST请求,要发送PUT请求,首先必须是一个POST请求-->
<form th:action="@{/user}" method="post">
    <!--隐藏域-->
    <input type="hidden" name="_method" value="put">

    用户名:<input type="text" name="username"><br>
    密码:<input type="password" name="password"><br>
    年龄:<input type="text" name="age"><br>
    <input type="submit" value="保存">
</form>

web.xml增加一个过滤器:

    <!--添加一个过滤器,这个过滤器是springmvc提前写好的,直接用就行,这个过滤器可以帮助你将请求POST转换成PUT请求/DELETE请求-->
    <filter>
        <filter-name>hiddenHttpMethodFilter</filter-name>
        <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>hiddenHttpMethodFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
 @RequestMapping(value = "/user", method = RequestMethod.PUT)
    public String update(User user){
        System.out.println("修改用户信息,用户名:" + user);
        return "ok";
    }

运行结果:
在这里插入图片描述
在这里插入图片描述
注:删除与修改同理。

6.2、使用RESTFul实现用户管理系统

user.css

.header {
    background-color: #f2f2f2;
    padding: 20px;
    text-align: center;
}

ul {
    list-style-type: none;
    margin: 0;
    padding: 0;
    overflow: hidden;
    background-color: #333;
}

li {
    float: left;
}

li a {
    display: block;
    color: white;
    text-align: center;
    padding: 14px 16px;
    text-decoration: none;
}

li a:hover:not(.active) {
    background-color: #111;
}

.active {
    background-color: #4CAF50;
}

form {
    width: 50%;
    margin: 0 auto;
    padding: 20px;
    border: 1px solid #ddd;
    border-radius: 4px;
}

label {
    display: block;
    margin-bottom: 8px;
}

input[type="text"], input[type="email"], select {
    width: 100%;
    padding: 6px 10px;
    margin: 8px 0;
    box-sizing: border-box;
    border: 1px solid #555;
    border-radius: 4px;
    font-size: 16px;
}

button[type="submit"] {
    padding: 10px;
    background-color: #4CAF50;
    color: #fff;
    border: none;
    border-radius: 4px;
    cursor: pointer;
}

button[type="submit"]:hover {
    background-color: #3e8e41;
}

table {
    border-collapse: collapse;
    width: 100%;
}

th, td {
    border: 1px solid #ddd;
    padding: 8px;
    text-align: left;
}

th {
    background-color: #f2f2f2;
}

tr:nth-child(even) {
    background-color: #f2f2f2;
}

.header {
    background-color: #f2f2f2;
    padding: 20px;
    text-align: center;
}

a {
    text-decoration: none;
    color: #333;
}

.add-button {
    margin-bottom: 20px;
    padding: 10px;
    background-color: #4CAF50;
    color: #fff;
    border: none;
    border-radius: 4px;
    cursor: pointer;
}

.add-button:hover {
    background-color: #3e8e41;
}

user_add.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>新增用户</title>
    <link rel="stylesheet" th:href="@{/static/css/user.css}" type="text/css"></link>
</head>
<body>
<h1>新增用户</h1>
<form th:action="@{/user}" method="post">
    <label>用户名:</label>
    <input type="text" name="username" required>

    <label>性别:</label>
    <select name="sex" required>
        <option value="">-- 请选择 --</option>
        <option value="1"></option>
        <option value="0"></option>
    </select>

    <label>邮箱:</label>
    <input type="email" name="email" required>

    <button type="submit">保存</button>
</form>
</body>
</html>

user_edit.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
  <meta charset="UTF-8">
  <title>修改用户</title>
  <link rel="stylesheet" th:href="@{/static/css/user.css}" type="text/css"></link>
</head>
<body>
<h1>修改用户</h1>
<form th:action="@{/user}" method="post">
  <!--隐藏域:设置请求方式-->
  <input type="hidden" name="_method" value="PUT">
  <!--隐藏域:提交id-->
  <input type="hidden" name="id" th:value="${user.id}">

  <label>用户名:</label>
  <input type="text" name="username" th:value="${user.username}" required>

  <label>性别:</label>
  <select name="sex" required>
    <option value="">-- 请选择 --</option>
    <option value="1" th:field="${user.sex}"></option>
    <option value="0" th:field="${user.sex}"></option>
  </select>

  <label>邮箱:</label>
  <input type="email" name="email" th:value="${user.email}" required>

  <button type="submit">修改</button>
</form>
</body>
</html>

user_index.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
  <meta charset="UTF-8">
  <title>用户管理系统</title>
  <link rel="stylesheet" th:href="@{/static/css/user.css}" type="text/css"></link>
</head>
<body>
<div class="header">
  <h1>用户管理系统</h1>
</div>
<ul>
  <li><a class="active" th:href="@{/user}">用户列表</a></li>
</ul>
</body>
</html>

user_list.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
  <meta charset="UTF-8">
  <title>用户列表</title>
  <link rel="stylesheet" th:href="@{/static/css/user.css}" type="text/css"></link>
</head>
<body>
<div class="header">
  <h1>用户列表</h1>
</div>
<div class="add-button-wrapper">
  <a class="add-button" th:href="@{/toAdd}">新增用户</a>
</div>
<table>
  <thead>
  <tr>
    <th>编号</th>
    <th>用户名</th>
    <th>性别</th>
    <th>邮箱</th>
    <th>操作</th>
  </tr>
  </thead>
    <tbody>

      <tr th:each="user : ${users}">
        <td th:text="${user.id}"></td>
        <td th:text="${user.username}"></td>
        <td th:text="${user.sex == 1 ? '男' : '女'}"></td>
        <td th:text="${user.email}"></td>
        <td>
          <a th:href="@{'/user/' + ${user.id}}">修改</a>
          <a th:href="@{'/user/' + ${user.id}}" onclick = "del(event)">删除</a>
        </td>
      </tr>

    </tbody>
  </table>

  <div style="display: none">
    <form id="delForm" method="post">
      <input type="hidden" name="_method" value="delete">
    </form>
  </div>

<script>
  function del(event){
    //获取表单
    let delForm = document.getElementById("delForm");
    //设置form的action
    delForm.action = event.target.href;
    if(window.confirm("你确定要删除吗?")){
      //提交表单
      delForm.submit();
    }
    //阻止超链接的默认行为
    event.preventDefault();
  }
</script>
</body>
</html>

bean层的User类

package com.powernode.springmvc.bean;

public class User {

    private Long id;
    private String username;
    private Integer sex;
    private String email;

    public User() {
    }

    public User(Long id, String username, Integer sex, String email) {
        this.id = id;
        this.username = username;
        this.sex = sex;
        this.email = email;
    }


    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", sex=" + sex +
                ", email='" + email + '\'' +
                '}';
    }

    public Long getId() {
        return id;
    }

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

    public String getUsername() {
        return username;
    }

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

    public Integer getSex() {
        return sex;
    }

    public void setSex(Integer sex) {
        this.sex = sex;
    }

    public String getEmail() {
        return email;
    }

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

dao层的UserDao

package com.powernode.springmvc.dao;


import com.powernode.springmvc.bean.User;
import org.springframework.stereotype.Repository;

import java.util.ArrayList;
import java.util.List;

@Repository
public class UserDao {

    private static List<User> users = new ArrayList<>();

    static {
        //类加载是初始化数据
        //创建User对象
        User user1 = new User(1001L,"张三",1,"zhangsan@qq.com");
        User user2 = new User(1002L,"孙悟空",1,"sunwukong@qq.com");
        User user3 = new User(1003L,"猪八戒",1,"zhubajie@qq.com");
        User user4 = new User(1004L,"白骨精",0,"baigujing@qq.com");
        User user5 = new User(1005L,"沙和尚",1,"shaheshang@qq.com");
        //将User对象存储到List集合中
        users.add(user1);
        users.add(user2);
        users.add(user3);
        users.add(user4);
        users.add(user5);
    }

    public List<User> selectAll(){
        return users;
    }

    public Long generateId(){
        //使用Stream API
        Long maxId = users.stream().map(user -> user.getId()).reduce((id1, id2) -> id1 > id2 ? id1 : id2).get();
        return maxId + 1;
    }

    public void insert(User user){
        //生成id
        Long id = generateId();
        //给user对象id属性赋值
        user.setId(id);
        users.add(user);
    }

    public User selectById(Long id){
        // Stream API
        User user1 = users.stream().filter(user -> user.getId().equals(id)).findFirst().get();
        return user1;
    }

    public void update(User user){
        for (int i = 0; i < users.size(); i++) {
            if(users.get(i).getId().equals(user.getId())){
                users.set(i,user);
            }
        }
    }

    public void deleteById(Long id){
        for (int i = 0; i < users.size(); i++) {
            if(users.get(i).getId().equals(id)){
                users.remove(i);
            }
        }
    }

}

controller的UserController

package com.powernode.springmvc.controller;


import com.powernode.springmvc.bean.User;
import com.powernode.springmvc.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import java.sql.SQLOutput;
import java.util.List;

@Controller
public class UserController {

    @Autowired
    private UserDao userDao;

    @RequestMapping(value = "/user", method = RequestMethod.GET)
    public String list(Model model){
        //查询数据库,获取用户列表List集合
        List<User> users = userDao.selectAll();
        //将用户列表存储到request域当中
        System.out.println(users);
        model.addAttribute("users",users);

        //转发到视图
        return "user_list";
    }

    @RequestMapping(value = "/user", method = RequestMethod.POST)
    public String save(User user){
        //调用UserDao保存用户信息
        userDao.insert(user);
        //重定向到用户列表页面(重新让浏览器发送一次全新的请求,去请求列表页面)
        return "redirect:/user";
    }

    @RequestMapping(value = "/user/{id}", method = RequestMethod.GET)
    public String detail(@PathVariable("id") Long id, Model model){
        //通过id查找用户信息
        User user = userDao.selectById(id);
        //将用户信息存储到request域
        model.addAttribute("user", user);
        //转发到视图
        return "user_edit";
    }

    @RequestMapping(value = "/user", method = RequestMethod.PUT)
    public String modify(User user){
        //修改用户信息
        userDao.update(user);
        //重定向到列表信息
        return "redirect:/user";
    }

    @RequestMapping(value = "/user/{id}", method = RequestMethod.DELETE)
    public String del(@PathVariable("id") Long id){
        //调用dao删除用户
        userDao.deleteById(id);
        //重定向到列表
        return "redirect:/user";
    }
}

七、HttpMessageConverter

7.1、HttpMessageConverter

HttpMessageConverter是Spring MVC中非常重要的一个接口。翻译为:HTTP消息转换器。该接口下提供了很多实现类,不同的实现类有不同的转换方式。
在这里插入图片描述

7.1.1、什么是HTTP消息

HTTP消息其实就是HTTP协议。HTTP协议包括请求协议和响应协议。以下是一份HTTP POST请求协议:
在这里插入图片描述
以下是一份HTTP GET请求协议:
在这里插入图片描述
以下是一份HTTP响应协议:
在这里插入图片描述

7.1.2、转换器转换的是什么

转换的是HTTP协议Java程序中的对象之间的互相转换。请看下图:
在这里插入图片描述
上图是我们之前经常写的代码。请求体中的数据是如何转换成user对象的,底层实际上使用了HttpMessageConverter接口的其中一个实现类FormHttpMessageConverter。通过上图可以看出FormHttpMessageConverter是负责将请求协议转换为Java对象的。

再看下图:
在这里插入图片描述
上图的代码也是之前我们经常写的,Controller返回值看做逻辑视图名称,视图解析器将其转换成物理视图名称,生成视图对象,StringHttpMessageConverter负责将视图对象中的HTML字符串写入到HTTP协议的响应体中。最终完成响应。
通过上图可以看出StringHttpMessageConverter是负责将Java对象转换为响应协议的。
通过以上内容的学习,大家应该能够了解到HttpMessageConverter接口是用来做什么的了:
在这里插入图片描述
如上图所示:HttpMessageConverter接口的可以将请求协议转换成Java对象,也可以把Java对象转换为响应协议。
HttpMessageConverter是接口,SpringMVC帮我们提供了非常多而丰富的实现类。每个实现类都有自己不同的转换风格。
对于我们程序员来说,Spring MVC已经帮助我们写好了,我们只需要在不同的业务场景下,选择合适的HTTP消息转换器即可。
怎么选择呢?当然是通过SpringMVC为我们提供的注解,我们通过使用不同的注解来启用不同的消息转换器。

在HTTP消息转换器这一小节,我们重点要掌握的是两个注解两个类:

  • @ResponseBody
  • @RequestBody
  • ResponseEntity
  • RequestEntity

7.2、Spring MVC中的AJAX请求

Vue3+Thymeleaf+Axios发送AJAX请求:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>首页</title>
    <script th:src="@{/static/js/vue3.4.21.js}"></script>
    <script th:src="@{/static/js/axios.min.js}"></script>
</head>
<body>
<h1>首页</h1>
<hr>

<div id="app">
    <h1>{{message}}</h1>
    <button @click="getMessage">获取消息</button>
</div>

<script th:inline="javascript">
    Vue.createApp({
        data(){
            return {
                message : "这里的信息将被刷新"
            }
        },
        methods:{
            async getMessage(){
                try {
                    const response = await axios.get([[@{/}]] + 'hello'). //动态获取根目录:springmvc
                    this.message = response.data
                }catch (e) {
                    console.error(e)
                }
            }
        }
    }).mount("#app")
</script>

</body>
</html>

重点来了,Controller怎么写呢,之前我们都是传统的请求,Controller返回一个**逻辑视图名**,然后交给**视图解析器**解析。最后跳转页面。而AJAX请求是不需要跳转页面的,因为AJAX是页面局部刷新,以前我们在Servlet中使用**response.getWriter().print("message")**的方式响应。在Spring MVC中怎么办呢?当然,我们在Spring MVC中也可以使用Servlet原生API来完成这个功能,代码如下:

package com.powernode.springmvc.controller;

import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import java.io.IOException;

@Controller
public class HelloController {

    @RequestMapping(value = "/hello")
    public String hello(HttpServletResponse response) throws IOException {
        response.getWriter().print("hello");
        return null; //或者没有返回值 void
    }
}

注意:如果采用这种方式响应,则和 springmvc.xml 文件中配置的视图解析器没有关系,不走视图解析器了。

7.3、@ResponseBody(非常重要)

7.3.1、StringHttpMessageConverter

上面的AJAX案例,Controller的代码可以修改为:

package com.powernode.springmvc.controller;

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

@Controller
public class HelloController {

    @RequestMapping(value = "/hello")
    @ResponseBody
    public String hello(){
        // 由于你使用了 @ResponseBody 注解
        // 以下的return语句返回的字符串则不再是“逻辑视图名”了
        // 而是作为响应协议的响应体进行响应。
        return "hello";
    }
}

最核心需要理解的位置是:return “hello”;
这里的"hello"不是逻辑视图名了,而是作为响应体的内容进行响应。直接输出到浏览器客户端。
以上程序中使用的消息转换器是:StringHttpMessageConverter,为什么会启用这个消息转换器呢?因为你添加@ResponseBody这个注解。

通常AJAX请求需要服务器给返回一段JSON格式的字符串,可以返回JSON格式的字符串吗?当然可以,代码如下:

@Controller
public class HelloController {

    @RequestMapping(value = "/hello")
    @ResponseBody
    public String hello(){
        return "{\"username\":\"zhangsan\",\"password\":\"1234\"}";
    }
}

在这里插入图片描述

7.3.2、MappingJackson2HttpMessageConverter

启用MappingJackson2HttpMessageConverter消息转换器的步骤如下:

第一步:引入jackson依赖,可以将java对象转换为json格式字符串,它也可以将json格式字符串转换成java对象。

<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-databind</artifactId>
  <version>2.17.0</version>
</dependency>

第二步:开启注解驱动
这一步非常关键,开启注解驱动后,在HandlerAdapter中会自动装配一个消息转换器:MappingJackson2HttpMessageConverter

<mvc:annotation-driven/>

第三步:准备一个POJO(如User,自行准备)
第四步:控制器方法使用 @ResponseBody 注解标注(非常重要),控制器方法返回这个POJO对象。

@Controller
public class HelloController {

    @RequestMapping(value = "/hello")
    @ResponseBody
    public User hello(){
        User user = new User("zhangsan", "22222");
        return user;
    }
}

测试:
在这里插入图片描述
以上代码底层启用的就是 MappingJackson2HttpMessageConverter 消息转换器。他的功能很强大,可以将POJO对象转换成JSON格式的字符串,响应给前端。其实这个消息转换器MappingJackson2HttpMessageConverter本质上只是比StringHttpMessageConverter多了一个json字符串的转换,其他的还是一样。

7.4、@RestController

因为我们现代的开发方式都是基于AJAX方式的,因此 @ResponseBody 注解非常重要,很常用。
为了方便,Spring MVC中提供了一个注解 @RestController。这一个注解代表了:@Controller + @ResponseBody。
@RestController 标注在类上即可。被它标注的Controller中所有的方法上都会自动标注 @ResponseBody

@RestController
public class HelloController {

    @RequestMapping(value = "/hello")
    public User hello(){
        User user = new User("zhangsan", "22222");
        return user;
    }
}

测试:
在这里插入图片描述

7.5、@RequestBody

7.5.1、FormHttpMessageConverter

这个注解的作用是直接将请求体传递给Java程序,在Java程序中可以直接使用一个String类型的变量接收这个请求体的内容。

在没有使用这个注解的时候:

@RequestMapping("/save")
public String save(User user){
    // 执行保存的业务逻辑
    userDao.save(user);
    // 保存成功跳转到成功页面
    return "success";
}

当请求体提交的数据是:

username=zhangsan&password=1234&email=zhangsan@powernode.com

那么Spring MVC会自动使用 FormHttpMessageConverter消息转换器,将请求体转换成user对象。

当使用这个注解的时候:这个注解只能出现在方法的参数上。

@RequestMapping("/save")
public String save(@RequestBody String requestBodyStr){
    System.out.println("请求体:" + requestBodyStr);
    return "success";
}

Spring MVC仍然会使用 FormHttpMessageConverter消息转换器,将请求体直接以字符串形式传递给 requestBodyStr 变量。
测试输出结果:
在这里插入图片描述

7.5.2、MappingJackson2HttpMessageConverter

如果在请求体中提交的是一个JSON格式的字符串,这个JSON字符串传递给Spring MVC之后,能不能将JSON字符串转换成POJO对象呢?答案是可以的。
此时必须使用@RequestBody 注解来完成 。并且底层使用的消息转换器是:MappingJackson2HttpMessageConverter。实现步骤如下:

  • 第一步:引入jackson依赖
  • 第二步:开启注解驱动
  • 第三步:创建POJO类,将POJO类作为控制器方法的参数,并使用 @RequestBody 注解标注该参数
@RequestMapping("/send")
@ResponseBody
public String send(@RequestBody User user){
    System.out.println(user);
    System.out.println(user.getUsername());
    System.out.println(user.getPassword());
    return "success";
}

第四步:在请求体中提交json格式的数据

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>首页</title>
    <script th:src="@{/static/js/vue3.4.21.js}"></script>
    <script th:src="@{/static/js/axios.min.js}"></script>
</head>
<body>

<div id="app">
    <button @click="sendJSON">通过POST请求发送JSON给服务器</button>
    <h1>{{message}}</h1>
</div>

<script>
    let jsonObj = {"username":"zhangsan", "password":"1234"}

    Vue.createApp({
        data(){
            return {
                message:""
            }
        },
        methods: {
            async sendJSON(){
                console.log("sendjson")
                try{
                    const res = await axios.post('/springmvc/send', JSON.stringify(jsonObj), {
                        headers : {
                            "Content-Type" : "application/json"
                        }
                    })
                    this.message = res.data
                }catch(e){
                    console.error(e)
                }
            }
        }
    }).mount("#app")
</script>

</body>
</html>

测试结果:
在这里插入图片描述

7.6、RequestEntity

RequestEntity不是一个注解,是一个普通的类。这个类的实例封装了整个请求协议:包括请求行、请求头、请求体所有信息。
出现在控制器方法的参数上:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>首页</title>
    <script th:src="@{/static/js/vue3.4.21.js}"></script>
    <script th:src="@{/static/js/axios.min.js}"></script>
</head>
<body>

<div id="app">
    <button @click="sendJSON">通过POST请求发送JSON给服务器</button>
    <h1>{{message}}</h1>
</div>

<script>
    let jsonObj = {"username":"zhangsan", "password":"1234"}

    Vue.createApp({
        data(){
            return {
                message:""
            }
        },
        methods: {
            async sendJSON(){
                console.log("sendjson")
                try{
                    const res = await axios.post('/springmvc/send', JSON.stringify(jsonObj), {
                        headers : {
                            "Content-Type" : "application/json"
                        }
                    })
                    this.message = res.data
                }catch(e){
                    console.error(e)
                }
            }
        }
    }).mount("#app")
</script>

</body>
</html>
@RequestMapping("/send")
@ResponseBody
public String send(RequestEntity<User> requestEntity){
    System.out.println("请求方式:" + requestEntity.getMethod());
    System.out.println("请求URL:" + requestEntity.getUrl());
    HttpHeaders headers = requestEntity.getHeaders();
    System.out.println("请求的内容类型:" + headers.getContentType());
    System.out.println("请求头:" + headers);

    User user = requestEntity.getBody();
    System.out.println(user);
    System.out.println(user.getUsername());
    System.out.println(user.getPassword());
    return "success";
}

测试结果:
在这里插入图片描述
在实际的开发中,如果你需要获取更详细的请求协议中的信息。可以使用RequestEntity

7.7、ResponseEntity

ResponseEntity不是注解,是一个类。用该类的实例可以封装响应协议,包括:状态行、响应头、响应体。也就是说:如果你想定制属于自己的响应协议,可以使用该类。
假如我要完成这样一个需求:前端提交一个id,后端根据id进行查询,如果返回null,请在前端显示404错误。如果返回不是null,则输出返回的user。

@Controller
public class UserController {
     
    @GetMapping("/users/{id}")
    public ResponseEntity<User> getUserById(@PathVariable Long id) {
        User user = userService.getUserById(id);
        if (user == null) {
            return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null);
        } else {
            return ResponseEntity.ok(user);
        }
    }
}

测试:当用户不存在时
在这里插入图片描述
测试:当用户存在时
在这里插入图片描述

八、文件上传与下载

8.1、文件上传

浏览器端向服务器端发送文件,最终服务器将文件保存到服务器上。(本质上还是IO流,读文件和写文件)
使用SpringMVC6版本,不需要添加以下依赖:

<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.5</version>
</dependency>

前端页面:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>文件上传</title>
</head>
<body>

<!--文件上传表单-->
<form th:action="@{/file/up}" method="post" enctype="multipart/form-data">
    文件:<input type="file" name="fileName"/><br>
    <input type="submit" value="上传">
</form>

</body>
</html>

重点是:form表单采用post请求,enctype是multipart/form-data,并且上传组件是:type=“file”。

web.xml文件:

<!--前端控制器-->
<servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:springmvc.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
    <multipart-config>
        <!--设置单个支持最大文件的大小-->
        <max-file-size>102400</max-file-size>
        <!--设置整个表单所有文件上传的最大值-->
        <max-request-size>102400</max-request-size>
        <!--设置最小上传文件大小-->
        <file-size-threshold>0</file-size-threshold>
    </multipart-config>
</servlet>
<servlet-mapping>
    <servlet-name>dispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

重点:在DispatcherServlet配置时,添加 multipart-config 配置信息。(这是Spring6,如果是Spring5,则不是这样配置,而是在springmvc.xml文件中配置:CommonsMultipartResolver)
SpringMVC6中把这个类已经删除了。废弃了。
Controller中的代码:

@Controller
public class FileController {

    @RequestMapping(value = "/file/up", method = RequestMethod.POST)
    public String fileUp(@RequestParam("fileName") MultipartFile multipartFile, HttpServletRequest request) throws IOException {
        String name = multipartFile.getName();
        System.out.println(name);
        // 获取文件名
        String originalFilename = multipartFile.getOriginalFilename();
        System.out.println(originalFilename);
        // 将文件存储到服务器中
        // 获取输入流
        InputStream in = multipartFile.getInputStream();
        // 获取上传之后的存放目录
        File file = new File(request.getServletContext().getRealPath("/upload"));
        // 如果服务器目录不存在则新建
        if(!file.exists()){
            file.mkdirs();
        }
        // 开始写
        //BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(file.getAbsolutePath() + "/" + originalFilename));
        // 可以采用UUID来生成文件名,防止服务器上传文件时产生覆盖
        BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(file.getAbsolutePath() + "/" + UUID.randomUUID().toString() + originalFilename.substring(originalFilename.lastIndexOf("."))));
        byte[] bytes = new byte[1024 * 100];
        int readCount = 0;
        while((readCount = in.read(bytes)) != -1){
            out.write(bytes,0,readCount);
        }
        // 刷新缓冲流
        out.flush();
        // 关闭流
        in.close();
        out.close();

        return "ok";
    }

}

最终测试结果:
在这里插入图片描述
建议:上传文件时,文件起名采用UUID。以防文件覆盖。

8.2、文件下载

<!--文件下载-->
<a th:href="@{/download}">文件下载</a>

文件下载核心程序,使用ResponseEntity:

@GetMapping("/download")
public ResponseEntity<byte[]> downloadFile(HttpServletResponse response, HttpServletRequest request) throws IOException {
    File file = new File(request.getServletContext().getRealPath("/upload") + "/1.jpeg");
    // 创建响应头对象
    HttpHeaders headers = new HttpHeaders();
    // 设置响应内容类型
    headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
    // 设置下载文件的名称
    headers.setContentDispositionFormData("attachment", file.getName());

    // 下载文件
    ResponseEntity<byte[]> entity = new ResponseEntity<byte[]>(Files.readAllBytes(file.toPath()), headers, HttpStatus.OK);
    return entity;
}

九、异常处理器

9.1、什么是异常处理器

Spring MVC在处理器方法执行过程中出现了异常,可以采用异常处理器进行应对。
一句话概括异常处理器作用:处理器方法执行过程中出现了异常,跳转到对应的视图,在视图上展示友好信息。

SpringMVC为异常处理提供了一个接口:HandlerExceptionResolver
在这里插入图片描述
核心方法是:resolveException。
该方法用来编写具体的异常处理方案。返回值ModelAndView,表示异常处理完之后跳转到哪个视图。

HandlerExceptionResolver 接口有两个常用的默认实现:

  • DefaultHandlerExceptionResolver
  • SimpleMappingExceptionResolver

9.2、默认的异常处理器

DefaultHandlerExceptionResolver 是默认的异常处理器。
核心方法:
在这里插入图片描述
当请求方式和处理方式不同时,DefaultHandlerExceptionResolver的默认处理态度是:
在这里插入图片描述

9.3、自定义的异常处理器

自定义异常处理器需要使用:SimpleMappingExceptionResolver
自定义异常处理机制有两种语法:

  • 通过XML配置文件
  • 通过注解

9.3.1、配置文件方式

<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
    <property name="exceptionMappings">
        <props>
            <!--用来指定出现异常后,跳转的视图-->
            <prop key="java.lang.Exception">tip</prop>
        </props>
    </property>
    <!--将异常信息存储到request域,value属性用来指定存储时的key。-->
    <property name="exceptionAttribute" value="e"/>
</bean>

在视图页面上展示异常信息:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>出错了</title>
</head>
<body>
<h1>出错了,请联系管理员!</h1>
<div th:text="${e}"></div>
</body>
</html>

在这里插入图片描述

9.3.2、注解方式

@ControllerAdvice
public class ExceptionController {

    @ExceptionHandler
    public String tip(Exception e, Model model){
        model.addAttribute("e", e);
        return "tip";
    }
}

十、拦截器

10.1、拦截器概述

拦截器(Interceptor)类似于过滤器(Filter)
Spring MVC的拦截器作用是在请求到达控制器之前或之后进行拦截,可以对请求和响应进行一些特定的处理。
拦截器可以用于很多场景下:

  1. 登录验证:对于需要登录才能访问的网址,使用拦截器可以判断用户是否已登录,如果未登录则跳转到登录页面。
  2. 权限校验:根据用户权限对部分网址进行访问控制,拒绝未经授权的用户访问。
  3. 请求日志:记录请求信息,例如请求地址、请求参数、请求时间等,用于排查问题和性能优化。
  4. 更改响应:可以对响应的内容进行修改,例如添加头信息、调整响应内容格式等。

拦截器和过滤器的区别在于它们的作用层面不同。

  • 过滤器更注重在请求和响应的流程中进行处理,可以修改请求和响应的内容,例如设置编码和字符集、请求头、状态码等。
  • 拦截器则更加侧重于对控制器进行前置或后置处理,在请求到达控制器之前或之后进行特定的操作,例如打印日志、权限验证等。

Filter、Servlet、Interceptor、Controller的执行顺序:
在这里插入图片描述

10.2、拦截器的创建与基本配置

10.2.1、定义拦截器

实现org.springframework.web.servlet.HandlerInterceptor 接口,共有三个方法可以进行选择性的实现:

  • preHandle:处理器方法调用之前执行
    • 只有该方法有返回值,返回值是布尔类型,true放行,false拦截。
  • postHandle:处理器方法调用之后执行
  • afterCompletion:渲染完成后执行

10.2.2、拦截器基本配置

在springmvc.xml文件中进行如下配置:
第一种方式:

<mvc:interceptors>
    <bean class="com.powernode.springmvc.interceptors.Interceptor1"/>
</mvc:interceptors>

第二种方式:

<mvc:interceptors>
    <ref bean="interceptor1"/>
</mvc:interceptors>

第二种方式的前提:

前提1:包扫描
在这里插入图片描述
前提2:使用 @Component 注解进行标注
在这里插入图片描述
注意:对于这种基本配置来说,拦截器是拦截所有请求的。

10.2.3、拦截器部分源码分析

10.2.3.1、方法执行顺序的源码分析
public class DispatcherServlet extends FrameworkServlet {
    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        // 调用所有拦截器的 preHandle 方法
        if (!mappedHandler.applyPreHandle(processedRequest, response)) {
            return;
        }
        // 调用处理器方法
        mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
        // 调用所有拦截器的 postHandle 方法
        mappedHandler.applyPostHandle(processedRequest, response, mv);
        // 处理视图
        processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    }

    private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
			@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
			@Nullable Exception exception) throws Exception {
        // 渲染页面
        render(mv, request, response);
        // 调用所有拦截器的 afterCompletion 方法
        mappedHandler.triggerAfterCompletion(request, response, null);
    }
}
10.2.3.2、拦截与放行的源码分析
public class DispatcherServlet extends FrameworkServlet {
    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        // 调用所有拦截器的 preHandle 方法
        if (!mappedHandler.applyPreHandle(processedRequest, response)) {
            // 如果 mappedHandler.applyPreHandle(processedRequest, response) 返回false,以下的return语句就会执行
            return;
        }
    }
}
public class HandlerExecutionChain {
    boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
		for (int i = 0; i < this.interceptorList.size(); i++) {
			HandlerInterceptor interceptor = this.interceptorList.get(i);
			if (!interceptor.preHandle(request, response, this.handler)) {
				triggerAfterCompletion(request, response, null);
                // 如果 interceptor.preHandle(request, response, this.handler) 返回 false,以下的 return false;就会执行。
				return false;
			}
			this.interceptorIndex = i;
		}
		return true;
	}
}

10.3、拦截器的高级配置

采用以上基本配置方式,拦截器是拦截所有请求路径的。如果要针对某些路径进行拦截,某些路径不拦截,可以采用高级配置:

<mvc:interceptors>
    <mvc:interceptor>
        <!--拦截所有路径-->
        <mvc:mapping path="/**"/>
        <!--除 /test 路径之外-->
        <mvc:exclude-mapping path="/test"/>
        <!--拦截器-->
        <ref bean="interceptor1"/>
    </mvc:interceptor>
</mvc:interceptors>

以上的配置表示,除 /test 请求路径之外,剩下的路径全部拦截。

10.3、拦截器的执行顺序

10.3.1、执行顺序

10.3.1.1、如果所有拦截器preHandle都返回true

按照springmvc.xml文件中配置的顺序,自上而下调用 preHandle:

<mvc:interceptors>
    <ref bean="interceptor1"/>
    <ref bean="interceptor2"/>
</mvc:interceptors>

执行顺序:
在这里插入图片描述

10.3.1.2、如果其中一个拦截器preHandle返回false
<mvc:interceptors>
    <ref bean="interceptor1"/>
    <ref bean="interceptor2"/>
</mvc:interceptors>

如果interceptor2的preHandle返回false,执行顺序:
在这里插入图片描述
规则:只要有一个拦截器preHandle返回false,任何postHandle都不执行。但返回false的拦截器的前面的拦截器按照逆序执行afterCompletion

10.3.2、源码分析

DispatcherServlet和 HandlerExecutionChain的部分源码:

public class DispatcherServlet extends FrameworkServlet {
    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        // 按照顺序执行所有拦截器的preHandle方法
        if (!mappedHandler.applyPreHandle(processedRequest, response)) {
            return;
        }
        // 执行处理器方法
        mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
        // 按照逆序执行所有拦截器的 postHanle 方法
        mappedHandler.applyPostHandle(processedRequest, response, mv);
        // 处理视图
        processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    }

    private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
			@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
			@Nullable Exception exception) throws Exception {
        // 渲染视图
        render(mv, request, response);
        // 按照逆序执行所有拦截器的 afterCompletion 方法
        mappedHandler.triggerAfterCompletion(request, response, null);
    }
}
public class HandlerExecutionChain {
    // 顺序执行 preHandle
    boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
        for (int i = 0; i < this.interceptorList.size(); i++) {
            HandlerInterceptor interceptor = this.interceptorList.get(i);
            if (!interceptor.preHandle(request, response, this.handler)) {
                // 如果其中一个拦截器preHandle返回false
                // 将该拦截器前面的拦截器按照逆序执行所有的afterCompletion
                triggerAfterCompletion(request, response, null);
                return false;
            }
            this.interceptorIndex = i;
        }
        return true;
	}
    // 逆序执行 postHanle
    void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv) throws Exception {
        for (int i = this.interceptorList.size() - 1; i >= 0; i--) {
            HandlerInterceptor interceptor = this.interceptorList.get(i);
            interceptor.postHandle(request, response, this.handler, mv);
        }
	}
    // 逆序执行 afterCompletion
	void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) {
		for (int i = this.interceptorIndex; i >= 0; i--) {
			HandlerInterceptor interceptor = this.interceptorList.get(i);
			try {
				interceptor.afterCompletion(request, response, this.handler, ex);
			}
			catch (Throwable ex2) {
				logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
			}
		}
	}
}

十一、全注解开发

11.1、web.xml文件的替代

11.1.1、Servlet3.0新特性

Servlet3.0新特性:web.xml文件可以不写了。
在Servlet3.0的时候,规范中提供了一个接口:
在这里插入图片描述
服务器在启动的时候会自动从容器中找 ServletContainerInitializer接口的实现类,自动调用它的onStartup方法来完成Servlet上下文的初始化。

在Spring3.1版本的时候,提供了这样一个类,实现以上的接口:
在这里插入图片描述
它的核心方法如下:
在这里插入图片描述
可以看到在服务器启动的时候,它会去加载所有实现WebApplicationInitializer接口的类:
在这里插入图片描述
这个接口下有一个子类是我们需要的:AbstractAnnotationConfigDispatcherServletInitializer
在这里插入图片描述
当我们编写类继承AbstractAnnotationConfigDispatcherServletInitializer之后,web服务器在启动的时候会根据它来初始化Servlet上下文。
在这里插入图片描述

11.1.2、编写WebAppInitializer

以下这个类就是用来代替web.xml文件的:

public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    /**
     * Spring的配置
     * @return
     */
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[]{SpringConfig.class};
    }

    /**
     * SpringMVC的配置
     * @return
     */
    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{SpringMVCConfig.class};
    }

    /**
     * 用于配置 DispatcherServlet 的映射路径
     * @return
     */
    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }

    /**
     * 配置过滤器
     * @return
     */
    @Override
    protected Filter[] getServletFilters() {
        CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
        characterEncodingFilter.setEncoding("UTF-8");
        characterEncodingFilter.setForceRequestEncoding(true);
        characterEncodingFilter.setForceResponseEncoding(true);
        HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();
        return new Filter[]{characterEncodingFilter, hiddenHttpMethodFilter};
    }
}

Spring配置如下:

@Configuration // 使用该注解指定这是一个配置类
public class SpringConfig {
}

SpringMVC配置如下:

@Configuration
public class SpringMVCConfig {
}

11.2、Spring MVC的配置

WebAppInitializer类:

//在这个配置类编写的其实就是web.xml文件中的配置
//用来标注这个类当作配置文件
@Configuration
public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {


    //配置Spring
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[0];
    }

    //配置Springmvc
    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{SpringMvcConfig.class};
    }

    //用来配置DispatcherServlet的<url-pattern>
    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }

    //配置过滤器
    @Override
    protected Filter[] getServletFilters() {
        //配置字符编码过滤器
        CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
        characterEncodingFilter.setEncoding("UTF-8");
        characterEncodingFilter.setForceResponseEncoding(true);
        characterEncodingFilter.setForceRequestEncoding(true);

        //配置HiddenHttpMethodFilter
        HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();

        return new Filter[]{characterEncodingFilter,hiddenHttpMethodFilter};
    }
}

SpringMvcConfig类:

//以下相当于是 springmvc.xml 配置文件
@Configuration
//组件扫描
@ComponentScan("com.powernodes.springmvc.controller")
//开启注解驱动
@EnableWebMvc
public class SpringMvcConfig implements WebMvcConfigurer {

    //视图解析器(以下三个方法)
    @Bean
    public ThymeleafViewResolver getViewResolver(SpringTemplateEngine springTemplateEngine) {
        ThymeleafViewResolver resolver = new ThymeleafViewResolver();
        resolver.setTemplateEngine(springTemplateEngine);
        resolver.setCharacterEncoding("UTF-8");
        resolver.setOrder(1);
        return resolver;
    }

    @Bean
    public SpringTemplateEngine templateEngine(ITemplateResolver iTemplateResolver) {
        SpringTemplateEngine templateEngine = new SpringTemplateEngine();
        templateEngine.setTemplateResolver(iTemplateResolver);
        return templateEngine;
    }

    @Bean
    public ITemplateResolver templateResolver(ApplicationContext applicationContext) {
        SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
        resolver.setApplicationContext(applicationContext);
        resolver.setPrefix("/WEB-INF/thymeleaf/");
        resolver.setSuffix(".html");
        resolver.setTemplateMode(TemplateMode.HTML);
        resolver.setCharacterEncoding("UTF-8");
        resolver.setCacheable(false);//开发时关闭缓存,改动即可生效
        return resolver;
    }

    //开启静态资源处理,开启默认的Servlet处理
    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }

    //视图控制器
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/test").setViewName("test");
    }

    //异常处理器
    @Override
    public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
        //可以配置多个异常处理器,这是其中一个
        SimpleMappingExceptionResolver resolver = new SimpleMappingExceptionResolver();
        //设置其中的exceptionMappings属性
        Properties prop = new Properties();
        prop.setProperty("java.lang.Exception", "tip");
        resolver.setExceptionMappings(prop);
        //设置其中的exceptionAttribute属性
        resolver.setExceptionAttribute("e");
        //将异常处理器添加到List集合中
        resolvers.add(resolver);
    }

    //拦截器
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        MyInterceptor myInterceptor = new MyInterceptor();
        registry.addInterceptor(myInterceptor).addPathPatterns("/**").excludePathPatterns("/test");
    }
}

MyInterceptor类:

public class MyInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("afterCompletion");
    }
}
  • 18
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值