一、初识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开发有什么区别?
- 入口控制:SpringMVC框架通过DispatcherServle(前端控制器)t作为入口控制器,负责接收请求和分发请求。而在Servlet开发中,需要自己编写Servlet程序,并在web.xml中进行配置,才能接受和处理请求。
- 在SpringMVC中,表单提交时可以自动将表单数据绑定到相应的JavaBean对象中,只需要在控制器方法的参数列表中声明该JavaBean对象即可,无需手动获取和赋值表单数据。而在纯粹的Servlet开发中,这些都是需要自己手动完成的。
- IoC容器:SpringMVC框架通过IoC容器管理对象,只需要在配置文件中进行相应的配置即可获取实例对象,而在Servlet开发中需要手动创建对象实例。
- 统一处理请求:SpringMVC框架提供了拦截器、异常处理器等统一处理请求的机制,并且可以灵活地配置这些处理器。而在Servlet开发中,需要自行编写过滤器、异常处理器等,增加了代码的复杂度和开发难度。
- 视图解析:SpringMVC框架提供了多种视图模板,如JSP、Freemarker、Velocity等,并且支持国际化、主题等特性。而在Servlet开发中需要手动处理视图层,增加了代码的复杂度。
总之,与Servlet开发相比,SpringMVC框架可以帮我们节省很多时间和精力,减少代码的复杂度,更加专注于业务开发。同时,也提供了更多的功能和扩展性,可以更好地满足企业级应用的开发需求。
1.1.4、SpringMVC框架的特点
- 轻量级:相对于其他Web框架,Spring MVC框架比较小巧轻便。(只有几个几百KB左右的Jar包文件)
- 模块化:请求处理过程被分成多个模块,以模块化的方式进行处理。
- 控制器模块:Controller
- 业务逻辑模块:Model
- 视图模块:View
- 依赖注入:Spring MVC框架利用Spring框架的依赖注入功能实现对象的管理,实现松散耦合。
- 易于扩展:提供了很多口子,允许开发者根据需要插入自己的代码,以扩展实现应用程序的特殊需求。
- Spring MVC框架允许开发人员通过自定义模块和组件来扩展和增强框架的功能。
- Spring MVC框架与其他Spring框架及第三方框架集成得非常紧密,这使得开发人员可以非常方便地集成其他框架,以获得更好的功能。
- 易于测试:支持单元测试框架,提高代码质量和可维护性。 (对SpringMVC中的Controller测试时,不需要依靠Web服务器。)
- 自动化配置:提供自动化配置,减少配置细节。
- Spring MVC框架基于约定大于配置的原则,对常用的配置约定进行自动化配置。
- 灵活性: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><!--Spring6和Thymeleaf整合依赖--><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应用程序的主要入口点之一,它的职责包括:
- 接收客户端的HTTP请求:DispatcherServlet监听来自Web浏览器的HTTP请求,然后根据请求的URL将请求数据解析为Request对象。
- 处理请求的URL:DispatcherServlet将请求的URL(Uniform Resource Locator)与处理程序进行匹配,确定要调用哪个控制器(Controller)来处理此请求。
- 调用相应的控制器:DispatcherServlet将请求发送给找到的控制器处理,控制器将执行业务逻辑,然后返回一个模型对象(Model)。
- 渲染视图:DispatcherServlet将调用视图引擎,将模型对象呈现为用户可以查看的HTML页面。
- 返回响应给客户端: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、执行流程总结
- 浏览器发送请求:http://localhost:8080/springmvc/test
- SpringMVC的前端控制器DispatcherServlet接收到请求
- DispatcherServlet根据请求路径 /test 映射到 FirstController#hehe(),调用该方法
- FirstController#hehe() 处理请求
- FirstController#hehe() 返回逻辑视图名称 first 给视图解析器
- 视图解析器找到 /WEB-INF/templates/first.html 文件,并进行解析,生成视图解析对象返回给前端控制器DispatcherServlet
- 前端控制器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 连接。
注意:
- 使用超链接以及原生的form表单只能提交get和post请求,put、delete、head请求可以使用发送ajax请求的方式来实现。
- 使用超链接发送的是get请求
- 使用form表单,如果没有设置method,发送get请求
- 使用form表单,设置method=“get”,发送get请求
- 使用form表单,设置method=“post”,发送post请求
- 使用form表单,设置method=“put/delete/head”,发送get请求。(针对这种情况,可以测试一下)
2.5.4、GET和POST的区别
HTTP请求协议之GET请求:
HTTP请求协议之POST请求:
区别是什么?
- get请求发送数据的时候,数据会挂在URI的后面,并且在URI后面添加一个“?”,"?"后面是数据。这样会导致发送的数据回显在浏览器的地址栏上。
http://localhost:8080/springmvc/login?username=zhangsan&userpwd=1111
- post请求发送数据的时候,在请求体当中发送。不会回显到浏览器的地址栏上。也就是说post发送的数据,在浏览器地址栏上看不到。
- get请求只能发送普通的字符串。并且发送的字符串长度有限制,不同的浏览器限制不同。这个没有明确的规范。get请求无法发送大数据量。
- post请求可以发送任何类型的数据,包括普通字符串,流媒体等信息:视频、声音、图片。post请求可以发送大数据量,理论上没有长度限制。
- get请求在W3C中是这样说的:get请求比较适合从服务器端获取数据。
- post请求在W3C中是这样说的:post请求比较适合向服务器端传送数据。
- get请求是安全的。因为在正确使用get请求的前提下,get请求只是为了从服务器上获取数据,不会对服务器数据进行修改。
- post请求是危险的。因为post请求是修改服务器端的资源。
- get请求支持缓存。 也就是说当第二次发送get请求时,会走浏览器上次的缓存结果,不再真正的请求服务器。(有时需要避免,怎么避免:在get请求路径后添加时间戳)
- 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失效,会话结束。
希望在多次请求之间共享同一个数据,可以使用会话域。
使用会话域的业务场景:
- 在A资源中通过重定向的方式跳转到B资源,因为是重定向,因此从A到B是两次请求,如果想让A资源和B资源共享同一个数据,可以将数据存储到session域中。
- 登录成功后保存用户的登录状态。
4.1.3、application
接口名:ServletContext
简称:application
application对象代表了整个web应用,服务器启动时创建,服务器关闭时销毁。对于一个web应用来说,application对象只有一个。
使用应用域的业务场景:记录网站的在线人数。
4.2、request域对象
在SpringMVC中,在request域中共享数据有以下几种方式:
- 使用原生Servlet API方式。
- 使用Model接口。
- 使用Map接口。
- 使用ModelMap类。
- 使用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;}
测试:
这种方式需要注意的是:
- 方法的返回值类型不是String,而是ModelAndView对象。
- ModelAndView不是出现在方法的参数位置,而是在方法体中new的。
- 需要调用addObject向域中存储数据。
- 需要调用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域共享数据,实现方式有多种,其中比较常见的两种方式:
- 使用原生Servlet API
- 使用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支持的常见视图包括(前三个使用较多):
- InternalResourceView:内部资源视图(Spring MVC框架内置的,专门为
JSP模板语法
准备的,也是为转发准备的) - RedirectView:重定向视图(Spring MVC框架内置的,用来完成重定向效果)
- ThymeleafView:Thymeleaf视图(第三方的,为
Thymeleaf模板语法
准备的) - FreeMarkerView:FreeMarker视图(第三方的,为
FreeMarker模板语法
准备的) - VelocityView:Velocity视图(第三方的,为
Velocity模板语法
准备的) - PDFView:PDF视图(第三方的,专门用来生成pdf文件视图)
- 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代码响应到浏览器。
- 编写类实现ViewResolver接口,实现resolveViewName方法,在该方法中完成
- 如果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调用InternalResourceViewResolver
的resolveViewName
方法,将逻辑视图名
转换为物理视图名
,并创建InternalResourceView
对象返回给DispatcherServlet
第七步:DispatcherServlet再调用InternalResourceView
的render
方法,render方法将模板语言转换为HTML代码,响应给浏览器,完成最终的渲染。
5.2、转发与重定向
5.2.1、回顾转发和重定向区别
- 转发是一次请求。因此浏览器地址栏上的地址不会发生变化。
- 重定向是两次请求。因此浏览器地址栏上的地址会发生变化。
- 转发的代码实现:request.getRequestDispatcher(“/index”).forward(request, response);
- 重定向的代码实现:response.sendRedirect(“/webapproot/index”);
- 转发是服务器内部资源跳转,由服务器来控制。不可实现跨域访问。
- 重定向可以完成内部资源的跳转,也可以完成跨域跳转。
- 转发的方式可以访问WEB-INF目录下受保护的资源。
- 重定向相当于浏览器重新发送了一次请求,在浏览器直接发送的请求是无法访问WEB-INF目录下受保护的资源的。
- 转发原理:
- 假设发送了 /a 请求,执行了 AServlet
- 在AServlet 中通过
request.getRequestDispatcher("/b").forward(request,response);
转发到BServlet - 从AServlet跳转到BServlet是服务器内部来控制的。对于浏览器而言,浏览器只发送了一个 /a 请求。
- 重定向原理:
- 假设发送了 /a 请求,执行了 AServlet
- 在AServlet 中通过
response.sendRedirect("/webapproot/b")
重定向到BServlet - 此时服务器会将请求路径
/webapproot/b
响应给浏览器 - 浏览器会自发的再次发送
/webapproot/b
请求来访问BServlet - 因此对于重定向来说,发送了两次请求,一次是
/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 是基于资源的结构和状态进行操作的。下面是一张表格,展示两者之间的具体区别:
传统的 URL | RESTful URL |
---|---|
GET /getUserById?id=1 | GET /user/1 |
GET /getAllUser | GET /user |
POST /addUser | POST /user |
POST /modifyUser | PUT /user |
GET /deleteUserById?id=1 | DELETE /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的拦截器作用是在请求到达控制器之前或之后进行拦截,可以对请求和响应进行一些特定的处理。
拦截器可以用于很多场景下:
- 登录验证:对于需要登录才能访问的网址,使用拦截器可以判断用户是否已登录,如果未登录则跳转到登录页面。
- 权限校验:根据用户权限对部分网址进行访问控制,拒绝未经授权的用户访问。
- 请求日志:记录请求信息,例如请求地址、请求参数、请求时间等,用于排查问题和性能优化。
- 更改响应:可以对响应的内容进行修改,例如添加头信息、调整响应内容格式等。
拦截器和过滤器的区别在于它们的作用层面不同。
- 过滤器更注重在请求和响应的流程中进行处理,可以修改请求和响应的内容,例如设置编码和字符集、请求头、状态码等。
- 拦截器则更加侧重于对控制器进行前置或后置处理,在请求到达控制器之前或之后进行特定的操作,例如打印日志、权限验证等。
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");}
}