SpringMVC基础详解

文章目录

一、SpringMVC简介

1、什么是MVC

  • MVC是一种软件架构模式(是一种软件架构设计思想,不止Java开发中用到,其它语言也需要用到),它将应用分为三块:
    • M:Model(模型),负责业务处理及数据的收集
    • V:View(视图),负责数据的展示
    • C:Controller(控制器),负责调度。它是一个调度中心,它来决定什么时候调用Model来处理业务,什么时候调用View视图来展示数据

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

2、MVC架构模式与三层模型的区别

什么是三层模型

  • 三层模型就是由Controller控制器和View视图组成的表现层,将Model数据模型拆封为业务层和与数据库交互的持久层

MVC架构模式与三层模型的区别?

  • MVC和三层模型都采用了分层结构来设计应用程序,都是降低耦合度,提高扩展力,提高组件复用性
  • 区别在于他们的关注点不同
    • 三层模型更加关注业务逻辑组件的划分
    • MVC架构模式关注的是整个应用程序的层次关系和分离思想
  • 现代的开发方式大部分都是MVC架构模式结合三层模型一起用

3、什么是SpringMVC

  • SpringMVC是一个实现了MVC架构模式的Web框架,底层基于Servlet实现
  • SpringMVC已经将MVC架构模式实现了,因此只要我们是基于SpringMVC框架写代码
  • Spring框架中有一个子项目叫做Spring Web,Spring Web子项目当中包含很多模块
    • Spring MVC
    • Spring WebFlux
    • Spring Web Services
    • Spring Web Flow
    • Spring WebSocket
    • Spring Web Services Client
  • Spring架构图如下,其中Web中的servlet指的就是Spring MVC

二、HelloWorld程序

1、pom文件

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.xc</groupId>
    <artifactId>springmvc-xml</artifactId>
    <version>1.0-SNAPSHOT</version>

    <packaging>war</packaging>

    <dependencies>
        <!--springmvc-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.3.1</version>
        </dependency>
        <!--servletAPI-->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <scope>provided</scope>
        </dependency>
        <!--spring5和thymeleaf整合-->
        <dependency>
            <groupId>org.thymeleaf</groupId>
            <artifactId>thymeleaf-spring5</artifactId>
            <version>3.0.11.RELEASE</version>
        </dependency>
    </dependencies>

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

</project>

2、springmvc.xml

  • 组件扫描。spring扫描这个包中的类,将这个包中的类实例化并纳入IoC容器的管理
  • 视图解析器。视图解析器(View Resolver)的作用主要是将Controller方法返回的逻辑视图名称解析成实际的视图对象。视图解析器将解析出的视图对象返回给DispatcherServlet,并最终由DispatcherServlet将该视图对象转化为响应结果,呈现给用户
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.xc.controller"/>
    <!--视图解析器-->
    <bean id="thymeleafViewResolver" class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
        <!--作用于视图渲染的过程中,可以设置视图渲染后输出时采用的编码字符集-->
        <property name="characterEncoding" value="UTF-8"/>
        <!--如果配置多个视图解析器,它来决定优先使用哪个视图解析器,它的值越小优先级越高-->
        <property name="order" value="1"/>
        <!--当 ThymeleafViewResolver 渲染模板时,会使用该模板引擎来解析、编译和渲染模板-->
        <property name="templateEngine">
            <bean class="org.thymeleaf.spring5.SpringTemplateEngine">
                <!--用于指定 Thymeleaf 模板引擎使用的模板解析器。模板解析器负责根据模板位置、模板资源名称、文件编码等信息,加载模板并对其进行解析-->
                <property name="templateResolver">
                    <bean class="org.thymeleaf.spring5.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>

3、配置web.xml文件

  • Spring MVC是一个web框架,在javaweb中谁来负责接收请求,处理请求,以及响应呢?当然是Servlet
  • 在SpringMVC框架中已经为我们写好了一个Servlet,它的名字叫做:DispatcherServlet,我们称其为前端控制器
  • 既然是Servlet,那么它就需要在web.xml文件中进行配置:
xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
         version="5.0">

    <!--配置前端控制器-->
    <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>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <!-- /* 表示任何一个请求都交给DispatcherServlet来处理 -->
        <!-- / 表示当请求不是xx.jsp的时候,DispatcherServlet来负责处理本次请求-->
        <!-- jsp本质就是Servlet,因此如果请求是jsp的话,应该走它自己的Servlet,而不应该走DispatcherServlet -->
        <!-- 因此我们的 url-pattern 使用 / -->
        <url-pattern>/</url-pattern>
    </servlet-mapping>

</web-app>

DispatcherServlet是SpringMVC框架为我们提供的最核心的类,它是整个SpringMVC框架的前端控制器,负责接收HTTP请求、将请求路由到处理程序处理响应信息,最终将响应返回给客户端。

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

4、html文件

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
hello world
</body>
</html>

5、执行Controller

java 复制代码
@Controller
public class HelloController {
    @RequestMapping("/test")
    public String test(){
        return "success";
    }
}

配置Tomcat

启动tomcat,调用test

三、RequestMapping注解

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

RequestMapping的出现位置

  • 通过源码可以看到RequestMapping注解只能出现在类上或者方法上
  • 当然类上和方法上也可以同时出现,类上是公共的,方法上是独有的

1、value属性

1.1、基础使用

  • value属性是该注解最核心的属性,value属性填写的是请求路径,也就是说通过该请求路径与对应的控制器的方法绑定在一起
  • value属性是一个字符串数组,表示可以提供多个路径,也就是说,多个不同的请求路径可以映射同一个控制器的同一个方法
  • value属性和path属性互为别名,两个属性一样

举例

java 复制代码
@Controller
@RequestMapping("/hello")
public class RequestMappingController {
    @RequestMapping(value = {"/test1","test2"})
    public String test(){
        return "success";
    }
}

1.2、Ant风格(模糊匹配路径)

  • value是可以用来匹配路径的,路径支持模糊匹配,我们把这种模糊匹配称之为Ant风格
    • ?:表示任意的单个字符
    • *:表示任意的0个或多个字符
    • **:表示任意的一层或多层目录(只能使用xxx/**的方式)

匹配?例子

java 复制代码
@RequestMapping("/x?z/testValueAnt")
public String testValueAnt(){
    return "success";
}
  • 匹配成功,可以正常访问到以上控制器的方法上
  • 匹配失败,抛错404

匹配*例子

java 复制代码
@RequestMapping("/x*z/testValueAnt")
public String testValueAnt(){
    return "success";
}
  • 匹配成功,可以正常访问到以上控制器的方法上
  • 匹配失败,抛错404

匹配**例子

  • spring6中**通配符只能出现在路径的末尾,否则抛错,spring5可以不用在末尾
java 复制代码
@RequestMapping("/testValueAnt/**")
public String testValueAnt(){
    return "success";
}
  • 匹配成功,可以正常访问到以上控制器的方法上

1.3、路径占位符(@PathVariable)

普通的请求路径:http://localhost:8080/springmvc/login?username=admin\&password=123\&age=20

restful风格的请求路径:http://localhost:8080/springmvc/login/admin/123/20

如果使用restful风格的请求路径,在控制器中应该如何获取请求中的数据呢?

  • 不加@PathVariable路径变量注解会抛500异常
java 复制代码
@RequestMapping(value = "/testRestful/{id}/{username}/{age}")
public String testRestful(
        @PathVariable("id") int id,
        @PathVariable("username") String username,
        @PathVariable("age") int age) {
    System.out.println(id + "," + username + "," + age);
    return "success";
}

2、method属性

2.1、基础使用

  • 如果前端发送请求的方式和后端的处理方式不一致时,会出现405错误
  • HTTP状态码405,这种机制的作用是:限制客户端的请求方式,以保证服务器中数据的安全
  • SpringMVC使用RequestMapping注解method属性来实现限制请求方式
  • 通过RequestMapping源码可以看到,method属性也是一个数组
  • 数组中的每个元素是RequestMethod,而RequestMethod是一个枚举类型的数据

举例

  • 只允许get和post请求方式,否则报错405
java 复制代码
@RequestMapping(value="/login", method = {RequestMethod.GET,RequestMethod.POST})
public String testMethod(){
    return "success";
}

2.2、衍生xxxMapping注解

  • SpringMVC提供了另外一些注解,使用更加的方便
    • GetMapping:要求前端必须发送get请求
    • PutMapping:要求前端必须发送put请求
    • DeleteMapping:要求前端必须发送delete请求
    • PatchMapping:要求前端必须发送patch请求

举例

  • 两种方式效果一样,对比衍生注解更加简洁
java 复制代码
//@RequestMapping(value="/login", method = RequestMethod.POST)
@PostMapping("/login")
public String testMethod(){
    return "success";
}

2.3、web的请求方式

前端向服务器发送请求的方式包括哪些?共9种

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

⚠️注意

  • 使用超链接以及原生的form表单只能提交get和post请求
  • put、delete、head请求可以使用发送ajax请求的方式来实现

GET和POST的区别

  • get请求比较适合从服务器端获取数据
  • post请求比较适合向服务器端传送数据
  • get请求支持缓存。 也就是说当第二次发送get请求时,会走浏览器上次的缓存结果,不再真正的请求服务器
  • post请求不支持缓存。每一次发送post请求都会真正的走服务器

3、params属性

  • 对于RequestMapping注解来说:
    • value属性是一个数组,只要满足数组中的任意一个路径,就能映射成功
    • method属性也是一个数组,只要满足数组中任意一个请求方式,就能映射成功
    • params属性也是一个数组,不过要求请求参数必须和params数组中要求的所有参数完全一致后,才能映射成功

params属性的4种用法

  1. @RequestMapping(value="/login", params={"username", "password"})
    • 请求参数中必须包含username 和 password,才能与当前标注的方法进行映射
  2. @RequestMapping(value="/login", params={"!username", "password"})
    • 请求参数中不能包含username参数,但必须包含password参数,才能与当前标注的方法进行映射
  3. @RequestMapping(value="/login", params={"username=admin", "password"})
    • 请求参数中必须包含username参数,并且参数的值必须是admin,另外也必须包含password参数,才能与当前标注的方法进行映射
  4. @RequestMapping(value="/login", params={"username!=admin", "password"})
    • 请求参数中必须包含username参数,但参数的值不能是admin,另外也必须包含password参数,才能与当前标注的方法进行映射

4、headers属性

  • headers和params原理相同,用法也相同
  • 当前端提交的请求头信息和后端要求的请求头信息一致时,才能映射成功

headers属性的4种用法

  1. @RequestMapping(value="/login", headers={"Referer", "Host"})
    • 请求头信息中必须包含Referer和Host,才能与当前标注的方法进行映射
  2. @RequestMapping(value="/login", headers={"!Referer", "Host"})
    • 请求头信息中不能包含Referer参数,但必须包含Host参数,才能与当前标注的方法进行映射
  3. @RequestMapping(value="/login", headers={"Referer=xxx", "Host"})
    • 请求头信息中必须包含Referer参数,并且参数的值必须是xxx,另外也必须包含Host参数,才能与当前标注的方法进行映射
  4. @RequestMapping(value="/login", headers={"Referer!=xxx", "Host"})
    • 请求头信息中必须包含Referer参数,但参数的值不能是xxx,另外也必须包含Host参数,才能与当前标注的方法进行映射

四、获取请求参数

1、原生Servlet API

前端表单提交数据


F12查询提交数据方式

后端控制器获取数据

java 复制代码
@PostMapping(value="/register")
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 "success";
}

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

2、RequestParam注解

2.1、value属性

  • RequestParam注解作用:将请求参数与方法上的形参映射
java 复制代码
@PostMapping(value = "/register")
public String register(
        @RequestParam(value = "username") String a,
        @RequestParam(value = "password") String b,
        @RequestParam(value = "sex") String c,
        @RequestParam(value = "hobby") String[] d,
        @RequestParam(name = "intro") String e
) {
    System.out.println(a);
    System.out.println(b);
    System.out.println(c);
    System.out.println(Arrays.toString(d));
    System.out.println(e);
    return "success";
}
  • @RequestParam注解的两个属性valuename,互为别名,作用相同
  • 发送请求时提交的数据是:name1=value1&name2=value2,则这个注解应该这样写:@RequestParam(value="name1")@RequestParam(value="name2")

2.2、required属性

  • required属性用来设置该方法参数是否为必须
  • 默认情况下,这个参数为 true,表示方法参数是必需的。如果请求中缺少对应的参数,则会抛出异常
  • 可以将其设置为false,false表示不是必须的,如果请求中缺少对应的参数,则方法的参数为null

举例

  • 添加了一个 age 形参,没有指定 required 属性时,默认是true,表示必需的
  • 但前端表单中没有年龄age,报错如下

2.2、defaultValue属性

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

举例

  • age属性设置为非必须,当前端不传值时候,默认年龄为18岁

3、根据形参名获取

  • 如果方法形参的名字和提交数据时的name相同,则@RequestParam可以省略
java 复制代码
@PostMapping(value="/register")
public String register(String username, String password, String sex, 
	String[] hobby, String intro){
    System.out.println(username + "," + password + "," + sex + "," 
    	+ Arrays.toString(hobby) + "," + intro);
    return "success";
}

4、根据实体类接收

  • 在SpringMVC中也可以使用POJO类/JavaBean实体类来接收请求参数
  • 不过有一个非常重要的要求:实体类的属性名必须和请求参数的参数名保持一致
java 复制代码
@PostMapping("/register")
public String register(User user){
    System.out.println(user);
    return "success";
}
  • 底层的实现原理:反射机制
    • 先获取User对象实例,没有则反射实例化
    • 然后获取请求参数的名字,通过请求参数名字拼接出set属性名的方法名
    • 最后User实例和set属性方法反射给属性赋值
  • 请求参数是否可以赋值到JavaBean对应的属性上,不是取决于属性名,而是setter方法名

5、RequestHeader注解

  • 该注解的作用是:将请求头信息映射到方法的形参上
  • 对于RequestHeader注解来说,也有三个属性:value、required、defaultValue,和RequestParam一样
java 复制代码
@PostMapping("/register")
public String register(User user, 
	@RequestHeader(value="Referer", required = false, defaultValue = "") String referer){
    System.out.println(user);
    System.out.println(referer);
    return "success";
}

6、CookieValue注解

  • 该注解的作用是:将请求提交的Cookie数据映射到方法的形参上
  • 对于CookieValue注解来说,也有三个属性:value、required、defaultValue,和RequestParam一样
java 复制代码
@GetMapping("/register")
public String register(User user,
	@CookieValue(value="id", required = false, defaultValue = "110") String id){
    System.out.println(user);
    System.out.println(id);
    return "success";
}

7、请求的中文乱码问题

7.1、get请求乱码

  • get请求数据在URI后面提交,这个乱码问题怎么解决呢?
  • 解决办法是找到 CATALINA_HOME/config/server.xml文件,找到其中配置端口号的标签<Connector>,在该标签中添加URIEncoding="UTF-8
  • 但是对于高版本的Tomcat服务器来说,是不需要设置的,例如Tomcat10Tomcat9,有如下的默认配置,在默认情况下URIEncoding使用的就是UTF-8的编码方式
  • 但对于低版本的Tomcat服务器,例如:Tomcat8,URIEncoding的默认配置是ISO-8859-1

7.2、post请求乱码

  • post请求是解决请求体的中文乱码问题
java 复制代码
request.setCharacterEncoding("UTF-8");
  • 同样,对于高版本的Tomcat10服务器来说,针对请求体中的字符编码也是配置好的,默认也是采用了UTF-8,web.xml配置如下
  • 一定要注意:Tomcat9以及之前的版本,以上的配置是没有的
  • 解决方法:web.xml中配置mvc自带的乱码过滤器
xml 复制代码
<!--配置SpringMVC自带的乱码过滤器-->
<filter>
    <filter-name>encoding</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
        <param-name>encoding</param-name>
        <param-value>utf-8</param-value>
    </init-param>
    <!-- 
    	设置forceEncoding为true,就是强制设置编码的意思
		forceEncoding为true就会设置forceRequestEncoding和forceResponseEncoding为true
		这样,如下源码的两个if条件成立,就会设置utf-8编码了
	 -->
    <init-param>
        <param-name>forceEncoding</param-name>
        <param-value>true</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>encoding</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
  • 源码解析

五、Servlet的三个域对象

  • 请求域:request、会话域:session、应用域:application
  • 三个域都有以下三个方法:
java 复制代码
// 向域中存储数据
void setAttribute(String name, Object obj);

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

// 删除域中的数据
void removeAttribute(String name);
  • 主要是通过:setAttribute + getAttribute方法来完成在域中数据的传递和共享

1、request域对象

  • request对象代表了一次请求,一次请求一个request
  • 使用请求域的业务场景
    • 在A资源中通过转发的方式跳转到B资源
    • 因为是转发,所以从A到B是一次请求
    • 如果想让A资源和B资源共享同一个数据,可以将数据存储到request域中
  • 在request域中共享数据有以下几种方式
    • 使用原生Servlet API方式
    • 使用Model接口
    • 使用Map接口
    • 使用ModelMap类
    • 使用ModelAndView类

使用原生Servlet API方式

java 复制代码
@RequestMapping("/testServletAPI")
public String testServletAPI(HttpServletRequest request){
    // 向request域中存储数据
    request.setAttribute("testRequestScope", "在SpringMVC中使用原生Servlet API实现request域数据共享");
    return "view";
}

使用Model接口

java 复制代码
@RequestMapping("/testModel")
public String testModel(Model model){
    // 向request域中存储数据
    model.addAttribute("testRequestScope", "在SpringMVC中使用Model接口实现request域数据共享");
    return "view";
}

使用Map接口

java 复制代码
@RequestMapping("/testMap")
public String testMap(Map<String, Object> map){
    // 向request域中存储数据
    map.put("testRequestScope", "在SpringMVC中使用Map接口实现request域数据共享");
    return "view";
}

使用ModelMap类

java 复制代码
@RequestMapping("/testModelMap")
public String testModelMap(ModelMap modelMap){
    // 向request域中存储数据
    modelMap.addAttribute("testRequestScope", "在SpringMVC中使用ModelMap实现request域数据共享");
    return "view";
}

Model、Map、ModelMap的关系?

  • 输出打印Model、Map、ModelMap的Class,底层实例化的对象都是:BindingAwareModelMap
  • BindingAwareModelMap的继承结构
  • BindingAwareModelMap继承ModelMap实现Model,而ModelMap又实现了Map接口

使用ModelAndView类

  • 为了更好的体现MVC架构模式,提供了一个类:ModelAndView。这个类的实例封装了ModelView
  • 也就是说这个类既封装业务处理之后的数据,也体现了跳转到哪个视图
  • 使用它也可以完成request域数据共享
java 复制代码
@RequestMapping("/testModelAndView")
public ModelAndView testModelAndView(){
    // 创建"模型与视图对象"
    ModelAndView modelAndView = new ModelAndView();
    // 绑定数据
    modelAndView.addObject("testRequestScope", "在SpringMVC中使用ModelAndView实现request域数据共享");
    // 绑定视图
    modelAndView.setViewName("view");
    // 返回
    return modelAndView;
}

注意:

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

以上我们通过了五种方式完成了request域数据共享,这几种方式在底层DispatcherServlet调用我们的Controller之后,返回的对象都是ModelAndView

2、session域对象

  • session对象代表了一次会话
    • 从打开浏览器开始访问,到最终浏览器关闭,这是一次完整的会话
    • 每个会话session对象都对应一个JSESSIONID,而JSESSIONID生成后以cookie的方式存储在浏览器客户端
    • 浏览器关闭,JSESSIONID失效,会话结束
  • 使用会话域的业务场景
    • 登录成功后保存用户的登录状态

使用原生Servlet API方式

java 复制代码
@RequestMapping("/testSessionScope1")
public String testServletAPI(HttpSession session) {
    // 向会话域中存储数据
    session.setAttribute("testSessionScope1", "使用原生Servlet API实现session域共享数据");
    return "view";
}

3、application域对象

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

使用原生Servlet API方式

java 复制代码
@RequestMapping("/testApplicationScope")
public String testApplicationScope(HttpServletRequest request){
    // 获取ServletContext对象
    ServletContext application = request.getServletContext();
    // 向应用域中存储数据
    application.setAttribute("applicationScope", "我是应用域当中的一条数据");
    return "view";
}

六、HttpMessageConverter消息转换器

  • HttpMessageConverter是Spring MVC中非常重要的一个接口
  • 翻译为:HTTP消息转换器。该接口下提供了很多实现类,不同的实现类有不同的转换方式
  • 转换器是HTTP协议Java程序中的对象之间的互相转换

1、Form表单转换器和默认转换器

Form表单转换器

请求体中的数据是如何转换成user对象的,底层实际上使用了HttpMessageConverter接口的其中一个实现类FormHttpMessageConverter

通过上图可以看出FormHttpMessageConverter是负责将请求协议转换为Java对象的。

默认转换器

Controller返回值看做逻辑视图名称,视图解析器将其转换成物理视图名称,生成视图对象,StringHttpMessageConverter负责将视图对象中的HTML字符串写入到HTTP协议的响应体中。最终完成响应。

通过上图可以看出StringHttpMessageConverter是负责将Java对象转换为响应协议的。

2、@ResponseBody

首页面AJAX请求获取数据,非跳转页面Controller

2.1、Servlet原生API方式

java 复制代码
// 有返回值
@RequestMapping(value = "/hello1")
public String hello1(HttpServletResponse response) throws IOException {
    response.getWriter().print("hello");
    return null;
}

// 无返回值
@RequestMapping(value = "/hello2")
public void hello2(HttpServletResponse response) throws IOException {
    response.getWriter().print("hello");
}

页面展示

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

2.2、@ResponseBody注解方式

  • 这里的"hello"不是逻辑视图名了,而是作为响应体的内容进行响应。直接输出到浏览器客户端
  • 程序中使用的消息转换器是:StringHttpMessageConverter,为什么会启用这个消息转换器呢?
    • 因为你添加@ResponseBody这个注解
java 复制代码
@Controller
public class HelloController {
    @RequestMapping(value = "/hello")
    @ResponseBody
    public String hello(){
        // 由于你使用了 @ResponseBody 注解
        // 以下的return语句返回的字符串则不再是"逻辑视图名"了
        // 而是作为响应协议的响应体进行响应。
        return "hello";
    }
}
  • 通常AJAX请求需要服务器给返回一段JSON格式的字符串,可以返回JSON格式的字符串吗?当然可以,代码如下:
java 复制代码
@Controller
public class HelloController {

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

页面展示

  • 此时底层使用的消息转换器还是:StringHttpMessageConverter
  • 那如果在程序中是一个POJO对象,怎么将POJO对象以JSON格式的字符串响应给浏览器呢?
    • 方式一:自己写代码将POJO对象转换成JSON格式的字符串,用上面的方式直接return即可
    • 方式二:启用MappingJackson2HttpMessageConverter消息转换器

2.3、MappingJackson2HttpMessageConverter(JSON转换器)

启动JSON消息转换器需要两个步骤

  • 第一步:引入jackson依赖,可以将java对象转换为json格式字符串
xml 复制代码
<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-databind</artifactId>
  <version>2.17.0</version>
</dependency>
  • 第二步:开启注解驱动,会自动装配一个消息转换器:MappingJackson2HttpMessageConverter
xml 复制代码
<mvc:annotation-driven/>

@ResponseBody最经典用法如下:

java 复制代码
@RequestMapping(value = "/hello")
@ResponseBody
public User hello(){
    User user = new User("zhangsan", "18");
    return user;
}
  • 将POJO对象转换成JSON格式的字符串,响应给前端

3、@RestController

  • 为了方便,Spring MVC中提供了一个注解@RestController。这一个注解代表了:@Controller + @ResponseBody
  • @RestController标注在类上即可。被它标注的Controller中所有的方法上都会自动标注@ResponseBody
java 复制代码
@RestController
public class HelloController {
    @RequestMapping(value = "/hello")
    public User hello(){
        User user = new User("zhangsan", "18");
        return user;
    }
}

4、@RequestBody

  • 作用是直接将请求体传递给Java程序

4.1、&拼接参数

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

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

请求体提交的数据是:

java 复制代码
username=zhangsan&password=1234&email=zhangsan@powernode.com

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

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

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

Spring MVC仍然会使用 FormHttpMessageConverter消息转换器,将请求体直接以字符串形式传递给requestBodyStr变量

4.2、JSON格式参数

  • 如果请求体JSON格式字符串,可以将其转化为POJO对象
  • 此时必须使用@RequestBody注解来完成
  • 底层使用的消息转换器是:MappingJackson2HttpMessageConverter
  • 启动步骤与@ResponseBody一样,引入jackson依赖、开启注解驱动
java 复制代码
@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";
}

5、RequestEntity

  • 这个类的实例封装了整个请求协议:包括请求行请求头请求体所有信息
java 复制代码
@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";
}

执行结果:

6、ResponseEntity

  • 用该类的实例可以封装响应协议,包括:状态行响应头响应体
  • 如果你想定制属于自己的响应协议,可以使用该类
java 复制代码
@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);
        }
    }
}

七、异常处理器

1、默认异常处理器

  • 方法执行过程中出现了异常,跳转到对应的视图,在视图上展示友好信息

默认处理器DefaultHandlerExceptionResolver核心方法:

当请求方式和处理方式不同时,DefaultHandlerExceptionResolver的默认处理态度是:

2、自定义异常处理器

2.1、跳转错误页面

java 复制代码
@ControllerAdvice
public class ExceptionController {
    @ExceptionHandler
    public String exceptionHandler(Exception e, Model model){
        model.addAttribute("e", e);
        return "error";
    }
}
html 复制代码
<!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>

2.2、返回错误响应对象

java 复制代码
@ControllerAdvice
public class ExceptionController {
    @ExceptionHandler(value = {Exception.class})
    @ResponseBody
    public ResponseEntity<String> exceptionHandler(Exception e, Model model) {
        // 这里先判断拦截到的Exceptiion是不是我们自定义的异常类型
        if (e instanceof MyException) {
            MyException myException = (MyException) e;
            return ResponseEntity.status(500).body(myException.getMeg());
        } else {
            // 如果拦截的异常不是我们自定义的异常(例如:数据库主键冲突)
            return ResponseEntity.status(500).body("服务器端异常");
        }
    }
}

八、拦截器

1、拦截器概述

  • 拦截器作用是在请求到达控制器之前或之后进行拦截,可以对请求和响应进行一些特定的处理
  • 拦截器可以用于很多场景下
    • 登录验证:对于需要登录才能访问的网址,使用拦截器可以判断用户是否已登录,如果未登录则跳转到登录页面
    • 权限校验:根据用户权限对部分网址进行访问控制,拒绝未经授权的用户访问
    • 请求日志:记录请求信息,例如请求地址、请求参数、请求时间等,用于排查问题和性能优化
    • 更改响应:可以对响应的内容进行修改,例如添加头信息、调整响应内容格式等

2、拦截器和过滤器的区别

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

Filter、Servlet、Interceptor、Controller的执行顺序:

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

定义拦截器

  • 实现org.springframework.web.servlet.HandlerInterceptor 接口,共有三个方法可以进行选择性的实现
    • preHandle:处理器方法调用之前执行(返回true放行,false拦截)
    • postHandle:处理器方法调用之后执行
    • afterCompletion:渲染完成后执行
java 复制代码
@Component
public class MyInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("处理器方法前调用");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("处理器方法后调用");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("渲染完成后调用");
    }
}

基本配置

springmvc.xml配置如下

xml 复制代码
<mvc:interceptors>
    <bean class="com.xc.interceptors.MyInterceptor"/>
</mvc:interceptors>
<!-- 或者 -->
<mvc:interceptors>
    <ref bean="myInterceptor"/>
</mvc:interceptors>

添加组件扫描

注意:对于这种基本配置来说,拦截器是拦截所有请求的

4、多个拦截器执行顺序

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

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

xml 复制代码
<mvc:interceptors>
    <ref bean="interceptor1"/>
    <ref bean="interceptor2"/>
</mvc:interceptors>

执行顺序:

如果其中一个拦截器preHandle返回false

xml 复制代码
<mvc:interceptors>
    <ref bean="interceptor1"/>
    <ref bean="interceptor2"/>
</mvc:interceptors>

如果interceptor2的preHandle返回false,执行顺序:


规则:只要有一个拦截器preHandle返回false,所有postHandle都不执行。但返回false的拦截器的前面的拦截器按照逆序执行afterCompletion

相关推荐
YuanlongWang3 分钟前
C# 基础——值类型与引用类型的本质区别
java·jvm·c#
Kay_Liang28 分钟前
大语言模型如何精准调用函数—— Function Calling 系统笔记
java·大数据·spring boot·笔记·ai·langchain·tools
自由的疯1 小时前
Java 如何学习Docker
java·后端·架构
自由的疯1 小时前
Java Docker本地部署
java·后端·架构
007php0071 小时前
猿辅导Java面试真实经历与深度总结(二)
java·开发语言·python·计算机网络·面试·职场和发展·golang
摇滚侠1 小时前
Spring Boot 3零基础教程,WEB 开发 内容协商机制 笔记34
java·spring boot·笔记·缓存
一勺菠萝丶1 小时前
在 macOS 上用 Docker 为 Java 后端 & 常见开发需求搭建完整服务(详尽教程)
java·macos·docker
顾漂亮1 小时前
JVM底层攻坚
java·jvm·spring
编程岁月1 小时前
java面试-0215-HashMap有序吗?Comparable和Comparator区别?集合如何排序?
java·数据结构·面试
William_cl1 小时前
ASP.NET MVC 前置基础:宿主环境 & HttpRuntime 管道,从部署到流程拆透(附避坑指南)
后端·asp.net·mvc