一、SpringMVC简介
1、什么是MVC
MVC是一种软件架构的思想,将软件按照模型、视图、控制器来划分
M
:Model,模型层,指工程中的
JavaBean
,作用是
处理数据
JavaBean分为两类:
- 一类称为实体类Bean:专门存储业务数据的,如 Student、User 等
- 一类称为业务处理 Bean:指 Service 或 Dao 对象,专门用于处理业务逻辑和数据访问。
V
:View,视图层,指工程中的
html或jsp等页面
,作用是与用户进行交互,展示数据
C
:Controller,
控制层,指工程中的servlet,作用是接收请求和响应浏览器
MVC的工作流程:
用户通过视图层发送请求到服务器,在服务器中请求被Controller接收,Controller调用相应的Model层处理请求,处理完毕将结果返回到Controller,Controller再根据请求处理的结果找到相应的View视图,渲染数据后最终响应给浏览器
2、什么是SpringMVC
SpringMVC是Spring的一个后续产品,是Spring的一个子项目(即:SpringMVC是spring框架的一部分)
SpringMVC 是 Spring 为表述层开发提供的一整套完备的解决方案。在
表述层框架
历经 Strust、WebWork、
Strust2
等诸多产品的历代更迭之后,目前业界普遍选择了 SpringMVC 作为 Java EE 项目表述层开发的
首选方案
。
注:三层架构分为表述层(或表示层)、业务逻辑层、数据访问层,表述层表示前台页面和后台servlet
3、SpringMVC的特点
-
Spring 家族原生产品
,与 IOC 容器等基础设施无缝对接 -
基于原生的Servlet
,通过了功能强大的
前端控制器DispatcherServlet
,对请求和响应进行统一处理。
即:
DispatcherServlet前端控制器的底层封装的就是Servlet
。 -
表述层各细分领域需要解决的问题
全方位覆盖
,提供
全面解决方案
-
代码清新简洁
,大幅度提升开发效率 -
内部组件化程度高,可插拔式组件
即插即用
,想要什么功能配置相应组件即可 -
性能卓著
,尤其适合现代大型、超大型互联网项目要求
二、HelloWorld
1、开发环境
IDE:idea 2019.2
构建工具:maven3.5.4
服务器:tomcat7
Spring版本:5.3.1
说明
:
当前文档是基于Thympleaf所编写的页面。
2、创建maven-web工程
2.1、打包方式:war
<packaging>war</packaging>
2.2、添加web模块
略,详情查看Maven的学习。
2.3、引入依赖
<dependencies>
<!-- SpringMVC 它还会自动导入相关的依赖包 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.1</version>
</dependency>
<!-- 日志 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<!-- ServletAPI tomact服务器自带的有,所以设置为provided,表示打成为war包时,
不包含此依赖 -->
<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.12.RELEASE</version>
</dependency>
</dependencies>
注:由于 Maven 的传递性,我们不必将所有需要的包全部配置依赖,而是配置最顶端的依赖,其他靠传递性导入。
2.4 、创建好的项目结构
3、配置前端控制器(web.xml)
为什么要在web.xml中 注册
SpringMVC的前端控制器DispatcherServlet
???
答
:之前访问servlet需要在web.xml中配置访问路径,现在使用的是SpringMVC框架封装的DispatcherServlet,所以需要在xml中配置前端控制器。
3.1、默认配置方式(方式一)
说明
:此配置作用下,SpringMVC的配置文件
默认
位于WEB-INF下,默认名称为
\<servlet-name>-servlet.xml
,例如,以下配置所对应SpringMVC的配置文件位于WEB-INF下,文件名为springMVC-servlet.xml。
即
:使用默认方式配置xml文件,那么这个文件必须在WEB-INF下,名字必须为springMVC-servlet.xml
<!-- 配置SpringMVC的前端控制器,对浏览器发送的请求统一进行处理 -->
<servlet>
<servlet-name>springMVC</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>springMVC</servlet-name>
<!--
设置springMVC的核心控制器所能处理的请求的请求路径:
错误1:/SpringTest 之前是一个请求对应一个servlet,现在是前端控制器对浏览器发送的
请求统一进行处理,所以不能配置成只处理一个请求。
错误2:/* 前端控制器不能处理.jsp文件,/*包含了jsp文件,所以错误。
正确:/ 代表除了匹配的请求可以是/login或.html或.js或.css方式的请求路径,
但是/不能匹配.jsp请求路径的请求
-->
<url-pattern>/</url-pattern>
</servlet-mapping>
前端控制器DispatcherServlet为什么不能匹配.jsp???
因为
:jsp本质上是servlet,它需要服务器中
特殊的
servlet进行处理的,如果现在写的路径能够匹配
.jsp
,会导致的问题:.jsp的请求也会被springMVC进行处理,
springMVC会把这个请求当做是普通的请求进行处理,而不会找到jsp页面
。
3.2、扩展配置方式(方式二:推荐)
说明
:这种方式目的是
不要把配置文件放在WEB-INFO目录下
,
WEB-INFO目录只放页面
,
配置文件应该放在resources 目录下
。
步骤1
:在
web.xml
可通过
init-param
标签设置SpringMVC配置文件的
位置和名称
,通过
load-on-startup
标签设置SpringMVC前端控制器DispatcherServlet的
初始化时间
。
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<!-- 配置SpringMVC的前端控制器,对浏览器发送的请求统一进行处理 -->
<servlet>
<servlet-name>springMVC</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<!-- contextConfigLocation为固定值 -->
<param-name>contextConfigLocation</param-name>
<!-- 使用classpath:表示从类路径查找配置文件,例如maven工程中的src/main/resources -->
<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>
<!--
设置springMVC的核心控制器所能处理的请求的请求路径
/所匹配的请求可以是/login或.html或.js或.css方式的请求路径
但是/不能匹配.jsp请求路径的请求
-->
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
步骤2
:在resources 创建配置文件springMVC.xml,配置的内容查看:二- – -5.
注:
<url-pattern>标签中使用/和/*的区别:
/所匹配的请求可以是/login或.html或.js或.css方式的请求路径,但是/不能匹配.jsp请求路径的请求
因此就可以避免在访问jsp页面时,该请求被DispatcherServlet处理,从而找不到相应的页面
/*则能够匹配所有请求,例如在使用过滤器时,若需要对所有请求进行过滤,就需要使用/*的写法
4、创建请求控制器(控制层类)
由于
前端控制器
对浏览器发送的请求进行了统一的处理,但是具体的请求有不同的处理过程,因此需要创建
处理具体请求的类,即请求控制器
请求控制器中每一个处理请求的方法称为
控制器方法
因为SpringMVC的控制器由一个POJO(
普通的Java类
)担任,因此需要
通过@Controller注解将其标识为一个控制层组件
,交给Spring的IOC容器管理
并在配置文件中配置组件扫描
,
此时SpringMVC才能够识别控制器的存在
。
package com.atguigu.mvc.controller;
import org.springframework.stereotype.Controller;
@Controller //需要使用注解进行标识,并配置组件扫描
public class HelloController {
}
5、创建springMVC的配置文件
<?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.atguigu.mvc.controller"></context:component-scan>
<!-- 配置Thymeleaf视图解析器 -->
<bean id="viewResolver" class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
<!--配置视图解析器的优先级-->
<property name="order" value="1"/>
<!--配置编码-->
<property name="characterEncoding" value="UTF-8"/>
<property name="templateEngine">
<bean class="org.thymeleaf.spring5.SpringTemplateEngine">
<property name="templateResolver">
<bean class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver">
<!-- 视图前缀 -->
<property name="prefix" value="/WEB-INF/templates/"/>
<!-- 视图后缀 -->
<property name="suffix" value=".html"/>
<property name="templateMode" value="HTML5"/>
<property name="characterEncoding" value="UTF-8" />
</bean>
</property>
</bean>
</property>
</bean>
</beans>
6、配置Thymeleaf视图解析器所对应的目录
说明:
- 配置在springMVC的配置文件中配置的Thymeleaf视图解析器的视图前缀所对应的目录。
- 添加后缀对应的文件如:index.html。
7、测试HelloWorld(手动配置tomact)
7.1、配置tomact
说明
:
- 创建javaWeb工程时会自动定位到此项目所使用的的Tomact,之后进行配置即可。
- 创建MavenWeb工程时不会自动定位到此项目所使用的的Tomact,你需要先找到此项目所使用的Tomact,之后进行配置。
- springboot工程,待定???
7.2、实现对首页的访问
1.在index.html页面中添加标签:
<!DOCTYPE html>
<!--配置thymeleaf的命名空间,配置后才能使用thymeleaf的语法-->
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<h1>首页</h1>
</body>
</html>
2.在请求控制器中创建处理请求的方法
package com.atguigu.mvc.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller //需要使用注解进行标识,配置文件进行扫描
public class HelloController {
// "/" --->/WEB-INF/templates/index.html
// @RequestMapping注解:处理请求和控制器方法之间的映射关系
// @RequestMapping(value = "/") 注解的value属性可以通过请求地址匹配请求,/表示的当前工程的上下文路径
// localhost:8080/springMVC/
@RequestMapping("/")//当属性值只有一个时,value可以省略。
public String index(){//方法名随意,和方法名没有关系。
//返回视图名称 会自动拼接在springMVC.xml配置的前缀和后缀。
return "index";// 代表请求转发的方式跳转 (index.html放在web-inf目录下)
}
}
3.启动tomact服务器 ,由于idea整合了tomact,所以会自动把项目发布到服务器 并进行访问。注意这和index.xxx页面没有关系,tomact自动访问index开头的页面是放在webapp目录下。
7.3、通过超链接跳转到指定页面
7.3.1、Thymeleaf自动获取上下文
在主页index.html中设置超链接
<!DOCTYPE html>
<!--配置thymeleaf的命名空间,配置后才能使用thymeleaf的语法-->
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<h1>首页</h1>
<!--解释1:可以使用相对路径-->
<!--解释2:可以使用绝对路径,在web中/代表就绝对路径,/被浏览器解析是得到的地址是:http://ip:port/,
被服务器解析,得到的地址是:http://ip:port/工程路径。
此时是被浏览器解析少了个工程名,工程名是tomact实例当中的配置,可以进行修改,如:springMVC,
此时使用可以写为/springMVC/target
缺点:在tomact实例当中的配置的访问路径上下文名字为springMVC 这个名字是可以修改的。如果以后修改了
那么每次都要修改当前页面的超链接访问地址。
解决:使用thymeleaf的标签如下,当它检测到使用的路径是绝对路径时,它会自动拼接上下文路径名
通过th: 来修饰属性, 通过@{}来写路径
-->
<a th:href="@{/target}">访问目标页面target.html</a><br/>
</body>
</html>
创建target.html页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
HelloWorld
</body>
</html>
在请求控制器中创建处理请求的方法
@RequestMapping("/target")
public String HelloWorld() {
return "target";
}
测试
:启动tomact服务器自动访问到index.html页面,在点击超链接通过访问controller层的方法返回target.html页面。
超链接的地址是超链接中的
http://localsthost:8080/springMvc/target
访问返回的页面地址是
http://localsthost:8080/springMvc/web-info/templates/target.html
说明:通过超链接发送请求给服务器,服务器请求转发到web-inf目录下的地址没有发生变化。地址没有变化,访问的是web-inf下的资源,这种在方法中
retutn "页面"
的形式是
请求转发
。
请求转发:指的是一次请求一次响应,一个请求的地址和响应的地址前后有没有发生变化,可不是多个请求。
8、简述总结:执行流程
浏览器发送请求,
若请求地址符合前端控制器的url-pattern,该请求就会被前端控制器DispatcherServlet处理
。前端控制器会读取SpringMVC的核心配置文件,通过扫描组件找到控制器,
将请求地址和控制器中@RequestMapping注解的value属性值进行匹配
,若匹配成功,该注解所标识的控制器方法就是处理请求的方法。
处理请求的方法需要返回一个字符串类型的视图名称,该视图名称会被视图解析器解析
,加上前缀和后缀组成视图的路径,
通过Thymeleaf对视图进行渲染,最终转发到视图所对应页面
相比servlet的流程,多了个前端控制器、SpringMVC的核心配置文件、匹配地址的@RequestMapping注解
三、@RequestMapping注解
1、@RequestMapping注解的功能
从注解名称上我们可以看到,
@RequestMapping注解的作用就是将请求和处理请求的控制器方法关联起来,建立映射关系。
SpringMVC 接收到指定的请求,就会来找到在映射关系中对应的控制器方法来处理这个请求。
注意:多个方法上注解指定的路径不能重复,否则会报错。
2、@RequestMapping注解的位置
@RequestMapping标识一个
类
:设置映射请求的请求路径的
初始信息
@RequestMapping标识一个
方法
:设置映射请求请求路径的
具体信息
用法
:访问时
类路径+方法的路径
。
使用场景
:
比如不同的模块,商品和用户都有列表功能
,也就是说表示列表功能的路径都是由
@RequestMapping("/list")
描述,而不同的方法上的路径相同,启动tomact服务器时会报错,此时在不同的模块 类路径和方法路径组合拼接的到是不同的路径,这样可以防止路径重复而导致的报错。虽然也可以
设置不同方法上的路径不同
,但是这样
不能够见名之意
,如:描述相同的列表功能都用英文单词list描述,再起个其它的名字做不到见名之意了。
@Controller
@RequestMapping("/test")
public class RequestMappingController {
//此时请求映射所映射的请求的请求路径为:/test/testRequestMapping
@RequestMapping("/testRequestMapping")
public String testRequestMapping(){
return "success";
}
}
3、查看@RequestMapping注解源码的属性
查看@RequestMapping注解的属性:
不同的属性分别代表不同的匹配方式
4、@RequestMapping注解中的参数
4.1、value属性(路径值)
@RequestMapping注解的
value属性
通过
请求的
请求地址
匹配请求映射
@RequestMapping注解的
value属性是一个字符串类型的数组,表示该请求映射能够匹配多个请求地址所对应的请求
@RequestMapping注解的
value属性必须设置
,
至少通过请求地址匹配请求映射
<a th:href="@{/testRequestMapping}">测试@RequestMapping的value属性-->/testRequestMapping</a><br>
<a th:href="@{/test}">测试@RequestMapping的value属性-->/test</a><br>
//value属性是一个字符串类型的数组,值可以设置为多个:表示发送的请求只要满足路径数组
//中的一个就可以匹配到。
@RequestMapping(value = {"/testRequestMapping", "/test"})
public String testRequestMapping(){
return "success";
}
4.2、method属性(请求方式)
@RequestMapping注解的
method属性
通过
请求的
请求方式
(
get或pos
t)匹配请求映射
@RequestMapping注解的method属性是一个
RequestMethod类型的数组
,
表示该请求映射能够匹配多种请求方式的请求,只要满足数组中的一个就可以匹配到。
注意:如果既设置了value属性又设置了method属性,表示2个都满足才能匹配成功。
若当前请求的请求地址满足请求映射的value属性,但是请求方式不满足method属性,则浏览器报错405:
Request method ‘POST’ not supported
html:
<a th:href="@{/test}">测试@RequestMapping的value属性-->/test</a><br>
<form th:action="@{/test}" method="post">
<input type="submit">
</form>
java:
//方式一:
//只设置value属性不设置method属性,表示满足路径的任意请求方式都可以访问。
//methodRequestMethod类型的数组:表示只要满足数组中的一个就可以匹配到
//value属性类型的数组+method属性类型的数组表示:在满足value数组中的一个路径+满足method数组中的一个请求方式,才能匹配成功。
@RequestMapping(value = {"/testRequestMapping", "/test"},
method = {RequestMethod.GET, RequestMethod.POST})
public String testRequestMapping(){
return "success";
}
//方式二:
//表示满足请求方式为get请求,并且请求的路径是/testGetMapping才能匹配成功,
//因为value属性的值只有一个所以可以省略。
@GetMapping("/testGetMapping")
public String testGetMapping(){
return "success";
}
注:
1、对于处理指定请求方式的控制器方法,SpringMVC中提供了@RequestMapping的派生注解
处理get请求的映射–>@GetMapping
处理post请求的映射–>@PostMapping
处理put请求的映射–>@PutMapping
处理delete请求的映射–>@DeleteMapping
2、常用的请求方式有get,post,put,delete
但是目前浏览器只支持get和post,若在form表单提交时,为method设置了其他请求方式的字符串(put或delete),则按照默认的请求方式get处理
若要发送put和delete请求,则需要通过
spring提供的过滤器HiddenHttpMethodFilter
,在
RESTful部分会讲到
4.3、params属性(请求参数 了解)
@RequestMapping注解的
params属性通过请求的
请求参数
匹配请求映射
@RequestMapping注解的params属性是
一个字符串类型的数组
,可以通过
四种表达式设置请求参数和请求映射的匹配关系
“param”
:要求请求映射所匹配的请求
必须携带param请求参数,它的值携不携带无所谓。
“!param”
:要求请求映射所匹配的请求必须
不能携带param请求参数
“param=value”
:要求请求映射所匹配的请求
必须携带param请求参数且param=value
“param!=value”
:要求请求映射所匹配的请求必
须携带param请求参数但是param!=value
注意:这个是必须满足数组中所有的参数才能匹配成功,之前的value和mtthod是满足数组中的一个就可以。
<!--也可以写为:@{/test?username='admin',password=123456} 这样?会报一个错误但是不影响使用,
可以使用下面thymeleaf提供的路径参数写法,他在解析是会自动转化为?拼接的形式。-->
<a th:href="@{/test(username='admin',password=123456)}">测试@RequestMapping的params属性-->/test</a><br>
//表示需要满足value属性数组的一个地址+method数组中的一个请求方式+params数组中请求的参
// 数必须携带username,password,并且password的值不能是123456。
@RequestMapping(
value = {"/testRequestMapping", "/test"}
,method = {RequestMethod.GET, RequestMethod.POST}
,params = {"username","password!=123456"}
)
public String testRequestMapping(){
return "success";
}
注:
若当前请求满足@RequestMapping注解的value和method属性,但是不满足params属性,此时页面回报错400:Parameter conditions “username, password!=123456” not met for actual request parameters: username={admin}, password={123456}
4.4、headers属性(请求头 了解)
@RequestMapping注解的headers属性
通过请求的
请求头信息
匹配请求映射
@RequestMapping注解的headers属性是
一个字符串类型的数组
,可以通过四种表达式设置请求头信息和请求映射的匹配关系
“header”
:要求请求映射所匹配的请求必须携带header请求头信息
“!header”
:要求请求映射所匹配的请求必须不能携带header请求头信息
“header=value”
:要求请求映射所匹配的请求必须携带header请求头信息且header=value
“header!=value”
:要求请求映射所匹配的请求必须携带header请求头信息且header!=value
注意:这个也是必须满足数组中所有的参数才能匹配成功,之前的value和method是满足数组中的一个就可以。用法类似params属性。
<a th:href="@{/testParamsAndHeaders(username='admin',password=123)}">测试RequestMapping注解的params属性-->/testParamsAndHeaders</a><br>
// 表示满足value属性数组的一个地址+任意的请求方式+params数组中请求的参
// 数必须携带username,password,并且password的值不能是123456 +headers请求头
// 信息必须携带Host作为key,并且值必须为localhost:8080
@RequestMapping(
value = "/testParamsAndHeaders",
params = {"username","password!=123456"},
headers = {"Host=localhost:8080"}
)
目录结构
若当前请求满足@RequestMapping注解的value和method属性,但是不满足headers属性,此时页面显示404错误,即资源未找到
4.5、ant风格的value路径值(模糊匹配)
针对的是@RequestMapping注解的
value属性
可以设置不同的格式来实现
模糊匹配
。
?
:表示任意的单个字符,但是不能写特殊字符如:?、/,不能少写不能多写。
*
:表示任意的0个或多个字符,但是不能写 / 路径分隔符。
**
:表示任意的一层或多层目录或没有一层目录,路径中可以写斜线/路径分隔符
注意:在使用**时,只能使用/**/xxx的方式,不能再**的前后添加内容如:/a**a/testAnt 表示2个单独的*被解析
<a th:href="@{/a1a/testAnt}">测试@RequestMapping可以匹配ant风格的路径-->使用?</a><br>
<a th:href="@{/a111a/testAnt}">测试@RequestMapping可以匹配ant风格的路径-->使用*</a><br>
<a th:href="@{/a1a/a/a/a/testAnt}">测试@RequestMapping可以匹配ant风格的路径-->使用**</a><br>
//@RequestMapping("/a?a/testAnt") 表示请求的地址可以是/a1a/testAnt 这个1可以是任意的单个字符
//@RequestMapping("/a*a/testAnt")表示任意的0个或多个字符
@RequestMapping("/**/testAnt")//表示任意的一层或多层目录
public String testAnt(){
return "success";
}
4.6、含有占位符”{ }”的value路径值(redtful风格)
针对的也是@RequestMapping注解的
value属性
原始方式传参
:
/deleteUser?id=1
RESTful方式传参
:
/deleteUser/1------>把请求参数以路径的方式拼接到url中进行传参
SpringMVC路径中的
占位符常用于RESTful风格中
,当请求路径中将
某些数据通过路径的方式
传输到服务器中,就可以在相应的
@RequestMapping注解的value属性中通过占位符{xxx}表示传输的数据
,在通过
@PathVariable注解,将占位符所表示的数据赋值给控制器方法的形参
html:
<a th:href="@{/testRest/1/admin}">测试路径中的占位符-->/testRest</a><br>
java:
/*
* 解释:首先:把地址/testRest/1/admin复制到注解中,使用占位符{}表示当前路径的请求参数,并且起一个名字。
* 其次:使用@PathVariable注解获取到对应的占位符id所代表的路径里面的参数值,并把值赋值给形参Integer id。
* 如果是多个参数就使用多个占位符,每个参数都要使用@PathVariable注解获取,并赋值给对应的形参。
* 要求:路径参数名和形参名保持一致
* */
@RequestMapping("/testRest/{id}/{username}")
public String testRest(@PathVariable("id") String id, @PathVariable("username") String username){
System.out.println("id:"+id+",username:"+username);
return "success";
}
//最终输出的内容为-->id:1,username:admin
四、SpringMVC获取请求参数
客户端请求参数的格式是
:
name=value&name=value… …
服务器端要获得请求的参数,有时还需要进行数据的封装,SpringMVC可以接收如下类型的参数:
单个参数:
多个不同类型的参数:
同一个参数有多个值:
- 基本类型/String类型参数
- POJO类型参数
- 数组类型参数
- 集合类型参数
1、通过ServletAPI获取(方式一)
将HttpServletRequest作为控制器方法的形参,此时HttpServletRequest类型的参数表示封装了当前请求的请求报文的对象。
要求
:
- 形参名和请求参数名保持一致
-
获取到的所有参数值都是
字符串类型
,如果想要得到基本类型要进行
类型转换
。
注意:一旦使用restful风格的以路径拼接参数的形式,就不能使用ServletAPI获取请求参数了。
<!--注意:因为使用?传参会报红线,所以这里使用thympleaf格式传参,他被浏览器解析时会自动转化为?的形式-->
<a th:href="@{/testServletAPI(username='admin',password=123456)}">测试使用servletAPI获取请求参数</a><br>
@RequestMapping("/testParam")
//形参位置的request表示当前请求
//解释:在springMVC的控制层方法中写了HttpServletRequest request作为形参,前端控制器DispatcherServlet
// 底层调用此方法时,会根据控制器方法的形参注入对应的值。
public String testParam(HttpServletRequest request){
String username = request.getParameter("username");
String password = request.getParameter("password");
System.out.println("username:"+username+",password:"+password);
return "success";
}
2、通过控制器方法的形参获取请求参数(方式二)
2.1、基本类型、String类型
说明:
-
要求
:Controller中的业务方法的
参数名称
要与
请求参数的name
一致,参数值会自动映射匹配。 -
用于
:接收一个或多个不同类型的参数、
会自动类型转换(SpringBoot接收简单参数)
。 -
注意
:如果请求参数有很多,都写在方法的参数上,代码太过臃肿。
浏览器地址:
http://localhost:8080/itheima_springmvc1/quick9?username=zhangsan&age=12
控制层方法:
@RequestMapping("/quick9")
@ResponseBody
public void quickMethod9(String username,int age) throws IOException {
System.out.println(username);//zhangsan
System.out.println(age);//12
}
2.2、简单POJO类型参数
实体类的参数都是简单参数。
说明:
-
要求
:Controller中的业务方法的
POJO对象参数的属性名
与
请求参数的name一致
,参数值会自动映射匹配。 -
用于
:接收一个或多个不同类型的参数。 -
注意
:底层通过反射调用的是set、get方法,
实际上
是保证请求参数在实体类中有对应的set、get方法,只不过一般属性名和set、get方法保持一致,所以通常说只要保证请求参数和实体类属性名保持一致即可。
浏览器地址:
http://localhost:8080/itheima_springmvc1/quick9?username=zhangsan&age=12
pojo类
public class User {
private String username;
private int age;
getter/setter…
}
控制层方法:
@RequestMapping("/quick10")
@ResponseBody
public void quickMethod10(User user) throws IOException {
//User{username='zhangsan', age=12}
System.out.println(user);
}
2.3、复杂POJO类型参数
实体类的参数包含对象类型。
User:
package com.cn.pojo;
public class User {
private String name;
private Integer age;
private Address address;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
", address=" + address +
'}';
}
}
Address:
package com.cn.pojo;
public class Address {
private String province;
private String city;
public String getProvince() {
return province;
}
public void setProvince(String province) {
this.province = province;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
@Override
public String toString() {
return "Address{" +
"province='" + province + '\'' +
", city='" + city + '\'' +
'}';
}
}
Test01Controller:
package com.cn.controller;
import com.cn.pojo.User;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class Test01Controller {
@RequestMapping("/complexPojo")
public String complexPojo(User user){
System.out.println(user);
return "ok";
}
}
运行测试:
2.4、数组,字符串、可变参数、list集合类型参数
说明:
-
要求
:Controller中的业务方法数组名称与请求参数的name一致,参数值会自动映射匹配。 -
用于
:接收同一类型的参数有多个值。 -
注意
:同一类型的多个参数,格式是
strs=111&strs=222&strs=333
Url:
http://localhost:8080/springMVC/quick13?strs=111&strs=222&strs=333
控制层方法:
@Controller
public class ControllerTee {
//字符串类型
//http://localhost:8080/springMVC/quick13?strs=111&strs=222&strs=333
@RequestMapping("/quick13")
@ResponseBody
public void quickMethod13(String strs) throws IOException {
System.out.println(strs);//111,222,333
}
//可变参数,本质上是数组,方法名相同 形参类型相同的可变参数和数组 不构成重载
// 一个方法中最多有一个,并且多个参数要写在最后 用法和数组相同
//http://localhost:8080/springMVC/quick12?strs=111&strs=222&strs=333
@RequestMapping("/quick12")
@ResponseBody
public void quickMethod12(String... strs) throws IOException {
//数组转化为集合在输出,因为集合可以直接输出里面的值。
System.out.println(Arrays.asList(strs));//[111, 222, 333]
}
//数组
//http://localhost:8080/springMVC/quick11?strs=111&strs=222&strs=333
@RequestMapping("/quick11")
@ResponseBody
public void quickMethod11(String[] strs) throws IOException {
//直接输出数组是地址,所以调用toString()输出里面的值
System.out.println(Arrays.toString(strs));//[111, 222, 333]
}
//list集合类型,形参集合名要和请求参数名保持一致,并且要添加@RequestParam注解
// 进行绑定,因为默认情况下多个值是封装在数组中的
//http://localhost:8080/springMVC/quick14?strs=111&strs=222&strs=333
@RequestMapping("/quick14")
@ResponseBody
public void quickMethod14(@RequestParam List<String> strs) throws IOException {
System.out.println(strs);//[111, 222, 333]
}
}
2.5、日期类型参数
- 可以封装到日期类Date(jdk1.8之前)或者本地日期时间类LocalDateTime(jdk1.8之后)
-
前端传递日期类型的参数格式有很多种类,可能是斜杠、横杠分割,也可能是年月日类型的。所以在服务端接收时需要通过注解
@DateTimeFormat(pattern="xxx")
指定前端传递来的格式。
eg:如果注解指定的格式为
@DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
,那么前端的请求在传递参数时必须按照这个格式来传递请求参数。 -
形参名和请求参数名保持一致。
测试:
控制层方法:
@RequestMapping("/dataParam")
public String dataParam(@DateTimeFormat(pattern ="yyyy-MM-dd HH:mm:ss") LocalDateTime updateTime){
System.out.println(updateTime);
return "ok";
}
2.6、json类型参数
-
步骤一:使用postman发送json类型数据
- 要求:json格式的请求数据需要放在请求体中携带到服务端的,因此使用post请求。
-
步骤二:在服务端通过
实体对象
来接收- 要求1:json数据的key与实体类的属性名保持一致。
-
要求2:需要使用
@RequestBody
注解标识(使用此注解标识后才能将json格式的数据封装到实体类中)
运行测试:
2.7、List集合对象类型参数
2.7.1、通过表单提交
说明:
-
要求一
:获得集合对象参数时,要将集合参数包装到一个pojo类中才可以,这个pojo类一般又叫作Vo视图对象。
解释
:不能在方法中直接写
List<User> list
,而是要把它封装成一个VO对象,再把VO对象类型作为方法的参数。 -
要求二
:需要在表单的name属性上表明,是往集合中的第几个对象的什么属性上赋值。 -
步骤
:先把请求参数封装为实体类,再把实体类对象放到List集合,在把list集合放到Vo类中
User类:
public class User {
private String username;
private int age;
getter/setter…
}
Vo对类:
package com.atguigu.mvc.bean;
import java.util.List;
public class Vo {
private List<User> list;
public List<User> getList() {
return list;
}
public void setList(List<User> list) {
this.list = list;
}
@Override
public String toString() {
return "Vo{" +
"list=" + list +
'}';
}
}
form.jsp:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<!--el表达式动态获取工程路径:http://localhost:8080/springMVC/-->
<form action="${pageContext.request.contextPath}/quick14" method="post">
<!--表明是集合中的第一个对象User的age属性-->
<input type="text" name="list[0].username"><br>
<input type="text" name="list[0].age"><br>
<input type="text" name="list[1].username"><br>
<input type="text" name="list[1].age"><br>
<input type="submit" value="提交"><br>
</form>
</body>
</html>
控制层:
@Controller
public class ControllerTee {
@RequestMapping("/quick14")
@ResponseBody
public void quickMethod12(Vo vo) throws IOException {
//通过对象获取里面的集合属性
//[User{username='xiaoming', age=22}, User{username='xiaohong', age=23}]
System.out.println(vo.getList());
}
}
2.7.2、通过Ajax提交
说明
:当使用ajax提交时,可以指定contentType为json形式,那么在方法参数位置使用@RequestBody可以直接接收集合数据而无需使用POJO进行包装。
User类:
public class User {
private String username;
private int age;
getter/setter…
}
ajax.jsp:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
<!--js属于静态资源,需要开启访问权限-->
<script src="${pageContext.request.contextPath}/js/jquery-3.3.1.js"></script>
<script>
var userList = new Array(); //定义一个js数组,有2种方式
userList.push({username:"zhangsan",age:18}); //push() 压入到数组尾部。
userList.push({username:"lisi",age:28});
alert(JSON.stringify(userList)); //打桩查看输出的形式[{"username":"zhangsan","age":18},{"username":"lisi","age":28}]
$.ajax({
type:"POST",
url:"${pageContext.request.contextPath}/quick15",
data:JSON.stringify(userList), //data有2种格式。name=aa&age=20和{key:value} 这里把数组转换后的格式恰好是第二种:
contentType: 'application/json;charset=utf-8' //发送数据到服务器时所使用的内容类型
});
</script>
</head>
<body>
</body>
</html>
控制层:
@RequestMapping("/quick15")
@ResponseBody
public void quickMethod15(@RequestBody List<Test> list) throws IOException {
//通过对象获取里面的集合属性
//[User{username='zhangsan', age=18}, User{username='lisi', age=28}]
System.out.println(list);
}
3、@RequestParam(请求参数名和形参不同)
@RequestParam是将请求参数和控制器方法的形参创建映射关系,
即:请求参数名字和方法上的形参名字不一致时进行匹配赋值。
@RequestParam注解一共有三个属性:
-
value
:指定为形参赋值的,请求参数名 -
required
:设置是否必须传输此请求参数,
默认值为true
若设置为true时
,则当前请求必须传输value所指定的请求参数,若没有传输该请求参数,且没有设置defaultValue属性,则页面报错400:Required String parameter ‘xxx’ is not present;
若设置为false
,则当前请求不是必须传输value所指定的请求参数,若没有传输,则注解所标识的形参的值为null -
defaultValue
:不管required属性值为true或false,当value所指定的请求参数
没有传输或传输的值为
""
时
,则使用默认值为形参赋值
<form th:action="@{/testParam}" method="get">
用户名:<input type="text" name="user_name"><br>
密码:<input type="password" name="password"><br>
爱好:<input type="checkbox" name="hobby" value="a">a
<input type="checkbox" name="hobby" value="b">b
<input type="checkbox" name="hobby" value="c">c<br>
<input type="submit" value="测试使用控制器的形参获取多个同名的请求参数---》请求参数名和形参名不一致。">
</form>
@RequestMapping("/testParam")
public String testParam(
@RequestParam(value = "user_name", required = false, defaultValue = "hehe") String username,
String password,
String[] hobby ){
System.out.println("username:"+username+",password:"+password+",hobby:"+ Arrays.toString(hobby));
System.out.println("host:"+host);
System.out.println("JSESSIONID:"+JSESSIONID);
return "success";
}
4、@RequestHeader(获取请求头信息)
@RequestHeader是将
请求头信息和控制器方法的形参创建映射关系
使用场景
:如果想要获取请求头信息可以加上此注解创建映射关系进行获取。
@RequestHeader
注解一共有
三个属性:value、required、defaultValue,
用法同@RequestParam
-
value
:指定为形参赋值的,请求参数名 -
required
:设置是否必须传输此请求参数,
默认值为true
若设置为true时
,则当前请求必须传输value所指定的请求参数,若没有传输该请求参数,且没有设置defaultValue属性,则页面报错400:Required String parameter ‘xxx’ is not present;
若设置为false
,则当前请求不是必须传输value所指定的请求参数,若没有传输,则注解所标识的形参的值为null -
defaultValue
:不管required属性值为true或false,当value所指定的请求参数
没有传输或传输的值为
""
时
,则使用默认值为形参赋值
<!--表单提交就不需要在地址中手动提交参数了。-->
<form th:action="@{/testParam}" method="get">
用户名:<input type="text" name="user_name"><br>
密码:<input type="password" name="password"><br>
爱好:<input type="checkbox" name="hobby" value="a">a
<input type="checkbox" name="hobby" value="b">b
<input type="checkbox" name="hobby" value="c">c<br>
<input type="submit" value="测试使用控制器的形参获取多个同名的请求参数---》请求参数名和形参名不一致。">
</form>
@RequestMapping("/testParam")
public String testParam(
@RequestParam(value = "user_name", required = false, defaultValue = "hehe") String username,
String password,
String[] hobby,
@RequestHeader(value = "Host", required = true, defaultValue = "haha") String host,
@CookieValue("JSESSIONID") String JSESSIONID){//通过k获取v
System.out.println("username:"+username+",password:"+password+",hobby:"+ Arrays.toString(hobby));
System.out.println("host:"+host);
System.out.println("JSESSIONID:"+JSESSIONID);
return "success";
}
5、@CookieValue(获取cookie信息)
@CookieValue是将
cookie数据和控制器方法的形参创建映射关系。
@CookieValue
注解一
共有三个属性:value、required、defaultValue,用法同
@RequestParam
-
value
:指定为形参赋值的,请求参数名 -
required
:设置是否必须传输此请求参数,
默认值为true
若设置为true时
,则当前请求必须传输value所指定的请求参数,若没有传输该请求参数,且没有设置defaultValue属性,则页面报错400:Required String parameter ‘xxx’ is not present;
若设置为false
,则当前请求不是必须传输value所指定的请求参数,若没有传输,则注解所标识的形参的值为null -
defaultValue
:不管required属性值为true或false,当value所指定的请求参数
没有传输或传输的值为
""
时
,则使用默认值为形参赋值
<form th:action="@{/testParam}" method="get">
用户名:<input type="text" name="user_name"><br>
密码:<input type="password" name="password"><br>
爱好:<input type="checkbox" name="hobby" value="a">a
<input type="checkbox" name="hobby" value="b">b
<input type="checkbox" name="hobby" value="c">c<br>
<input type="submit" value="测试使用控制器的形参获取多个同名的请求参数---》请求参数名和形参名不一致。">
</form>
@RequestMapping("/testServletAPI")
public String testServletAPI(HttpServletRequest request){
//想要测试cookie就需要先有cookie,可以通过创建session的方式创建cookie
HttpSession session = request.getSession();
String username = request.getParameter("username");
String password = request.getParameter("password");
System.out.println("username:"+username+",password:"+password);
return "success";
}
@RequestMapping("/testParam")
public String testParam(
@RequestParam(value = "user_name", required = false, defaultValue = "hehe") String username,
String password,
String[] hobby,
@RequestHeader(value = "sayHaha", required = true, defaultValue = "haha") String host,
@CookieValue("JSESSIONID") String JSESSIONID){
System.out.println("username:"+username+",password:"+password+",hobby:"+ Arrays.toString(hobby));
System.out.println("host:"+host);
System.out.println("JSESSIONID:"+JSESSIONID);
return "success";
}
7、解决post请求获取请求参数中文的乱码问题
7.1 包含请求和响应
说明
:解决获取请求参数的乱码问题,可以使用SpringMVC提供的编码过滤器CharacterEncodingFilter,但是必须
在web.xml中进行注册
解释:
- get请求tomact8.0之后中文乱码问题已经解决了,7.0之前的需要在tomact配置文件中进行配置。
-
post请求
原生servlet方式
解决中文乱码问题,需要在获取请求参数之前设置
req.setCharacterEncoding("UTF-8")
,现在使用springMvc方式方式,如果写在方法中明显是先获取了请求参数在设置编码,显然错误。需要找到一个前端控制器DispatcherServlet没有获取请求参数之前的地方才有效果,这个地方就
是过滤器
。
<!--配置springMVC的编码过滤器-->
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<!--设置请求的编码-->
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<!--设置响应的编码:如果是单单获取请求的话可以不写,但是建议写上,
因为响应给浏览器除了转发,重定向,还有 response.getOutputStream(),
直接响应给浏览器中文也会出现乱码问题。-->
<init-param>
<param-name>forceResponseEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
注:
SpringMVC中处理编码的过滤器一定要配置到其他过滤器之前,否则无效
<form th:action="@{/testBean}" method="post">
用户名:<input type="text" name="username"><br>
密码:<input type="password" name="password"><br>
性别:<input type="radio" name="sex" value="男">男<input type="radio" name="sex" value="女">女<br>
年龄:<input type="text" name="age"><br>
邮箱:<input type="text" name="email"><br>
<input type="submit" value="使用实体类接收请求参数">
</form>
package com.atguigu.mvc.bean;
/**
* Date:2021/7/6
* Author:ybc
* Description:
*/
public class User {
private Integer id;
private String username;
private String password;
private Integer age;
private String sex;
private String email;
public User() {//因为框架里面的很多技术用到的都是反射,通过反射创建对象时默认使用的是无参构造,所以
// 建议在使用有参构造时要把无参构造也加上。
}
public User(Integer id, String username, String password, Integer age, String sex, String email) {
this.id = id;
this.username = username;
this.password = password;
this.age = age;
this.sex = sex;
this.email = email;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", age=" + age +
", sex='" + sex + '\'' +
", email='" + email + '\'' +
'}';
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
@RequestMapping("/testBean")
/*post请求得提交方式,在获取中文时会发生乱码,之前学些servlet时要求在获取请求参数之前设置才有效,
这里springMVC在方法上的形参方式获取请求参数明显已经是获取到了,所以肯定不能再此方法中设置,
应该找一个比servlet执行之前的组件进行设置,这个组件就是过滤器,springMVC提供的有过滤器,我们只需要在web.xml
中进行配置即可。*/
public String testBean(User user){
System.out.println(user);
return "success";
}
//最终结果-->User{id=null, username='张三', password='123', age=23, sex='男', email='123@qq.com'}
五、域对象共享数据
说明:
- 用户输入的请求参数传递到控制层在调用service,dao层访问数据库的结果,在一步一步的返回给控制层,在控制层需要将查询到的数据返回给页面就要使用域对象。
-
一共有4种域对象
(pageContext、request、session、application ),但是最小的一种是pageContext只对当前的jsp页面有效,jsp现在基本上淘汰了,所以不在学习pageContext域对象,
我们只需要掌握3种即可
。
pageContext (PageContextImpl 类) 当前 jsp 页面范围内有效
request (HttpServletRequest 类) 一次请求内有效
session (HttpSession 类) 一个会话范围内有效(打开浏览器访问服务器,直到关闭浏览器)
application(ServletContext ) (ServletContext 类) 整个 web 工程范围内都有效(只要 web 工程不停止,数据都在)
说明1:1~5都是往request域对象中共享数据的,7是向session中共享数据的,8是向application中共享数据的
.
说明2:向request域对象共享数据的几种方式(1,2,3,4,5),最终都是模型数据和视图信息都封装在一个ModelAndView对象中。
1、向request域对象共享数据
1.1、使用ServletAPI向request域对象共享数据
说明:选择域对象不是范围越大越好,选择最合适范围的是最好的,因为域对象范围越大造成的空间就越浪费,小的话又不能实现效果。
index.html:
<a th:href="@{/testRequestByServletAPI}">通过servletAPI向request域对象共享数据</a><br>
class ScopeController类:
//使用servletAPI向request域对象共享数据
@RequestMapping("/testRequestByServletAPI")
public String testRequestByServletAPI(HttpServletRequest request){
request.setAttribute("testRequestScope", "hello,servletAPI");
return "success";
}
success.html:
<p th:text="${testRequestScope}"></p>
说明
:启动tomact服务器访问到Index.html页面,在此页面点击超链接发送请求到服务器的对应控制层方法,在方法中存入域对象数据,返回到success.html页面上并在页面把域对象的数据取出来。
1.2、使用ModelAndView向request域对象共享数据
说明:向request域对象共享数据的几种方式(1,2,3,4,5),最终都是模型数据和视图信息都封装在一个ModelAndView对象中。
index.html:
<a th:href="@{/testModelAndView}">通过ModelAndView向request域对象共享数据</a><br>
class ScopeController类:
@RequestMapping("/testModelAndView")
/**
* ModelAndView有Model和View的功能
* Model主要用于向请求域共享数据
* View主要用于设置视图,实现页面跳转
*/
public ModelAndView testModelAndView(){//要求返回值必须是:ModelAndView
ModelAndView mav = new ModelAndView();
//处理模型数据,即向请求域request共享数据
mav.addObject("testRequestScope", "hello,ModelAndView");
//设置视图名称
mav.setViewName("success");
return mav;
}
success.html:
<p th:text="${testRequestScope}"></p>
说明
:启动tomact服务器访问到Index.html页面,在此页面点击超链接发送请求到服务器的对应控制层方法,在方法中存入域对象数据,返回到success.html页面上并在页面把域对象的数据取出来。
1.3、使用Model向request域对象共享数据
说明:Model指的就是ModelAndView中的Model.
index.html:
<a th:href="@{/testModel}">通过Model向request域对象共享数据</a><br>
class ScopeController类:
@RequestMapping("/testModel")
public String testModel(Model model){
model.addAttribute("testRequestScope", "hello,model");
System.out.println(model.getClass().getName());
return "success";
}
success.html:
<p th:text="${testRequestScope}"></p>
说明
:启动tomact服务器访问到Index.html页面,在此页面点击超链接发送请求到服务器的对应控制层方法,在方法中存入域对象数据,返回到success.html页面上并在页面把域对象的数据取出来。
1.4、使用map向request域对象共享数据
说明:只需要在形参位置声明一个Map集合类型的形参,这时候往Map集合中存储的数据就是向request域对象中共享的数据。
index.html:
<a th:href="@{/testMap}">通过map向request域对象共享数据</a><br>
class ScopeController类:
@RequestMapping("/testMap")
public String testMap(Map<String, Object> map){//键:一般是String类型 值:一般是Object类型
map.put("testRequestScope", "hello,map");
System.out.println(map.getClass().getName());
return "success";
}
success.html:
<p th:text="${testRequestScope}"></p>
说明
:启动tomact服务器访问到Index.html页面,在此页面点击超链接发送请求到服务器的对应控制层方法,在方法中存入域对象数据,返回到success.html页面上并在页面把域对象的数据取出来。
1.5、使用ModelMap向request域对象共享数据
说明:类似于Model向request域对象共享数据的用法。
index.html:
<a th:href="@{/testModelMap}">通过ModelMap向request域对象共享数据</a><br>
class ScopeController类:
@RequestMapping("/testModelMap")
public String testModelMap(ModelMap modelMap){
modelMap.addAttribute("testRequestScope", "hello,ModelMap");
System.out.println(modelMap.getClass().getName());
return "success";
}
success.html:
<p th:text="${testRequestScope}"></p>
说明
:启动tomact服务器访问到Index.html页面,在此页面点击超链接发送请求到服务器的对应控制层方法,在方法中存入域对象数据,返回到success.html页面上并在页面把域对象的数据取出来。
1.6、Model、ModelMap、Map的关系
Model、ModelMap、Map类型的参数其实本质上都是 BindingAwareModelMap 类型的
public interface Model{}
public class ModelMap extends LinkedHashMap<String, Object> {}
public class ExtendedModelMap extends ModelMap implements Model {}
public class BindingAwareModelMap extends ExtendedModelMap {}
2、向session域共享数据
说明:向session域共享数据,建议使用原生的ServletAPI,使用SpringMVC提供的反而更麻烦。
index.html:
<a th:href="@{/testSession}">通过servletAPI向session域对象共享数据</a><br>
class ScopeController类:
@RequestMapping("/testSession")
public String testSession(HttpSession session){
session.setAttribute("testSessionScope", "hello,session");
return "success";
}
success.html:
<p th:text="${session.testSessionScope}"></p>
说明
:启动tomact服务器访问到Index.html页面,在此页面点击超链接发送请求到服务器的对应控制层方法,在方法中存入域对象数据,返回到success.html页面上并在页面把域对象的数据取出来。
3、向application域共享数据
<a th:href="@{/testApplication}">通过servletAPI向application域对象共享数据</a><br>
class ScopeController类:
@RequestMapping("/testApplication")
public String testApplication(HttpSession session){
//获取ServletContext对象的方式有很多,比如:request,session,servletConfig等等.
ServletContext application = session.getServletContext();
application.setAttribute("testApplicationScope", "hello,application");
return "success";
}
success.html:
<p th:text="${application.testApplicationScope}"></p>
说明
:启动tomact服务器访问到Index.html页面,在此页面点击超链接发送请求到服务器的对应控制层方法,在方法中存入域对象数据,返回到success.html页面上并在页面把域对象的数据取出来。
六、SpringMVC的视图
SpringMVC中的视图是View接口,视图的作用渲染数据,将模型Model中的数据展示给用户
SpringMVC视图的种类:
-
默认视图
:
转发视图InternalResourceView
和
重定向视图RedirectView
-
JstlView(标签库)图
:引入jstl的
依赖
,
转发
视图
会自动转换为
JstlView(标签库)视图
-
Thymeleaf视图
:在SpringMVC的
配置文件中
配置了Thymeleaf的视图解析器,由此视图解析器解析之后所得到的是
ThymeleafView视图
- 等等
注意
:jsp视图是引入依赖,Thymeleaf视图是在SpringMVC的配置文件中配置。
1、转发视图InternalResourceView
SpringMVC中默认的转发视图是
InternalResourceView
SpringMVC中创建转发视图的情况:
当控制器方法中所设置的视图名称
以
"forward:"为前缀
时
,创建InternalResourceView视图,
此时的视图名称不会被SpringMVC配置文件中所配置的视图解析器解析
,而是会将
前缀"forward:"
去掉,剩余部分作为最终路径通过转发的方式实现跳转
例如:
"forward:/","forward:/employee"
java:
@RequestMapping("/test/view")
public String testView(){
return "test_view";
}
html:
<a th:href="@{/testForward}">测试InternalResourceView</a><br>
java:
@RequestMapping("/testForward")
public String testForward(){
/*说明:什么时候用的才是默认的转发视图???
1.以"forward:"为前缀时
2.这种方式是请求转发
3.只能用于跳转到另一个方法上的路径,不能直接跳转到一个页面。
转发视图一般很少用
*/
return "forward:/testThymeleafView";
}
@RequestMapping("/testThymeleafView")
public String testThymeleafView(){
return "success";
}
说明:启动tomact服务器,首先在浏览器输入地址访问到控制层方法的test_view.html页面,在此页面上点击超链接跳转到控制器的方法上通过请求转发间接访问到success.html页面。
其中test_view.html页面和success.html页面都是使用的Thymeleaf技术写的页面。
2、重定向视图RedirectView
SpringMVC中默认的重定向视图是RedirectView
当控制器方法中所设置的视图名称
以
"redirect:"
为前缀时,创建RedirectView视图
,此时的视图名称
不会被SpringMVC配置文件中所配置的视图解析器解析
,而是会将前缀
"redirect:"
去掉,剩余部分作为最终路径通过重定向的方式实现跳转
例如”redirect:/”,“redirect:/employee”
java:
@RequestMapping("/test/view")
public String testView(){
return "test_view";
}
html:
<a th:href="@{/testRedirect}">测试RedirectView</a><br>
java:
@RequestMapping("/testRedirect")
public String testRedirect(){
/*说明:什么时候用的才是默认的重定向视图???
1.以"redirect:"为前缀时
2.这种方式是重定向
3.也是只能跳转到一个方法上的路径,不能直接跳转到页面。
重定向视图比较常用,一般用于请求执行成功后跳转到首页,此时2个请求
的业务逻辑没有关联,所以url地址应该不一样。
*/
return "redirect:/testThymeleafView";
}
@RequestMapping("/testThymeleafView")
public String testThymeleafView(){
return "success";
}
注:
重定向视图在解析时,会先将redirect:前缀去掉,然后会判断剩余部分是否以/开头,若是则会自动拼接上下文路径
3、ThymeleafView视图
当控制器方法中所设置的视图名称
没有任何前缀
时,此时的视图名称会被
SpringMVC配置文件中所配置的视图解析器解析
,视图名称拼接视图前缀和视图后缀所得到的最终路径,会通过
转发
的方式实现跳转
class TestController:返回test_view页面的方法。
@RequestMapping("/test/view")
public String testView(){
return "test_view";
}
test_view页面的超链接:
<a th:href="@{/testThymeleafView}">测试ThymeleafView</a><br>
ViewController:超链接跳转的控制层方法:
@RequestMapping("/testThymeleafView")
public String testThymeleafView(){
/*当页面是Thymeleaf编写的html页面,里面使用的视图种类:
说明一:什么时候用的才是ThymeleafView视图???
1.需要在SpringMVC配置文件中进行配置Thymeleaf的视图解析器
2.return 后没有任何前缀和后缀 (即便配置了视图解析器,一旦有前缀
和后缀仍然不是ThymeleafView视图)
3.这种方式是请求转发
4.用于跳转到一个页面中。
说明二:默认视图:
1.方法上加上前缀forword 仍然使用的是默认转发视图InternalResourceView
2.方法上加上前缀rediret 仍然使用的是默认重定向视图RedirectView
*/
return "success";//不能有任何前缀
}
测试:启动tomact服务器,首先通过浏览器地址访问控制层的方法返回test_view页面,在页面上点击超连接跳转到控制层返回success.html页面的方法。
4、JSP视图默认使用转发视图解析器
4.1、jsp获取上下文
说明
:
-
当工程引入jstl的依赖,转发视图会自动转换为JstlView(
jstl标签库视图
) -
如果没有引入标签库依赖,jsp默认使用的是转发视图解析器
InternalResourceViewResolver
测试:
步骤1:创建一个新的Maven web工程:springMVX-jsp,并配置tomact实例。
步骤2:添加依赖
<?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.mvc</groupId>
<artifactId>springMVC-jsp</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>
<!-- 日志 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<!-- ServletAPI -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
步骤3:编写配置文件web.xml,springMVC.xml页面
web.xml:
<?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">
<!--配置编码过滤器-->
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceResponseEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!--配置springMVC的前端控制器DispatcherServlet-->
<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>
</servlet>
<servlet-mapping>
<servlet-name>DispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
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.atguigu.mvc.controller"></context:component-scan>
<!--配置jsp页面视图的前缀和后缀,之前配置的是thympleaf视图的前缀和后缀-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/templates/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
</beans>
步骤4:添加控制层方法和页面
JspController:
package com.atguigu.mvc.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* Date:2021/7/7
* Author:ybc
* Description:
*/
@Controller
public class JspController {
@RequestMapping("/success")
public String success(){
/* 当页面是jsp,里面使用的视图种类:
说明一:什么时候用的才是JSP视图???
1.没有添加jstl依赖,jsp视图默认使用的是转发视图解析器InternalResourceViewResolver
2.方法中不能有前缀和后缀
3.用于跳转到页面。
说明二:什么时候用的才是JSTL视图???
1. 当工程引入jstl的依赖,转发视图会自动转换为JstlView(jstl标签库视图)
说明三:默认视图:
1.方法上加上前缀forword 仍然使用的是默认转发视图InternalResourceView
2.方法上加上前缀rediret 仍然使用的是默认重定向视图RedirectView
*/
return "success";//请求转发
}
}
index.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h1>首页</h1>
<%--路径被浏览器解析:http://ip:port/,所以需要添加配置的上下文对象,一般是工程名--%>
<%--路径:/springMVC-jsp/success 但是这种路径写死了不好,如果上下文对象改变了还需要修改页面。--%>
<%--之前使用Thympleaf标签可以自动获取拼接上下文对象,在jsp中可以通过el表达式,动态的获取域对象中的上下文路径--%>
<a href="${pageContext.request.contextPath}/success">success.jsp</a>
</body>
</html>
success.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
成功
</body>
</html>
访问
:启动tomact服务器会默认访问webappp目录下的以index为开头的页面如index.jsp,之后点击超链接跳转到控制层方法,在此方法中返回success.jsp页面。
5、视图控制器:view-controller
5.1、注解驱动标签
当控制器方法中,仅仅用来实现页面跳转,即只需要设置视图名称时,可以将处理器方法使用view-controller标签进行表示
说明
:
在控制器方法中,仅仅用来实现页面跳转,没有其它请求过程的处理,就可以在配置文件中设置路径和视图的映射关系,在控制层中就可以省略返回页面的方法了
。
<!--
path:设置处理的请求地址
view-name:设置请求地址所对应的视图名称
-->
<mvc:view-controller path="/" view-name="index"></mvc:view-controller>
<!--开启mvc的注解驱动-->
<!--当在配置文件中写了一个映射标签之后,那么当前控制器所写的请求映射将全部失效。
也就是说,即便只写一个标签用来省略控制器的一个请求映射方法,那么其它的控制器所写的请求映射方法也会失效。-->
<!--解决:需要添加一个标签,作用是开启mvc的注解驱动。-->
<mvc:annotation-driven />
注意
:注解驱动标签
<mvc:annotation-driven />
,在不同的地方代表的功能不同。
如:
-
在配置文件中添加映射关系标签
省略一个只有返回页面功能的控制层的方法时
,
其它的控制层方法会失效
,加上此标签可以让别的控制层的方法生效。 -
在将resful案例里面,需要开放对静态资源的访问,这个时候就需要用到一个servlet标签来处理静态资源,如果加上servlet标签来处理静态资源,那么仅仅
只有静态资源能够被处理
,我们所写的
控制器的请求方法也会全部失效
,此时同样需要配置mvc的注解驱动。 -
将
java转化为json对象时
,此时同样需要配置mvc的注解驱动,
才能转化成功
。
注:
当SpringMVC中设置任何一个view-controller时,其他控制器中的请求映射将全部失效,此时需要在SpringMVC的核心配置文件中设置开启mvc注解驱动的标签:
<mvc:annotation-driven />
七、RESTful
1、RESTful简介
REST
:RepresentationalStateTransfer,表现层资源状态转移。
RESTFul
:
-
概述
:是一种软件架构风格。(你可以用也可以不用,但是开发中用的比较多) -
本质
:
相同的请求路径,不同的请求方式来表示不同的操作。
-
使用背景
:传统方式项目中每个人都可以定义不同方式url风格 太过随意,没有一个统一的标准 规范,这样会导致增加项目中后期的维护成本。
-
注意事项:
1.1 资源
资源是一种看待服务器的方式,即,将服务器看作是由很多离散的资源组成
。每个资源是服务器上一个可命名的抽象概念。因为资源是一个抽象的概念,所以它不仅仅能代表服务器文件系统中的一个文件、数据库中的一张表等等具体的东西,可以将资源设计的要多抽象有多抽象,只要想象力允许而且客户端应用开发者能够理解。与面向对象设计类似,资源是以名词为核心来组织的,首先关注的是名词。一个资源可以由一个或多个URI来标识。URI既是资源的名称,也是资源在Web上的地址。对某个资源感兴趣的客户端应用,可以通过资源的URI与其进行交互。
1.2 资源的表述
资源的表述是一段对于资源在某个特定时刻的状态的描述
。可以在客户端-服务器端之间转移(交换)。资源的表述可以有多种格式,例如
HTML/XML/JSON/纯文本/图片/视频/音频等等
。资源的表述格式可以通过协商机制来确定。请求-响应方向的表述通常使用不同的格式。
1.3 状态转移
状态转移说的是:
在客户端和服务器端之间转移(transfer)代表资源状态的表述
。通过转移和操作资源的表述,来间接实现操作资源的目的。
2、RESTful的实现
RESTFul总结
:
-
概述
:是一种软件架构风格。(你可以用也可以不用,但是开发中用的比较多) -
本质
:
相同的请求路径,不同的请求方式来表示不同的操作。
- 如何携带参数:
- 注意事项:
具体说,就是 HTTP 协议里面,四个表示操作方式的动词:
GET、POST、PUT、DELETE
。
它们分别对应四种基本操作:
GET 用来获取资源(查询),POST 用来新建资源(插入),PUT 用来更新资源(更新),DELETE 用来删除资源。
REST 风格提倡
URL 地址使用统一
的风格设计,从前到后
各个单词使用斜杠分开
,
不使用问号键值
对方式
携带请求参数
,而是将要
发送给服务器的数据作为 URL 地址的一部分
,以保证整体风格的一致性。
操作 | 传统方式 | REST风格 |
---|---|---|
查询操作 | getUserById?id=1 | user/1–>get请求方式 |
保存操作 | saveUser | user–>post请求方式 |
删除操作 | deleteUser?id=1 | user/1–>delete请求方式 |
更新操作 | updateUser | user–>put请求方式 |
说明:
-
之前传参用的是?拼接的形式进行传参,使用restful风格是
以参数拼接到路径的方式进行传参
。 -
@RequestMapping注解的method属性设置以请求方式匹配路径,常用的请求方式有get,post,put,delete。但是
目前浏览器只支持get和post
,若在form表单提交时,为method设置了其他请求方式的字符串(put或delete),则按照默认的请求方式
get
处理,
若要发送put和delete请求
,则需要通过spring提供的过滤器HiddenHttpMethodFilter,在RESTful部分会讲到。
3、HiddenHttpMethodFilter(解析post请求)
由于浏览器只支持发送get和post方式的请求,那么该如何发送put和delete请求呢?
SpringMVC 提供了
HiddenHttpMethodFilter过滤器
帮助我们
将 POST 请求转换为 DELETE 或 PUT 请求
HiddenHttpMethodFilter
处理put和delete请求的条件:
-
当前请求的请求方式必须为
post
-
当前请求必须传输请求参数
_method
满足以上条件,
HiddenHttpMethodFilter
过滤器就会将当前请求的请求方式转换为请求参数_method的值,因此请求参数_method的值才是最终的请求方式
在web.xml
中注册
HiddenHttpMethodFilter
<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>
注:
目前为止,SpringMVC中提供了两个过滤器:CharacterEncodingFilter和HiddenHttpMethodFilter
在web.xml中注册时,必须先注册CharacterEncodingFilter,再注册HiddenHttpMethodFilter (在web.xml中先配置谁的过滤器就先执行那个过滤器)
原因:
在 CharacterEncodingFilter 中通过 request.setCharacterEncoding(encoding) 方法设置字符集的
request.setCharacterEncoding(encoding) 方法要求前面不能有任何获取请求参数的操作
而 HiddenHttpMethodFilter 恰恰有一个获取请求方式的操作:
String paramValue = request.getParameter(this.methodParam);
4、使用RESTFUL模拟操作用户资源crud
控制层返回test_rest.html页面的方法:
@RequestMapping("/test/rest")
public String testRest(){
return "test_rest";
}
web.xml配置的过滤器:
<!--配置HiddenHttpMethodFilter 多个过滤器,谁先配置谁先执行。在这个解析
post请求的过滤器中获取了请求参数,所以编码过滤器要写在此过滤器之前,不然
就不起作用了。
-->
<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>
test_rest.html页面:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--查询测试:get-->
<a th:href="@{/user}">查询所有用户信息</a><br>
<a th:href="@{/user/1}">根据id查询用户信息</a><br>
<!--添加测试:post-->
<form th:action="@{/user}" method="post">
用户名:<input type="text" name="username"><br>
密码:<input type="password" name="password"><br>
<input type="submit" value="添加"><br>
</form>
<!--更新测试:put-->
<form th:action="@{/user}" method="post">
<!--页面需要满足2个条件: 1.请求方式必须是post请求
2.必须要传一个参数_method 这个参数对用户没有用处,用户不需要看见所以设置为hidden隐藏表单项。
3.value为被解析成的请求方式。
-->
<input type="hidden" name="_method" value="PUT">
用户名:<input type="text" name="username"><br>
密码:<input type="password" name="password"><br>
<input type="submit" value="修改"><br>
</form>
<!--删除测试:delete
问题:在没有使用ajax的情况下,浏览器是通过解析form表单的post请求来发送put、delete请求。
如果是put更新,业务逻辑一般是点击更新按钮跳转到更新页面,在更新页面上直接通过form表
单提交数据,提交按钮本来就是在表单上,所以可以直接使用表单进行提交。
如果是删除,业务逻辑用的是超链接,一般是通过超连接地址携带参数发送get请求进行删除,
现在想要使用超链接发送restful风格的put请求进行删除,而put删除用的又是form表
单解析post请求来发送的,那么该如何通过超链接关联表单,如何通过通过超链接控制
表单进行提交呢????
详情查看restful案例测试:删除功能
-->
</body>
</html>
class UserController类:
package com.atguigu.mvc.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
/**
* Date:2021/7/7
* Author:ybc
* Description:
*/
@Controller
public class UserController {
/**
* 使用RESTFul模拟用户资源的增删改查
* 1. /user GET 查询所有用户信息
* 2. /user/1 GET 根据用户id查询用户信息
* 3. /user POST 添加用户信息
* 4. /user PUT 更新用户信息
* 5. /user/1 DELETE 删除用户信息
*/
//1.原生方式 查询(get)所有用户信息 通过超链接发送get请求
@RequestMapping(value = "/user", method = RequestMethod.GET)
public String getAllUser(){
System.out.println("查询所有用户信息");
return "success";
}
//2.原生方式 根据用户id查询(get)用户信息 通过超链接发送get请求
@RequestMapping(value = "/user/{id}", method = RequestMethod.GET)
public String getUserById(){
System.out.println("根据id查询用户信息");
return "success";
}
//3.原生方式 添加(post)用户信息 通过表单发送post请求
@RequestMapping(value = "/user", method = RequestMethod.POST)
public String insertUser(String username, String password){
System.out.println("添加用户信息:"+username+","+password);
return "success";
}
//4.原生方式 修改(put)用户信息 页面是通过表单发送请求的
//注意:情况1:浏览器只支持发送get和post方式的请求,即便通过form表单的method属性设置
// 为put delete,也会被解析为get请求处理。
// 情况2:使用AJAX默认是get,可以发送post,put,delete,但是只有部分浏览器支持。
//问题:那么要如何发送put和 delete类型的请求呢????
//答: SpringMVC 提供了过滤器HiddenHttpMethodFilter帮助我们将POST请求转换为DELETE 或 PUT 请求,
// 我们只需要在web.xml中配置即可,在web.xml配置过滤器,是过滤器生效的页面需要满足2个条件:
// 1.请求方式必须是post请求
// 2.必须要传一个参数_method 这个参数对用户没有用处,用户不需要看见所以设置为hidden隐藏。
@RequestMapping(value = "/user", method = RequestMethod.PUT)
public String updateUser(String username, String password){
System.out.println("修改用户信息:"+username+","+password);
return "success";
}
//5. 删除(delete)功能到restfule案例中在讲解
}
}
success.html页面:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
success
</body>
</html>
启动tomact服务器,访问到test_rest.html页面,之后在此页面上分别发送请求,可以看到控制台输出的提示信息,说明路径匹配成功。
八、RESTful案例
1、准备工作
和传统 CRUD 一样,实现对员工信息的增删改查。
1.1 搭建环境
创建Maven-java项目springMVC-rest, 配置修改为Maven-web工程,引入依赖,添加配置文件,配置tomact实例。
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.atguigu.mvc</groupId>
<artifactId>springMVC-rest</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>
<!-- 日志 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</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.12.RELEASE</version>
</dependency>
</dependencies>
</project>
web.xml文件:
<?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">
<!--配置编码过滤器-->
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceResponseEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!--配置处理请求方式put和delete的HiddenHttpMethodFilter的过滤器-->
<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>
<!--配置SpringMVC的前端控制器DispatcherServlet-->
<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>
</servlet>
<servlet-mapping>
<servlet-name>DispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
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"
xmlns:mvc="http://www.springframework.org/schema/mvc"
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 http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!--扫描组件-->
<context:component-scan base-package="com.atguigu.rest"></context:component-scan>
<!--配置Thymeleaf视图解析器-->
<bean id="viewResolver" class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
<property name="order" value="1"/>
<property name="characterEncoding" value="UTF-8"/>
<property name="templateEngine">
<bean class="org.thymeleaf.spring5.SpringTemplateEngine">
<property name="templateResolver">
<bean class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver">
<!-- 视图前缀 -->
<property name="prefix" value="/WEB-INF/templates/"/>
<!-- 视图后缀 -->
<property name="suffix" value=".html"/>
<property name="templateMode" value="HTML5"/>
<property name="characterEncoding" value="UTF-8" />
</bean>
</property>
</bean>
</property>
</bean>
</beans>
1.2 准备实体类
package com.atguigu.mvc.bean;
public class Employee {
private Integer id;
private String lastName;
private String email;
//1 male, 0 female
private Integer gender;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Integer getGender() {
return gender;
}
public void setGender(Integer gender) {
this.gender = gender;
}
public Employee(Integer id, String lastName, String email, Integer gender) {
super();
this.id = id;
this.lastName = lastName;
this.email = email;
this.gender = gender;
}
public Employee() {
}
}
1.3 准备dao模拟数据
说明:暂时写死了。
package com.atguigu.rest.dao;
import com.atguigu.rest.bean.Employee;
import org.springframework.stereotype.Repository;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
/**
* Date:2021/7/9
* Author:ybc
* Description:
*/
@Repository
public class EmployeeDao {
private static Map<Integer, Employee> employees = null;
static{
employees = new HashMap();
employees.put(1001, new Employee(1001, "E-AA", "aa@163.com", 1));
employees.put(1002, new Employee(1002, "E-BB", "bb@163.com", 1));
employees.put(1003, new Employee(1003, "E-CC", "cc@163.com", 0));
employees.put(1004, new Employee(1004, "E-DD", "dd@163.com", 0));
employees.put(1005, new Employee(1005, "E-EE", "ee@163.com", 1));
}
private static Integer initId = 1006;
//添加和修改功能
//如果获取的id为null,说明是添加功能,id值加上1并且存入数据
//如果获取的id不为null,直接修改即可
public void save(Employee employee){
//因为业务逻辑是,不管你这个条件是否成立它都会走后面的put添加数据到Map集合
if(employee.getId() == null){//添加功能
//符号在后,先使用后变化 即:先使用initId++=6,initId=7
employee.setId(initId++);
}
employees.put(employee.getId(), employee);//修改功能
}
//查询所有用户信息
public Collection<Employee> getAll(){
return employees.values();//返回所有value构成的Collection集合
}
//根据id进行查询一个用户信息
public Employee get(Integer id){
return employees.get(id);
}
//根据id删除用户信息
public void delete(Integer id){
employees.remove(id);
}
}
2、功能清单
功能 | URL 地址 | 请求方式 |
---|---|---|
访问首页√ | / | GET |
查询全部数据√ | /employee | GET |
删除√ | /employee/2 | DELETE |
跳转到添加数据页面√ | /toAdd | GET |
执行保存√ | /employee | POST |
跳转到更新数据页面√ | /employee/2 | GET |
执行更新√ | /employee | PUT |
3、具体功能:访问首页
a>配置view-controller
说明:配置访问首页的路径映射关系,如果控制类方法的作用只是返回一个页面没有其它请求处理,使用标签可以省略控制类返回页面的映射方法。
注意:
- 此页面在webapp下的web-info目录下,如果是在webapp目录下,tomact可以自动访问以Index开头的页面,原因是在tomact服务器的web.xml中有配置。
- 为了是其他控制类方法生效,需要添加一个标签,作用是开启mvc的注解驱动。
<!--配置视图控制器-->
<mvc:view-controller path="/" view-name="index"></mvc:view-controller>
<!--开启mvc注解驱动-->
<mvc:annotation-driven />
b>创建首页页面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8" >
<title>Title</title>
</head>
<body>
<h1>首页</h1>
<a th:href="@{/employee}">访问员工信息</a>
</body>
</html>
4、具体功能:查询所有员工数据
a>控制器方法
//查询所有用户信息,并把数据存入到域对象中,返回给浏览器页面,在页面中把域对象中的数据取出来显示到页面上。
//域对象选择:requtst域对象就够使用了,因为查询的范围是对一次请求有效,下次在查就在发送一次请求,用不到太大的域对象范围,浪费空间。
@RequestMapping(value = "/employee", method = RequestMethod.GET)
public String getAllEmployee(Model model){
Collection<Employee> employeeList = employeeDao.getAll();
model.addAttribute("employeeList", employeeList);
return "employee_list";
}
b>创建employee_list.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Employee Info</title>
<script type="text/javascript" th:src="@{/static/js/vue.js}"></script>
</head>
<body>
<table border="1" cellpadding="0" cellspacing="0" style="text-align: center;" id="dataTable">
<tr>
<th colspan="5">Employee Info</th>
</tr>
<tr>
<th>id</th>
<th>lastName</th>
<th>email</th>
<th>gender</th>
<th>options(<a th:href="@{/toAdd}">add</a>)</th>
</tr>
<!--注意:thympleaf标签在idea中显示会报错,但是不影响使用-->
<tr th:each="employee : ${employeeList}">
<td th:text="${employee.id}"></td>
<td th:text="${employee.lastName}"></td>
<td th:text="${employee.email}"></td>
<td th:text="${employee.gender}"></td>
<td>
<!--使用Thymeleaf和restful风格的方式动态获取传参的写法:
错误写法:<a th:href="@{/employee/${employee.id}}"> </a> $后面的大括号会被当成路径来解析,里面的值不会被解析。
正确写法: 方式一:<a th:href="@{/employee/}+${employee.id}}"> </a>
方式二:<a th:href="@{'/employee/'+${employee.id}}"></a>
-->
<a class="deleteA" @click="deleteEmployee" th:href="@{'/employee/'+${employee.id}}">delete</a>
<a th:href="@{'/employee/'+${employee.id}}">update</a>
</td>
</tr>
</table>
</body>
</html>
c> 测试:页面显示效果
启动tomact服务器,首先输入访问首页的地址(在springMVC配置的有映射标签),在首页点击超链接发送请求到服务器的控制类方法中,在控制层方法中调用dao层方法查询用户所有的数据,并把数据存入到域对象中返回页面,在页面中通过thympleaf标签把域对象中的数据遍历获取后拼接到标签对应位置后,回显给浏览器。
5、具体功能:删除
a>创建处理delete请求方式的表单
<!--删除测试:delete
问题:在没有使用ajax的情况下,浏览器是通过解析form表单的post请求来发送put、delete请求。
如果是put更新,业务逻辑一般是点击更新按钮跳转到更新页面,在更新页面上直接通过form表
单提交数据,提交按钮本来就是在表单上,所以可以直接使用表单进行提交。
如果是删除,业务逻辑用的是超链接,一般是通过超连接地址携带参数发送get请求进行删除,
现在想要使用超链接发送restful风格的put请求进行删除,而put删除用的又是form表
单解析post请求来发送的,那么该如何通过超链接关联表单,如何通过通过超链接控制
表单进行提交呢????
说明:1.这个用于删除的表单里面不需要提交按钮。不需要添加提交的数据,不需要写action的地址值,
只需要有一个隐藏的表单设置_method值,并设置解析成的提交方式。
2.可以用原生的js,jquery, vue.js来实现超链接关联表单和提交数据,这里以vue.js为例子。
-->
<form id="deleteForm" method="post">
<input type="hidden" name="_method" value="delete">
</form>
b>删除超链接绑定点击事件(vue.js)
添加vue.js文件:
引入vue.js
<!--绝对路径写法:-->
<script type="text/javascript" th:src="@{/static/js/vue.js}"></script>
删除超链接
<!--方式一:-->
<!-- <a @click="deleteEmployee" th:href="@{/employee/}+${employee.id}">delete</a>-->
<!--方式二:-->
<a @click="deleteEmployee" th:href="@{'/employee/'+${employee.id}}">delete</a>
通过vue处理点击事件
<script type="text/javascript">
var vue = new Vue({
el:"#dataTable",
methods:{
//event表示当前事件
deleteEmployee:function (event) {
//通过id获取表单标签
var delete_form = document.getElementById("delete_form");
//将触发事件的超链接的href属性为表单的action属性赋值
delete_form.action = event.target.href;
//提交表单
delete_form.submit();
//阻止超链接的默认跳转行为
event.preventDefault();
}
}
});
</script>
c>控制器方法
@RequestMapping(value = "/employee/{id}", method = RequestMethod.DELETE)
public String deleteEmployee(@PathVariable("id") Integer id){
//删除成功之后应该跳转到首页页面,首页页面即在查询一次,这个方法已经写好了只需要跳转即可。
//那么使用转发还是重定向呢????删除成功后就和之前的请求就没有关系了,使用重定向即可。
employeeDao.delete(id);
return "redirect:/employee";//重定向相当于在浏览器重新发送一次请求,所以是get请求方式
}
d>开启静态资源配置(静态资源配置)
说明
:vue.js是静态资源,springMVC的前端控制器不能处理静态资源,所以需要配置可以处理静态资源的标签default-servlet。
执行流程
:静态资源先被访问的时候先被springMVC的前端控制器进行处理,如果在前端控制器中找不到请求映射就会交给默认的servlet来处理,如果默认的servlet找到对应的资源就可以访问,找不到就404。
<mvc:default-servlet-handler />
e>测试:
6、具体功能:跳转到添加数据页面
a>超链接跳转到添加页面
<th>options(<a th:href="@{/toAdd}">add</a>)</th>
b>配置view-controller
<mvc:view-controller path="/toAdd" view-name="employee_add"></mvc:view-controller>
c>创建employee_add.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Add Employee</title>
</head>
<body>
<form th:action="@{/employee}" method="post">
lastName:<input type="text" name="lastName"><br>
email:<input type="text" name="email"><br>
gender:<input type="radio" name="gender" value="1">male
<input type="radio" name="gender" value="0">female<br>
<input type="submit" value="add"><br>
</form>
</body>
</html>
e>测试
7、具体功能:执行保存
a>控制器方法
@RequestMapping(value = "/employee", method = RequestMethod.POST)
public String addEmployee(Employee employee){
employeeDao.save(employee);
return "redirect:/employee";
}
b>测试
8、具体功能:跳转到更新数据页面
a>修改超链接
说明
:超链接跳转到更新页面,并携带数据id ,
先进行一次查询把数据显示到更新页面,在进行更新
。
<a th:href="@{'/employee/'+${employee.id}}">update</a>
b>控制器方法
//更新:先进行查询需要更新的数据
@RequestMapping(value = "/employee/{id}", method = RequestMethod.GET)
public String getEmployeeById(@PathVariable("id") Integer id, Model model){
Employee employee = employeeDao.get(id);
model.addAttribute("employee", employee);
return "employee_update";//根据id查询完数据后跳转到更新页面
}
c>创建employee_update.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>update employee</title>
</head>
<body>
<form th:action="@{/employee}" method="post">
<!--restful风格更新-->
<input type="hidden" name="_method" value="put">
<!--id更新时要用,所以也要回显,id对于用户没啥意义,所以设置为hidden-->
<input type="hidden" name="id" th:value="${employee.id}">
lastName:<input type="text" name="lastName" th:value="${employee.lastName}"><br>
email:<input type="text" name="email" th:value="${employee.email}"><br>
<!--
th:field="${employee.gender}"可用于单选框或复选框的回显 ,判断流程:
若单选框的value和employee.gender的值一致,则自动添加checked="checked"属性
-->
gender:<input type="radio" name="gender" value="1" th:field="${employee.gender}">male
<input type="radio" name="gender" value="0" th:field="${employee.gender}">female<br>
<input type="submit" value="update"><br>
</form>
</body>
</html>
e>测试
9、具体功能:执行更新
a>控制器方法
@RequestMapping(value = "/employee", method = RequestMethod.PUT)
public String updateEmployee(Employee employee){
employeeDao.save(employee);
return "redirect:/employee";//更新完成后重定向到查询的方法回显页面
}
b>测试
10、springMVC处理静态资源的过程
说明:
- 当工程中有静态资源和非静态的资源时,默认的tomact服务器的web.xml配置文件的处理静态资源的标签中的路径标签< url-pattern >/< url-pattern >配置的是/ ,在当前工程中配置的前端控制器的路径标签也是< url-pattern >/< url-pattern >,在tomact配置文件web.xml的配置是对部署到tomact的所有工程有效,在当前工程中配置的web.xml只对当前工程有效,2者相当于是继承关系,如果当前工程中的配置文件web.xml中的配置和tomact配置文件的web.xml中的配置标签有冲突,那么以当前工程的web.xml标签为准,也就是说当前工程只剩下web.xml配置的前端控制器,而没有处理静态资源文件的配置了。
-
静态资源访问标签和mvc注解驱动注解驱动标签要配合使用。
情况1:当工程中有静态资源和非静态的资源时,由于当前工程配置的前端控制器会覆盖tomact配置的静态资源标签,所以此时只剩下前端控制器了,而前端控制器不能处理静态资源所以报错。
情况2:当前工程的web.xml配置了前端控制器的标签,在springMVC.xml配置了静态资源标签,没有配置注解驱动标签,那么现在当前工程所有的请求都会交给这个静态资源标签进行处理,只能访问静态资源不能访问处理映射方法。
情况3:如果在springMVC.xml添加时注解驱动标签处理流程:请求先被前端控制器处理,如果没有匹配到再找静态资源标签匹配处理,还没有找到会报404。
注解驱动标签作用:
注意
:注解驱动标签
<mvc:annotation-driven />
,在不同的地方代表的功能不同。
如:
-
在配置文件中添加映射关系标签
省略一个只有返回页面功能的控制层的方法时
,
其它的控制层方法会失效
,加上此标签可以让别的控制层的方法生效。 -
在将resful案例里面,需要开放对静态资源的访问,这个时候就需要用到一个servlet标签来处理静态资源,如果加上servlet标签来处理静态资源,那么仅仅
只有静态资源能够被处理
,我们所写的
控制器的请求方法也会全部失效
,此时同样需要配置mvc的注解驱动。 -
将
java转化为json对象时
,此时同样需要配置mvc的注解驱动,
才能转化成功
。
注:
当SpringMVC中设置任何一个view-controller时,其他控制器中的请求映射将全部失效,此时需要在SpringMVC的核心配置文件中设置开启mvc注解驱动的标签:
<mvc:annotation-driven />
<!--开放对静态资源的访问:如果不开放是通过springMVC前端控制器处理的,前段控制器不能处理静态资源,处理静态资源的是default-servlet
所以需要配置。-->
<mvc:default-servlet-handler />
<!--开启mvc注解驱动-->
<mvc:annotation-driven />
八、HttpMessageConverter(报文转化器)
HttpMessageConverter,
报文信息转换器,将请求报文转换为Java对象,或将Java对象转换为响应报文
解释
:为什么是请求报文—->java对象,java对象——>响应报文,因为浏览器发送给服务器的是请求,服务器发送给浏览器的是响应。
HttpMessageConverter提供了
两个注解和两个类型
:@RequestBody,@ResponseBody(常用),RequestEntity,ResponseEntity(常用)
1、@RequestBody(获取请求实体)
@RequestBody
可以获取请求体
,需要
在
控制器方法设置一个形参
,
使用@RequestBody进行标识
,当前请求的请求体就会为
当前注解所标识的形参赋值
注意
:只有Post请求并且携带了请求参数,请求实体中才有内容。
配置文件设置了访问首页的路径:
index.html:
<form th:action="@{/testRequestBody}" method="post">
用户名:<input type="text" name="username"><br>
密码:<input type="password" name="password"><br>
<input type="submit">
</form>
控制层方法:
@RequestMapping("/testRequestBody")
public String testRequestBody(@RequestBody String requestBody){
System.out.println("requestBody:"+requestBody);
return "success";
}
success.html:
测试:
输出结果:
requestBody:username=admin&password=123456
2、RequestEntity(获取整个请求的信息)
说明:上面那个是获取的请求体的,这个是获取的整个请求报文的信息。
RequestEntity
封装请求报文
的一种类型,需要
在
控制器方法的形参中
设置该类型的形参
,当前请求的请求报文
就会赋值给该形参
,可以
通过getHeaders()获取请求头信息,通过getBody()获取请求体信息
index.html:
<form th:action="@{/testRequestEntity}" method="post">
<input type="text" name="username">
<input type="text" name="password">
<input type="submit" value="测试RequestEntity">
</form>
控制层方法:
@RequestMapping("/testRequestEntity")
//泛型String 说明以String字符串的类型获取请求报文
public String testRequestEntity(RequestEntity<String> requestEntity){
//当前requestEnity表示整个请求报文的信息
System.out.println("请求头:"+requestEntity.getHeaders());
System.out.println("请求体:"+requestEntity.getBody());
return "success";
}
测试:
输出结果:
requestHeader:[host:"localhost:8080", connection:"keep-alive", content-length:"27", cache-control:"max-age=0", sec-ch-ua:"" Not A;Brand";v="99", "Chromium";v="90", "Google Chrome";v="90"", sec-ch-ua-mobile:"?0", upgrade-insecure-requests:"1", origin:"http://localhost:8080", user-agent:"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36"]
requestBody:username=admin&password=123
3、@ResponseBody(返回数据 作为响应体)
说明
:
-
在javaWeb阶段阶段:
返回一个页面给浏览器:使用请求转发和重定向。
返回数据给浏览器:response.getWriter().write(“response text”); -
现在使用SpringMvc返回数据
返回一个页面给浏览器:使用视图解析器
返回数据给浏览器:原生的、使用@ResponseBody注解。 -
位置
:类上、controller方法上 -
作用
:返回值如果是字符串则直接把字符串返回,如果返回值是实体对象/集合 将会转化为json格式响应。
3.1、使用servletAPI响应浏览器数据(字符串)
说明:测试响应给浏览器的是个字符串
<a th:href="@{/testResponse}">通过servletAPI的response对象响应浏览器数据</a><br>
@RequestMapping("/testResponse")
//注意是void,因为不需要返回页面
//这用的是response,可不是return。
public void testResponse(HttpServletResponse response) throws IOException {
//将数据返回给浏览器,返回什么,浏览器就显示什么。
response.getWriter().print("hello,response");
}
测试:
3.2、使用@ResponseBody响应(字符串)
说明:测试响应给浏览器的是个字符串
@ResponseBody
用于
标识一个控制器方法
,可以
将该方法的返回值直接作为响应报文的响应体响应到浏览器
<a th:href="@{/testResponseBody}">通过@ResponseBody响应浏览器数据</a><br>
@RequestMapping(value = "/testResponseBody", produces = "text/html;charset=UTF-8")
@ResponseBody //不加上此注解会被thymeleaf视图解析器为一个页面,加上此注解后返回的就是响应体内容
public String testResponseBody(){
//响应的是普通文本字符串
return "成功";
}
测试:
3.3、SpringMVC处理json(返回一个对象)
说明:测试响应给浏览器的是个对象
注意
:
-
服务器可以
直接返回
给浏览器一个
文本字符串
,但是
不能直接
返回一个
对象
,因为浏览器不认识java对象,http协议就是为了规范浏览器和服务器之间通信的格式,这个时候就和浏览器的种类和服务器编程语言的种类没有关系了,只要按照请求报文和响应报文发送请求和响应 别的就不管了,所以说浏览器不知道服务器所用的语言是什么,也就不能直接接收java对象。直接返回java对象访问时会报错:
-
解决:需要把java对象转化为一个对应的类型,既能保留对象中的数据又可以正确的响应给浏览器,
这个对应的类型就是json.
@ResponseBody处理json的步骤:
a
>导入jackson的依赖
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.1</version>
</dependency>
b
>在SpringMVC的核心配置文件中
开启mvc的注解驱动
,此时在HandlerAdaptor中会自动装配一个消息转换器:MappingJackson2HttpMessageConverter,
可以将响应到浏览器的Java对象转换为
Json格式的字符串
<mvc:annotation-driven />
c
>在处理器方法上使用@ResponseBody注解进行标识
d
>将
Java对象直接作为控制器方法的返回值返回
,就会自动转换为
Json格式的字符串
注意:
是json字符串,而不是json对象。
@RequestMapping("/testResponseUser")
//@ResponseBody注解作用:返回数据给浏览器(作为响应体)
//如果是字符串,使用此注解,可以直接返回。
//如果是java对象,需要导入依赖,添加驱动配置,使用注解标识,之后会自动转化为
// json字符串返回给浏览器。
@ResponseBody
public User testResponseUser(){
return new User(1001, "admin", "123456", 23, "男");
}
测试:
浏览器的页面中展示的结果:
{“id”:1001,“username”:“admin”,“password”:“123456”,“age”:23,“sex”:“男”}
3.4、SpringMVC处理ajax请求 (使用axios中的ajax)
说明:Ajax本身是页面不刷新与服务器进行交互,所以在控制层的方法就不能在用转发和重定向了(转发和重定向会刷新整个页面),只需要响应给浏览器数据即可
。
所以:使用Ajax发送请求,并获取响应数据,就不需要像原先那样业务完成后,重定向到当前首页重新刷新,现在Ajax会局部刷新。
a>请求超链接:
注意
:这是通过超链接绑定ajax发送请求,而不是直接通过Ajax发送请求。
<div id="app">
<a th:href="@{/testAjax}" @click="testAjax">testAjax</a><br>
</div>
b>
通过vue和axios处理点击事件
:
<script type="text/javascript" th:src="@{/static/js/vue.js}"></script>
<script type="text/javascript" th:src="@{/static/js/axios.min.js}"></script>
<script type="text/javascript">
var vue = new Vue({
el:"#app",
methods:{
testAjax:function (event) {
axios({
method:"post",
url:event.target.href,
params:{
username:"admin",
password:"123456"
}
}).then(function (response) {
alert(response.data);
});
event.preventDefault();
}
}
});
</script>
c>控制器方法:
@RequestMapping("/testAjax")
@ResponseBody
public String testAjax(String username, String password){
System.out.println("username:"+username+",password:"+password);
return "hello,ajax";
}
测试:
3.5、@RestController注解(复合注解)
@RestController注解是springMVC提供的一个复合注解,标识在控制器的类上,就相当于为类添加了@Controller注解,并且为其中的每个方法添加了@ResponseBody注解
总结
:
-
@RestController
注解用在
类
上 -
作用等于
@Controller+@ResponseBody
(注意此时这个
@ResponseBody
注解标识在
类上
,代表类中的每一个方法都使用了此注解,也就是说这个注解在方法上就不能使用组合注解了)
3.6、统一响应结果 / 统一结果处理类
3.6.1 概述
-
背景:以下三个功能接口的返回值分别是,String字符串、对象、list集合,经过@responseBody处理后返回给前端的分别是,字符串、json格式数据、json格式数组,每一个接口返回去的数据都很随意。
-
问题:如果开发的是一个大型项目所涉及到的功能接口可能成千上万个,如果没有一个统一的项目规范,那么前端人员发起请求访问后端开发的接口,响应回去的数据各种各样的都有。此时前端人员需要拿到返回的结果进行解析,因为返回值结果的种类各种各样,所以也要进行各种各样的解析,这样无疑增加了开发成本,项目难以维护。
-
解决:在项目开发中一般都会给所有的功能接口,设置一个统一的响应结果—-实体类对象result来接收。
3.6.2 测试
实体类:
package com.cn.pojo;
public class Address {
private String province;
private String city;
public String getProvince() {
return province;
}
public void setProvince(String province) {
this.province = province;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
@Override
public String toString() {
return "Address{" +
"province='" + province + '\'' +
", city='" + city + '\'' +
'}';
}
}
ResponseController类:
package com.cn.controller;
import com.cn.pojo.Address;
import com.cn.pojo.Result;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;
/**
* 测试响应数据
*/
@RestController
public class ResponseController {
@RequestMapping("/hello")
public Result hello(){
System.out.println("Hello World ~");
//使用有参构造方法,或者调用静态方法
//return new Result(1,"success","Hello World ~");
return Result.success("Hello World ~");
}
@RequestMapping("/getAddr")
public Result getAddr(){
Address addr = new Address();
addr.setProvince("广东");
addr.setCity("深圳");
return Result.success(addr);
}
@RequestMapping("/listAddr")
public Result listAddr(){
List<Address> list = new ArrayList<>();
Address addr = new Address();
addr.setProvince("广东");
addr.setCity("深圳");
Address addr2 = new Address();
addr2.setProvince("陕西");
addr2.setCity("西安");
list.add(addr);
list.add(addr2);
return Result.success(list);
}
}
统一响应结果封装类Result:
package com.cn.pojo;
/**
* 统一响应结果封装类
*/
public class Result {
private Integer code ;//1 成功 , 0 失败
private String msg; //提示信息
private Object data; //数据 data
public Result() {
}
public Result(Integer code, String msg, Object data) {
this.code = code;
this.msg = msg;
this.data = data;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public static Result success(Object data){
return new Result(1, "success", data);
}
public static Result success(){
return new Result(1, "success", null);
}
public static Result error(String msg){
return new Result(0, msg, null);
}
@Override
public String toString() {
return "Result{" +
"code=" + code +
", msg='" + msg + '\'' +
", data=" + data +
'}';
}
}
4、ResponseEntity(返回整个响应报文 文件下载)
ResponseEntity用于控制器
方法的返回值类型
,该控制器方法的
返回值
就是
响应到浏览器的响应报文
。
说明
:可以
自定义
一个
响应报文
去响应给浏览器,
常用于文件下载
。
九、文件上传和下载
文件上传
:
将浏览器中的文件上传到服务器端
文件下载
:
将服务器的文件下载到浏览器的客户端
1、文件下载
说明
:文件上传和下载
都是文件复制
的过程,
文件下载
只有
先把文件读进来
你才能去
写出去
。
1.1、下载步骤
使用ResponseEntity实现下载文件的功能
springMVC.xml配置的映射标签访问file.html页面:
<mvc:view-controller path="/file" view-name="file"></mvc:view-controller>
file.html:
<a th:href="@{/testDown}">下载1.jpg</a><br>
控制类方法:
package com.atguigu.mvc.controller;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpSession;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.UUID;
/**
* Date:2021/7/9
* Author:ybc
* Description:
*/
@Controller
public class FileUpAndDownController {
//文件下载
@RequestMapping("/testDown")
//ResponseEntity:报文转换对象 详情查看八-4 获取了一个HttpSession 会话对象
public ResponseEntity<byte[]> testResponseEntity(HttpSession session) throws IOException {
//获取ServletContext对象
ServletContext servletContext = session.getServletContext();
/*
获取服务器中文件的真实路径(即:当前工程部署到tomact中的路径),
如果有字符串参数那么获取的是,当前字符串所对应的文件在工程中的路径 */
String realPath = servletContext.getRealPath("/static/img/1.jpg");
//E:\idea-workspace\springMVC-demo4\target\springMVC-demo4-1.0-SNAPSHOT\static\img\1.jpg
System.out.println(realPath);
//创建输入流(文件上传和下载都是文件复制的过程,文件下载只有先把文件读进来你才能去写出去)
InputStream is = new FileInputStream(realPath);
//创建字节数组 is.available():获取当前的输入流所对应的文件的所有字节数 即:这个文件有100个字节就是100,有1000个字节就是1000
byte[] bytes = new byte[is.available()];
//将流读到字节数组中 把输入流所对应文件的所有字节都读取到数组中 为什么能放得下:因为上面创建数组的长度就是文件字节数
is.read(bytes);
//创建HttpHeaders对象设置响应头信息 (接口,通过它的实现类创建对象)
MultiValueMap<String, String> headers = new HttpHeaders();
/*
设置要下载方式以及下载文件的名字 只有1.jpg可以改变,其他都是固定的。
Content-Disposition响应头:表示收到的数据怎么处理
attachment:表示以附件方式下载,如,在谷歌中下载完成后显示:xxxx下载完毕
filename表示:为当前下载的文件所设置的默认名 可以和原来文件的名字不一样 */
headers.add("Content-Disposition", "attachment;filename=1.jpg");
//设置响应状态码
HttpStatus statusCode = HttpStatus.OK;
/*创建ResponseEntity对象(把当前响应到浏览器的数据转化为ResponseEntity对象):因为我们需
要返回值类型就是ResponseEntity响应报文对象。
参数1:bytes,当前要下载文件中所有的字节 也就是响应体
参数2:响应头
参数3:响应状态码 */
ResponseEntity<byte[]> responseEntity = new ResponseEntity<>(bytes, headers, statusCode);
//关闭输入流
is.close();
return responseEntity;
}
}
1.1、运行测试
2、文件上传
说明一
:
-
要有一个 form 标签,
method=post
请求(
get请求有长度限制
) -
form 标签
的
encType 属性值
必须为
multipart/form-data
值(这样表单提交的数据形式就由
原来的键值对name=value方式
,变为
二进制形式
提交数据到服务器,这样才能接收到文件) -
在
form 标签中
使用
<input type="file" name="xxx">
文件上传表单 -
SpringMVC中将上传的文件封装到
MultipartFile对象
中,通过此对象可以
获取文件相关信息
。
说明二
:文件上传和下载
都是文件复制
的过程,
文件上传
只有
先把文件读进来
你才能去
上传
。
文件上传就是文件复制的功能,所以要进行先读在写。
2.1、上传步骤:
a>添加依赖:
说明
:
只有上传
用到jar包,下载不需要。
<!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
b>在SpringMVC的配置文件中添加配置:
<!--配置文件上传解析器,将上传的文件转换为 MultipartFile对象-->
<!--必须指定id="multipartResolver" 后才能获取 MultipartFile对象
class:全类名,multipartResolver是接口 用的是他的实现类的全类名
-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"></bean>
c>file.html:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>测试文件上传和下载</title>
</head>
<body>
<a th:href="@{/testDown}">下载1.jpg</a><br>
<form th:action="@{/testUp}" method="post" enctype="multipart/form-data">
头像:<input type="file" name="photo"><br>
<input type="submit" value="上传">
</form>
</body>
</html>
d>控制器方法:
@RequestMapping("/testUp")
/*
SpringMVC中将上传的文件封装到 MultipartFile对象中,通过此对象可以获取文件相关信息。
所以:形参类型必须是MultipartFile ,参数名与文件上传file标签的name属性值一样为:photo
*/
public String testUp(MultipartFile photo, HttpSession session) throws IOException {
//获取上传的文件的文件名
String fileName = photo.getOriginalFilename();
//如果上传的是文件名重复会把原来的文件覆盖掉,所以在上传时需要解决文件的重名问题
//获取上传的文件的后缀名(因为文件名中也可能有"." 点所以使用lastIndexOf以最后一个"."点进行分割)
String suffixName = fileName.substring(fileName.lastIndexOf("."));
//将UUID作为文件名(uuid 生成的字符串含有-,可以去掉也可以不去)
String uuid = UUID.randomUUID().toString().replaceAll("-","");
//将uuid和后缀名拼接后的结果作为最终的文件名
fileName = uuid + suffixName;
/*
通过ServletContext获取服务器中photo目录的路径:
解释:现在没有专门的文件服务器,所以只能上传到当前的tomact中,例如把文件上传到当前tomact服务器下的photo目录中。
第四阶段亿万发中是上传到本地磁盘目录,这是部署到tomact服务器当前工程的photo路径下 */
ServletContext servletContext = session.getServletContext();//通过session获取servletContext 对象
//获取服务器中文件的真实路径 即使目录不存在,它也可以获取到地址值,只不过这个目录不存在
String photoPath = servletContext.getRealPath("photo");
/*
说明:
当前tomact服务器下可能没有这个photo目录,所以需要先进行判断。如果存在则直接使用获取到的目录,
如果不存在则先创建photo目录在获取。*/
File file = new File(photoPath);//创建内存方面的目录对象
//判断photoPath所对应路径是否存在
if(!file.exists()){
//若不存在,则创建目录。(真正的创建目录)
file.mkdir();
}
//File.separator代表分隔符,不同的操作系统会自动判断是/还是\
String finalPath = photoPath + File.separator + fileName;
//将浏览器上传的资源转移到服务器中
photo.transferTo(new File(finalPath));
return "success";
}
e>success.html页面:
2.2、运行测试
2.3、查看上传的位置:
也就是项目编译后生成的目录结构。
十、拦截器
1、过滤器和拦截器的区别
-
过滤器
:用于过滤浏览器发送到服务器的请求,所以
过滤器用于
浏览器和前端控制器DispatcherServlet之间
的。
-
springMVC拦截器
:作用于
控制器方法执行的前后
,在拦截其中有三个不同的方法分别用于不同时期。(过滤器后面) -
拦截器和过滤器
都可以写多个
。
2、拦截器的配置
SpringMVC中的拦截器
用于拦截控制器方法的执行
SpringMVC中的拦截器
需要实现HandlerInterceptor接口
SpringMVC的拦截器
必须在SpringMVC的配置文件中进行配置:
说明
:
- 注意:是对DispatcherServlet所处理的所有的请求进行拦截,只有DispatcherServlet能处理的方法才有对应的控制器方法,没有对应的控制器方法无法拦截。
-
配置详情查看3.1测试案例
<!--配置拦截器-->
<mvc:interceptors>
<!--方式一-->
<bean class="com.atguigu.interceptor.FirstInterceptor"></bean>
<!--方式二-->
<ref bean="firstInterceptor"></ref>
<!-- 以上两种配置方式都是对DispatcherServlet所处理的所有的请求进行拦截 -->
<!--方式三-->
<mvc:interceptor>
<mvc:mapping path="/**"/>
<mvc:exclude-mapping path="/testRequestEntity"/>
<ref bean="firstInterceptor"></ref>
</mvc:interceptor>
<!--
以上配置方式可以通过ref或bean标签设置拦截器,通过mvc:mapping设置需要拦截的请求,
通过mvc:exclude-mapping设置需要排除的请求,即不需要拦截的请求
-->
</mvc:interceptors>
3、拦截器的三个抽象方法
SpringMVC中的拦截器
有三个抽象方法
:
-
preHandle
:
控制器方法执行之前执行
preHandle(),其boolean类型的返回值表示是否拦截或放行,
返回true为放行
,即调用控制器方法;
返回false表示拦截
,即不调用控制器方法 -
postHandle
:
控制器方法执行之后
执行postHandle() -
afterComplation
:处理完视图和模型数据,
渲染视图完毕之后
执行afterComplation()
3.1、测试拦截器的使用
a>准备工作:
在springMVC.xml中配置访问首页的标签:当然也可以直接使用控制器方法,解释查看 六-5 视图控制器
<!--这个映射表标签等价于在控制器中写方法,所以也会被拦截器拦截,也会输出拦截器的3个方法-->
<mvc:view-controller path="/" view-name="index"></mvc:view-controller>
index.html页面:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>首页</h1>
<a th:href="@{/testInterceptor}">测试拦截器</a><br>
</body>
</html>
控制类方法:TestController
package com.atguigu.mvc.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* Date:2021/7/10
* Author:ybc
* Description:
*/
@Controller
public class TestController {
@RequestMapping("/testInterceptor")
public String testInterceptor(){
return "success";//返回成功页面
}
}
success.html页面:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
success
</body>
</html>
b>
创建拦截器
:
创建拦截器
说明:创建一个类实现HandlerInterceptor 接口,并重写里面的抽象方法
package com.atguigu.mvc.interceptors;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Date:2021/7/10
* Author:ybc
* Description:
*/
@Component //拦截器配置的方式二需要加上此注解
public class FirstInterceptor implements HandlerInterceptor {
//控制器方法执行之前执行
@Override
//boolean类型的返回值表示是请求拦截后是否 拦截或放行
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("FirstInterceptor-->preHandle");
return true;//返回true表示放行,false表示拦截
}
//控制器方法执行之后执行
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("FirstInterceptor-->postHandle");
}
//控制器方法执行之后,视图渲染完成之后执行
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("FirstInterceptor-->afterCompletion");
}
}
c>配置拦截器:
说明
:
- 在springMVC.xml中配置拦截器,使创建好的拦截器生效。
- 有三种配置方式
<!--配置拦截器-->
<mvc:interceptors>
<!--方式一:所有的请求都会被拦截 class值为拦截器的全类名(指定拦截器)
<bean class="com.atguigu.mvc.interceptors.FirstInterceptor"></bean>
注意:不需要在拦截器类上加上@compotent注解
-->
<!--方式二:所有的请求都会被拦截 bean值为拦截器类的对象名(默认是类名首字母小写,指定拦截器)
<ref bean="firstInterceptor"></ref>
需要:1.在拦截器的类中加上@compotent注解,把拦截器对象交给spring容器去管理
2.需要在配置文件中添加组件扫描(因为要扫描控制器类和扫描拦截器类,所以增大原先只扫描
控制器类的组件标签范围)
-->
<!--方式三:可以设置拦截规则:那些路径可以被拦截,那些不需要拦截。
mapping path:设置需要拦截的路径:
/*:表示拦截上下文路径(项目名)之后的一层目录,如
http://localhost:8080/项目名/a(也就是从/a开始后的路径才会被拦截)
http://localhost:8080/项目名/xx/xx
/**:表示拦截所有请求路径
exclude-mapping path:设置需要排除的,不需要拦截的请求-->
<mvc:interceptor>
<mvc:mapping path="/**"/>
<mvc:exclude-mapping path="/"/>
<!--里面可以写方式一的bean class标签 也可以写成方式二的ref bean标签-->
<ref bean="firstInterceptor"></ref>
</mvc:interceptor>
</mvc:interceptors>
d>测试:
说明
:首先访问首页,由于在springMVC配置的映射标签也被拦截器拦截,所以也应该会输出重写的三个抽象方法的提示信息,之后点击首页超链接,再次被拦截拦截输出重写的三个抽象方法的提示信息。由于在springMVC配置的第三种过滤规则,所有请求被拦截,首页除外没有被拦截,
所以最终执行的结果是
:访问首页,首页配置的没有被拦截,所以不会输出重写的三个抽象方法的提示信息,点击超链接发送的请求路径被拦截了,preHandle方法返回值又为true表示放行,所以会输出重写的三个抽象方法的提示信息。
4、多个拦截器的执行顺序(通过观察源码)
4.1、若每个拦截器的preHandle()都返回true
说明
:
-
多个拦截器的执行顺序
:和拦截器在SpringMVC配置文件中配置的顺序有关,谁配置在前面就先执行谁。 -
多个拦截器里面的拦截方法执行顺序
:
preHandle()
会按照拦截器配置的
顺序
执行,而
postHandle()和afterComplation()
会按照拦截器配置的
反序执行
测试
:2个拦截器的preHandle()都设置设置为true
4.2、若某个拦截器的preHandle()返回了false
说明
:
preHandle()返回false和它之前的拦截器的preHandle()都会执行,postHandle()都不执行,返回false的拦截器之前的拦截器的afterComplation()会倒序执行
测试
:第一个拦截器的preHandle()设置为true,第二个设置为false:
5、补充;响应乱码处理(没讲过)
在第80集突然出现了,没讲过。
https://www.bilibili.com/video/BV1Ry4y1574R?p=81&spm_id_from=pageDriver&vd_source=47cc8ff7e1b2b25a9a062c51f8b85d17
<!--开启mvc的注解驱动-->
<mvc:annotation-driven>
<mvc:message-converters>
<!-- 处理响应中文内容乱码 -->
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<property name="defaultCharset" value="UTF-8" />
<property name="supportedMediaTypes">
<list>
<value>text/html</value>
<value>application/json</value>
</list>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
十一、异常处理器
1、概述
SpringMVC提供了一个处理
控制器方法
执行过程中所出现的异常的接口
:
HandlerExceptionResolver
HandlerExceptionResolver接口的实现类有
:
-
DefaultHandlerExceptionResolver
:springMVC默认是使用的异常处理器,一旦出现了异常会跳转到,对应的springMVC默认提供的视图页面,如:500,404等。 -
SimpleMappingExceptionResolver
:是让我们自定义异常处理的,如果当前的控制器方法出现了某些异常,那么就可以指定一个视图进行跳转。
SpringMVC提供的
自定义的异常处理器SimpleMappingExceptionResolver
,有2种配置方式:
- 基于配置文件实现
- 基于注解实现
总结
:
异常处理器的作用是,一旦控制器方法中出现了异常,就可以返回一个页面用来表示异常。比如默认的是404、500,也可以手动指定一个页面。
2、基于配置的异常处理
<!--配置异常处理-->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<!--
key:异常的全类名。eg:下面这个测试用的是算术异常的全类名。
标签中的值为跳转的页面名 eg:error
-->
<prop key="java.lang.ArithmeticException">error</prop>
</props>
</property>
<!--
exceptionAttribute属性设置一个属性名,将出现的异常信息在请求域中进行共享:
这个异常默认储存在请求域中, value设置的是存入域中异常的键 key。之后在跳转的
视图页面error.html可以通过key获取域对象中的异常信息
-->
<property name="exceptionAttribute" value="ex"></property>
</bean>
测试:
index.html
<a th:href="@{/testExceptionHandler}">测试异常处理</a><br>
控制类:
@RequestMapping("/testExceptionHandler")
public String testExceptionHandler(){
//一旦出错不会在跳转到success.html页面,
// 而是会被在配置文件中配置的异常处理所捕获,跳转到异常页面
System.out.println(1/0);
return "success";
}
error.html页面:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
出现错误
<!--通过key获取域对象中的异常信息-->
<p th:text="${ex}"></p>
</body>
</html>
3、基于注解的异常处理
//将当前类标识为异常处理的组件,它相当于是@Controller的扩展注解,也有@Controller的功能。
@ControllerAdvice
public class ExceptionController {
/*@ExceptionHandler用于设置所标识方法处理的异常,如果出现了ArithmeticException
异常或者NullPointerException异常,那么就会使用下面注解所标识的方法,来作为新的当
前控制类方法执行。 */
@ExceptionHandler(value = {ArithmeticException.class, NullPointerException.class})
public String testException(Exception ex, Model model){//ex表示当前请求处理中出现的异常对象
model.addAttribute("ex", ex);//把异常对象存入到共享域中
return "error";
}
}
测试:把之前的基于springMVC.xm方式的异常处理配置注释掉:
error.html页面和idex.html页面同上:
控制类方法:
@RequestMapping("/testExceptionHandler")
public String testExceptionHandler(){
/*一旦出错不会在跳转到success.html页面,而是会被注解标识的配置的异常处理所捕获,
跳转到异常页面 */
System.out.println(1/0);
return "success";
}
异常处理方法:
//将当前类标识为异常处理的组件,它相当于是@Controller的扩展注解,也有@Controller的功能。
@ControllerAdvice
public class ExceptionController {
/*@ExceptionHandler用于设置所标识方法处理的异常,如果出现了ArithmeticException
异常或者NullPointerException异常,那么就会使用下面注解所标识的方法,来作为新的当
前控制类方法执行。 */
@ExceptionHandler(value = {ArithmeticException.class, NullPointerException.class})
public String testException(Exception ex, Model model){//ex表示当前请求处理中出现的异常对象
model.addAttribute("ex", ex);//把异常对象存入到共享域中
return "error";
}
}
启动tomact服务器,访问首页,点击超链接仍然可以跳转到处理异常的error.html页面。
十二、注解配置SpringMVC
目的
:
使用配置类和注解代替web.xml和SpringMVC配置文件的功能
注意
:
-
spring完全注解开发
,使用
配置类+注解
代替的是
xml
文件 -
springMvc
使用
初始化类+注解
代替
web.xml
-
springMvc
使用
配置类+注解
代替
SpringMVC.xml
1、创建初始化类,代替SpringMVC的web.xml
在
Servlet3.0
环境中,容器会在类路径中查找实现javax.servlet.ServletContainerInitializer接口的类,如果找到的话就用它来配置
Servlet容器(tomact服务器
)。
Spring提供了这个接口的实现
,名为SpringServletContainerInitializer,这个类反过来又会查找实现WebApplicationInitializer的类并将配置的任务交给它们来完成。
Spring3.2引入了一个便利的WebApplicationInitializer基础实现
,名为AbstractAnnotationConfigDispatcherServletInitializer,
当我们的类
扩展了(继承)
AbstractAnnotationConfigDispatcherServletInitializer
并将其部署
到Servlet3.0容器
的时候,容器会自动发现它,并用它来配置Servlet上下文。
package com.atguigu.mvc.config;
import org.springframework.web.filter.CharacterEncodingFilter;
import org.springframework.web.filter.HiddenHttpMethodFilter;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
import javax.servlet.Filter;
//web工程的初始化类,用来代替web.xml
public class WebInit extends AbstractAnnotationConfigDispatcherServletInitializer {
/**
* 指定spring的配置类
* 返回值是数组:说明spring的配置类可以有多个
* @return
*/
@Override
protected Class<?>[] getRootConfigClasses() {
//返回的是Class类型的数组,里面放的是配置类的反射对象
return new Class[]{SpringConfig.class};
}
/**
* 指定springMVC的配置类
* 返回值是数组:说明springMVC的配置类可以有多个
* @return
*/
@Override
protected Class<?>[] getServletConfigClasses() {
//返回的是Class类型的数组,里面放的是配置类的反射对象
return new Class[]{WebConfig.class};
}
/**
* 指定DispatcherServlet的映射规则,即url-pattern(设置springMVC的核心
* 控制器所能处理的请求的请求路径)
* 返回值是数组:说明url-pattern映射路径可以有多个
* @return
*/
@Override
protected String[] getServletMappings() {
//存的值为url-pattern的映射路径值 <url-pattern>/</url-pattern>
return new String[]{"/"};
}
/**
* 注册过滤器
* 返回值是数组:说明过滤器可以有多个
* @return
*/
@Override
protected Filter[] getServletFilters() {
//编码过滤器(解决Post请求中文乱码问题)
CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
characterEncodingFilter.setEncoding("UTF-8");
characterEncodingFilter.setForceResponseEncoding(true);
//解析post请求为put delete过滤器
HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();
return new Filter[]{characterEncodingFilter, hiddenHttpMethodFilter};
}
}
2、创建SpringConfig配置类,代替spring的配置文件
package com.atguigu.mvc.config;
import org.springframework.context.annotation.Configuration;
//代替spring的xml配置文件
@Configuration//标识此类是配置类
public class SpringConfig {
//ssm整合之后,spring的配置信息写在此类中
}
3、创建WebConfig配置类,代替SpringMVC的配置文件
package com.atguigu.mvc.config;
import com.atguigu.mvc.interceptor.TestInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.ContextLoader;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.multipart.MultipartResolver;
import org.springframework.web.multipart.commons.CommonsMultipartResolver;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.*;
import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;
import org.thymeleaf.spring5.SpringTemplateEngine;
import org.thymeleaf.spring5.view.ThymeleafViewResolver;
import org.thymeleaf.templatemode.TemplateMode;
import org.thymeleaf.templateresolver.ITemplateResolver;
import org.thymeleaf.templateresolver.ServletContextTemplateResolver;
import java.util.List;
import java.util.Properties;
/**
* 代替SpringMVC的配置文件:
* 1、扫描组件(创建对象) 2、视图解析器(拼接前缀后缀 返回页面) 3、view-controller(视图控制器 简写控制层方法)
* 4、default-servlet-handler(静态资源处理) 5、mvc注解驱动(防止控制层方法失效 3个作用) 6、文件上传解析器
* 7、异常处理 8、拦截器
*/
//将当前类标识为一个配置类
@Configuration
//1、扫描组件
@ComponentScan("com.atguigu.mvc.controller")
//5、mvc注解驱动
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer { //第4、8、3、7 个功能才需要实现此接口,其它功能不需要
//4、default-servlet-handler 需要实现WebMvcConfigurer接口,重写此方法
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
//8、拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
TestInterceptor testInterceptor = new TestInterceptor();
registry.addInterceptor(testInterceptor).addPathPatterns("/**");
}
//3、view-controller
@Override
public void addViewControllers(ViewControllerRegistry registry) {设置路径
// 设置路径 视图名称
registry.addViewController("/hello").setViewName("hello");
}
//6、文件上传解析器
@Bean
public MultipartResolver multipartResolver(){
CommonsMultipartResolver commonsMultipartResolver = new CommonsMultipartResolver();
return commonsMultipartResolver;
}
//7、异常处理 (可以使用@Bean返回对象的方式,也可以使用这种重写异常处理方法的形式)
@Override
public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
SimpleMappingExceptionResolver exceptionResolver = new SimpleMappingExceptionResolver();
Properties prop = new Properties();
prop.setProperty("java.lang.ArithmeticException", "error");
exceptionResolver.setExceptionMappings(prop);
exceptionResolver.setExceptionAttribute("exception");
resolvers.add(exceptionResolver);
}
//2、配置thymeleaf视图解析器,如果是jsp视图解析器就不用这么麻烦了,因为jsp默认使用的是转发视图
//2.1、配置生成模板解析器(springMvc.xml配置视图解析器有3个Bean标签,按照从里到外的顺序配置Bean对象)
@Bean //方法的返回值为spring容器所管理的一个Bean对象,默认名为方法名。
public ITemplateResolver templateResolver() {
WebApplicationContext webApplicationContext = ContextLoader.getCurrentWebApplicationContext();
// ServletContextTemplateResolver需要一个ServletContext作为构造参数,可通过WebApplicationContext 的方法获得
ServletContextTemplateResolver templateResolver = new ServletContextTemplateResolver(
webApplicationContext.getServletContext());
templateResolver.setPrefix("/WEB-INF/templates/");
templateResolver.setSuffix(".html");
templateResolver.setCharacterEncoding("UTF-8");
templateResolver.setTemplateMode(TemplateMode.HTML);
return templateResolver; //和方法的返回值类型不同,因为只要类型兼容就行,不一定相同。
}
//2.2、生成模板引擎并为模板引擎注入模板解析器
@Bean
//根据方法参数的类型为方法的参数注入属性(自动装配),之后执行完方法后返回一个bean对象。
// 省略的是@Autowired注解
public SpringTemplateEngine templateEngine(ITemplateResolver templateResolver) {
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(templateResolver);
return templateEngine;
}
//2.3、生成视图解析器并未解析器注入模板引擎
@Bean
public ViewResolver viewResolver(SpringTemplateEngine templateEngine) {
ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
viewResolver.setCharacterEncoding("UTF-8");
viewResolver.setTemplateEngine(templateEngine);
return viewResolver;
}
}
4、测试功能
创建项目,配置Tomact 略。
4.1、pom依赖
<?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.mvc</groupId>
<artifactId>springMVC-annotation</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>
<!-- 日志 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<!-- ServletAPI -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.1</version>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
<!-- Spring5和Thymeleaf整合包 -->
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
<version>3.0.12.RELEASE</version>
</dependency>
</dependencies>
</project>
4.2、控制层方法
package com.atguigu.mvc.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class TestController {
@RequestMapping("/")
public String index(){
return "index";
}
}
4.3、创建拦截器
package com.atguigu.mvc.interceptor;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
// 创建拦截器
public class TestInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("TestInterceptor-->preHandle");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
4.4、创建html页面
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>首页</h1>
</body>
</html>
hello.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>hello</h1>
</body>
</html>
说明
:运行项目自动访问index首页,之后输入hello路径访问到hello页面(因为配置了视图控制器,即 简写控制层方法),控制台输出了前置拦截器方法(配置了拦截器),至于其它功能 自己在添加代码进行测试。
十三、SpringMVC执行流程
1、SpringMVC常用组件
-
(1)DispatcherServlet
:
前端控制器
,不需要工程师开发,由框架提供
作用
:
统一处理请求和响应
,整个流程控制的中心,由它
调用其它组件
处理用户的请求(
统一处理请求
)
-
(2)HandlerMapping
:
处理器映射器
,不需要工程师开发,由框架提供
作用
:根据请求的url、method等信息查找Handler,
即控制器方法
(
将请求和控制器方法进行映射
)
-
(3)Handler
:
处理器
,需要工程师开发
作用
:在DispatcherServlet的控制下Handler对具体的用户请求进行处理(
在控制器方法中对请求进行处理
)
-
(4)HandlerAdapter
:
处理器适配器
,不需要工程师开发,由框架提供
作用
:通过HandlerAdapter对处理器(控制器方法)进行执行(
调用控制器方法来执行
)
-
(5)ViewResolver
:
视图解析器
,不需要工程师开发,由框架提供
作用
:进行视图解析,得到相应的视图,例如:ThymeleafView、InternalResourceView、RedirectView(
解析视图,返回一个具体的页面
)
-
(6)View
:
视图
,需要工程师开发
作用
:将模型数据通过页面展示给用户(
将视图页面进行渲染,渲染完毕后,前端控制器DispatcherServlet将填充了数据的网页响应给用户。
)
2、DispatcherServlet初始化过程
DispatcherServlet
本质上是一个 Servlet
,所以天然的遵循 Servlet 的生命周期。所以宏观上是 Servlet 生命周期来进行调度。
a>初始化WebApplicationContext
所在类:org.springframework.web.servlet.FrameworkServlet
protected WebApplicationContext initWebApplicationContext() {
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
// A context instance was injected at construction time -> use it
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
// The context has not yet been refreshed -> provide services such as
// setting the parent context, setting the application context id, etc
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent -> set
// the root application context (if any; may be null) as the parent
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
// No context instance was injected at construction time -> see if one
// has been registered in the servlet context. If one exists, it is assumed
// that the parent context (if any) has already been set and that the
// user has performed any initialization such as setting the context id
wac = findWebApplicationContext();
}
if (wac == null) {
// No context instance is defined for this servlet -> create a local one
// 创建WebApplicationContext
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
// Either the context is not a ConfigurableApplicationContext with refresh
// support or the context injected at construction time had already been
// refreshed -> trigger initial onRefresh manually here.
synchronized (this.onRefreshMonitor) {
// 刷新WebApplicationContext
onRefresh(wac);
}
}
if (this.publishContext) {
// Publish the context as a servlet context attribute.
// 将IOC容器在应用域共享
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
}
return wac;
}
b>创建WebApplicationContext
所在类:org.springframework.web.servlet.FrameworkServlet
protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
Class<?> contextClass = getContextClass();
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException(
"Fatal initialization error in servlet with name '" + getServletName() +
"': custom WebApplicationContext class [" + contextClass.getName() +
"] is not of type ConfigurableWebApplicationContext");
}
// 通过反射创建 IOC 容器对象
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
wac.setEnvironment(getEnvironment());
// 设置父容器
wac.setParent(parent);
String configLocation = getContextConfigLocation();
if (configLocation != null) {
wac.setConfigLocation(configLocation);
}
configureAndRefreshWebApplicationContext(wac);
return wac;
}
c>DispatcherServlet初始化策略
FrameworkServlet创建WebApplicationContext后,刷新容器,调用onRefresh(wac),此方法在DispatcherServlet中进行了重写,调用了initStrategies(context)方法,初始化策略,即初始化DispatcherServlet的各个组件
所在类:org.springframework.web.servlet.DispatcherServlet
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
3、DispatcherServlet调用组件处理请求
a>processRequest()
FrameworkServlet重写HttpServlet中的service()和doXxx(),这些方法中调用了processRequest(request, response)
所在类:org.springframework.web.servlet.FrameworkServlet
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
long startTime = System.currentTimeMillis();
Throwable failureCause = null;
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
LocaleContext localeContext = buildLocaleContext(request);
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
initContextHolders(request, localeContext, requestAttributes);
try {
// 执行服务,doService()是一个抽象方法,在DispatcherServlet中进行了重写
doService(request, response);
}
catch (ServletException | IOException ex) {
failureCause = ex;
throw ex;
}
catch (Throwable ex) {
failureCause = ex;
throw new NestedServletException("Request processing failed", ex);
}
finally {
resetContextHolders(request, previousLocaleContext, previousAttributes);
if (requestAttributes != null) {
requestAttributes.requestCompleted();
}
logResult(request, response, failureCause, asyncManager);
publishRequestHandledEvent(request, response, startTime, failureCause);
}
}
b>doService()
所在类:org.springframework.web.servlet.DispatcherServlet
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
logRequest(request);
// Keep a snapshot of the request attributes in case of an include,
// to be able to restore the original attributes after the include.
Map<String, Object> attributesSnapshot = null;
if (WebUtils.isIncludeRequest(request)) {
attributesSnapshot = new HashMap<>();
Enumeration<?> attrNames = request.getAttributeNames();
while (attrNames.hasMoreElements()) {
String attrName = (String) attrNames.nextElement();
if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
attributesSnapshot.put(attrName, request.getAttribute(attrName));
}
}
}
// Make framework objects available to handlers and view objects.
request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
if (this.flashMapManager != null) {
FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
if (inputFlashMap != null) {
request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
}
request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
}
RequestPath requestPath = null;
if (this.parseRequestPath && !ServletRequestPathUtils.hasParsedRequestPath(request)) {
requestPath = ServletRequestPathUtils.parseAndCache(request);
}
try {
// 处理请求和响应
doDispatch(request, response);
}
finally {
if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Restore the original attribute snapshot, in case of an include.
if (attributesSnapshot != null) {
restoreAttributesAfterInclude(request, attributesSnapshot);
}
}
if (requestPath != null) {
ServletRequestPathUtils.clearParsedRequestPath(request);
}
}
}
c>doDispatch()
所在类:org.springframework.web.servlet.DispatcherServlet
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.
/*
mappedHandler:调用链
包含handler、interceptorList、interceptorIndex
handler:浏览器发送的请求所匹配的控制器方法
interceptorList:处理控制器方法的所有拦截器集合
interceptorIndex:拦截器索引,控制拦截器afterCompletion()的执行
*/
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
// 通过控制器方法创建相应的处理器适配器,调用所对应的控制器方法
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
// 调用拦截器的preHandle()
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
// 由处理器适配器调用具体的控制器方法,最终获得ModelAndView对象
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
// 调用拦截器的postHandle()
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
// 后续处理:处理模型数据和渲染视图
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
d>processDispatchResult()
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
boolean errorView = false;
if (exception != null) {
if (exception instanceof ModelAndViewDefiningException) {
logger.debug("ModelAndViewDefiningException encountered", exception);
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
}
else {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
mv = processHandlerException(request, response, handler, exception);
errorView = (mv != null);
}
}
// Did the handler return a view to render?
if (mv != null && !mv.wasCleared()) {
// 处理模型数据和渲染视图
render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
else {
if (logger.isTraceEnabled()) {
logger.trace("No view rendering, null ModelAndView returned.");
}
}
if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Concurrent handling started during a forward
return;
}
if (mappedHandler != null) {
// Exception (if any) is already handled..
// 调用拦截器的afterCompletion()
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
4、SpringMVC的执行流程(老师版)
-
用户向服务器发送请求,请求被SpringMVC 前端控制器 DispatcherServlet捕获。
-
DispatcherServlet对请求URL进行解析,得到请求资源标识符(URI),判断请求URI对应的映射:
a) 不存在
i. 再判断是否配置了mvc:default-servlet-handler(
静态资源处理
)
ii. 如果没配置,则控制台报映射查找不到,客户端展示404错误
iii. 如果有配置,则访问目标资源(一般为静态资源,如:JS,CSS,HTML),找不到客户端也会展示404错误
b) 存在则执行下面的流程
-
根据该URI,调用HandlerMapping获得该Handler配置的所有相关的对象(包括Handler对象以及Handler对象对应的拦截器),最后以HandlerExecutionChain执行链对象的形式返回。
-
DispatcherServlet 根据获得的Handler,选择一个合适的HandlerAdapter。
-
如果成功获得HandlerAdapter,此时将开始执行拦截器的preHandler(…)方法【正向】
-
提取Request中的模型数据,填充Handler入参,开始执行Handler(Controller)方法,处理请求。在填充Handler的入参过程中,根据你的配置,Spring将帮你做一些额外的工作:
a) HttpMessageConveter: 将请求消息(如Json、xml等数据)转换成一个对象,将对象转换为指定的响应信息
b) 数据转换:对请求消息进行数据转换。如String转换成Integer、Double等
c) 数据格式化:对请求消息进行数据格式化。 如将字符串转换成格式化数字或格式化日期等
d)
数据验证
: 验证
数据的有效性(长度、格式等)
,验证结果存储到BindingResult或Error中(
说明:这种是后端验证方式,现在数据的有效性校验一般是放在前端中进行验证,而不是在后端,因为验证不成功的话重复验证会增加访问服务器的负担
) -
Handler执行完成后,向DispatcherServlet 返回一个ModelAndView对象。
-
此时将开始执行拦截器的postHandle(…)方法【逆向】。
-
根据返回的ModelAndView(此时会判断是否存在异常:如果存在异常,则执行HandlerExceptionResolver进行异常处理)选择一个适合的ViewResolver进行视图解析,根据Model和View,来渲染视图。
-
渲染视图完毕执行拦截器的afterCompletion(…)方法【逆向】。
-
将渲染结果返回给客户端。
5、SpringMVC的执行流程(自己版)
-
首先 浏览器发送请求,如果请求的路径符合web.xml中配置的拦截路径,则会被
前端控制器(DispatcherServlet)
所接收。 -
之后前端控制器调用
处理器映射器(HandlerMapping)
,根据@RequestMapping注解将请求和控制器方法进行映射。 -
在
控制器方法中(Handler)
编写代码对请求进行具体的处理。如:控制器方法中调用 业务层—>数据层—->数据库,把查询到的数据返回给控制器方法中。 -
通过前端控制器调用
处理器适配器(HandlerAdapter)
,来调用控制器方法的执行,控制器方法执行后返回ModelAndView(数据和视图名)。 -
前端控制器将执行的结果(ModelAndView)传给
视图解析器(ViewReslover)
进行解析,返回一个具体的页面。 -
最后前端控制器
根据Model(数据)对View(页面)进行渲染
(即将模型数据填充至视图中),并将渲染后的页面响应给浏览器;