文章目录
- 一、SpringMVC 简介
- 1.1 什么是MVC
- 1.2 什么是Spring MVC
- 1.3 Spring MVC的特点
- 二、SpringMVC 快速入门
- 三、@RequestMapping注解说明
- 四、SpringMVC获取请求参数
- 4.1 通过ServletAPI获取请求参数
- 4.2 通过控制器方法的形参获取请求参数
- 4.3 通过@RequestParam接收请求参数
- 4.4 通过@RequestHeader接收请求头信息参数
- 4.5 通过@CookieValue接收cookie数据
- 4.6 通过pojo获取请求参数
- 4.7 解决请求参数的乱码问题
- 五、域对象共享数据
- 5.1 向request域对象共享数据
- 5.2 向session域对象共享数据
- 5.3 向application域对象共享数据
- 六、SpringMVC中的视图
- 6.1 Thymeleaf 视图
- 6.2 转发视图
- 6.3 重定向视图
- 6.4 视图控制器
- 七、RESTFul
- 7.1 简介
- 7.2 RESTful的实现方式
- 7.3 案例展示
- 八、HttpMessageConverter
- 8.1 @RequestBody注解
- 8.2 RequestEntity类
- 8.3 @ResponseBody注解
- 8.4 ResponseEntity类
- 九、SpringMVC实现文件上传
- 十、拦截器
- 10.1 拦截器的配置
- 10.2 拦截器的实现
- 10.3 多个拦截器的执行顺序
- 10.4 拦截器与过滤器
- 十一、SpringMVC的异常处理
- 11.1 基于配置的异常处理
- 11.2 基于注解的异常处理
- 十二、SpringMVC的全注解开发
- 12.1 创建配置类替换web.xml
- 12.2 创建配置类替换SpringMvc的配置文件
- 十三、SpringMVC的执行流程
- 13.1 SpringMVC 常用组件
- 13.2 DispatcherServlet初始化过程
- 13.3 SpringMVC的执行流程
一、SpringMVC 简介
1.1 什么是MVC
MVC是一种软件架构的思想,将软件按照模型、视图、控制器来划分。
M:Model(模型层)是指工程中的JavaBean,作用是处理数据。JavaBean分为两类
- 实体类Bean:专门存储业务数据
- 业务处理Bean:专门处理业务逻辑和数据访问,比如service和dao对象
V:View(视图层):指工程中的html或jsp等页面,作用是与用户进行交互,展示数据
C:Controller(控制层):指工程中的servlet,作用是接收请求和响应浏览器
MVC的工作流程
用户通过视图层发送请求到服务器,在服务器中请求被Controller接收,Controller调用相应的Model层处理请求,处理完毕将结果返回到Controller,Controller再根据请求处理的结果找到相应的View视图,渲染数据后最终响应给浏览器。
1.2 什么是Spring MVC
Spring MVC属于Spring 的后续产品,是Spring的一个子项目。
Spring MVC 是Spring 为表述层开发提供的一整套完整的解决方案。在经历了Struts1(现在一般不用),Struts 2(一般老项目使用)等诸多产品的更迭之后,目前Spring MVC 作为JavaEE项目表述层开发的首选方案
java三层架构分为:表述层、业务逻辑层、数据访问层,其中表述层包含了前台页面和后台servlet
1.3 Spring MVC的特点
- Spring 家族的原生产品,能与Spring无缝衔接
- 基于原生的Servlet,通过强大的前端控制器DispatcherServlet,对请求和响应进行统一处理
- 对表述层各细分领域需要解决的问题,提供了全面的解决方案
- 代码清新简洁,大幅度提升开发效率
- 内部组件化程度高,想要什么功能配置相应组件即可
- 性能卓越,满足企业大部分场景需求
二、SpringMVC 快速入门
2.1 创建maven工程
2.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.ityfc</groupId>
<artifactId>springmvc</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>war</packaging>
<dependencies>
<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>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
<version>3.0.12.RELEASE</version>
</dependency>
</dependencies>
</project>
2.3 配置web.xml
1)默认配置方式
此配置作用下,SpringMVC的配置文件默认位于 WEB-INF 下,默认名称为 xxx-servlet.xml。例如,以下配置所对应 SpringMVC 的配置文件位于 WEB-INF下,文件名为 SpringMVC-servlet.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_3_1.xsd"
version="3.1">
<!--配置 SpringMVC 的前端控制器,对浏览器发送的请求进行统一处理-->
<servlet>
<servlet-name>SpringMVC</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>SpringMVC</servlet-name>
<!--
1、设置 SpringMVC 的核心控制器所能处理的请求的请求路径
2、/ 所匹配的请求可以是 /login 或 .html 或 .js 或 .css 方式的请求路径,
但 / 不能匹配 .jsp 请求路径的请求
-->
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
2)扩展配置方式
可通过 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_3_1.xsd"
version="3.1">
<!--配置 SpringMVC 的前端控制器,对浏览器发送的请求进行统一处理-->
<servlet>
<servlet-name>SpringMVC</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--通过初始化参数指定 SpringMVC 配置文件的位置和名称-->
<init-param>
<!-- contextConfigLocation 为固定值-->
<param-name>contextConfigLocation</param-name>
<!--使用 classpath 表示从类路径查找配置文件,例如maven工程中的 src/main/resources -->
<param-value>classpath:springMVC.xml</param-value>
</init-param>
<!--
服务器启动时就初始化 DispatcherServlet :
作为框架的核心组件,在启动过程中有大量的初始化操作要做
而这些操作放在第一次请求时才执行会严重影响访问速度
因此需要通过此标签将启动控制 DispatcherServlet 的初始化时间提前到服务器启动时(值为1时就是服务器启动时初始化)
-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>SpringMVC</servlet-name>
<!--
1、设置 SpringMVC 的核心控制器所能处理的请求的请求路径
2、/ 所匹配的请求可以是 /login 或 .html 或 .js 或 .css 方式的请求路径,
但 / 不能匹配 .jsp 请求路径的请求
-->
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
2.4 创建请求控制器
@Controller
public class HelloController {
@RequestMapping("/index")
public String index(){
return "index";
}
@RequestMapping("/target")
public String toTarget(){
return "target";
}
}
2.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 http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 配置组件扫描 -->
<context:component-scan base-package="com.ityang"/>
<!-- 配置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>
2.6 配置tomcat
配置上下文路径
2.7 创建页面
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="@{/target}">跳转到目标页面target.html</a>
</body>
</html>
target.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>helloworld</h1>
</body>
</html>
2.8 启动测试
2.9 总结
浏览器发送请求,若请求地址符合前端控制器的url-pattern,该请求就会被前端控制器DispatcherServlet处理。前端控制器会读取SpringMVC的核心配置文件,通过扫描组件找到控制器,将请求地址和控制器中@RequestMapping注解的value属性值进行匹配,若匹配成功,该注解所标识的控制器方法就是处理请求的方法。处理请求的方法需要返回一个字符串类型的视图名称,该视图名称会被视图解析器解析,加上前缀和后缀组成视图的路径,通过Themeleaf对视图进行渲染,最终转发到视图所在页面。
三、@RequestMapping注解说明
1、@RequestMapping注解的作用
将请求和处理请求的控制器方法关联起来,建立映射关系。一个请求进来之后会根据此映射关系找到具体的处理方法。
2、@RequestMapping注解的作用位置
标识在类上:设置映射请求的请求路径的初始信息
标识在方法上:设置映射请求的请求路径的具体信息
@Controller
@RequestMapping("/hello")
public class HelloController {
@RequestMapping("/index")
public String index(){
return "index";
}
}
3、@RequestMapping注解的属性
1)value 属性
通过请求的请求地址匹配请求映射,它是一个字符串类型的数组,表示可以设置多个路径。value属性必须设置,至少设置一个路径
@RequestMapping(
value = {"/index","/index1"}
)
public String index(){
return "index";
}
2)method 属性
通过请求的请求方式匹配请求映射,它是一个RequestMethod类型的数组,表示可以设置多个请求方式。如果一个请求满足请求映射的value属性,但是不满足method属性,则浏览器报405
@RequestMapping(
value = {"/index","/index1"},
method = {RequestMethod.GET,RequestMethod.POST}
)
public String index(){
return "index";
}
1、SpringMVC中提供了@RequestMapping 的派生注解:
@GetMapping
@PostMapping
@PutMapping
@DeleteMapping
2、常用的请求方式有get、post、put、delete
但是目前浏览器只支持get和post请求,即使我们在form表单的method属性设置了put或者delete,也不会生效,会默认按照get请求方式处理。若要发送put和delete请求,则需要通过spring提供的过滤器HiddenHttpMethodFilter(后面会写到)
3)params 属性
通过请求的请求参数匹配请求映射,它是一个字符串类型的数组,可以通过四种表达式设置请求参数和请求映射的匹配关系,比如以下四种情况:
“id”:要求请求的参数必须要有id属性
“!id”:要求请求的参数不能有id属性
“id=1”:要求请求的参数的id属性必须等于1
“id!=1”:要求请求的参数的id属性不能等于1
@RequestMapping(
value = {"/index","/index1"},
method = {RequestMethod.GET,RequestMethod.POST},
params = {"id=1","username"}
)
public String index(){
return "index";
}
4)headers 属性
通过请求的请求头信息匹配请求映射,它是一个字符串类型的数组,可以通过四种表达式设置请求参数和请求映射的匹配关系,比如以下四种情况:
“Host”:要求请求的请求头中必须要有Host
“!Host”:要求请求的请求头中不能有Host
“Host=localhost:8080”:要求请求的请求头中的Host必须等于localhost:8080
“Host!=localhost:8080”:要求请求的请求头中的Host不能等于localhost:8080
及时满足了method和value属性映射,但是不满足headers属性映射,此时页面会报404。
@RequestMapping(
value = {"/index","/index1"},
method = {RequestMethod.GET,RequestMethod.POST},
params = {"id=1","username"},
headers = {"Host=localhost:8080"}
)
public String index(){
return "index";
}
4、SpringMVC支持ant风格的路径
?:表示任意的单个字符
@RequestMapping("/t?st/index")
public String index(){
return "index";
}
*:表示任意的0个或多个字符
@RequestMapping("/t*st/index")
public String index(){
return "index";
}
**:表示任意的一层或多层目录,注意 在使用**时,只能使用/**/xxx的方式
@RequestMapping("/**/index")
public String index(){
return "index";
}
5、SpringMVC支持路径中的占位符
SpringMVC路径中的占位符常用语restful风格,当请求路径中将某些数据通过路径的方式传输到服务器中,就可以在相应的@RequestMapping注解的value属性中通过占位符{xxx}表示传输的数据,再通过@PathVariable注解,将占位符所表示的数据赋值给控制器方法的形参
@RequestMapping("/user/{id}")
public String index(@PathVariable("id")Long id){
return "index";
}
四、SpringMVC获取请求参数
4.1 通过ServletAPI获取请求参数
将HttpServletRequest作为控制器方法的形参,此时HttpServletRequest类型的参数表示封装了当前请求的请求报文对象。代码示例如下:
@RequestMapping("/index")
public String index(HttpServletRequest request){
String username = request.getParameter("username");
String password = request.getParameter("password");
return "index";
}
4.2 通过控制器方法的形参获取请求参数
在控制器方法的形参位置,设置和请求参数同名的形参,当浏览器发送请求,匹配到请求映射时,在DispatcherServlet中就会将请求参数赋值给相应的形参
@RequestMapping("/index")
public String index(String username,String password, String[] hobby){
return "index";
}
注意:
String[] hobby 使用数组可以接收多个同名的请求参数,也可以用字符串String hobby接收,springmvc会自动将多个值用逗号进行拼接
4.3 通过@RequestParam接收请求参数
作用:将请求参数和控制器方法的形参创建映射关系。
该注解一共有三个属性:
value:指定为形参赋值的请求参数的参数名
required:设置是否必须传输此请求参数,默认值为true
defaultValue:不管required属性值是true还是false,当value所指定的请求参数没有传输时,则使用默认值为形参赋值
@RequestMapping("/index")
public String index(@RequestParam(value = "user_name", required = false, defaultValue = "jack") String username){
return "index";
}
4.4 通过@RequestHeader接收请求头信息参数
作用:将请求头信息和控制器方法的形参创建映射关系。
该注解一共有三个属性:
value:指定为形参赋值的请求参数的参数名
required:设置是否必须传输此请求参数,默认值为true
defaultValue:不管required属性值是true还是false,当value所指定的请求参数没有传输时,则使用默认值为形参赋值
@RequestMapping("/index")
public String index(@RequestHeader(value = "Host", required = false, defaultValue = "127.0.0.1:8080") String host){
return "index";
}
4.5 通过@CookieValue接收cookie数据
作用:将cookie数据和控制器方法的形参创建映射关系
该注解一共有三个属性:
value:指定为形参赋值的请求参数的参数名
required:设置是否必须传输此请求参数,默认值为true
defaultValue:不管required属性值是true还是false,当value所指定的请求参数没有传输时,则使用默认值为形参赋值
@RequestMapping("/index")
public String index(@CookieValue(value = "JSESSIONID", required = false, defaultValue = "127.0.0.1:8080") String jsessionid){
return "index";
}
4.6 通过pojo获取请求参数
我们可以在控制器方法的形参位置设置一个实体类类型的参数,此时若浏览器传输的请求参数的参数名和实体类中的属性名一致,那么请求参数就会为此属性赋值。
@RequestMapping("/index")
public String index(User user){
return "index";
}
4.7 解决请求参数的乱码问题
1)GET请求参数乱码问题解决
Tomcat 安装包的conf/server.xml 配置文件中设置编码URIEncoding="UTF-8"
2)POST请求参数乱码问题解决
web.xml 中配置CharacterEncodingFilter
<!--编码过滤器,解决post参数乱码问题-->
<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 向request域对象共享数据
1、使用ServletAPI向request域对象共享数据
@RequestMapping("/target")
public String toTarget(HttpServletRequest request){
request.setAttribute("key","value");
request.getAttribute("key");
request.removeAttribute("key");
return "target";
}
2、使用ModelAndView向request域对象共享数据
@RequestMapping("/target")
public ModelAndView toTarget(){
/**
* ModelAndView有Model和View的功能
* Model主要用于向request域共享数据
* View主要用于设置视图,实现页面跳转
*/
ModelAndView mav = new ModelAndView();
mav.addObject("key","value");
mav.setViewName("target");
return mav;
}
3、使用Model向request域对象共享数据
@RequestMapping("/target")
public String toTarget(Model model){
model.addAttribute("key","value");
return "target";
}
4、使用Map向request域对象共享数据
@RequestMapping("/target")
public String toTarget(Map<String, Object> map){
map.put("key","value");
return "target";
}
5、使用ModelMap向request域对象共享数据
@RequestMapping("/target")
public String toTarget(ModelMap modelMap){
modelMap.addAttribute("key","value");
return "target";
}
6、Model、Map、ModelMap之间的关系
Model、Map、ModelMap类型的参数其实本质上都是 BindingAwareModelMap 类型的
5.2 向session域对象共享数据
@RequestMapping("/target")
public String toTarget(HttpSession session){
session.setAttribute("key","value");
return "target";
}
5.3 向application域对象共享数据
@RequestMapping("/target")
public String toTarget(HttpSession session){
ServletContext application = session.getServletContext();
application.setAttribute("key", "value");
return "target";
}
六、SpringMVC中的视图
SpringMVC中的视图是View接口,视图的作用是渲染数据,将模型Model中的数据展示给用户。
SpringMVC中的视图种类有很多,默认有转发视图InternalResourceView和重定向视图RedirectView。
当工程引入jstl的依赖时,转发视图会自动转换为jstlView。
若使用的视图技术为Thymeleaf,在SpringMVC的配置文件中配置了Thymeleaf的视图解析器,由此视图解析器解析所得到的是ThymeleafView
6.1 Thymeleaf 视图
当控制器方法中所设置的视图名称没有任何前缀时,此时的视图名称会被SpringMVC配置文件中配置的视图解析器解析,视图名称拼接视图前缀和视图后缀所得到的最终路径,会通过转发的方式实现跳转。
@RequestMapping("/hello")
public String hello(){
return "hello";
}
6.2 转发视图
SpringMVC中默认的转发视图是 InternalResourceView
SpringMVC中创建转发视图的情况:
当控制器方法中所设置的视图名称以forward:
为前缀时,创建 InternalResourceView 视图,此时的视图名称不会被SpringMVC配置文件中配置的视图解析器解析,而是会将前缀forward:
去掉,剩余部分作为最终路径通过转发的方式实现跳转。
@RequestMapping("/hello")
public String hello(){
return "forward:/test";
}
手动配置 InternalResourceView 视图解析器
6.3 重定向视图
SpringMVC中默认的重定向视图是 RedirectView
当控制器方法中所设置的视图名称以redirect:
为前缀时,创建 RedirectView视图,此时的视图名称不会被SpringMVC配置文件中配置的视图解析器解析,而是会将前缀redirect:
去掉,剩余部分作为最终路径通过重定向的方式实现跳转。
@RequestMapping("/hello")
public String hello(){
return "redirect:/test";
}
6.4 视图控制器
当控制器方法中,仅仅用来实现页面跳转,即只需要设置视图名称时,可以将处理器方法使用view-controller标签进行表示
<!--
path:设置处理的请求地址
view-name:设置请求地址所对应的视图名称
-->
<mvc:view-controller path="/" view-name="success"></mvc:view-controller>
<!-- 开启mvc注解驱动-->
<mvc:annotation-driven />
注:当SpringMVC配置文件中配置了任何一个view-controller,其他控制器中的请求映射将全部失效,此时需要配置一个注解驱动才能解决失效问题
七、RESTFul
7.1 简介
REST:Representational State Transfer,表现层资源状态转移。
1、资源
资源是一种看待服务器的方式,即,将服务器看作是由很多离散的资源组成。每个资源是服务器上一个可命名的抽象概念。因为资源是一个抽象的概念,所以它不仅仅能代表服务器文件系统中的一个文件、数据库中的一张表等等具体的东西,可以将资源设计的要多抽象有多抽象,只要想象力允许而且客户端应用开发者能够理解。与面向对象设计类似,资源是以名词为核心来组织的,首先关注的是名词。一个资源可以由一个或多个URL来标识。URL既是资源的名称,也是资源在Web上的地址。对某个资源感兴趣的客户端应用,可以通过资源的URL与其进行交互。
2、资源的表述
资源的表述是一段对于资源在某个特定时刻的状态的描述。可以在客户端-服务器端之间转移(交换)。资源的表述可以有多种格式,例如HTML/XML/JSON/纯文本/图片/视频/音频等等。资源的表述格式可以通过协商机制来确定。请求-响应方向的表述通常使用不同的格式。
3、状态转移
在客户端和服务器端之间转移(transfer)代表资源状态的表述。通过转移和操作资源的表述,来间接实现操作资源的目的。
7.2 RESTful的实现方式
简单来说,就是HTTP协议里面,四个表示操作方式的动词:GET、POST、PUT、DELETE。
它们分别对应四种基本操作:GET用来获取资源,POST用来新建资源,PUT用来更新资源,DELETE用来删除资源。
REST风格提倡URL地址使用统一的风格设计,从前到后各个单词使用斜杠分开,不使用问号键值对方式携带请求参数,而是将要发送给服务器的数据作为URL地址的一部分,以保证整体风格的一致性。
7.3 案例展示
@PostMapping("/user")
public String addUser(String username,String password) {
System.out.println("username:"+username+",password:"+password);
System.out.println("添加用户");
return "success";
}
@GetMapping("/user")
public String queryUserList() {
System.out.println("查询所有用户");
return "success";
}
@GetMapping("/user/{id}")
public String queryUserById(@PathVariable("id") String id) {
System.out.println("查询id为"+id+"的用户");
return "success";
}
@PutMapping("/user")
public String alterUser(String username,String password) {
System.out.println("username:"+username+",password:"+password);
System.out.println("修改用户");
return "success";
}
@DeleteMapping("/user/{id}")
public String deleteUser(@PathVariable("id") String id) {
System.out.println("删除id为"+id+"的用户");
return "success";
}
由于目前浏览器只支持get和post请求,即使我们在form表单的method属性设置了put或者delete,也不会生效,会默认按照get请求方式处理。若要发送put和delete请求,则需要通过spring提供的过滤器HiddenHttpMethodFilter来解决。
需要在web.xml中添加一个过滤器HiddenHttpMethodFilter
<?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">
<!--解决post乱码问题-->
<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请求的问题,处理编码的过滤器一定要在最前面,否则会失效-->
<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的前端控制器,对浏览器所发送的请求统一进行处理
在此配置下,springMVC的配置文件具有默认的位置和名称
默认的位置:WEB-INF
默认的名称:<servlet-name>-servlet.xml
若要为springMVC的配置文件设置自定义的位置和名称
需要在servlet标签中添加init-param
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springMVC.xml</param-value>
</init-param>
load-on-startup:将前端控制器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>
对于delete请求超链接处理form表单之类的问题这里不过多阐述了,实际项目中可能就是用的框架处理。
最后贴一个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"
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 http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!--打开组件扫描-->
<context:component-scan base-package="com"></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>
<!--视图控制器view-controller实现页面跳转
path:请求地址
view-name:请求地址所对应的视图名称-->
<mvc:view-controller path="/" view-name="index"/>
<!--开启mvc注解驱动,不开启超链接会失效-->
<mvc:annotation-driven/>
<!--开放对静态资源的访问,先由sprngmvc进行处理,如果找不到再由默认handler处理,如果还是找不到,则报404-->
<mvc:default-servlet-handler/>
</beans>
八、HttpMessageConverter
报文信息转换器(HttpMessageConverter),有两个作用:1、将请求报文转化为java对象;2、将java对象转化为响应报文。
HttpMessageConverter提供了两个注解和两个类供我们使用:@RequestBody、@ResponseBody;RequestEntity、ResponseEntity。
8.1 @RequestBody注解
@RequestBody可以获取请求体
,需要在控制器方法
设置一个形参,使用@RequestBody进行标识,当前请求的请求体就会为当前注解所标识的形参赋值。示例如下:将请求体
赋值给当前注解所标识的形参
package com.example.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.servlet.ModelAndView;
@Controller
public class HttpController {
@PostMapping("/testRequestBody")
public ModelAndView testRequestBody(ModelAndView modelAndView, @RequestBody String requestBody){
System.out.println("requestBody:"+requestBody);
modelAndView.setViewName("success");
return modelAndView;
}
}
控制台输出:
8.2 RequestEntity类
RequestEntity类是封装请求报文的一种类型,需要在控制器方法的形参中设置该类型的形参,当前请求的请求报文就会赋值给该形参,可以通过getHeaders()方法获取请求头信息,用getBody()获取请求体信息。示例如下:RequestEntity类型参数封装了整个请求报文
@PostMapping("/testRequestEntity")
public ModelAndView testRequestEntity(ModelAndView modelAndView, RequestEntity<String> requestEntity){
System.out.println("request-head:"+requestEntity.getHeaders());
System.out.println("request-body:"+requestEntity.getBody());
modelAndView.setViewName("success");
return modelAndView;
}
控制台输出:
8.3 @ResponseBody注解
@ResponseBody注解用于标识一个控制器方法,可以将该方法的返回值直接作为响应报文的响应体响应浏览器。
1、@ResponseBody注解返回字符串
/**
* 如果没加@ResponseBody注解,字符串会被当做视图名解析,如果加了则直接作为响应体响应给浏览器
**/
@RequestMapping("/testResponseBody")
@ResponseBody
public String testResponseBody(){
return "success";
}
// 相当于原始写法
public void testResponseBody(HttpServletResponse response) throws IOException {
response.getWriter().write("success");
}
2、@ResponseBody注解返回JSON字符串对象
我们直接返回java对象给浏览器是不行的,它不认识整个对象,所以我们需要转换成JSON字符串对象给浏览器,需要做的工作如下:
- 导入jackson依赖
- 在SpringMVC配置文件中添加<mvc:annotation-driven />
- 在处理器上使用@ResponseBody注解
- 直接在控制器方法中返回我们自己的类对象
<!--JSON数据绑定-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.3</version>
</dependency>
代码示例
@RequestMapping("/testResponseUser")
@ResponseBody
public User testResponseUser(){
return new User(1001,"Keeling","10086",18,"男");
}
3、@RestController 组合注解(实际开发中常用
)
这个注解是SpringMVC提供的一个复合注解,标识在控制器的类上,相当于为这个类添加@Controller注解,并且为这个类中所有的方法添加@ResponseBody注解。
8.4 ResponseEntity类
ResponseEntity用于控制器方法的返回值类型,该控制器方法的返回值就是响应到服务器的响应报文。(实际开发中我们一般自己封装返回类型)
ResponseEntity实现文件下载(了解)
package com.example.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 javax.servlet.ServletContext;
import javax.servlet.http.HttpSession;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
@Controller
public class FileUpAndDownController {
@RequestMapping("/testDownload")
public ResponseEntity<byte[]> testDownload(HttpSession session) throws IOException {
/*获取ServletContext对象*/
ServletContext context = session.getServletContext();
/*获取服务器中文件的真实路径*/
String path = context.getRealPath("/static/img/cat.jpg");
/*创建输入流*/
InputStream inputStream = new FileInputStream(path);
/*创建字节数组*/
byte[] buffer = new byte[inputStream.available()];
/*将流读取到字节数组中*/
inputStream.read(buffer);
/*创建HttpHeaders对象设置响应头信息*/
MultiValueMap<String, String> headers = new HttpHeaders();
/*设置下载的方式和文件名*/
headers.add("Content-Disposition", "attachment;filename=hello.jpg");
/*设置响应状态码*/
HttpStatus status = HttpStatus.OK;
/*创建ResponseEntity对象*/
ResponseEntity<byte[]> responseEntity = new ResponseEntity<>(buffer,headers,status);
/*关闭输入流*/
inputStream.close();
return responseEntity;
}
}
九、SpringMVC实现文件上传
第一步:添加文件上传需要的依赖
<!--文件上传-->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>
第二步:SpringMVC配置文件,添加下面这个Bean
<!--配置文件上传解析器,将上传的文件封装为MultipartFile-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"></bean>
第三步:前端页面
<h2>测试文件上传</h2>
<!--
enctype="multipart/form-data"表示通过二进制形式传输
只有这样,服务器才能成功接收文件
-->
<form th:action="@{/testUpload}" method="post" enctype="multipart/form-data">
图片:<input type="file" name="photo"><br>
<input type="submit" value="提交">
</form>
第四步:控制器方法
/**
* SpringMVC将我们当前上传的文件封装到MultipartFile中
* 然后使用该对象的transferTo方法实现上传即可
*/
@RequestMapping("/testUpload")
public ModelAndView testUpload(MultipartFile photo, HttpSession session, ModelAndView modelAndView) throws IOException {
/*获取上传的文件名*/
String name = photo.getOriginalFilename();
/*获取上传文件的后缀名*/
String suffix = name.substring(name.lastIndexOf("."));
/*将UUID作为文件名*/
String uuid = UUID.randomUUID().toString();
//将uuid和后缀名拼接后成为最终的文件名
name = uuid + suffix;
/*获取服务器中upload目录的路径*/
ServletContext context = session.getServletContext();
String photoPath = context.getRealPath("upload");
/*判断photoPath所对应路径是否存在*/
File file = new File(photoPath);
//不存在创建目录
if (!file.exists()){
file.mkdir();
}
/*
设置上传后的文件路径(包括文件名)
File.separator表示的是文件的分隔符
*/
String filePath = photoPath + File.separator + name;
/*将该文件上传到服务器*/
photo.transferTo(new File(filePath));
modelAndView.setViewName("success");
return modelAndView;
}
十、拦截器
10.1 拦截器的配置
springmvc中的拦截器必须在SpringMVC的配置文件中配置
1、配置全局的拦截器
<!-- 配置拦截器 -->
<mvc:interceptors>
<bean class="ssm.interceptor.HandlerInterceptor1"/>
<!-- 写法二:<ref bean="handlerInterceptor1" /> -->
</mvc:interceptors>
2、通过映射路径配置拦截器
<!-- 配置拦截器 -->
<mvc:interceptors>
<!-- 多个拦截器,按顺序执行 -->
<mvc:interceptor>
<mvc:mapping path="/**"/> <!-- 表示拦截所有的url包括子url路径,/*只表示拦截一层路径 -->
<mvc:exclude-mapping path="/a"/> <!-- 排除掉某个映射路径 -->
<bean class="ssm.interceptor.HandlerInterceptor1"/>
<!-- 写法二:<ref bean="handlerInterceptor1" /> -->
</mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="ssm.interceptor.HandlerInterceptor2"/>
</mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="ssm.interceptor.HandlerInterceptor3"/>
</mvc:interceptor>
</mvc:interceptors>
3、针对具体的HandlerMapping进行配置
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping">
<property name="interceptors">
<list>
<ref bean="handlerInterceptor1"/>
<ref bean="handlerInterceptor2"/>
</list>
</property>
</bean>
<bean id="handlerInterceptor1" class="ssm.intercapter.HandlerInterceptor1"/>
<bean id="handlerInterceptor2" class="ssm.intercapter.HandlerInterceptor2"/>
10.2 拦截器的实现
@Component
public class HelloInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("【HelloInterceptor】preHandler...");
// false表示拦截,不向下执行;true表示放行
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("【HelloInterceptor】postHandle....");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("【HelloInterceptor】afterCompletion..");
}
}
拦截器的三个抽象方法
- preHandle():执行控制器方法之前执行。可以用于身份认证、身份授权。比如如果认证没有通过表示用户没有登陆,需要此方法拦截不再往下执行(return false),否则就放行(return true)。
- postHandle():执行控制器方法之后,返回ModelAndView之前执行。可以看到该方法中有个modelAndView的形参。应用场景:从modelAndView出发:将公用的模型数据(比如菜单导航之类的)在这里传到视图,也可以在这里同一指定视图。
- afterCompletion():处理好ModelAndView数据,渲染完视图之后执行。应用场景:统一异常处理,统一日志处理等。
10.3 多个拦截器的执行顺序
1、情况一:所有拦截器都放行
控制台输出:
HandlerInterceptor1….preHandle
HandlerInterceptor2….preHandle
HandlerInterceptor3….preHandle
HandlerInterceptor3….postHandle
HandlerInterceptor2….postHandle
HandlerInterceptor1….postHandle
HandlerInterceptor3….afterCompletion
HandlerInterceptor2….afterCompletion
HandlerInterceptor1….afterCompletion
总结:
preHandle() 会按照配置文件中的配置顺序从上到下执行,postHandle()和afterCompletion() 则是反序执行
2、情况二:有一个拦截器不放行
控制台输出:
HandlerInterceptor1….preHandle
HandlerInterceptor2….preHandle
HandlerInterceptor3….preHandle
HandlerInterceptor2….afterCompletion
HandlerInterceptor1….afterCompletion
总结:
- 由于拦截器1和2放行,所以拦截器3的preHandle才能执行。也就是说前面的拦截器放行,后面的拦截器才能执行preHandle。
- 拦截器3不放行,所以其另外两个方法没有被执行。即如果某个拦截器不放行,那么它的另外两个方法就不会背执行。
- 只要有一个拦截器不放行,所有拦截器的postHandle方法都不会执行,但是只要执行过preHandle并且放行的,就会执行afterCompletion方法。
10.4 拦截器与过滤器
1、什么是过滤器(Filter)
依赖于servlet容器。在实现上基于函数回调,可以对几乎所有请求进行过滤,但是缺点是一个过滤器实例,只能在容器初始化时调用一次。使用过滤器的目的是用来做一些过滤操作,比如:在过滤器中修改字符编码; 在过滤器中修改HttpServletRequest的一些参数,包括:过滤低俗文字、危险字符等。
2、什么是拦截器(interceptor)
拦截器,在AOP(Aspect-Oriented Programming)中用于在某个方法或字段被访问之前,进行拦截然后在之前或之后加入某些操作。拦截是AOP的一种实现策略。
在 Webwork的中文文档的解释为——拦截器是动态拦截Action调用的对象。它提供了一种机制可以使开发者可以定义在一个action执行的前后执行的代码,也可以在一个action执行前阻止其执行。同时也是提供了一种可以提取action中可重用的部分的方式。
3、拦截器链(多拦截器)
拦截器链的概念:如果多个拦截器能够对相同的请求进行拦截,则多个拦截器会形成一个拦截器链,主要理解拦截器链中各个拦截器的执行顺序。拦截器链中多个拦截器的执行顺序,根拦截器的配置顺序有关,先配置的先执行。
4、区别
过滤器(filter):
1) filter属于Servlet技术,只要是web工程都可以使用
2) filter主要对所有请求过滤
3) filter的执行时机早于Interceptor
拦截器(interceptor)
1) interceptor属于SpringMVC技术,必须要有SpringMVC环境才可以使用
2) interceptor通常对处理器Controller进行拦截
3) interceptor只能拦截dispatcherServlet处理的请求
十一、SpringMVC的异常处理
SpringMVC中拥有一套非常强大的异常处理机制,SpringMVC通过 HandlerExceptionResolver处理程序的异常,包括请求映射,数据绑定以及目标方法的执行时发生的异常。
SpringMVC提供了一个处理控制器方法执行过程中所出现的异常的接口:
HandlerExceptionResolver,该接口的实现类有:DefaultHandlerExceptionResolver和
SimpleMappingExceptionResolver
11.1 基于配置的异常处理
我们基于 Spring MVC 提供的简单异常处理器 SimpleMappingExceptionResolver 来进行配置指定异常处理。
<!--配置异常处理器-->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<!--
key : 表示处理器方法执行过程中出现的异常 value:表示出现异常跳转的视图名称
-->
<prop key="java.lang.ArithmeticException">error</prop>
</props>
</property>
<!--
向请求域中设置异常信息,key就是这里指定的属性名 e,值就是异常信息,页面上就可以通过这里的属性名拿到异常信息
-->
<property name="exceptionAttribute" value="e"/>
</bean>
11.2 基于注解的异常处理
// 将当前类标识为异常处理的组件,是Spring提供的新注解,它是对Controller的增强,可对 controller中被 @RequestMapping注解的方法加一些逻辑处理;
@ControllerAdvice
public class ExceptionController {
// 用于设置所表示的方法处理的异常,@ExceptionHandler加在ControllerAdvice中,处理全局异常
@ExceptionHandler(value = {ArithmeticException.class,NullPointerException.class})
public String exception(Exception e, Model model){
// 向请求域中放异常信息
model.addAttribute("e",e);
// 遇到异常跳转的视图名称
return "error";
}
}
十二、SpringMVC的全注解开发
12.1 创建配置类替换web.xml
/**
* web工程的初始化类,用来替换web.xml
*/
@Configuration
public class WebInit extends AbstractAnnotationConfigDispatcherServletInitializer {
/**
* 指定Spring的配置类
*/
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{
SpringConfig.class
};
}
/**
* 指定springMVC的配置类
*/
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{
SpringMvcConfig.class
};
}
/**
* 指定DispatchServlet的映射规则,即url-pattern
*/
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
/**
* 配置过滤器
*/
@Override
protected Filter[] getServletFilters() {
// 解决post乱码问题
CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
characterEncodingFilter.setEncoding("UTF-8");
characterEncodingFilter.setForceRequestEncoding(true);
// 解决无法使用put和delete请求的问题,处理编码的过滤器一定要在最前面,否则会失效
HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();
return new Filter[]{characterEncodingFilter, hiddenHttpMethodFilter};
}
}
12.2 创建配置类替换SpringMvc的配置文件
/**
* 代替SpringMVC的配置文件
*
*/
@Configuration
@ComponentScan // 开启扫描组建
@EnableWebMvc // mvc注解驱动
public class SpringMvcConfig implements WebMvcConfigurer {
// ----------- 配置视图解析器 --------------
/**
* 配置生成模板解析器
*/
@Bean
public ITemplateResolver templateResolver() {
WebApplicationContext webApplicationContext = ContextLoader.getCurrentWebApplicationContext();
ServletContextTemplateResolver servletContextTemplateResolver = new ServletContextTemplateResolver(webApplicationContext.getServletContext());
servletContextTemplateResolver.setPrefix("/WEB-INF/templates/");
servletContextTemplateResolver.setSuffix(".html");
servletContextTemplateResolver.setTemplateMode(TemplateMode.HTML);
servletContextTemplateResolver.setCharacterEncoding("UTF-8");
return servletContextTemplateResolver;
}
/**
* 生成模板引擎并为模板引擎注入模板解析器
*/
@Bean
public SpringTemplateEngine templateEngine(ITemplateResolver templateResolver) {
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(templateResolver);
return templateEngine;
}
/**
* 生成视图解析器并未解析器注入模板引擎
*/
@Bean
public ViewResolver viewResolver(SpringTemplateEngine templateEngine) {
ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
viewResolver.setCharacterEncoding("UTF-8");
viewResolver.setTemplateEngine(templateEngine);
return viewResolver;
}
// ----------- 开启静态资源访问 --------------
/**
* 设置默认servlet对静态资源处理
* @param configurer
*/
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
// ----------- 添加拦截器 --------------
/**
* 设置拦截器和拦截路径
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
MyInterceptor interceptor = new MyInterceptor();
registry.addInterceptor(interceptor).addPathPatterns("/**");
}
// ----------- 配置视图控制器 --------------
/**
* 配置视图的请求路径和视图名
* @param registry
*/
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
}
// ----------- 配置文件上传解析器 --------------
@Bean
public MultipartResolver multipartResolver(){
CommonsMultipartResolver commonsMultipartResolver = new CommonsMultipartResolver();
return commonsMultipartResolver;
}
// ----------- 指定异常处理 --------------
@Override
public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
SimpleMappingExceptionResolver resolver = new SimpleMappingExceptionResolver();
Properties properties = new Properties();
properties.setProperty("java.lang.ArithmeticException", "error");
resolver.setExceptionMappings(properties);
resolver.setExceptionAttribute("exception");
resolvers.add(resolver);
}
}
十三、SpringMVC的执行流程
13.1 SpringMVC 常用组件
1、DispatcherServlet 前端控制器:统一处理请求和响应,整个流程控制的中心,由它调用其它组件处理用户的请求(框架提供,无需开发)
2、HandlerMapping 处理器映射器:根据请求的url、method等信息查找Handler,即控制器方法(框架提供,无需开发)
3、HandlerAdapter 处理器适配器:通过HandlerAdapter对处理器(控制器方法)进行执行(框架提供,无需开发)
4、Handler 处理器:在DispatcherServlet的控制下Handler对具体的用户请求进行处理(需要开发)
5、ViewResolver 视图解析器:进行视图解析,得到相应的视图(框架提供,无需开发)
6、View 视图:将模型数据通过页面展示给用户(框架提供,无需开发)
13.2 DispatcherServlet初始化过程
1、DispatcherServlet的继承关系图
2、具体的初始化步骤
-
DispatcherServlet 本质上还是一个Servlet,所以初始化的时候还是遵循着Servlet的初始化过程,Servlet在初始化的过程中,容器将调用servlet的init(ServletConfig config) 方法初始化这个对象,DispatcherServlet 的初始化实际上就是调用Servlet 的 init(ServletConfig config) 方法
-
在GenericServlet 中 对 init (ServletConfig config) 方法进行了实现,并在 init (ServletConfig config)方法中调用了 init() 方法(方法重载),而在GenericServlet 中并没有对 init() 方法有具体的代码实现,而在HttpServletBean类中对 init() 方法进行了重写
-
而在HttpServletBean中 调用 init() 方法,其中最主要的是,在 init() 方法中,又调用了initServletBean() 方法,该方法是由 HttpServletBean定义,但是HttpServletBean 并没有对 initServletBean() 方法进行代码实现,而是由它的子类 FrameworkServlet进行重写实现
-
FrameworkServlet 重写并调用 initServletBean(),在initServletBean()方法中进行了创建并初始化 WebApplicationContext(上下文),并刷新(onRefresh(WebApplication wac) 方法),在FrameworkServlet 中没有对 onRefresh(WebApplication wac) 方法进行具体的代码实现,而是由DispatcherServlet 进行重写并调用 onRefresh(WebApplication wac) 方法,在方法中调用了具体的初始化方法 调用了initStrategies(context)方法,初始化策略,即初始化DispatcherServlet的各个组件
-
通过 DispatcherServlet 调用 init(ServletConfig config)方法,到最后调用到 initStrategies(context)方法(初始化策略,是 DispatcherServlet 具体的初始化代码,里面对 DispatcherServlet 各种组件进行了初始化)
-
具体调用过程代码如下
/**
* servlet 接口中的初始化方法
**/
public void init(ServletConfig config) throws ServletException;
/**
* GenericServlet 中对 init (ServletConfig config) 方法进行了实现
**/
public void init(ServletConfig config) throws ServletException {
this.config = config;
// 调用本类中 init() 方法(方法重载),但是没有具体实现
this.init();
}
public void init() throws ServletException {}
/**
* HttpServletBean类中对 GenericServlet 中 init() 方法进行了重写
**/
public final void init() throws ServletException {
PropertyValues pvs = new HttpServletBean.ServletConfigPropertyValues(this.getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(this.getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, this.getEnvironment()));
this.initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
} catch (BeansException var4) {
if (this.logger.isErrorEnabled()) {
this.logger.error("Failed to set bean properties on servlet '" + this.getServletName() + "'", var4);
}
throw var4;
}
}
// 核心方法,这里初始化,
this.initServletBean();
}
// HttpServletBean类中重载的方法,但是没具体实现
protected void initServletBean() throws ServletException {}
/**
* FrameworkServlet 重写了 initServletBean()
**/
@Override
protected final void initServletBean() throws ServletException {
getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");
if (logger.isInfoEnabled()) {
logger.info("Initializing Servlet '" + getServletName() + "'");
}
long startTime = System.currentTimeMillis();
try {
// 核心代码 这里通过调用 initWebApplicationContext() 来初始化并获取 WebApplicationContext对象
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
}
catch (ServletException | RuntimeException ex) {
logger.error("Context initialization failed", ex);
throw ex;
}
if (logger.isDebugEnabled()) {
String value = this.enableLoggingRequestDetails ?
"shown which may lead to unsafe logging of potentially sensitive data" :
"masked to prevent unsafe logging of potentially sensitive data";
logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails +
"': request parameters and headers will be " + value);
}
if (logger.isInfoEnabled()) {
logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");
}
}
// 上面方法中调用了本方法初始化 WebApplicationContext
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
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) {
//调用 刷新的方法,该方法具体是在 DispatcherServlet中实现
onRefresh(wac);
}
}
if (this.publishContext) {
// Publish the context as a servlet context attribute.
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
}
return wac;
}
// 该方法具体是在 DispatcherServlet中实现
protected void onRefresh(ApplicationContext context) {
// For subclasses: do nothing by default.
}
/**
* DispatcherServlet 重写了 onRefresh()
**/
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
// 进行了一系列初始化
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
13.3 SpringMVC的执行流程
1、用户发送请求到前端控制器(DispatcherServlet)。
2、前端控制器 ( DispatcherServlet ) 收到请求调用处理器映射器 (HandlerMapping),去查找处理器(Handler)。
3、处理器映射器(HandlerMapping)找到具体的处理器(可以根据 xml 配置、注解进行查找),生成处理器对象及处理器拦截器(如果有则生成)一并返回给 DispatcherServlet。
4、前端控制器(DispatcherServlet)调用处理器适配器(HandlerAdapter)。
5、处理器适配器(HandlerAdapter)去调用自定义的处理器类(Controller)。
6、自定义的处理器类(Controller)将得到的参数进行处理并返回结果给处理器适配器(HandlerAdapter)。
7、处理器适配器 ( HandlerAdapter )将得到的结果返回给前端控制器 (DispatcherServlet)。
8、前端控制器(DispatcherServlet )将 ModelAndView 传给视图解析器 (ViewReslover)。
9、视图解析器(ViewReslover)将得到的参数从逻辑视图转换为物理视图并返回给前端控制器(DispatcherServlet)。
10、前端控制器(DispatcherServlet)调用物理视图进行渲染并返回。
11、前端控制器(DispatcherServlet)将渲染后的结果最终返回给用户。