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.htmlreturn "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的actiondelForm.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;}@Overridepublic 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 APILong maxId = users.stream().map(user -> user.getId()).reduce((id1, id2) -> id1 > id2 ? id1 : id2).get();return maxId + 1;}public void insert(User user){//生成idLong id = generateId();//给user对象id属性赋值user.setId(id);users.add(user);}public User selectById(Long id){// Stream APIUser 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 {@Autowiredprivate 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'). //动态获取根目录:springmvcthis.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")@ResponseBodypublic String hello(){// 由于你使用了 @ResponseBody 注解// 以下的return语句返回的字符串则不再是“逻辑视图名”了// 而是作为响应协议的响应体进行响应。return "hello";}
}

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

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

@Controller
public class HelloController {@RequestMapping(value = "/hello")@ResponseBodypublic 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")@ResponseBodypublic 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 {@ExceptionHandlerpublic 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 {// 顺序执行 preHandleboolean 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// 将该拦截器前面的拦截器按照逆序执行所有的afterCompletiontriggerAfterCompletion(request, response, null);return false;}this.interceptorIndex = i;}return true;}// 逆序执行 postHanlevoid 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);}}// 逆序执行 afterCompletionvoid 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*/@Overrideprotected Class<?>[] getRootConfigClasses() {return new Class[]{SpringConfig.class};}/*** SpringMVC的配置* @return*/@Overrideprotected Class<?>[] getServletConfigClasses() {return new Class[]{SpringMVCConfig.class};}/*** 用于配置 DispatcherServlet 的映射路径* @return*/@Overrideprotected String[] getServletMappings() {return new String[]{"/"};}/*** 配置过滤器* @return*/@Overrideprotected 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@Overrideprotected Class<?>[] getRootConfigClasses() {return new Class[0];}//配置Springmvc@Overrideprotected Class<?>[] getServletConfigClasses() {return new Class[]{SpringMvcConfig.class};}//用来配置DispatcherServlet的<url-pattern>@Overrideprotected String[] getServletMappings() {return new String[]{"/"};}//配置过滤器@Overrideprotected Filter[] getServletFilters() {//配置字符编码过滤器CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();characterEncodingFilter.setEncoding("UTF-8");characterEncodingFilter.setForceResponseEncoding(true);characterEncodingFilter.setForceRequestEncoding(true);//配置HiddenHttpMethodFilterHiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();return new Filter[]{characterEncodingFilter,hiddenHttpMethodFilter};}
}

SpringMvcConfig类:

//以下相当于是 springmvc.xml 配置文件
@Configuration
//组件扫描
@ComponentScan("com.powernodes.springmvc.controller")
//开启注解驱动
@EnableWebMvc
public class SpringMvcConfig implements WebMvcConfigurer {//视图解析器(以下三个方法)@Beanpublic ThymeleafViewResolver getViewResolver(SpringTemplateEngine springTemplateEngine) {ThymeleafViewResolver resolver = new ThymeleafViewResolver();resolver.setTemplateEngine(springTemplateEngine);resolver.setCharacterEncoding("UTF-8");resolver.setOrder(1);return resolver;}@Beanpublic SpringTemplateEngine templateEngine(ITemplateResolver iTemplateResolver) {SpringTemplateEngine templateEngine = new SpringTemplateEngine();templateEngine.setTemplateResolver(iTemplateResolver);return templateEngine;}@Beanpublic 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处理@Overridepublic void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {configurer.enable();}//视图控制器@Overridepublic void addViewControllers(ViewControllerRegistry registry) {registry.addViewController("/test").setViewName("test");}//异常处理器@Overridepublic 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);}//拦截器@Overridepublic void addInterceptors(InterceptorRegistry registry) {MyInterceptor myInterceptor = new MyInterceptor();registry.addInterceptor(myInterceptor).addPathPatterns("/**").excludePathPatterns("/test");}
}

MyInterceptor类:

public class MyInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {System.out.println("preHandle");return true;}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {System.out.println("postHandle");}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {System.out.println("afterCompletion");}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/395328.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数学建模--蒙特卡洛算法之电子管更换刀片寿命问题

目录 1.电子管问题重述 2.电子管问题分析 3.电子管问题求解 4.刀片问题重述 5.刀片问题分析 6.刀片问题求解 1.电子管问题重述 某设备上安装有4只型号规格完全相同的电子管&#xff0c;已知电子管寿命服从100&#xff5e;200h之间的均匀分布&#xff0e; 只要有一个电子管…

在线办公小程序的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;通知公告管理&#xff0c;员工管理&#xff0c;部门信息管理&#xff0c;职位信息管理&#xff0c;会议记录管理&#xff0c;待办事项管理&#xff0c;系统管理 微信端账号功能包括&#xff1a;系统首…

Android经典实战之如何获取图片的经纬度以及如何根据经纬度获取对应的地点名称

本文首发于公众号“AntDream”&#xff0c;欢迎微信搜索“AntDream”或扫描文章底部二维码关注&#xff0c;和我一起每天进步一点点 在Android中&#xff0c;可以通过以下步骤获取图片的经纬度信息以及根据这些经纬度信息获取对应的地点名称。这里主要涉及两部分&#xff1a;从…

从0开始搭建vue + flask 旅游景点数据分析系统(七):可视化前后端对接实现

这一期继续编写flask后端&#xff0c;并且完成echarts折线图、柱状图和饼图的对接。 1 新增一些依赖 pip install Flask-SQLAlchemy Flask-Marshmallow pymysql修改 init.py文件&#xff0c;下面给出完整代码&#xff1a; from flask import Flask from flask_sqlalchemy im…

leetcode70_爬楼梯

思路 动态规划 爬到第n阶楼梯的方法数为&#xff1a;第n-1阶楼梯的方法数 第n-2阶楼梯的方法数 func climbStairs(n int) int {if n < 2 {return 1}dp : make([]int, n1)dp[1] 1dp[2] 2for i:3; i<n; i {dp[i] dp[i-1] dp[i-2]}return dp[n] }

Kubernetes中的PV)和 PVC深度剖析

在容器化的世界里&#xff0c;持久化存储一直是一个重要且复杂的问题。Kubernetes&#xff08;以下简称K8s&#xff09;为了解决容器中的数据持久化问题&#xff0c;提出了Persistent Volume&#xff08;PV&#xff09;和Persistent Volume Claim&#xff08;PVC&#xff09;这…

大数据信用报告查询哪家平台的比较好?

相信在搜索大数据信用的你&#xff0c;已经因为大数据信用不好受到了挫折&#xff0c;想详细了解一下自己的大数据信用&#xff0c;但是找遍了网络上的平台之后才发现&#xff0c;很多平台都只提供查询服务&#xff0c;想要找一个专业的平台查询和讲解很困难。下面本文就为大家…

LeetCode 150.逆波兰表达式求值

LeetCode 150.逆波兰表达式求值 思路&#x1f9d0;&#xff1a; 用栈存储该字符串&#xff0c;如果遇到数字就入栈&#xff0c;遇到符号就将数字出栈计算后再入栈&#xff0c;当整个字符串遍历完后&#xff0c;栈顶值就是该表达式的值。 代码&#x1f50e;&#xff1a; class …

【OpenCV C++20 学习笔记】范围阈值操作

范围阈值操作 原理HSV颜色空间RGB与HSV颜色空间之间的转换 代码实现颜色空间的转换范围阈值操作 原理 HSV颜色空间 HSV(色相hue, 饱和度sarturation, 色明度value)颜色空间与RGB颜色空间相似。hue色相通道代表颜色类型&#xff1b;saturation饱和度通道代表颜色的饱和度&…

MySQL-MHA高可用配置及故障切换

目录 案例搭建 1&#xff1a;所有服务器关闭防火墙 2&#xff1a;设置hosts文件 3&#xff1a;安装 MySQL 数据库 4&#xff1a;修改参数 5&#xff1a;安装 MHA 软件 6&#xff1a;配置无密码认证 7&#xff1a;配置 MHA 8&#xff1a;模拟 master 故障 MHA(MasterHi…

【Python修改所有可执行程序的图标】

孩子还小&#xff0c;不懂事写着玩的 警告&#xff1a;请谨慎使用。该程序会修改全系统所有可执行文件图标(其实就是注册表)&#xff0c;在重新开机后生效 演示&#xff1a; 把应用程序图标改为记事本&#x1f5d2; 原理&#xff1a; Windows 操作系统通过注册表来存储和管…

不懂期权怎么交易?看这个例子就懂了

期权就是股票&#xff0c;唯一区别标的物上证指数&#xff0c;会看大盘吧&#xff0c;期权交易两个方向认购做多&#xff0c;认沽做空&#xff0c;双向t0交易没了&#xff0c;跟期货一样&#xff0c;对的&#xff0c;玩的也是合约&#xff0c;唯一区别没有保证金不会爆仓&#…

CAD二次开发IFoxCAD框架系列(15)- IFox的介绍和初始化

背景介绍 因为本人在光伏行业从事软件研发&#xff0c;最近我一直在做CAD方面技术的预研和探索。在研究CAD的SDK的时候&#xff0c;发现很多写法比较繁琐&#xff0c;所以一直想封装工具&#xff0c;提高开发效率&#xff0c;也做了很多的CAD工具的封装&#xff0c;大家可以看…

一文读懂如何选择视频孪生三维建模方式及建模精度等级

导言/INTRODUCTION 三维模型是视频孪生应用的基础&#xff0c;建模方式与模型精度将直接影响到最终孪生场景的呈现和应用效果。各种建模方式和模型精度在成本、场景还原真实度、实施周期方面都有自己的特点&#xff0c;因而有着各自的优劣势和适用场景&#xff0c;同一场景可能…

鸿蒙开发5.0【应用异常处理】运维

应用异常处理 介绍 本示例介绍了通过应用事件打点hiAppEvent获取上一次应用异常信息的方法&#xff0c;主要分为应用崩溃、应用卡死两种。 效果图预览 使用说明 点击构建应用崩溃事件&#xff0c;3s之后应用退出&#xff0c;然后打开应用进入应用异常页面&#xff0c;隔1mi…

pdf怎么加密码怎么设置密码?pdf加密码的几种设置方法

在数字化时代&#xff0c;信息的保密性与安全性日益成为我们不可忽视的重要环节。尤其对于包含敏感信息或个人隐私的PDF文档而言&#xff0c;保护其免受未授权访问的侵扰显得尤为重要。通过为PDF文档设置密码保护&#xff0c;我们能够筑起一道坚实的防线&#xff0c;确保只有拥…

Java常见面试题-13-FastDFS

文章目录 FastDFS 是什么&#xff1f;FastDFS 组成FastDFS 的流程FastDFS 如何现在组内的多个 storage server 的数据同步&#xff1f; FastDFS 是什么&#xff1f; FastDFS 是一个开源的轻量级分布式文件系统&#xff0c;它可以对文件进行管理&#xff0c;功能包括&#xff1…

创意指南丨VR游览沉浸式空间体验

欢迎来到我们制作的VR幻想世界。玩家的起点是一条蓝色水晶大道&#xff0c;让我们一起探索这个如梦似幻的境地。 在这条大道的两侧&#xff0c;漂浮着半透明的大水晶水母。它们轻盈地在空中飘动&#xff0c;仿佛在欢迎我们的到来。这条道路上方&#xff0c;一个个半圆环不停地…

【数据结构】Map与Set

前言 前两篇文章我们研究了二叉搜索树与哈希表的结构与特点&#xff0c;他们二者是Map与Set这两个接口实现的底层结构&#xff0c;他们利用了搜索树与哈希表查找效率高这一特点&#xff0c;是一种专门用来进行搜索操作的容器或数据结构。本篇文章就让我们一起来梳理这两个接口的…

基于Hadoop的国内手机销售大数据分析与可视化研究【百万数据集】

文章目录 有需要本项目的代码或文档以及全部资源&#xff0c;或者部署调试可以私信博主项目介绍 绪论研究背景研究目的研究意义 相关技术理论介绍Hadoop相关理论HIve数据仓库flume组件介绍sqoop组件介绍Pyecharts介绍 数据来源及处理数据介绍数据预处理 Hadoop集群搭建Hadoop全…