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..");
    }
}

拦截器的三个抽象方法

  1. preHandle():执行控制器方法之前执行。可以用于身份认证、身份授权。比如如果认证没有通过表示用户没有登陆,需要此方法拦截不再往下执行(return false),否则就放行(return true)。
  2. postHandle():执行控制器方法之后,返回ModelAndView之前执行。可以看到该方法中有个modelAndView的形参。应用场景:从modelAndView出发:将公用的模型数据(比如菜单导航之类的)在这里传到视图,也可以在这里同一指定视图。
  3. 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. 由于拦截器1和2放行,所以拦截器3的preHandle才能执行。也就是说前面的拦截器放行,后面的拦截器才能执行preHandle。
  2. 拦截器3不放行,所以其另外两个方法没有被执行。即如果某个拦截器不放行,那么它的另外两个方法就不会背执行。
  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、具体的初始化步骤

  1. DispatcherServlet 本质上还是一个Servlet,所以初始化的时候还是遵循着Servlet的初始化过程,Servlet在初始化的过程中,容器将调用servlet的init(ServletConfig config) 方法初始化这个对象,DispatcherServlet 的初始化实际上就是调用Servlet 的 init(ServletConfig config) 方法

  2. 在GenericServlet 中 对 init (ServletConfig config) 方法进行了实现,并在 init (ServletConfig config)方法中调用了 init() 方法(方法重载),而在GenericServlet 中并没有对 init() 方法有具体的代码实现,而在HttpServletBean类中对 init() 方法进行了重写

  3. 而在HttpServletBean中 调用 init() 方法,其中最主要的是,在 init() 方法中,又调用了initServletBean() 方法,该方法是由 HttpServletBean定义,但是HttpServletBean 并没有对 initServletBean() 方法进行代码实现,而是由它的子类 FrameworkServlet进行重写实现

  4. FrameworkServlet 重写并调用 initServletBean(),在initServletBean()方法中进行了创建并初始化 WebApplicationContext(上下文),并刷新(onRefresh(WebApplication wac) 方法),在FrameworkServlet 中没有对 onRefresh(WebApplication wac) 方法进行具体的代码实现,而是由DispatcherServlet 进行重写并调用 onRefresh(WebApplication wac) 方法,在方法中调用了具体的初始化方法 调用了initStrategies(context)方法,初始化策略,即初始化DispatcherServlet的各个组件

  5. 通过 DispatcherServlet 调用 init(ServletConfig config)方法,到最后调用到 initStrategies(context)方法(初始化策略,是 DispatcherServlet 具体的初始化代码,里面对 DispatcherServlet 各种组件进行了初始化)

  6. 具体调用过程代码如下

/**
* 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)将渲染后的结果最终返回给用户。

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值