一、简介
1.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视图,渲染数据后最终响应给浏览器。
1.2 什么是SpringMVC
SpringMVC是Spring的一个后续产品,是Spring的一个子项目。SpringMVC 是 Spring 为表述层开发提供的一整套完备的解决方案。
注:三层架构分为表述层(或表示层)、业务逻辑层、数据访问层,表述层表示前台页面和后台 servlet。(也就是web层,service层,dao层)
1.3 SpringMVC的特点
- Spring 家族原生产品,与 IOC 容器等基础设施无缝对接
- 基于原生的Servlet,通过了功能强大的前端控制器DispatcherServlet,对请求和响应进行统一 处理
- 表述层各细分领域需要解决的问题全方位覆盖,提供全面解决方案
- 代码清新简洁,大幅度提升开发效率
- 内部组件化程度高,可插拔式组件即插即用,想要什么功能配置相应组件即可
- 性能卓著,尤其适合现代大型、超大型互联网项目要求
二、HelloWorld
2.1 创建maven工程,新建一个maven模块springMVC-demo1
- 添加web模块
- 打包方式:war
- 引入依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.ice.mvc</groupId>
<artifactId>springMVC-demo1</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>
<!--设置使用范围 provided 只在编译和测试阶段生效使用
表示将web工程打为war包,该jar不会存在于项目WEB-INFO下的lib中-->
<scope>provided</scope>
</dependency>
<!--Spring5和Thymeleaf整合包-->
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
<version>3.0.12.RELEASE</version>
</dependency>
</dependencies>
</project>
由于 Maven 的传递性,我们不必将所有需要的包全部配置依赖,而是配置最顶端的依赖,其他靠传递性导入。
2.2 配置web.xml
src/main——>new——>directory:创建一个目录webapp
打开项目结构,创建web.xml文件
2.2.1 默认配置方式(了解一下哈)
此配置作用下,SpringMVC的配置文件默认位于WEB-INF下,默认名称为<servlet-name>- servlet.xml。例如,以下配置所对应SpringMVC的配置文件位于WEB-INF下,文件名为springMVCservlet.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>
<!--/ 表示当前浏览器发送的所有请求
包括/login或者.html或者.css等
【但是不包括.jsp后缀的!!!】
因为jsp本质上也是servlet,如果使用/*拦截了全部请求,
那jsp就被当做servlet拦截,看不到页面了
-->
<url-pattern>/</url-pattern>
</servlet-mapping>
2.2.2 扩展配置方式(推荐使用)
可通过init-param标签设置SpringMVC配置文件的位置和名称,通过load-on-startup标签设置 SpringMVC前端控制器DispatcherServlet的初始化时间:
<!--扩展配置方式
配置SpringMVC前端控制器,对浏览器发送的请求进行统一处理
-->
<servlet>
<servlet-name>springMVC</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--配置SpringMVC配置文件的位置和名称-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springMVC.xml</param-value>
</init-param>
<!--将DispatcherServlet的初始化时间提前到服务器启动时
有很多东西需要加载,提前到服务器启动时,第一次访问就不会很慢
(之前是第一次访问时初始化)
-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springMVC</servlet-name>
<!--/ 表示当前浏览器发送的所有请求
包括/login或者.html或者.css等
【但是不包括.jsp后缀的!!!】
因为jsp本质上也是servlet,如果使用/*拦截了全部请求,
那jsp就被当做servlet拦截,看不到页面了
-->
<url-pattern>/</url-pattern>
</servlet-mapping>
2.3 创建请求控制器
import org.springframework.stereotype.Controller;
@Controller
public class HelloController {
}
2.4 配置SpringMVC配置文件springMVC.xml
在main\resources下创建springMVC.xml配置文件
<!--配置扫描组件-->
<context:component-scan base-package="com.ice.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">
<!-- 视图前缀 WEB-INF下创建templates目录 -->
<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>
<!--默认servlet ,必须和开启mvc注解驱动一起使用-->
<mvc:default-servlet-handler/>
<!--开启mvc注解驱动-->
<mvc:annotation-driven>
<mvc:message-converters>
<!--处理响应中文乱码-->
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<property name="defaultCharset" value="UTF-8"></property>
<property name="supportedMediaTypes">
<list>
<value>text/html</value>
<value>application/json</value>
</list>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
</bean>
2.5 测试HelloWorld
WEB-INF下创建index.html
(WEB-INF目录客户端无法访问,只有服务器端可以访问。WEB-INF目录下页面重定向不能访问,只能请求转发访问)
2.5.1 访问首页
1. 在请求控制器中创建处理请求的方法
@Controller
public class HelloController {
/*
* 访问路径 "/"——> /WEB-INF/templates/index.html
* 通过视图解析器配置的前缀和后缀
* 该方法返回应该为 index
* 需要使用请求映射注解 @RequestMapping(value = "/") :处理请求和控制器方法之间的映射关系
* 只有一个value属性的时候value可以不写@RequestMapping( "/")
* value属性可以通过请求地址匹配请求,/表示的当前工程的上下文路径:localhost:8080/springMVC-demo1/
* */
@RequestMapping(value = "/")
public String index(){
//返回视图名称
return "index";
}
}
2. 配置并启动Tomcat
(Tomcat配置文件中配置首页默认访问webapp下的index)
pom.xml中一定要配置jdk,否则启动Tomcat报错:不再支持源选项5,。请使用6或更高版本。配置后一定要Reimport maven!同时要保证Module、SDK等Java版本一致!
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
<java.version>11</java.version>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
启动成功:
2.5.2 通过超链接跳转到指定页面
1. 在主页index.html中设置超链接
2. 在请求控制器中创建处理请求的方法
@RequestMapping(value="/target")
public String toTarget(){
return "target";
}
3. 启动Tomcat
2.6 总结
浏览器发送请求,若请求地址符合前端控制器的url-pattern,该请求就会被前端控制器 DispatcherServlet处理。前端控制器会读取SpringMVC的核心配置文件,通过扫描组件找到控制器, 将请求地址和控制器中@RequestMapping注解的value属性值进行匹配,若匹配成功,该注解所标识的控制器方法就是处理请求的方法。处理请求的方法需要返回一个字符串类型的视图名称,该视图名称会被视图解析器解析,加上前缀和后缀组成视图的路径,通过Thymeleaf对视图进行渲染,最终转发到视图所对应页面。
三、@RequestMapping注解
3.1 @RequestMapping注解的功能
从注解名称上可以看到,@RequestMapping注解的作用就是将请求和处理请求的控制器方法关联起来,建立映射关系。 SpringMVC 接收到指定的请求,就会来找到在映射关系中对应的控制器方法来处理这个请求。
3.2 @RequestMapping注解的位置
@RequestMapping标识一个类:设置映射请求的请求路径的初始信息
@RequestMapping标识一个方法:设置映射请求请求路径的具体信息(经常用于不同模块)
@Controller
@RequestMapping("/hello")
public class RequestMappingController {
//此时请求映射所映射的请求的请求路径为:/hello/testRequestMapping
@RequestMapping("/testRequestMapping")
public String success(){
return "success";
}
}
<!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="@{/hello/testRequestMapping}">测试RequestMapping注解的位置</a>
</body>
</html>
3.3 @RequestMapping注解的value属性
@RequestMapping注解的value属性通过请求的请求地址匹配请求映射 。
@RequestMapping注解的value属性是一个字符串类型的数组,表示该请求映射能够匹配多个请求地址所对应的请求 ,至少满足其一即可。
@RequestMapping注解的value属性必须设置,至少通过请求地址匹配请求映射。
@Controller
public class RequestMappingController {
@RequestMapping(value = {"/testRequestMapping","/test"})
public String success(){
return "success";
}
}
<!--a标签都是get请求方式-->
<a th:href="@{/testRequestMapping}">测试RequestMapping注解的value属性-->/testRequestMapping</a><br/>
<a th:href="@{/test}">测试RequestMapping注解的value属性-->/test</a>
3.4 @RequestMapping注解的method属性
@RequestMapping注解的method属性通过请求的请求方式(get或post)匹配请求映射。
- get请求会把请求参数以name=value的方式拼接在地址后面;post请求会把请求参数放在请求体中(get方式没有请求体),请求体中请求参数的格式依然是name=value&name=value
- get请求不安全,但是速度快(伴随请求地址传过去);post请求相对安全,但是速度慢
- get传输数据量有限,post传输数据量大,可以认为是无限
@RequestMapping注解的method属性是一个RequestMethod类型的数组,表示该请求映射能够匹配多种请求方式的请求,满足其一即可。如果不设置method,则表示不以请求方式为条件,任意的请求方式都ok哒。
@Controller
public class RequestMappingController {
@RequestMapping(value = {"/testRequestMapping","/test"},
method = {RequestMethod.GET,RequestMethod.POST})
public String success(){
return "success";
}
}
<a th:href="@{/test}">测试RequestMapping注解的method属性-->GET</a><br/>
<form th:action="@{/test}" method="post">
<input type="submit" value="测试RequestMapping注解的method属性-->POST">
</form>
若当前请求的请求地址满足请求映射的value属性,但是请求方式不满足method属性,则浏览器报错 405:Request method 'POST' not supported
1. 对于处理指定请求方式的控制器方法,SpringMVC中提供了@RequestMapping的派生注解 (注解参数不需要再写method,写value就够了)
- 处理get请求的映射-->@GetMapping
- 处理post请求的映射-->@PostMapping
- 处理put请求的映射-->@PutMapping
- 处理delete请求的映射-->@DeleteMapping
2. 常用的请求方式有get,post,put,delete
但是目前浏览器只支持get和post,若在form表单提交时为method设置了其他请求方式的字符串(put或delete),则按照默认的请求方式get处理。
3.5 @RequestMapping注解的params属性(了解)
@RequestMapping注解的params属性通过请求的请求参数匹配请求映射
@RequestMapping注解的params属性是一个字符串类型的数组(必须同时满足),可以通过四种表达式设置请求参数 和请求映射的匹配关系
- "param":要求请求映射所匹配的请求必须携带param请求参数
- "!param":要求请求映射所匹配的请求必须不能携带param请求参数
- "param=value":要求请求映射所匹配的请求必须携带param请求参数且param=value
- "param!=value":要求请求映射所匹配的请求必须携带param请求参数但是param!=value
@RequestMapping(value = "/testParamsandHeaders",params = {"username","password"})
public String testParams(){
return "success";
}
<a th:href="@{/testParamsandHeaders(username=zt,password)}">测试RequestMapping注解的params属性</a><br/>
若当前请求满足@RequestMapping注解的value和method属性,但是不满足params属性,此时 页面回报错400:
3.6 @RequestMapping注解的headers属性(了解)
@RequestMapping注解的headers属性通过请求的请求头信息匹配请求映射
@RequestMapping注解的headers属性是一个字符串类型的数组,可以通过四种表达式设置请求头信息和请求映射的匹配关系
- "header":要求请求映射所匹配的请求必须携带header请求头信息
- "!header":要求请求映射所匹配的请求必须不能携带header请求头信息
- "header=value":要求请求映射所匹配的请求必须携带header请求头信息且header=value
- "header!=value":要求请求映射所匹配的请求必须携带header请求头信息且header!=value
若当前请求满足@RequestMapping注解的value和method属性,但是不满足headers属性,此时页面 显示404错误,即资源未找到 。
3.7 SpringMVC支持ant风格(模糊匹配)的路径
- ?:表示任意的单个字符
- *:表示任意的0个或多个字符
- **:表示任意的一层或多层目录 注意:在使用**时,只能使用/**/xxx的方式
//@RequestMapping("/a?a/testAnt")
//@RequestMapping("/a*a/testAnt")
@RequestMapping("/**/testAnt")
public String testAnt() {
return "success";
}
<a th:href="@{/asa/testAnt}">测试RequestMapping匹配Ant风格路径-->?</a><br/>
<a th:href="@{/a a/testAnt}">测试RequestMapping匹配Ant风格路径-->*</a><br/>
<a th:href="@{/123/abc/testAnt}">测试RequestMapping匹配Ant风格路径-->**</a><br/>
3.8 SpringMVC支持路径中的占位符(重点)
原始方式:/deleteUser?id=1
rest方式:/deleteUser/1
SpringMVC路径中的占位符常用于RESTful风格中,当请求路径中将某些数据通过路径的方式传输到服务器中,就可以在相应的@RequestMapping注解的value属性中通过占位符{xxx}表示传输的数据,再到参数列表中通过@PathVariable注解,将占位符所表示的数据赋值给控制器方法的形参
@RequestMapping("/testPath/{id}/{username}")
public String testPath(@PathVariable("id") Integer id,@PathVariable("username") String username) {
System.out.println("id:"+id+",username:"+username);
return "success";
}
<a th:href="@{/testPath/1/zt}">测试RequestMapping支持路径中的占位符</a><br/>
控制台输出内容为:id:1,username:zt
四、SpringMVC获取请求参数
4.1 通过servletAPI获取
将HttpServletRequest作为控制器方法的形参,此时HttpServletRequest类型的参数表示封装了当前请求的请求报文的对象
@RequestMapping("/testServletAPI")
public String testServletAPI(HttpServletRequest request){
String username = request.getParameter("username");
String password = request.getParameter("password");
System.out.println(username+"-"+password);
return "success";
}
<a th:href="@{/testServletAPI(username='zt',password=123)}">测试使用servletAPI获取请求参数</a>
4.2 通过控制器方法的形参获取请求参数
在控制器方法的形参位置,设置和请求参数同名的形参,当浏览器发送请求,匹配到请求映射时,在 DispatcherServlet中就会将请求参数赋值给相应的形参
@RequestMapping("/testParam")
public String testParam(String username, String password,String[] hobby){
//若请求参数中出现多个同名的请求参数,可以在控制器方法的形参位置设置字符串类型或字符串数组接收参数
//若使用字符串类型的形参,结果为请求参数的每一个值用逗号拼接
System.out.println(username+"+"+password+"+"+ Arrays.toString(hobby));
return "success";
}
<a th:href="@{/testParam(username='zt',password=123)}">测试使用控制器形参获取请求参数</a><br/>
<form th:action="@{/testParam}" method="get">
用户名:<input type="text" name="username"/><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>
4.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所指定的请求参数没有传输或传输的值为""(空字符串)时,则使用默认值为形参赋值
4.4 @RequestHeader
@RequestHeader是将请求头信息和控制器方法的形参创建映射关系
@RequestHeader注解一共有三个属性:value、required、defaultValue,用法同@RequestParam
4.5 @CookieValue
@CookieValue是将cookie数据和控制器方法的形参创建映射关系
@CookieValue注解一共有三个属性:value、required、defaultValue,用法同@RequestParam
4.6 通过POJO获取请求参数
可以在控制器方法的形参位置设置一个实体类类型的形参,此时若浏览器传输的请求参数的参数名和实 体类中的属性名一致,那么请求参数就会为此属性赋值
public class User {
private Integer id;
private String password;
private String username;
private Integer age;
private String sex;
private String email;
@RequestMapping("/testPojo")
public String testPojo(User user){
System.out.println(user);
return "success";
}
<form th:action="@{/testPojo}" 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="使用POJO实体类接收请求参数">
</form>
4.7 解决获取请求参数的乱码问题
GET请求乱码是Tomcat造成的,在Tomcat的配置文件server.xml中设置编码
POST请求乱码问题,可以使用SpringMVC提供的编码过滤器CharacterEncodingFilter,必须在web.xml中进行注册。SpringMVC中处理编码的过滤器一定要配置到其他过滤器之前,否则无效
<!--配置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>
<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>
五、域对象共享数据
5.1 使用ServletAPI向request域对象共享数据
ServletAPI、 Model、Map、ModelMap的所有请求都会将数据封装到一个ModelAndView对象中!
//使用servletAPI向request域共享数据
@RequestMapping("/testServletAPI")
public String testRequestByServletAPI(HttpServletRequest request){
request.setAttribute("testRequestScope","servletAPI");
return "success";
}
<a th:href="@{/testServletAPI}">通过servletAPI向request域共享数据</a><br/>
<p th:text="${testRequestScope}"></p><!--报错不影响。。。-->
(两个页面的HTML,a访问p所在页面。下面HTML不写了,都差不多)
5.2 使用ModelAndView向request域对象共享数据
Spring容器不会自动创建ModelAndView对象,需要在控制器中自己创建对象。而其他Spring会自动创建,所以可以写在形参位置。
@RequestMapping("/testModelAndView")
public ModelAndView testModelAndView(){
ModelAndView mav = new ModelAndView();
//处理模型数据,即 向请求域request共享数据
mav.addObject("testRequestScope","ModelAndView");
//设置视图名称
mav.setViewName("success");
return mav;
}
5.3 使用Model向request域对象共享数据
@RequestMapping("/testModel")
public String testModel(Model model){
model.addAttribute("testRequestScope","Model");
return "success";
}
5.4 使用Map向request域对象共享数据
@RequestMapping("/testMap")
public String testMap(Map<String ,Object> map){
map.put("testRequestScope","map");
return "success";
}
5.5 使用ModelMap向request域对象共享数据
@RequestMapping("/testModelMap")
public String testModelMap(ModelMap modelMap){
modelMap.addAttribute("testRequestScope","ModelMap");
return "success";
}
5.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
5.7 向session域共享数据(建议使用原生ServletAPI)
session默认最大不操作时间30分钟,超过就失效。
@RequestMapping("/testSession")
public String testSession(HttpSession session){
session.setAttribute("testSessionScope","session");
return "success";
}
<a th:href="@{/testSession}">通过servletAPI向session域共享数据</a><br/>
<p th:text="${session.testSessionScope}"></p>
5.8 向application域共享数据
application就是servletContext
@RequestMapping("/testApplication")
public String testApplication(HttpSession session){
ServletContext application = session.getServletContext();
application.setAttribute("testApplicationScope", "application");
return "success";
}
<a th:href="@{/testApplication}">通过servletAPI向application域共享数据</a><br/>
<p th:text="${application.testApplicationScope}"></p>
六、SpringMVC的视图
SpringMVC中的视图是View接口,视图的作用渲染数据,将模型Model中的数据展示给用户
SpringMVC视图的种类很多,默认有转发视图InternalResourceView和重定向视图 RedirectView
- 当工程引入jstl的依赖,转发视图会自动转换为JstlView
- 若使用的视图技术为Thymeleaf,在SpringMVC的配置文件中配置了Thymeleaf的视图解析器,由此视 图解析器解析之后所得到的是ThymeleafView
6.1 ThymeleafView
当控制器方法中所设置的视图名称没有任何前缀时,此时的视图名称会被SpringMVC配置文件中所配置的视图解析器解析,视图名称拼接视图前缀和视图后缀所得到的最终路径,会通过转发的方式实现跳转
@RequestMapping("/testHello")
public String testHello(){
return "hello";
}
6.2 转发视图InternalResourceView
SpringMVC中默认的转发视图是InternalResourceView(内部资源视图)
SpringMVC中创建转发视图的过程:
当控制器方法中所设置的视图名称以"forward:"为前缀时,创建InternalResourceView视图,此时的视图名称不会被SpringMVC配置文件中所配置的视图解析器解析,而是会将前缀"forward:"去掉,剩余部分作为最终路径通过转发的方式实现跳转,地址栏不变。
例如"forward:/"(重定向到首页,地址栏就是首页的地址),"forward:/employee"
@RequestMapping("/testForward")
public String testForward(){
return "forward:/testHello";
}
6.3 重定向视图RedirectView
SpringMVC中默认的重定向视图是RedirectView
SpringMVC中创建转发视图的过程:
当控制器方法中所设置的视图名称以"redirect:"为前缀时,创建RedirectView视图,此时的视图名称不会被SpringMVC配置文件中所配置的视图解析器解析,而是会将前缀"redirect:"去掉,剩余部分作为最终路径通过重定向的方式实现跳转 ,地址栏变了。
例如"redirect:/","redirect:/employee"
@RequestMapping("/testRedirect")
public String testRedirect(){
return "redirect:/testHello";
}
注意:重定向视图在解析时,会先将redirect:前缀去掉,然后会判断剩余部分是否以/开头,若是则会自动拼接上下文路径。
6.4 视图控制器view-controller
当控制器方法中,仅仅用来实现页面跳转,即只需要设置视图名称时,可以将处理器方法使用view-controller标签进行表示(当SpringMVC中设置任何一个view-controller时,其他控制器中的请求映射将全部失效,此时需 要在SpringMVC的核心配置文件中设置开启mvc注解驱动的标签<mvc:annotation-driven/> ),需要引入mvc命名空间。
<!--设置视图控制器
使用条件:当前请求映射所对应的控制器方法中,没有其他请求的处理,只需要设置视图名称
path:设置处理的请求地址
view-name:设置请求地址所对应的视图名称
-->
<mvc:view-controller path="/" view-name="index"></mvc:view-controller>
<!--开启mvc注解驱动,必写,很重要,很多时候都需要-->
<mvc:annotation-driven/>
代替下面的controller
/*@RequestMapping("/")
public String index(){
return "index";
}*/
七、RESTful
是一种风格,也就是一种格式。
( springMVC-jspdemo)
7.1 RESTful简介
REST:Representational State Transfer,表现层资源状态转移。
- 资源
资源是一种看待服务器的方式,即,将服务器看作是由很多离散的资源组成。每个资源是服务器上一个 可命名的抽象概念。因为资源是一个抽象的概念,所以它不仅仅能代表服务器文件系统中的一个文件、 数据库中的一张表等等具体的东西,可以将资源设计的要多抽象有多抽象,只要想象力允许而且客户端 应用开发者能够理解。与面向对象设计类似,资源是以名词为核心来组织的,首先关注的是名词。一个 资源可以由一个或多个URI来标识。URI既是资源的名称,也是资源在Web上的地址。对某个资源感兴 趣的客户端应用,可以通过资源的URI与其进行交互。
- 资源的表述
资源的表述是一段对于资源在某个特定时刻的状态的描述,可以在客户端-服务器端之间转移(交换)。资源的表述可以有多种格式,例如HTML/XML/JSON/纯文本/图片/视频/音频等等。资源的表述格式可以通过协商机制来确定。请求-响应方向的表述通常使用不同的格式。
- 状态转移
状态转移说的是:在客户端和服务器端之间转移(transfer)代表资源状态的表述。通过转移和操作资源的表述,来间接实现操作资源的目的。
7.2 RESTful的实现
具体说,就是 HTTP 协议里面,四个表示操作方式的动词:GET、POST、PUT、DELETE。 它们分别对应四种基本操作:GET 用来获取资源,POST 用来新建资源,PUT 用来更新资源,DELETE 用来删除资源。 REST 风格提倡 URL 地址使用统一的风格设计,从前到后各个单词使用斜杠分开,不使用问号键值对方式携带请求参数,而是将要发送给服务器的数据作为 URL 地址的一部分,以保证整体风格的一致性。 (请求地址URL不变,根据请求操作的不同,对操作资源的方式进行区分)
@RequestMapping(value = "/user", method = RequestMethod.GET)
public String getAllUser(){
System.out.println("查询所有用户信息");
return "success";
}
@RequestMapping(value = "/user/{id}",method = RequestMethod.GET)
public String getUserById(){
System.out.println("根据id查询用户信息");
return "success";
}
@RequestMapping(value = "/user", method = RequestMethod.POST)
public String insertUser(String username,String password){
System.out.println("添加用户信息:"+username+"=="+password);
return "success";
}
<a th:href="@{/user}">查询所有用户信息</a><br/>
<a th:href="@{/user/1}">根据id查询用户信息</a><br/>
<form th:action="@{/user}" method="post">
用户名:<input type="text" name="username"/><br/>
密码:<input type="password" name="password"/><br/>
<input type="submit" value="添加"/><br/>
</form>
7.3 HiddenHttpMethodFilter
浏览器只支持发送get和post方式的请求,所以SpringMVC 提供了HiddenHttpMethodFilter 帮助我们将 POST 请求转换为DELETE 或 PUT请求。
HiddenHttpMethodFilter 处理put和delete请求的条件:
- 当前请求的请求方式必须为post
- 当前请求必须传输请求参数_method
满足以上条件,HiddenHttpMethodFilter 过滤器就会将当前请求的请求方式转换为请求参数 _method的值,因此请求参数_method的值才是最终的请求方式 。
<!--配置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>
@RequestMapping(value = "/user", method = RequestMethod.PUT)
public String updateUser(String username,String password){
System.out.println("修改用户信息:"+username+"=="+password);
return "success";
}
<form th:action="@{/user}" method="post">
<input type="hidden" name="_method" value="PUT">
用户名:<input type="text" name="username"/><br/>
密码:<input type="password" name="password"/><br/>
<input type="submit" value="修改"/><br/>
</form>
(删除见后RESTFul案例)
注意:
目前为止,SpringMVC中提供了两个过滤器:CharacterEncodingFilter和 HiddenHttpMethodFilter 在web.xml中注册时,必须先注册CharacterEncodingFilter,再注册HiddenHttpMethodFilter。因为:
- 在CharacterEncodingFilter中通过request.setCharacterEncoding(encoding) 方法设置字符集的
- request.setCharacterEncoding(encoding) 方法要求前面不能有任何获取请求参数的操作,而 HiddenHttpMethodFilter 恰恰有一个获取请求方式的操作:
- String paramValue = request.getParameter(this.methodParam);
八、RESTful案例
8.1 准备工作
- 搭建环境(springMVC-rest)
- 准备实体类
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() {
}
}
- 准备dao模拟数据
@Repository
public class EmployeeDao {
private static Map<Integer, Employee> employees = null;
static {
employees = new HashMap<Integer, Employee>();
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;
public void save(Employee employee) {
if (employee.getId() == null) {
employee.setId(initId++);
}
employees.put(employee.getId(), employee);//既能添加还能修改,妙!
}
public Collection<Employee> getAll() {//获取全部
return employees.values();
}
public Employee get(Integer id) {//根据id获取
return employees.get(id);
}
public void delete(Integer id) {//删除
employees.remove(id);
}
}
8.2 功能清单
8.3 具体功能:访问首页
8.3.1 配置视图控制器
<!--配置视图控制器 设置跳转首页-->
<mvc:view-controller path="/" view-name="index"></mvc:view-controller>
<!--开启mvc注解驱动-->
<mvc:annotation-driven/>
8.3.2 创建页面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<h1>首页</h1>
<a th:href="@{/employee}">查看员工信息</a>
</body>
</html>
8.4 具体功能:查询所有员工数据
@Controller
public class EmployeeController {
@Autowired
private EmployeeDao employeeDao;
@RequestMapping(value = "/employee",method = RequestMethod.GET)
public String getAllEmployee(Model model){
Collection<Employee> employeeList = employeeDao.getAll();
model.addAttribute("employeeList",employeeList);
return "employee_list";
}
}
employee_list.html:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>employee_list</title>
</head>
<body>
<table border="1" cellspacing="0" cellpadding="0" style="text-align: center">
<tr>
<th colspan="5">Employee Info</th>
</tr>
<tr>
<th>id</th>
<th>lastName</th>
<th>email</th>
<th>gender</th>
<th>options</th>
</tr>
<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>
<a href>update</a>
<a href>delete</a>
</td>
</tr>
</table>
</body>
</html>
8.5 具体功能:删除
删除需要把a标签的链接设置为form表单的提交地址,以达到method为DELETE的提交方式
- 创建处理delete请求方式的表单( employee_list.html中)
<form id="deleteForm" method="post">
<input type="hidden" name="_method" value="delete"/>
</form>
- 删除超链接绑定点击事件
删除超链接:
<!--@click vue绑定单击事件-->
<a @click="deleteEmployee" th:href="@{'/employee/'+${employee.id}}">delete</a>
引入vue.js,通过vue处理点击事件:(引入静态资源后要重新打包,否则引入的静态资源没有被打包,报404错误)
springMVC.xml中必须配置放行静态资源的访问,否则会被thymeleaf解析,找不到资源!
DispatcherServlet处理不了的请求,交给defaultServlet处理,放行对静态资源的访问。
<mvc:default-servlet-handler/>必须和<mvc:annotation-driven/>配合一起使用,达到先被DispatcherServlet处理,若没有找到则defaultServlet处理的效果!
<script type="text/javascript" th:src="@{/static/js/vue.js}"></script>
<script type="text/javascript">
var vue = new Vue({
el: "#dataTable",/*table添加id属性,id=dataTable,当前容器table的id*/
methods: {
deleteEmployee: function (event) {
//根据id获取表单元素
var deleteForm = document.getElementById("deleteForm");
//将触发点击事件的超链接的href属性赋值给表单的action
deleteForm.action = event.target.href;
//提交表单
deleteForm.submit();
//取消超链接的默认行为
event.preventDefault();
}
}
});
</script>
- 控制器方法
@RequestMapping(value = "/employee/{id}",method = RequestMethod.DELETE)
public String deleteEmployeeById(@PathVariable("id") Integer id){
employeeDao.delete(id);
return "redirect:/employee";//相当于浏览器直接访问,默认GET请求
}
8.6 具体功能:跳转到添加数据页面
employee_list.html中:
<th>options(<a th:href="@{/toAdd}">add</a>)</th>
springMVC.xml中配置视图解析器:(只是跳转页面,没有其他操作,就可以直接配置)
<mvc:view-controller path="/toAdd" view-name="employee_add"></mvc:view-controller>
employee_add.html:
(其中form表单的action也是/employee ,与之前的查询全部用户信息请求路径一直,但是提交方式不同,添加的method是post,而查询全部是通过超链接,超链接的提交方式为get)
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>employee_add</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" value="1" name="gender">male
<input type="radio" value="0" name="gender">female<br/>
<input type="submit" value="add"><br/>
</form>
</body>
</html>
8.7 具体功能:执行保存
@RequestMapping(value = "/employee",method = RequestMethod.POST)
public String addEmployee(Employee employee){
//用实体类获取对象,必须保证属性名和请求参数名一致
employeeDao.save(employee);
return "redirect:/employee";
}
8.8 具体功能:跳转到更新数据页面
employee_list.html中:
<a href th:href="@{'/employee/'++${employee.id}}">update</a>
控制器方法:
@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";
}
新建employee_update.html:(回显数据)
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>employee_update</title>
</head>
<body>
<form th:action="@{/employee}" method="post">
<input type="hidden" name="_method" value="put">
<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/>
gender:<input type="radio" value="1" name="gender" th:field="${employee.gender}">male
<input type="radio" value="0" name="gender" th:field="${employee.gender}">female<br/>
<input type="submit" value="update"><br/>
</form>
</body>
</html>
8.9 具体功能:执行更新
@RequestMapping(value = "/employee",method = RequestMethod.PUT)
public String updateEmployee(Employee employee){
employeeDao.save(employee);
return "redirect:/employee";
}
九、HttpMessageConverter
HttpMessageConverter(报文信息转换器)可以将请求报文转换为Java对象,或将Java对象转换为响应报文。HttpMessageConverter提供了两个注解:
- @RequestBody:将请求报文转换为Java对象
- @ResponseBody:将Java对象转换为响应报文
和两个类型:
- RequestEntity
- ResponseEntity
关于响应的注解和类型用得多!
9.1 @RequestBody
@RequestBody可以获取请求体,需要在控制器方法设置一个形参,使用@RequestBody进行标识,当前请求的请求体就会为当前注解所标识的形参赋值(post方式才有请求体)
/templates/index.html
<form th:action="@{/testRequestBody}" method="post">
username:<input type="text" name="username"><br/>
password:<input type="password" name="password"><br/>
<input type="submit" value="测试@RequestBody">
</form>
@Controller
public class HttpController {
@RequestMapping(value="/testRequestBody",method = RequestMethod.POST)
public String testRequestBody(@RequestBody String requestBody){
System.out.println("requestBody:"+requestBody);
return "success";
}
}
控制台: requestBody:username=zt&password=123
9.2 RequestEntity
RequestEntity是封装请求报文的一种类型,需要在控制器方法的形参中设置该类型的形参,当前请求的请求报文就会赋值给该形参,可以通过getHeaders()获取请求头信息,通过getBody()获取请求体信息。
<form th:action="@{/testRequestEntity}" method="post">
username:<input type="text" name="username"><br/>
password:<input type="password" name="password"><br/>
<input type="submit" value="测试@RequestEntity">
</form>
@RequestMapping("/testRequestEntity")
public String testRequestEntity(RequestEntity<String> requestEntity){
System.out.println("requestHeader:"+requestEntity.getHeaders());
System.out.println("requestBody:"+requestEntity.getBody());
return "success";
}
控制台:
- requestHeader:[host:"localhost:8080", connection:"keep-alive", content-length:"24", cache-control:"max-age=0", sec-ch-ua:"" Not;A Brand";v="99", "Microsoft Edge";v="97", "Chromium";v="97"", sec-ch-ua-mobile:"?0", sec-ch-ua-platform:""Windows"", 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/97.0.4692.71 Safari/537.36 Edg/97.0.1072.55", accept:"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", sec-fetch-site:"same-origin", sec-fetch-mode:"navigate", sec-fetch-user:"?1", sec-fetch-dest:"document", referer:"http://localhost:8080/springMVC_demo4/", accept-encoding:"gzip, deflate, br", accept-language:"zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6", Content-Type:"application/x-www-form-urlencoded;charset=UTF-8"]
- requestBody:username=zt&password=123
9.3 @ResponseBody
@ResponseBody用于标识一个控制器方法,可以将该方法的返回值直接作为响应报文的响应体响应到浏览器(下面代码不会跳转到success.html,而是直接响应“success”显示在浏览器上)
@RequestMapping("/testResponseBody")
@ResponseBody
public String testResponseBody(){
return "success";
}
9.4 SpringMVC处理json
需要将Java对象响应到浏览器时要加JSON的jar
@ResponseBody处理json的步骤:
1. 导入jackson的依赖
<!--JSON-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.1</version>
</dependency>
2. 在SpringMVC的核心配置文件springMVC.xml中开启mvc的注解驱动,此时在HandlerAdaptor中会自动装配一个消息转换器:MappingJackson2HttpMessageConverter,可以将响应到浏览器的Java对象转换为Json格式的字符串
JSON是一种数据交互格式(与XML类似,但现在用JSON比较多)
JSON对象:大括号{ key=value,...},JSON数组:方括号[ ]
<!--开启mvc注解驱动-->
<mvc:annotation-driven>
<mvc:message-converters>
<!--处理响应中文乱码-->
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<property name="defaultCharset" value="UTF-8"></property>
<property name="supportedMediaTypes">
<list>
<value>text/html</value>
<value>application/json</value>
</list>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
3. 在处理器方法上使用@ResponseBody注解进行标识
4. 将Java对象直接作为控制器方法的返回值返回,就会自动转换为JSON对象
@RequestMapping("/testResponseBodyUser")
@ResponseBody
public User testResponseBodyUser(){
return new User(1001,"zt","123",23,"女");
}
9.5 SpringMVC处理ajax
1.通过vue和axios处理点击事件:
<div id="app">
<a @click="testAxios" th:href="@{/testAxios}">SpringMVC处理ajax</a>
</div>
<script type="text/javascript" th:src="@{/static/js/vue.js}"></script>
<script type="text/javascript" th:src="@{/static/js/axios.min.js}"></script>
<script>
var vue = new Vue({
el:"#app",/*当前容器的id*/
methods:{
testAxios:function(event){
axios({
method:"post",
url:event.target.href,
params:{
username:"zt",
password:"123"
}
}).then(function (response) {
alert(response.data)
});
event.preventDefault();//取消当前超链接的默认行为
}
}
});
</script>
2. 控制器方法:
@RequestMapping("/testAxios")
@ResponseBody
public String testAxios(String username,String password){
System.out.println(username+"=="+password);
return "hello,axios";
}
9.6 @RestController注解
@RestController注解是springMVC提供的一个复合注解,标识在控制器的类上,就相当于为类添加了 @Controller注解,并且为其中的每个方法添加了@ResponseBody注解
9.6 ResponseEntity
ResponseEntity用于控制器方法的返回值类型,该控制器方法的返回值就是响应到浏览器的响应报文(用于文件下载)
十、文件上传和下载
10.1 文件下载
使用ResponseEntity实现下载文件的功能
@Controller
public class FileUpAndDownController {
@RequestMapping("/testDown")
public ResponseEntity<byte[]> testResponseEntity(HttpSession session) throws IOException {
//获取ServletContext对象,表示当前整个工程
ServletContext servletContext = session.getServletContext();
//获取服务器中文件的真实路径
String realPath = servletContext.getRealPath("/static/img/hxzl.png");
System.out.println(realPath);
//部署到服务器的路径,target中的
//D:\software\IDEA\ideaProjects\springMVC\springMVC-demo4\target\springMVC-demo4-1.0-SNAPSHOT\static\img\hxzl.png
//创建输入流
InputStream is = new FileInputStream(realPath);
//创建字节数组 available()获取数据流里可读取字节的总数
byte[] bytes = new byte[is.available()];
//将流读到字节数组中
is.read(bytes);
//创建HttpHeaders对象设置响应头信息 MultiValueMap是一个接口,实现了Map
MultiValueMap<String, String> headers = new HttpHeaders();
//设置要下载方式以及下载文件默认的名字 attachment:以附件的方式下载
headers.add("Content-Disposition", "attachment;filename=hxzl.png");
//设置响应状态码
HttpStatus statusCode = HttpStatus.OK;
//创建ResponseEntity对象 当前bytes就是响应体
ResponseEntity<byte[]> responseEntity = new ResponseEntity<>(bytes, headers, statusCode);
//关闭输入流
is.close();
return responseEntity;
}
}
10.2 文件上传
文件上传要求form表单请求方式必须为post,并且添加属性enctype="multipart/form-data"(不再以name=value的方式传输,而是以二进制形式传输)。
SpringMVC中将上传的文件封装到MultipartFile对象中,通过此对象可以获取该文件的相关信息。上传步骤:
1.添加依赖(pom.xml)
<!--文件上传-->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
2. 在SpringMVC的配置文件中添加配置(springMVC.xml)
<!--配置文件上传解析器,将上传的文件封装为MultipartFile对象
id必须是multipartResolver
-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"></bean>
3. 控制器方法
@RequestMapping(value = "/testUp", method = RequestMethod.POST)
public String testUp(MultipartFile photo, HttpSession session) throws IOException {
//获取上传文件的文件名
String fileName = photo.getOriginalFilename();
//获取上传的文件的后缀名 文件名最后一个.后面才是后缀
String suffixName = fileName.substring(fileName.lastIndexOf("."));
//将UUID作为文件名 replaceAll("-",""):把-替换为空字符串
String uuid = UUID.randomUUID().toString().replaceAll("-","");
//将uuid和后缀名拼接后的结果作文最终的文件名
fileName = uuid + suffixName;
//通过ServletContext获取服务器中photo目录的路径
ServletContext servletContext = session.getServletContext();
String photoPath = servletContext.getRealPath("/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";
}
4. file.html
<form th:action="@{/testUp}" method="post" enctype="multipart/form-data">
图片:<input type="file" name="photo"><br/>
<input type="submit" value="上传">
</form>
十一、拦截器
springMVC-demo5
11.1 拦截器的配置
SpringMVC中的拦截器用于拦截控制器方法执行(作用于filter和DispatcherServlet之间),拦截器需要实现HandlerInterceptor。
- preHandler在控制器方法执行之前执行
- postHandler在控制器方法执行之后执行
- afterCompletion在视图渲染之后执行
@Component//标记,放入IOC容器,还需要在SpringMVC.xml中配置扫描该包
public class FirstInterceptor implements HandlerInterceptor {
//Ctrl+O 重写快捷键
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("FirstInterceptor-------preHandle");
return true;
}
@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");
}
}
SpringMVC的拦截器必须在SpringMVC的配置文件中进行配置:
<context:component-scan base-package="com.ice.mvc.controller,com.ice.mvc.interceptor"></context:component-scan>
<!--配置拦截器-->
<mvc:interceptors>
<!--直接配置bean,不需要扫描该包,也不需要在拦截器方法上加注解
可以通过全类名直接找到拦截器方法
-->
<!--<bean class="com.ice.mvc.interceptor.FirstInterceptor"></bean>-->
<!--需要扫描包,并且注解标注拦截器方法-->
<!--<ref bean="firstInterceptor"></ref>-->
<!--【以上两种配置】
会对DispatcherServlet处理的所有请求都拦截
因为拦截器作用于filter和DispatcherServlet之间,所有的请求都要被DispatcherServlet处理
所以所有的请求都会被拦截
-->
<mvc:interceptor>
<mvc:mapping path="/**"/><!--包括 /*指标是单层目录 /**表示拦截所有-->
<mvc:exclude-mapping path="/"/><!--排除 /为排除首页(设置过视图控制器,/为跳转首页)-->
<!--指定拦截器-->
<ref bean="firstInterceptor"></ref>
</mvc:interceptor>
</mvc:interceptors>
11.2 拦截器的三个抽象方法
- preHandle:控制器方法执行之前执行preHandle(),其boolean类型的返回值表示是否拦截或放行,返回true为放行,即调用控制器方法;返回false表示拦截,即不调用控制器方法
- postHandle:控制器方法执行之后执行postHandle()
- afterComplation:处理完视图和模型数据,渲染视图完毕之后执行afterComplation()
11.3 多个拦截器的执行顺序
1. 若每个拦截器的preHandle()都返回true
此时多个拦截器的执行顺序和拦截器在SpringMVC的配置文件的配置顺序有关:(先打开的后关闭) preHandle()会按照配置的顺序执行,而postHandle()和afterComplation()会按照配置的反序执行。
2. 若某个拦截器的preHandle()返回了false
返回false的preHandle()方法所在的拦截器和它之前的拦截器的preHandle()都会执行,postHandle()都不执行,返回false的拦截器之前的拦截器的afterComplation()会执行。(返回false的preHandle一定会执行,要不然你咋知道它返回false了!)
十二、异常处理器
12.1 基于配置的异常处理
SpringMVC提供了一个处理控制器方法执行过程中出现异常的接口:HandlerExceptionResolver
HandlerExceptionResolver接口的实现类有:DefaultHandlerExceptionResolver(默认)和 SimpleMappingExceptionResolver(自定义)。
自定义的异常处理器SimpleMappingExceptionResolver,使用方式:
<!--配置异常处理-->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<!--
properties的键表示处理器方法执行过程中出现的异常
properties的值表示若出现指定异常时,设置一个新的视图名称,跳转到指定页面
-->
<!--error会被视图解析器解析,遵循视图解析规则-->
<prop key="java.lang.ArithmeticException">error</prop>
</props>
</property>
<!--异常信息,默认存储在请求域中 ,value为键,当前出现的异常为值-->
<property name="exceptionAttribute" value="exception"></property>
</bean>
<a th:href="@{/testExceptionHandler}">测试异常处理器</a><br/>
error.html
<p th:text="${exception}"></p>
@RequestMapping(value="/testExceptionHandler")
public String testExceptionHandler(){
//模拟异常
System.out.println(12/0);
return "success";
}
12.2 基于注解的异常处理
//@ControllerAdvice将当前类标识为异常处理的组件,@Component的扩展注解
@ControllerAdvice
public class ExceptionController {
//@ExceptionHandler用于设置所标识方法处理的异常
@ExceptionHandler(value = {ArithmeticException.class, NullPointerException.class})
public String testException(Exception exception, Model model) {
//exception表示当前请求处理中出现的异常对象
model.addAttribute("exception", exception);
return "error";
}
}
十三、注解配置SpringMVC
使用配置类和注解代替web.xml和SpringMVC.xml配置文件的功能(SpringMVC-annotation)
13.1 创建初始化类,代替web.xml
在Servlet3.0环境中,容器会在类路径中查找实现javax.servlet.ServletContainerInitializer接口的类, 如果找到的话就用它来配置Servlet容器(也就是Tomcat服务器)。 Spring提供了这个接口的实现,名为 SpringServletContainerInitializer,这个类反过来又会查找实现WebApplicationInitializer的类并将配置的任务交给它们来完成。Spring3.2引入了一个便利的WebApplicationInitializer基础实现,名为 AbstractAnnotationConfigDispatcherServletInitializer,当我们的类扩展了 AbstractAnnotationConfigDispatcherServletInitializer并将其部署到Servlet3.0容器的时候,容器会自动发现它,并用它来配置Servlet上下文。
//web工程的初始化类,用于代替web.xml
public class WebInit extends AbstractAnnotationConfigDispatcherServletInitializer {
/**
* 获取根配置
* 也就是 指定spring的配置类
* @return
*/
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{SpringConfig.class};
}
/**
* 指定SpringMVC的配置类
* @return
*/
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{WebConfig.class};
}
/**
* 指定DispatcherServlet的映射规则,即url-pattern
* @return
*/
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
/**
* 注册过滤器
* @return
*/
@Override
protected Filter[] getServletFilters() {
//编码过滤器
CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
characterEncodingFilter.setEncoding("UTF-8");
characterEncodingFilter.setForceResponseEncoding(true);
//处理PUT和DELETE方式请求的过滤器
HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();
return new Filter[]{characterEncodingFilter,hiddenHttpMethodFilter};
}
}
13.2 创建SpringConfig配置类,代替spring的配置文件
@Configuration //标识为配置类
public class SpringConfig {
//ssm整合之后,spring的配置信息写在这里
}
13.3 创建WebConfig配置类,代替SpringMVC的配置文件
(2. 视图解析器有问题!启动不了404!)
在SpringMVC.xml文件中使用<bean>标签配置的,在配置类中@Bean标注即可返回给IOC容器自动装配使用。其他可以重写实现接口中的方法来实现。
准备:
//拦截器类
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 {
}
}
WebConfig配置类:
/*
* 代替SpringMVC的配置文件
* 1. 扫描组件
* 2. 视图解析器
* 3. view-controller视图控制器
* 4. default-servlet-handler 默认的servlet处理器,处理静态资源
* 5. MVC注解驱动
* 6. 文件上传解析器
* 7. 异常处理
* 8. 拦截器
* */
@Configuration//标注为配置类
@ComponentScan("com.ice.mvc.controller")//1. 扫描组件
@EnableWebMvc//5. 开启MVC注解驱动
public class WebConfig implements WebMvcConfigurer {
//3. view-controller视图控制器
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/hello").setViewName("hello");
}
//4. default-servlet-handler 默认servlet处理
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
//6. 文件上传解析器
@Bean
public MultipartResolver multipartResolver(){
CommonsMultipartResolver commonsMultipartResolver = new CommonsMultipartResolver();
return commonsMultipartResolver;
}
//7. 异常处理
@Override
public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
SimpleMappingExceptionResolver simpleMappingExceptionResolver = new SimpleMappingExceptionResolver();
Properties properties = new Properties();
properties.setProperty("java.lang.ArithmeticException","error");
simpleMappingExceptionResolver.setExceptionMappings(properties);
simpleMappingExceptionResolver.setExceptionAttribute("exception");
resolvers.add(simpleMappingExceptionResolver);
}
//8. 拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
TestInterceptor testInterceptor = new TestInterceptor();
registry.addInterceptor(testInterceptor).addPathPatterns("/**");
}
//2. 配置视图解析器
// 配置生成模板解析器
/* @Bean//该注解表示返回值就是IOC容器中的一个bean,@Bean方法的返回值注入到IOC容器中
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;
}*/
// 配置生成模板解析器
@Bean
public SpringResourceTemplateResolver templateResolver() {
SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver();
templateResolver.setPrefix("/WEB-INF/templates/");
templateResolver.setSuffix(".html");
templateResolver.setCharacterEncoding("UTF-8");
templateResolver.setTemplateMode(TemplateMode.HTML);
return templateResolver;
}
//生成模板引擎并为模板引擎注入模板解析器
@Bean
public SpringTemplateEngine templateEngine(SpringResourceTemplateResolver templateResolver) {
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(templateResolver);
return templateEngine;
}
//生成视图解析器并未解析器注入模板引擎
@Bean//viewResolver返回给IOC容器,需要的时候框架自动获取
public ViewResolver viewResolver(SpringTemplateEngine templateEngine) {
ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
viewResolver.setCharacterEncoding("UTF-8");
//viewResolver.setOrder(1);
viewResolver.setTemplateEngine(templateEngine);
return viewResolver;
}
}
13.4 测试
(404!)
@Controller
public class TestController {
@RequestMapping("/")
public String index(){
return "index";
}
}
十四、SpringMVC执行流程*
handler就是controller,喜欢就好。
14.1 SpringMVC常用组件
14.1.1 DispatcherServlet:前端控制器
不需要工程师开发,由框架提供
作用:统一处理请求和响应,整个流程控制的中心,由它调用其它组件处理用户的请求
14.1.2 HandlerMapping:处理器映射器
找处理器!
不需要工程师开发,由框架提供 @RequestMapping
作用:根据请求的url、method等信息查找Handler,即控制器方法
14.1.3 Handler:处理器
需要工程师开发 @Controller
作用:在DispatcherServlet的控制下Handler对具体的用户请求进行处理
14.1.4 HandlerAdapter:处理器适配器
执行找到的处理器!
不需要工程师开发,由框架提供
作用:通过HandlerAdapter对处理器(控制器方法)进行执行
14.1.5 ViewResolver:视图解析器
不需要工程师开发,由框架提供
作用:进行视图解析,得到相应的视图,例如:ThymeleafView、InternalResourceView、 RedirectView
14.1.6 View:视图
作用:将模型数据通过页面展示给用户
14.2 DispatcherServlet初始化过程
DispatcherServlet 本质上是一个 Servlet,天然地遵循 Servlet 的生命周期,所以宏观上是 Servlet 生命周期来进行调度。
源码重点看中文注释地方!了解流程!
1. 初始化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 explicitparent -> 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;
}
2. 创建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;
}
3. 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);
}
14.3 DispatcherServlet调用组件处理请求
1. 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);
}
}
2. 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);
}
}
}
3. 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);
}
}
}
}
4. 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);
}
}
14.4 SpringMVC的执行流程
1. 用户向服务器发送请求,请求被SpringMVC前端控制器 DispatcherServlet捕获。
2. DispatcherServlet对请求URL进行解析,得到请求资源标识符(URI),判断请求URI对应的映射:
(1)不存在
a. 判断是否配置了mvc:default-servlet-handler
b. 如果没配置,所有的请求都会被DispatcherServlet处理,控制台报映射查找不到,客户端展示404错误
c. 如果有配置,则访问目标资源(一般为静态资源,如:JS,CSS,HTML),找不到客户端也会展示404错误
(2)存在则执行下面的流程
3. 根据该URI,调用HandlerMapping获得该Handler配置的所有相关的对象【也就是调用链:控制器方法、拦截器集合和当前拦截器索引】(包括Handler对象以及Handler对象对应的拦截器),最后以HandlerExecutionChain执行链对象的形式返回。
4. DispatcherServlet根据获得的Handler,选择一个合适的HandlerAdapter。
5. 如果成功获得HandlerAdapter,此时将开始先执行拦截器的preHandler(…)方法【正向】
6. 提取Request中的模型数据,填充Handler入参(形参),开始执行Handler(Controller)方法,处理请求。 在填充Handler的入参过程中,根据你的配置,Spring将帮你做一些额外的工作:
(1)HttpMessageConveter: 将请求消息(如Json、xml等数据)转换成一个对象,将对象转换为指定 的响应信息
(2) 数据转换:对请求消息进行数据转换。如String转换成Integer、Double等
(3) 数据格式化:对请求消息进行数据格式化。 如将字符串转换成格式化数字或格式化日期等
(4)数据验证: 验证数据的有效性(长度、格式等),验证结果存储到BindingResult或Error中
7. Handler执行完成后,向DispatcherServlet 返回一个ModelAndView对象。
8. 此时将开始执行拦截器的postHandle(...)方法【逆向】。
9. 根据返回的ModelAndView(此时会判断是否存在异常:如果存在异常,则执行 HandlerExceptionResolver进行异常处理)选择一个适合的ViewResolver进行视图解析,根据Model和View,来渲染视图。
10. 渲染视图完毕执行拦截器的afterCompletion(…)方法【逆向】。
11. 将渲染结果返回给客户端。