1.SpringMVC简介

1.总结

视图控制器的处理结果会包装成ModelAndView然后然后交给视图解析器,视图解析器可以是themeleaf视图解析器,也可以时传统的servlet视图解析器,映射到jsp文件。关于视图解析器时在springMVC中配置的,因为视图控制器也是在springmvc中配置的。web配置一个springMV的统一的DispatcherServlet,然后在进一步交给控制器来处理。处理的结果为ModelAndView视图和数据,交给视图解析器来处理,展示相应的文件

1.两种视图解析器的配置

  1. 传统的servlet
        <context:component-scan base-package="com.demo03.control"/>
<!--        配置传统的servlet视图解析器-->
        <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
            <property name="prefix" value="/WEB-INF/templates/"/>
            <property name="suffix" value=".jsp"/>
        </bean>
2.thymeleaf视图解析器
<!--        配置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>

2.控制器的配置

​ 控制器的配置主要方式有两种,一种是注入bean对象,可以进行一定的业务逻辑处理。另一种是视图标签配置(mvc:view-controller),不需要进行业务逻辑的处理。

​ 注入bean方法的返回值有两种形式,一种是返回一个ModelAndView视图,另一种是返回一个String字符窜,实际上是视图的名称,交给系统进一步处理成ModelAndView视图。

​ 此外当使用mvc:view-controller标签配置视图时,需要开启注解驱动,才能进行包扫描。如果没有任何标签配置,默认为注解驱动,只需要开启包扫描就好。方式如下:

<!--    开启注解驱动-->
        <mvc:annotation-driven/>
1. 配置驱动(优先级低于bean配置)
<context:component-scan base-package="com.demo02.control"/>
<mvc:view-controller path="/" view-name="test_view"></mvc:view-controller>
<!--    开启注解驱动-->
        <mvc:annotation-driven/>
2.bean配置
<context:component-scan base-package="com.demo02.control"/>
@Controller
public class UserController {

    @RequestMapping("/")
    public String index(){
        return "index";
    }

}

3.web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <filter>
        <filter-name>CharacterEncodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
        <init-param>
            <param-name>forceResponseEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>CharacterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <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>


    <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>
        <!--
            斜线表示当前发送的所有请求
            但是不包括jsp文件,
            和/*有所区别-->
        <url-pattern>/</url-pattern>
    </servlet-mapping>

</web-app>

2.SpringMVC的特点

  • 配置文件和spring无缝连接
  • 基于原生的Servlet,通过了功能强大的前端控制器DispatchServlet,对请求和响应进行了统一的处理
  • 代码简单,开发效率很大的提高了
  • 内部组件化程度高,可以随意插入组件
  • 性能好

3.细碎知识点

1.script脚本

script脚本不支持折叠。

<script th:src="@{/static/js/vue.js}" type="text/javascript"></script>

html文件是自上而下的执行方式,但引入的css和javascript的顺序有所不同,css引入执行加载时,程序仍然往下执行,而执行到<script>脚本则中断线程,待该script脚本执行结束之后程序才继续往下执行。

如果dom没生成,不会被选中,所以我们可以加个onload指定记载完dom之后再执行代码。

onload=function (){
    //里面放需要执行的代码
}
2.单选框回显

themeleaf${}解析request域中的数据

gender: <input type="radio" name="gender" value="0" th:field="${employee.gender}">male
<input type="radio" name="gender" value="1" th:field="${employee.gender}">female<br>

如果th:field的值和radio的值相同就会自动选中。

二.HelloWord

1.创建空的web的项目,并将打包方式设置为war包

2.添加依赖。

    <dependencies>
<!--        Spring-MVC-->
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.3.10</version>
        </dependency>
<!--        日志-->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.3</version>
        </dependency>

<!--        SpringAPI tomcat自己已经提供了这个jar包,所以我们的范围选成provided,打包的时候不会加入到war包中,由服务器提供-->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <scope>provided</scope>
        </dependency>
<!--spring和thymeleaf的整合-->
        <!--thymeleaf是个视图技术-->
        <dependency>
            <groupId>org.thymeleaf</groupId>
            <artifactId>thymeleaf-spring5</artifactId>
            <version>3.0.12.RELEASE</version>
        </dependency>

    </dependencies>

thymeleaf就是控制前端显示内容的jar包。

3.配置webappweb.xml,web.xml是用来控制web请求的配置文件

srpingmvc实际就是一个统一处理的servlet

  1. 首先配置SpringMVC的前端控制器
<!--    配置SpringMVC的前端控制器,对浏览器发出的请求统一处理-->
    <servlet>
        <servlet-name>SrpingMVc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>SpringMVC</servlet-name>
        <!--       
            斜线表示当前发送的所有请求
            但是不包括jsp文件,
            和/*有所区别-->
        <url-pattern>/</url-pattern>
    </servlet-mapping>

jsp的本质就是个servlet,他需要经过服务器指定特殊的servlet来处理,所以不需要经过springMVC/DispatcherServlet处理。如果被处理就找不到*.jsp页面了一般的jsp语言可以直接写在webapp目录下面,可以直接通过路径访问到,WEB-INF是一个不可以外部访问的资源文件夹,可以内部访问然后展示出来,其他文件夹里面的内容可以通过路径访问,是开放的。

  • <url-pattern>/*会拦截JSP,静态文件,不拦截Servlet,拦截陌生URL

  • <url-pattern>/</url-pattern>拦截静态文件,不拦截JSP和Servlet,拦截陌生URL,静态资源会被拦截
    

​ 默认的配置文件,SpringMVC的配置文件默认应该放到WEB-INF下,默认名称为-servlet.xml,就是springmvc-servlet.xml

  1. 扩展配置方式

可以通过配置初始化参数的方式,指定扩展配置文件的位置

<!--    配置SpringMVC的前端控制器,对浏览器发出的请求统一处理-->
    <servlet>
        <servlet-name>SrpingMVc</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>

4.创建请求控制器

  • 通过普通的java类来承担,需要加上注解@Controller

5.配置视图解析器

<!--        配置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>

同时,对应的需要在html中添加对应的标签

<html lang="en" xmlns:th="http://www.thymeleaf.org">
    
</html>

6.其他配置

​ 配置tomcat,部署war包,正常来说,页面中的请求分为有绝对路径,可能需要加上上下文,用th的标签可以帮我们自动加上固定的上下文路径。

    <a th:href="@{/target}">通过thymeleaf导航</a>

image

3.@RequestMapping

  • 如果RequestMapping注解添加到方法上,指的是请求的最终地址

  • 如果RequestMapping注解添加到controller类上,则是请求地址的公共前缀

    经常用于表示当前不同的模块

  • RequesMapping不用管默认上下文的接收

1.不同的属性

  • value string[]是个数组,可能接收处理多个地址404资源找不到
value={"",""};

- **method属性**:请求方式,**数组**   **报错报400**

  ```java
  RequestMethod.POST
RequestMethod.GET
RequestMethod.HEAD
RequestMethod.PUT

同时存在派生注解

  • GetMapping
  • PostMapping
知识点1 form表单的请求方式

form表单只有两种请求方式:postget,如果不是则这两种请求方式,则默认是get方式

  • params 根据请求参数去匹配请求 字符窜数组 报错405 ,@RequestMapping的参数
    • params = {"username"} 必须携带username参数,也可以有其他的参数
    • params={"!username"} 不能携带username参数
    • params={"username=a"} 必须携带参数并且参数值固定
    • params={"username!=a"} 必须携带参数并且参数值不为
知识点2 thymeleaf路径传参
<a href="/test/testParams?username=123">testParams测试</a>
<a th:href="@{/test/testParams(username='123',password=123)}">thymeleaf新的get传递参数的方法</a>
  • headers属性(了解),头信息也是键值对,和param的用法一模一样,有四种方式

2.请求路径中的占位符

  • ?相当于任意的字符,处理特殊符号?和/
  • *表示0个或者多个 相似的字符 字符的内容任意
  • **任意的一层或者多层目录 /**/代表任意层任意目录,没有也可以
@RequestMapping("/tes?/zhanweifu")
public String success{
    return "success";
}

3.springmvc支持的路径中的占位符

路径上的参数/{}/{},来指定路径参数对应的id,然后通过@PathVariable注解来获取对应的参数

image

@RequestMapping("/testRest/{id}/{username}")
public String testRest(@PathVariable("id")String id, @PathVariable("username")String username){
    System.out.println("id:"+id+",username:"+username);
    return "success";
}

四、SpringMVC获取请求参数

1.通过serletAPI获取

request.getParameter();

通过HttpServletRequest对象获取

因为响应方法是框架调用的,他会根据我们的形参传入响应的参数。

所以我们可以直接获取HttpServletRequest参数

没必要用原生的Servlet获取请求参数.

    @RequestMapping("/test")
    public String testParams(HttpServletRequest request) {
        String username = request.getParameter("next");
        return username;
    }

2.通过控制器方法的形参获取参数

方法getParameter(),getParameterValues()

  • 只要形参名字请求方式一致就自动注入实参

  • 如果多个同名的请求参数,在原生的中,使用getParameterValues获取请求参数数组,

    • 控制器方法如果参数是一个字符窜,则将请求参数的内容通过逗号拼接成一个字符窜
    • 如果形参是个数组,则赋值就是个数组
  • 如果形参名和需要被注入的实参不同,使用注解@RequestParam(value="")

    • value(name)实参名
    • require是否必需注入,自动装配装配不了就是null,但如果开启必须注入没有则报错
    • defaultValue:默认值,当没有对应的实参,或者实参为空

3.@RequestHeader

image

请求头信息和参数进行映射。

4.@CookieValue

5.通过POJO获取请求参数

可以写个java类接收一些列参数,会自动封装到对应的属性上

本质就是,控制器去分析参数,然后根据现有的参数往里面注入实参。

6.解决实参的字符出现乱码问题

以前的解决方法是用request.setCharacterEncoding("utf-8");将原有的编码方式转变成新的编码方式,一般使用baseServle处理。

乱码按请求方式分为。

需要在request第一次被读取数据之前写成转陈utf-8编码

  • get,是由tomcat造成的,所以设置tomcat的配置文件即可。

    image

  • post 只有post会获取到乱码

小知识 三大组件的声明周期
  • 监听器
  • 过滤器
  • servlet
  • 我们要在servlet获取到参数前设置编码数据的编码方式,所以创建一个过滤器实现重置数据的编码该过滤器要求在此之前没有获取过任何请求参数,否则失效,与后文为了使用putdelete新增的过滤器形成矛盾,因为新增过滤器会获取请求参数,所以需要先配置下述过滤器。
<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>

image-20211029131109551

五、域对象中共享数据

如果我们要数据在视图中显示,就需要在域对象中共享。

域对象:

  • request:一次请求,一次request
  • application(servletContext),只与服务器有关,整个服务器开启的时候只会运行一次。可以共享数据
  • session:浏览器开始,浏览器关闭。session中的数据会自动序列话写入到;cookie一致,session一致。

一般常用session和request

1.使用servletAPI向request域对象共享数据

request.setAtrribute("username","此电脑");

themeleaf中只有标签里面的属性可以被th解析,前提是属性前面要加上th:的标签。然后用${域对象中的变量名}。获取到request中对应的变量

2.使用ModelAndView向request域对象中共享数据

springmvc为我们提供了一个对象 ModelAndView,所有的请求都会被封装成ModelAndVIew对象,即使只返回一个字符窜,也会被封装成一个ModelAndView对象

@RequestMapping("/testModelAndView")
public ModelAndView testModelAndView(){
    ModelAndView mav = new ModelAndView();
    mav.addObject("testScope","hello,ModelAndView");
    mav.setViewName("success");
    return mav;
}

3.通过Model对象向Request域对象中共享数据

@RequestMapping("/testModel")
public String testModel(Model model){
	model.addAttribute("testScope","hello,model~");
    return "success";
}

4.通过Map对象向Request域对象中共享数据

@RequestMapping("/testMap")
public String testMap(Map<String,Object> map){
    map.put("testScope","hello,map~");
    return "success";
}

5.通过ModelMap对象向Request域对象中共享数据

@RequestMapping("/testModelMap")
public String testModelMap(ModelMap map){
    map.addAttribute("testScope","hello,Modelmap~");
    return "success";
}

6.Model,ModelMap和Map之间的关系

springmvc向形参中在注入实参的是同一个,对于这三个形参传入实参为BindingAwareModelMap

![image](https://img2020.cnblogs.com/blog/1981378/202111/1981378-20211124205517894-1697621752.png)

BindingAwareModelMap实现了Model的接口和ModelMap的类,其中ModelMap类继承了map接口

image

7.向session域共享数据

    @RequestMapping("/testSession")
    public String testSession(HttpSession session){
        session.setAttribute("testSessionScope","hello,session~");
        return "success";
    }

通过session.键的形式获取到共享的数据。

image

8.向application域共享数据

通过application.xxx获取数据

<p th:text="${application.testSessionScope}"></p>
@RequestMapping("/testApplication")
public String testApplication(HttpSession session){
    session.setAttribute("testSessionScope","hello,session~");
    ServletContext context = session.getServletContext();
    context.setAttribute("testSessionScope","hello,application~");
    return "success";
}

六、springmvc视图

Springmvc中的视图接口是View接口,将模型Model的数据展示给客户。

springmvc的视图的种类有很多,默认有转发视图(forward:)和重定向视图(redirect:)

  • jstl,是个依赖,转发视图自动转换成jstlView

  • 如果使用的视图技术为themeleaf,在springmvc配置文件中配置了Thymeleaf视图。

  • 没有前缀就是themeleaf视图

1.themeleaf视图

就是没有前缀的themeleaf视图

2.转发视图forword

@RequestMapping("/testForward")
public String testForward(){
    return "forward:/testView";
}

ha.handle执行的是控制器方法。转发发生在服务器内部,可以获取到request里面的内容,但是不能跨域访问外部资源。

3.重定向视图

SpringMVC中默认的是重定向视图是RedirectView。

@RequestMapping("/testRedirect")
public String testRedirect(){
    return "redirect:/testView";
}

其中,Redirect不可以获取request的对象,是一个全新的请求,可以跨域。

4.视图控制器view-controller

当不需要进行任何业务逻辑的处理的时候,我们可以直接用请求名作为视图名称。可以直接在springmvc配置文件中配置view-controller

<mvc:view-controller path="/" view-name="test_view"></mvc:view-controller>

如果使用了标签的view-controller,所有的controller控制器全部失效

开启注解驱动可以解决这个问题。themeleaf处理的优先级更高

<mvc:annotation-driven/>

view-controller的控制级别小于themeleaf和controller的组合相同路径由controller处理

5.jsp页面

​ 如果想通过视图控制器访问jsp资源,则需要配置另外一个视图解析器现在的themeleaf配置只能解析html页面

        <context:component-scan base-package="com.demo03.control"/>
<!--        配置传统的servlet视图解析器-->
        <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
            <property name="prefix" value="/WEB-INF/templates/"/>
            <property name="suffix" value=".jsp"/>
        </bean>

tomcat默认的首页为webapp下的index.**页面,我们也可以在web.xml中自己配置欢迎页面。

​ 有些路径具有一定的路径前缀,例如/xxx_war/success前缀可能会随时变化,可以通过EL表达式写一个动态的路径前缀

<a href="${pageContext.request.contextPath}/success">Success.jsp</a>

${pageContext.request.contextPath}用于解决使用相对路径时出现的问题,它的作用是取出所部署项目的名字。

七、RESTFUL

1.简介

![image](https://img2020.cnblogs.com/blog/1981378/202111/1981378-20211124205725640-1773071380.png)

​ REST:全称表现层资源状态转移

操作资源的方式四种:POST,DELETE,GET,PUT增删查改

  • 其中,POST只能通过表单形式发送,或者ajax

  • get就是form和a

delet和put通过过滤器实现请求。使用到的过滤器是HiddenHttpMethodFilter,它默认的提交方式是post,然后将post请求按照参数_method的值转换请求方式,变成delete和put。

<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>
  • delete请求:如果面对超链接的情况,我们只要阻止超链接的正常行为,新加入响应事件,通过ajax或者隐藏表单实现请求的封装和转发
  • put请求:

REST风格就是不在用传递参数,而是用斜线把所有的参数写在请求路径中

![image](https://img2020.cnblogs.com/blog/1981378/202111/1981378-20211124205749294-1928946613.png)

2.准备工作

  1. bean类
@Data
public class Employee {
    private Integer id;
    private String lastName;

    private String email;
    //1 male, 0 female
    private Integer gender;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public Integer getGender() {
        return gender;
    }

    public void setGender(Integer gender) {
        this.gender = gender;
    }

    public Employee(Integer id, String lastName, String email, Integer gender) {
        super();
        this.id = id;
        this.lastName = lastName;
        this.email = email;
        this.gender = gender;
    }

    public Employee() {
    }
}
  1. 仓库
@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(@NotNull 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){
        return employees.get(id);
    }

    public void delete(Integer id){
        employees.remove(id);
    }
}

2.功能清单

![image](https://img2020.cnblogs.com/blog/1981378/202111/1981378-20211124205808282-496762719.png)

1.展现所有员工

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Employee Info</title>
</head>
<body>

<table id="table" border="1" cellspacing="2px" cellpadding="2px" style="text-align: center;vertical-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(<a th:href="@{'/toAdd'}">Add</a>)</th>
    </tr>
    <tr th:each="eachOne : ${employees}">
        <td th:text="${eachOne.id}"></td>
        <td th:text="${eachOne.lastName}"></td>
        <td th:text="${eachOne.email}"></td>
        <td th:text="${eachOne.gender}"></td>
        <td>
            <a @click="deleteMethod" th:href="@{'/employee/'+${eachOne.id}}">delete</a>
            <a th:href="@{'/employee/'+${eachOne.id}}">update</a>
        </td>
    </tr>
</table>
    <form method="post" id="hiddenForm">
        <input type="hidden" name="_method" value="delete">
    </form>
<script th:src="@{/static/js/vue.js}" type="text/javascript"></script>
<script type="text/javascript">

    var vue  = new Vue({
        el: '#table',
        methods: {
            deleteMethod: function (event){
                var form1 = document.getElementById('hiddenForm');
                form1.action = event.target.href;
                form1.submit();
                event.preventDefault();
            }
        }
    });
</script>
</body>
</html>

通过th:text:注入内容
​ 需要通过a标签控制form表单,需要通过js实现,本案例中,使用了vue组件,绑定table,然后给a添加事件函数,同时取消原本的执行方式。

更新信息

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>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" name="gender" value="0" th:field="${employee.gender}">male
        <input type="radio" name="gender" value="1" th:field="${employee.gender}">female<br>
        <input type="submit" value="update"><br>
    </form>
</body>
</html>

静态资源访问

<mvc:default-servlet-handler/>

该标签可以使服务器现有springmvc处理,然后找不到,交给默认的servlet处理,这样就可以找到静态资源了。如果还找不到,也不会是springmvc报错。

因为tomcat默认的servlet被dispatcherServlet覆盖了,所以我们需要进行特殊声明,同时一旦使用到mvc标签。我们就要开启mvc的注解驱动,否则默认是关闭的。

八、HttpMessageConverter

全称报文信息转换器,可以将请求报文转换成java对象,或者将java对象转换成响应报文。

HttpMessageConverter提供了两个注解和两个类型:@RequestBody,@ResponseBodyRequestEntity,ResponseEntity

1.RequestBody

作用:获得请求体

@PostMapping("/testRequestBody")
public String testRequestBody(@RequestBody String requestBody){
    System.out.println("requestBody:"+requestBody);
    return "success";
}

requestBody:username=cjmcjm&password=111111

2.获取请求实体

@PostMapping(value ="/testRequestEntity")
public String testRequestEntity(RequestEntity<String> requestEntity){
    System.out.println("请求头:"+requestEntity.getHeaders());
    System.out.println("请求体:"+requestEntity.getBody());
    return "success";
}

作用:将请求报文分装成java对象

输出:

请求头:[host:"localhost:8080", user-agent:"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:93.0) Gecko/20100101 Firefox/93.0", accept:"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,/;q=0.8", accept-language:"zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2", accept-encoding:"gzip, deflate", content-length:"31", origin:"http://localhost:8080", connection:"keep-alive", referer:"http://localhost:8080/", cookie:"Idea-9283b9b8=75e03715-35ff-4285-a180-77ea3120546f", upgrade-insecure-requests:"1", sec-fetch-dest:"document", sec-fetch-mode:"navigate", sec-fetch-site:"same-origin", sec-fetch-user:"?1", Content-Type:"application/x-www-form-urlencoded;charset=UTF-8"]
请求体:username=cjmcjm&password=111111

3.@ResponseBody

返回值直接写回到浏览器上,以前是通过response写出的。实际上响应体是什么我们就只能看到什么,一般而言响应体是页面,所以我们能看到页面,如果响应体是其他信息那我们就只能看到其他信息。ajax就是这样的情况。

@GetMapping("/testResponse")
public void testResponse(HttpServletResponse response) throws IOException {
    response.getWriter().println("<h1>Hello,response<h1>");
}

不需要视图解析,就不用返回任何值。

通过注解实现:

@GetMapping("/testResponse1")
@ResponseBody
public String testResponse1(){
    return "<h1>Hello,response<h1>";
}

4.springmvc来处理json

直接写回一个对象

  • 浏览器没办法解析对象,但是可以统一成json字符窜需要导入json相关的依赖包
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.12.1</version>
</dependency>
  • 引入依赖之后,需要开启mvc的注解驱动依赖的jar包中自动注解了一个消息转换器MappingJackson2HttpMessageConvert,所以回写的对象会自动转换成json对象。换成其他包不一定可以。****

<mvc:annotatio-driven/>(三个用处1.view-controller,2.default-servlet-handler,3.json包引入)

  • 然后直接写回浏览器对象,会自动转换成json字符窜

5.springmvc处理axios请求

本质就是在springmvc中使用axios

@PostMapping("/testAxios")
@ResponseBody
public String testAxios(User user){
    return "hello,"+user.getUsername();
}
<script th:src="@{/static/js/vue.js}" type="text/javascript"></script>
<script th:src="@{/static/js/axios.min.js}" type="text/javascript"></script>
<script type="text/javascript">
    new Vue({
        el:"#app",
        methods:{
            testAxios:function (event) {
                axios({
                    method:"post",
                    url:event.target.href,
                    params:{
                        username:"admin",
                        password:"123456"
                    }
                }).then(function (response) {
                    alert(response.data);
                });
                event.preventDefault();
            }
        }
    });
</script>

6.RestController

=@ResponseBody+@Controller

7.ResponseEntity

用于控制器的返回值,该控制器方法返回值就是响应到浏览器的响应报文对应的java对象。需要自己封装响应头响应体,状态码。

8.文件下载

代码:自己写个响应报文,需要注意的是响应头的内容不能改

  • 通过servlet获取服务器对象,从而找到某个文件url的真实路径
  • 通过fileInputStream读出文件
  • 通过封装报文返回数据
    @GetMapping("/testDown")
    public ResponseEntity<byte[]> downloadPic(HttpSession session) throws IOException {
        ServletContext servletContext = session.getServletContext();
//        获取到请求在服务器中对应的真实地址
        String realPath = servletContext.getRealPath("/static/img/1.jpg");
//        创建输入流
        InputStream is = new FileInputStream(new File(realPath));
//        创建文件等长的字节数组
        byte[] bytes = new byte[is.available()];
        is.read(bytes);
//        设置响应头
        MultiValueMap<String, String> headers = new HttpHeaders();
        headers.add("Content-Disposition", "attachment;filename=1.jpg");
//        设置状态码
        HttpStatus ok = HttpStatus.OK;
//        创建响应体报文
        ResponseEntity<byte[]> response = new ResponseEntity<>(bytes,headers,ok);
        is.close();
        return response;
    }

9.文件上传

<form th:action="@{/testUp}" method="post" enctype="multipart/form-data">
    头像:<input type="file" name="photo"><br>
    <input type="submit" value="上传">
</form>
  1. 为了处理二进制的文件,我们需要添加依赖
<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.3.1</version>
</dependency>

SpringMVC中将上传的文件封装到MultipartFile对象中,通过此对象可以获取文件相关的信息

  1. 在SpringMVC的配置文件中添加配置。它由框架执行,将上传的二进制文件分装成MultipartFile对象。同时我们预测springmvc是通过bean的id使用bean的。
<!--配置文件上传解析器,将上传的文件封装为MultipartFile-->

<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"></bean>
  1. 传输的对象需要放到服务器上,需要先获取到服务器上的路径,然后判断路径文件夹是否存在,最后在通过tranferto转换成对应的文件。
@PostMapping("/testUp")
public String testUp(MultipartFile photo,HttpSession session) throws IOException {
    String filename = photo.getOriginalFilename();
    String photo1 = session.getServletContext().getRealPath("photo");
    File file = new File(photo1);
    if(!file.exists()){
        file.mkdir();
    }
    filename = photo1+"/"+filename;
    photo.transferTo(new File(filename));
    return "success";
}
  1. 为了避免文件重复通常的做法是将uuid作为文件名。
String filename = photo.getOriginalFilename();
String rand1 = UUID.randomUUID().toString();
filename  = rand1 + filename.substring(filename.lastIndexOf("."));

九、拦截器

![image](https://img2020.cnblogs.com/blog/1981378/202111/1981378-20211124211541097-895915088.png)

执行顺序: filter-->DispatcherServlet-->controller

  • 拦截器就是在controller前后执行的,在之前使用preHandle方法

  • 之后执行PostHandler方法(具体来时就是controller创建ModelAndView前后)

  • 所有结束,渲染视图完毕之后。

1.自定义拦截器

​ 自定义拦截器的类需要继承专门的接口HandlerInterceptor接口

public class MyIntercept implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandler");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandler");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("afterHandler");
    }
}

2.在配置文件中配置对应的控制器

1.直接植入一个bean作为拦截器

特点是对所有的有控制器方法的请求进行拦截

<mvc:interceptors>
    <bean class="com.atguigu.mvc.Intercept.MyIntercept"></bean>
</mvc:interceptors>

image

2.ref和bean的作用相同
<bean id="intercept" class="com.atguigu.mvc.Intercept.MyIntercept"></bean>
<mvc:interceptors>
    <ref bean="intercept"></ref>
</mvc:interceptors>
3.自定义拦截规则的拦截器
  • 拦截其中路径有自己的含义,/**拦截所有请求
  • /*拦截一层目录的请求
  • /拦截主页面的请求

特点: 只有有控制器的方法才会被对应的拦截器

<!--    配置拦截器-->
    <mvc:interceptors>
<!--        <bean class="com.atguigu.mvc.Intercept.MyIntercept"></bean>-->
<!--        <ref bean="intercept"></ref>-->
<!--        自定义拦截方式-->
        <mvc:interceptor>
            <mvc:mapping path="/**"/>
            <mvc:exclude-mapping path="/"/>
            <ref bean="intercept"></ref>
        </mvc:interceptor>
    </mvc:interceptors>
<!--    配置拦截器-->
    <mvc:interceptors>
<!--        <bean class="com.atguigu.mvc.Intercept.MyIntercept"></bean>-->
<!--        <ref bean="intercept"></ref>-->
<!--        自定义拦截方式-->
        <mvc:interceptor>
            <mvc:mapping path="/**"/>
            <mvc:exclude-mapping path="/"/>
            <ref bean="intercept"></ref>
        </mvc:interceptor>
    </mvc:interceptors>

2.多个拦截器时执行顺序

特点:与我们在拦截器中的配置顺序一致。

如下配置两个拦截器:

<!--    配置拦截器-->
    <mvc:interceptors>
<!--        <bean class="com.atguigu.mvc.Intercept.MyIntercept"></bean>-->
        <ref bean="intercept"></ref>
<!--        自定义拦截方式-->
        <mvc:interceptor>
            <mvc:mapping path="/**"/>
            <mvc:exclude-mapping path="/"/>
            <ref bean="myIntercept"></ref>
        </mvc:interceptor>
    </mvc:interceptors>

image-20211104135158826

拦截器包裹在controller的外面,最先装配的包裹在最外层,一个拦截器可以看成包裹了两样东西,controller方法(postHandle)controller+视图解析(afterCompletion)

image-20211104140009035

源码
![image](https://img2020.cnblogs.com/blog/1981378/202111/1981378-20211124211610568-1539232483.png)

mapperHandler:执行链,包含了两部分数据,当前的控制器方法和拦截器。

image

倒序执行

![image](https://img2020.cnblogs.com/blog/1981378/202111/1981378-20211124211628521-946316924.png)

afterCompition和postHandler处理的方法类似。

3.如果有拦截器返回false

  1. preHandler方法停止执行
  2. 调用已经执行了postHandler方法拦截器的AfterCompition方法
  3. 程序直接结束,不进行视图处理。

十一、异常处理器

1.基于配置的异常处理

SpringMVC提供了处理控制器方法执行过程中所出现的异常的接口HandlerExceptionResoler,控制器方法在执行过程中发生异常则会被处理。

HandlerExceptionResoler接口的实现类有DefaultHandlerExceptionResolverSimpleMappingExceptionResolver

SpringMVC自定义的异常处理器为SimpleMappingExceptionResolver,使用方式:

<!--   配置控制器异常处理器-->
    <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver" id="exceptionResolver">
        <property name="exceptionMappings">
            <!--
                pros的键表示输入执行过程发生的异常
                值是发生异常时跳转的视图页面
            -->
            <props>
                <prop key="java.lang.ArithmeticException">error</prop>
            </props>
        </property>
<!--        exceptionAttribute属性设置一个属性名,来接收异常信息在请求域进行共享-->
        <property name="exceptionAttribute" value="ex"></property>
    </bean>

2.基于注解的异常处理@ControllerAdvice

这样标注的类可以专门用来处理controller发出的异常,

然后就是@ExceptionHander来指定每个方法要处理的异常类。

@ControllerAdvice
public class MyException {

    @ExceptionHandler(value = {ArithmeticException.class, IOException.class})
    public String test01(Exception exception, Model model){
        model.addAttribute("ex",exception);
        System.out.println(exception);
        return "error";//异常时的错误页面的视图
    }
}

十二、全注解配置

如何取代springmvc的配置文件。

1.创建初始化类,代替web.xml

image

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
     * @return
     */
    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }

    /**
     * 注册过滤器
     */
    @Override
    protected Filter[] getServletFilters() {
        CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
        characterEncodingFilter.setEncoding("UTF-8");
        characterEncodingFilter.setForceResponseEncoding(true);
        HiddenHttpMethodFilter hiddenHttpMethodFilter  = new HiddenHttpMethodFilter();
        return new Filter[]{characterEncodingFilter, hiddenHttpMethodFilter};
    }
}

2.生成springmvc的配置文件

1.thyleaf视图解析器的配置
    //配置生成模板解引擎
    @Bean
    public ITemplateResolver templateResolver(){
        WebApplicationContext webApplicationContext = ContextLoader.getCurrentWebApplicationContext();
        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 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;
    }



    <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>


2.WebMvcConfigurer

对于不是bean配置我们需要继承一个接口WebMvcConfigurer

3.最终的配置文件
package com.hjw.config;

import com.hjw.intercpter.MyInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.ContextLoader;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.multipart.MultipartResolver;
import org.springframework.web.multipart.commons.CommonsMultipartResolver;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.*;
import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;
import org.thymeleaf.spring5.SpringTemplateEngine;
import org.thymeleaf.spring5.view.ThymeleafViewResolver;
import org.thymeleaf.templatemode.TemplateMode;
import org.thymeleaf.templateresolver.ITemplateResolver;
import org.thymeleaf.templateresolver.ServletContextTemplateResolver;

import java.util.List;
import java.util.Properties;

/**
 * @Author coder
 * @Date 2021/11/4 19:53
 * @Description
 */
//用来代替springmvc的配置文件

/**
 * 1. 扫描包
 * 2. 视图解析器
 * 3.view-controller
 * 4.default-servlet-handler
 * 5.mvc注解驱动
 * 6.文件上传解析器
 * 7.异常处理
 * 8.拦截器
 */
@Configuration
//开启mvc注解驱动
@EnableWebMvc
@ComponentScan(value = {"com.hjw"})
public class WebConfig implements WebMvcConfigurer {

    //配置生成模板解引擎
    @Bean
    public ITemplateResolver templateResolver(){
        WebApplicationContext webApplicationContext = ContextLoader.getCurrentWebApplicationContext();
        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 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.setOrder(1);
        viewResolver.setTemplateEngine(templateEngine);
        return viewResolver;
    }

//    开启默认的servlet处理器default-servlet-hander
    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }

//   创建view-controller
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/hello").setViewName("hello");
        registry.addViewController("/").setViewName("index");
    }

//    配置视图拦截器
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**").excludePathPatterns("/","/hello");
    }

//    配置文件上传解析器
    @Bean
    public MultipartResolver multipartResolver(){
        return new CommonsMultipartResolver();
    }

//    配置异常处理


    @Override
    public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
        SimpleMappingExceptionResolver exceptionResolver = new SimpleMappingExceptionResolver();
        Properties properties = new Properties();
//        可以存放多个
        properties.setProperty("java.lang.ArithmeticException","error");
        exceptionResolver.setExceptionMappings(properties);
        exceptionResolver.setExceptionAttribute("ex");
        resolvers.add(exceptionResolver);
    }
}

十三、SpringMVC的执行流程

1.SpringMVC常用组件

  • Handler:处理器或者控制器
  • HandlerMapping:是找控制器的
  • HandlerAdapter: 是调用控制器,封装成ModelAndView
  • viewReSolver:对视图解析,得到相应的视图

image-20211104212559202

2.DispatcherServlet初始化

DispatcherServlet 本质上是一个 Servlet,所以天然的遵循 Servlet 的生命周期。所以宏观上是 Servlet 生命周期来进行调度。

images

a>初始化WebApplicationContext

所在类:org.springframework.web.servlet.FrameworkServlet

protected WebApplicationContext initWebApplicationContext() {
    WebApplicationContext rootContext =
        WebApplicationContextUtils.getWebApplicationContext(getServletContext());
    WebApplicationContext wac = null;

    if (this.webApplicationContext != null) {
        // A context instance was injected at construction time -> use it
        wac = this.webApplicationContext;
        if (wac instanceof ConfigurableWebApplicationContext) {
            ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
            if (!cwac.isActive()) {
                // The context has not yet been refreshed -> provide services such as
                // setting the parent context, setting the application context id, etc
                if (cwac.getParent() == null) {
                    // The context instance was injected without an explicit parent -> set
                    // the root application context (if any; may be null) as the parent
                    cwac.setParent(rootContext);
                }
                configureAndRefreshWebApplicationContext(cwac);
            }
        }
    }
    if (wac == null) {
        // No context instance was injected at construction time -> see if one
        // has been registered in the servlet context. If one exists, it is assumed
        // that the parent context (if any) has already been set and that the
        // user has performed any initialization such as setting the context id
        wac = findWebApplicationContext();
    }
    if (wac == null) {
        // No context instance is defined for this servlet -> create a local one
        // 创建WebApplicationContext
        wac = createWebApplicationContext(rootContext);
    }

    if (!this.refreshEventReceived) {
        // Either the context is not a ConfigurableApplicationContext with refresh
        // support or the context injected at construction time had already been
        // refreshed -> trigger initial onRefresh manually here.
        synchronized (this.onRefreshMonitor) {
            // 刷新WebApplicationContext
            onRefresh(wac);
        }
    }

    if (this.publishContext) {
        // Publish the context as a servlet context attribute.
        // 将IOC容器在应用域共享
        String attrName = getServletContextAttributeName();
        getServletContext().setAttribute(attrName, wac);
    }

    return wac;
}
b>创建WebApplicationContext

所在类:org.springframework.web.servlet.FrameworkServlet

protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
    Class<?> contextClass = getContextClass();
    if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
        throw new ApplicationContextException(
            "Fatal initialization error in servlet with name '" + getServletName() +
            "': custom WebApplicationContext class [" + contextClass.getName() +
            "] is not of type ConfigurableWebApplicationContext");
    }
    // 通过反射创建 IOC 容器对象
    ConfigurableWebApplicationContext wac =
        (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

    wac.setEnvironment(getEnvironment());
    // 设置父容器
    wac.setParent(parent);
    String configLocation = getContextConfigLocation();
    if (configLocation != null) {
        wac.setConfigLocation(configLocation);
    }
    configureAndRefreshWebApplicationContext(wac);

    return wac;
}
c>DispatcherServlet初始化策略

FrameworkServlet创建WebApplicationContext后,刷新容器,调用onRefresh(wac),此方法在DispatcherServlet中进行了重写,调用了initStrategies(context)方法,初始化策略,即初始化DispatcherServlet的各个组件

所在类:org.springframework.web.servlet.DispatcherServlet

protected void initStrategies(ApplicationContext context) {
   initMultipartResolver(context);
   initLocaleResolver(context);
   initThemeResolver(context);
   initHandlerMappings(context);
   initHandlerAdapters(context);
   initHandlerExceptionResolvers(context);
   initRequestToViewNameTranslator(context);
   initViewResolvers(context);
   initFlashMapManager(context);
}

3、DispatcherServlet调用组件处理请求

a>processRequest()

FrameworkServlet重写HttpServlet中的service()和doXxx(),这些方法中调用了processRequest(request, response)

所在类:org.springframework.web.servlet.FrameworkServlet

protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {

    long startTime = System.currentTimeMillis();
    Throwable failureCause = null;

    LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
    LocaleContext localeContext = buildLocaleContext(request);

    RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
    ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);

    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());

    initContextHolders(request, localeContext, requestAttributes);

    try {
		// 执行服务,doService()是一个抽象方法,在DispatcherServlet中进行了重写
        doService(request, response);
    }
    catch (ServletException | IOException ex) {
        failureCause = ex;
        throw ex;
    }
    catch (Throwable ex) {
        failureCause = ex;
        throw new NestedServletException("Request processing failed", ex);
    }

    finally {
        resetContextHolders(request, previousLocaleContext, previousAttributes);
        if (requestAttributes != null) {
            requestAttributes.requestCompleted();
        }
        logResult(request, response, failureCause, asyncManager);
        publishRequestHandledEvent(request, response, startTime, failureCause);
    }
}
b>doService()

所在类:org.springframework.web.servlet.DispatcherServlet

@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
    logRequest(request);

    // Keep a snapshot of the request attributes in case of an include,
    // to be able to restore the original attributes after the include.
    Map<String, Object> attributesSnapshot = null;
    if (WebUtils.isIncludeRequest(request)) {
        attributesSnapshot = new HashMap<>();
        Enumeration<?> attrNames = request.getAttributeNames();
        while (attrNames.hasMoreElements()) {
            String attrName = (String) attrNames.nextElement();
            if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
                attributesSnapshot.put(attrName, request.getAttribute(attrName));
            }
        }
    }

    // Make framework objects available to handlers and view objects.
    request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
    request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
    request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
    request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());

    if (this.flashMapManager != null) {
        FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
        if (inputFlashMap != null) {
            request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
        }
        request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
        request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
    }

    RequestPath requestPath = null;
    if (this.parseRequestPath && !ServletRequestPathUtils.hasParsedRequestPath(request)) {
        requestPath = ServletRequestPathUtils.parseAndCache(request);
    }

    try {
        // 处理请求和响应
        doDispatch(request, response);
    }
    finally {
        if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
            // Restore the original attribute snapshot, in case of an include.
            if (attributesSnapshot != null) {
                restoreAttributesAfterInclude(request, attributesSnapshot);
            }
        }
        if (requestPath != null) {
            ServletRequestPathUtils.clearParsedRequestPath(request);
        }
    }
}
c>doDispatch()

所在类:org.springframework.web.servlet.DispatcherServlet

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    HttpServletRequest processedRequest = request;
    HandlerExecutionChain mappedHandler = null;
    boolean multipartRequestParsed = false;

    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

    try {
        ModelAndView mv = null;
        Exception dispatchException = null;

        try {
            processedRequest = checkMultipart(request);
            multipartRequestParsed = (processedRequest != request);

            // Determine handler for the current request.
            /*
            	mappedHandler:调用链
                包含handler、interceptorList、interceptorIndex
            	handler:浏览器发送的请求所匹配的控制器方法
            	interceptorList:处理控制器方法的所有拦截器集合
            	interceptorIndex:拦截器索引,控制拦截器afterCompletion()的执行
            */
            mappedHandler = getHandler(processedRequest);
            if (mappedHandler == null) {
                noHandlerFound(processedRequest, response);
                return;
            }

            // Determine handler adapter for the current request.
           	// 通过控制器方法创建相应的处理器适配器,调用所对应的控制器方法
            HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

            // Process last-modified header, if supported by the handler.
            String method = request.getMethod();
            boolean isGet = "GET".equals(method);
            if (isGet || "HEAD".equals(method)) {
                long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                    return;
                }
            }
			
            // 调用拦截器的preHandle()
            if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                return;
            }

            // Actually invoke the handler.
            // 由处理器适配器调用具体的控制器方法,最终获得ModelAndView对象
            mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

            if (asyncManager.isConcurrentHandlingStarted()) {
                return;
            }

            applyDefaultViewName(processedRequest, mv);
            // 调用拦截器的postHandle()
            mappedHandler.applyPostHandle(processedRequest, response, mv);
        }
        catch (Exception ex) {
            dispatchException = ex;
        }
        catch (Throwable err) {
            // As of 4.3, we're processing Errors thrown from handler methods as well,
            // making them available for @ExceptionHandler methods and other scenarios.
            dispatchException = new NestedServletException("Handler dispatch failed", err);
        }
        // 后续处理:处理模型数据和渲染视图
        processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    }
    catch (Exception ex) {
        triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
    }
    catch (Throwable err) {
        triggerAfterCompletion(processedRequest, response, mappedHandler,
                               new NestedServletException("Handler processing failed", err));
    }
    finally {
        if (asyncManager.isConcurrentHandlingStarted()) {
            // Instead of postHandle and afterCompletion
            if (mappedHandler != null) {
                mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
            }
        }
        else {
            // Clean up any resources used by a multipart request.
            if (multipartRequestParsed) {
                cleanupMultipart(processedRequest);
            }
        }
    }
}
d>processDispatchResult()
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
                                   @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
                                   @Nullable Exception exception) throws Exception {

    boolean errorView = false;

    if (exception != null) {
        if (exception instanceof ModelAndViewDefiningException) {
            logger.debug("ModelAndViewDefiningException encountered", exception);
            mv = ((ModelAndViewDefiningException) exception).getModelAndView();
        }
        else {
            Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
            mv = processHandlerException(request, response, handler, exception);
            errorView = (mv != null);
        }
    }

    // Did the handler return a view to render?
    if (mv != null && !mv.wasCleared()) {
        // 处理模型数据和渲染视图
        render(mv, request, response);
        if (errorView) {
            WebUtils.clearErrorRequestAttributes(request);
        }
    }
    else {
        if (logger.isTraceEnabled()) {
            logger.trace("No view rendering, null ModelAndView returned.");
        }
    }

    if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
        // Concurrent handling started during a forward
        return;
    }

    if (mappedHandler != null) {
        // Exception (if any) is already handled..
        // 调用拦截器的afterCompletion()
        mappedHandler.triggerAfterCompletion(request, response, null);
    }
}

4、SpringMVC的执行流程

  1. 用户向服务器发送请求,请求被SpringMVC 前端控制器 DispatcherServlet捕获。
  2. DispatcherServlet对请求URL进行解析,得到请求资源标识符(URI),判断请求URI对应的映射:

a) 不存在

i. 再判断是否配置了mvc:default-servlet-handler

ii. 如果没配置,则控制台报映射查找不到,客户端展示404错误

image20210709214911404

image20210709214947432

iii. 如果有配置,则访问目标资源(一般为静态资源,如:JS,CSS,HTML),找不到客户端也会展示404错误

image20210709215255693

image20210709215336097

b) 存在则执行下面的流程

  1. 根据该URI,调用HandlerMapping获得该Handler配置的所有相关的对象(包括Handler对象以及Handler对象对应的拦截器),最后以HandlerExecutionChain执行链对象的形式返回。
  2. DispatcherServlet 根据获得的Handler,选择一个合适的HandlerAdapter。
  3. 如果成功获得HandlerAdapter,此时将开始执行拦截器的preHandler(…)方法【正向】
  4. 提取Request中的模型数据,填充Handler入参,开始执行Handler(Controller)方法,处理请求。在填充Handler的入参过程中,根据你的配置,Spring将帮你做一些额外的工作:

a) HttpMessageConveter: 将请求消息(如Json、xml等数据)转换成一个对象,将对象转换为指定的响应信息

b) 数据转换:对请求消息进行数据转换。如String转换成Integer、Double等

c) 数据格式化:对请求消息进行数据格式化。 如将字符串转换成格式化数字或格式化日期等

d) 数据验证: 验证数据的有效性(长度、格式等),验证结果存储到BindingResult或Error中

  1. Handler执行完成后,向DispatcherServlet 返回一个ModelAndView对象。
  2. 此时将开始执行拦截器的postHandle(...)方法【逆向】。
  3. 根据返回的ModelAndView(此时会判断是否存在异常:如果存在异常,则执行HandlerExceptionResolver进行异常处理)选择一个适合的ViewResolver进行视图解析,根据Model和View,来渲染视图。
  4. 渲染视图完毕执行拦截器的afterCompletion(…)方法【逆向】。
  5. 将渲染结果返回给客户端。
posted on 2021-11-24 21:17  很绝望  阅读(73)  评论(0编辑  收藏  举报