参考视频:SpringMVC教程,SpringMVC从零到精通,老杜SpringMVC,动力节点SpringMVC

1. SpringMVC中视图的实现原理

1.1 Spring MVC视图支持可配置

在Spring MVC中,视图View是支持定制的,例如我们之前在 springmvc.xml 文件中进行了如下的配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!--视图解析器-->
<bean id="thymeleafViewResolver" class="org.thymeleaf.spring6.view.ThymeleafViewResolver">
<!--作用于视图渲染的过程中,可以设置视图渲染后输出时采用的编码字符集-->
<property name="characterEncoding" value="UTF-8"/>
<!--如果配置多个视图解析器,它来决定优先使用哪个视图解析器,它的值越小优先级越高-->
<property name="order" value="1"/>
<!--当 ThymeleafViewResolver 渲染模板时,会使用该模板引擎来解析、编译和渲染模板-->
<property name="templateEngine">
<bean class="org.thymeleaf.spring6.SpringTemplateEngine">
<!--用于指定 Thymeleaf 模板引擎使用的模板解析器。模板解析器负责根据模板位置、模板资源名称、文件编码等信息,加载模板并对其进行解析-->
<property name="templateResolver">
<bean class="org.thymeleaf.spring6.templateresolver.SpringResourceTemplateResolver">
<!--设置模板文件的位置(前缀)-->
<property name="prefix" value="/WEB-INF/templates/"/>
<!--设置模板文件后缀(后缀),Thymeleaf文件扩展名不一定是html,也可以是其他,例如txt,大部分都是html-->
<property name="suffix" value=".html"/>
<!--设置模板类型,例如:HTML,TEXT,JAVASCRIPT,CSS等-->
<property name="templateMode" value="HTML"/>
<!--用于模板文件在读取和解析过程中采用的编码字符集-->
<property name="characterEncoding" value="UTF-8"/>
</bean>
</property>
</bean>
</property>
</bean>

以上的配置表明当前SpringMVC框架使用的视图View是Thymeleaf的。

如果你需要换成其他的视图View,修改以上的配置即可。这样就可以非常轻松的完成视图View的扩展。

这种设计是完全符合OCP开闭原则的。视图View和框架是解耦合的,耦合度低扩展能力强。视图View可以通过配置文件进行灵活切换。

1.2 Spring MVC支持的常见视图

Spring MVC支持的常见视图包括:

  • InternalResourceView:内部资源视图(是SpringMVC内置的,专门负责解析 JSP模板语法 的,另外也负责 转发forward 功能的实现)
  • RedirectView:重定向视图(是SpringMVC内置的,专门负责 重定向redirect 功能的实现)
  • ThymeleafView:Thymeleaf视图(第三方的,为Thymeleaf模板语法准备的)
  • FreeMarkerView:FreeMarker视图(第三方的,为FreeMarker模板语法准备的)
  • VelocityView:Velocity视图(第三方的,为Velocity模板语法准备的)
  • PDFView:PDF视图(第三方的,专门用来生成pdf文件视图)
  • ExcelView:Excel视图(第三方的,专门用来生成excel文件视图)
  • ……

1.3 实现视图机制的核心接口

实现视图的核心类与接口包括:

DispatcherServlet类(前端控制器)

  • 职责:在整个Spring MVC执行流程中,负责中央调度
  • 核心方法:doDispatch

核心方法doDispatch

ViewResolver接口(视图解析器)

  • 职责:负责将逻辑视图名转换为物理视图名,最终创建View接口的实现类,即视图实现类对象
  • 核心方法:resolveViewName

核心方法resolveViewName

View接口(视图)

  • 职责:负责将模型数据Model渲染为视图格式(HTML代码),并最终将生成的视图(HTML代码)输出到客户端。(它负责将模板语言转换成HTML代码)
  • 核心方法:render

核心方法render

ViewResolverRegistry(视图解析器注册器)

  • 负责在web容器(Tomcat)启动的时候,完成视图解析器的注册。如果有多个视图解析器,会将视图解析器对象按照order的配置放入List集合。

总结

  • 实现视图的核心类和接口包括:ViewResolverRegistry、DispatcherServlet、ViewResolver、View
  • 如果你想定制自己的视图组件:
    • 编写类实现ViewResolver接口,实现resolveViewName方法,在该方法中完成逻辑视图名转换为物理视图名,并返回View对象。
    • 编写类实现View接口,实现render方法,在该方法中将模板语言转换成HTML代码,并将HTML代码响应到浏览器。
  • 如果Spring MVC框架中使用Thymeleaf作为视图技术。那么相关的类包括:
    • ThymeleafView
    • ThymeleafViewResolver

1.4 实现视图机制的原理描述

假设我们SpringMVC中使用了Thymeleaf作为视图。

  1. 浏览器发送请求给web服务器

  2. Spring MVC中的DispatcherServlet接收到请求

  3. DispatcherServlet根据请求路径分发到对应的Controller

  4. DispatcherServlet调用Controller的方法

  5. Controller的方法处理业务并返回一个逻辑视图名给DispatcherServlet

  6. DispatcherServlet调用ThymeleafViewResolver的resolveViewName方法,将逻辑视图名转换为物理视图名,并创建ThymeleafView对象返回给DispatcherServlet

  7. DispatcherServlet再调用ThymeleafView的render方法,render方法将模板语言转换为HTML代码,响应给浏览器,完成最终的渲染。

假设我们SpringMVC中使用了JSP作为视图。

  1. 浏览器发送请求给web服务器

  2. Spring MVC中的DispatcherServlet接收到请求

  3. DispatcherServlet根据请求路径分发到对应的Controller

  4. DispatcherServlet调用Controller的方法

  5. Controller的方法处理业务并返回一个逻辑视图名给DispatcherServlet

  6. DispatcherServlet调用InternalResourceViewResolverresolveViewName方法,将逻辑视图名转换为物理视图名,并创建InternalResourceView对象返回给DispatcherServlet

  7. DispatcherServlet再调用InternalResourceViewrender方法,render方法将模板语言转换为HTML代码,响应给浏览器,完成最终的渲染。

1.5 逻辑视图名到物理视图名的转换

逻辑视图名最终转换的物理视图名是什么,取决再springmvc.xml文件中视图解析器的配置:

假如视图解析器配置的是ThymeleafViewResolver,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<bean id="thymeleafViewResolver" class="org.thymeleaf.spring6.view.ThymeleafViewResolver">
<property name="characterEncoding" value="UTF-8"/>
<property name="order" value="1"/>
<property name="templateEngine">
<bean class="org.thymeleaf.spring6.SpringTemplateEngine">
<property name="templateResolver">
<bean class="org.thymeleaf.spring6.templateresolver.SpringResourceTemplateResolver">
<property name="prefix" value="/WEB-INF/templates/"/>
<property name="suffix" value=".html"/>
<property name="templateMode" value="HTML"/>
<property name="characterEncoding" value="UTF-8"/>
</bean>
</property>
</bean>
</property>
</bean>

以下程序返回逻辑视图名:index

1
2
3
4
@RequestMapping("/index")
public String toIndex(){
return "index";
}

最终逻辑视图名index转换为物理视图名:/WEB-INF/templates/index.html

假如视图解析器配置的是InternalResourceViewResolver,如下:

1
2
3
4
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/templates/"/>
<property name="suffix" value=".jsp"/>
</bean>

以下程序返回逻辑视图名:index

1
2
3
4
@RequestMapping("/index")
public String toIndex(){
return "index";
}

最终逻辑视图名index转换为物理视图名:/WEB-INF/templates/index.jsp

2. Thymeleaf视图

我们在学习前面内容的时候,采用的都是Thymeleaf视图。我们再来测试一下,看看底层创建的视图对象是不是ThymeleafView

1、springmvc.xml配置内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

<!--组件扫描-->
<context:component-scan base-package="com.muyoukule.controller"/>

<!--视图解析器-->
<bean id="thymeleafViewResolver" class="org.thymeleaf.spring6.view.ThymeleafViewResolver">
<property name="characterEncoding" value="UTF-8"/>
<property name="order" value="1"/>
<property name="templateEngine">
<bean class="org.thymeleaf.spring6.SpringTemplateEngine">
<property name="templateResolver">
<bean class="org.thymeleaf.spring6.templateresolver.SpringResourceTemplateResolver">
<property name="prefix" value="/WEB-INF/templates/"/>
<property name="suffix" value=".html"/>
<property name="templateMode" value="HTML"/>
<property name="characterEncoding" value="UTF-8"/>
</bean>
</property>
</bean>
</property>
</bean>

</beans>

2、Controller代码如下:

1
2
3
4
5
6
7
@Controller
public class IndexController {
@RequestMapping("/index")
public String toIndex(){
return "index";
}
}

3、视图页面:

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<h1>SpringMVC视图实现原理</h1>
</body>
</html>

4、添加断点:在DispatcherServletdoDispatch方法的下图位置添加断点

在doDispatch方法添加断点

5、启动Tomcat,在浏览器地址栏上发送请求:http://localhost:8080/springmvc/index

发送请求程序到达位置

程序走到以上位置,这行代码是调用对应的Controller,并且Controller最终会返回ModelAndView对象:mv

6、按照我们之前所讲,返回mv之后,接下来就是视图处理与渲染,接着往下走,走到下图这一行:

处理分发结果的方法

7、这个方法的作用是处理分发结果,就是在这个方法当中进行了视图的处理与渲染,进入该方法:

进入处理分发结果的方法

8、进去之后走到上图位置:这个方法就是用来渲染页面的方法,再进入该方法:

进入用来渲染页面的方法

走到上图位置就可以看到底层创建的是ThymeleafView对象。

3. JSP视图(了解)

我们再来跟一下源码,看看JSP视图底层创建的是不是InternalResourceView对象。

我们前面说过 InternalResourceView 是SpringMVC框架内置的,翻译为内部资源视图,SpringMVC把JSP看做是内部资源。可见JSP在之前的技术栈中有很高的地位。

不过,当下流行的开发中JSP使用较少,这里不再详细讲解。只是测试一下。

1、springmvc.xml配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

<!--组件扫描-->
<context:component-scan base-package="com.muyoukule.controller"/>

<!--JSP的视图解析器-->
<bean id="jspViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>

</beans>

2、Controller代码如下:

1
2
3
4
5
6
7
@Controller
public class IndexController {
@RequestMapping("/index")
public String toIndex(){
return "index";
}
}

3、视图页面:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<%--
Created by IntelliJ IDEA.
User: muyoukule
Date: 2024/4/7
Time: 12:29
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Jsp页面</title>
</head>
<body>
<h1>我的第N个Jsp页面</h1>
</body>
</html>

4、启动web容器,添加断点跟踪:

添加断点测试JSP视图底层创建的对象

通过测试得知:对于JSP视图来说,底层创建的视图对象是InternalResourceView

4. 转发与重定向

4.1 回顾转发和重定向区别

  • 转发是一次请求。因此浏览器地址栏上的地址不会发生变化;重定向是两次请求。因此浏览器地址栏上的地址会发生变化

  • 转发的代码实现:request.getRequestDispatcher("/index").forward(request, response);重定向的代码实现:response.sendRedirect("/webapproot/index")

  • 转发是服务器内部资源跳转,由服务器来控制。不可实现跨域访问;重定向可以完成内部资源的跳转,也可以完成跨域跳转

  • 转发的方式可以访问WEB-INF目录下受保护的资源;重定向相当于浏览器重新发送了一次请求,在浏览器直接发送的请求是无法访问WEB-INF目录下受保护的资源的

    转发原理:

    • 假设发送了 /a 请求,执行了 AServlet
    • 在AServlet 中通过request.getRequestDispatcher("/b").forward(request,response);转发到BServlet
    • 从AServlet跳转到BServlet是服务器内部来控制的。对于浏览器而言,浏览器只发送了一个 /a 请求

    重定向原理:

    • 假设发送了 /a 请求,执行了 AServlet
    • 在AServlet 中通过response.sendRedirect("/webapproot/b")重定向到BServlet
    • 此时服务器会将请求路径/webapproot/b响应给浏览器
    • 浏览器会自发的再次发送/webapproot/b请求来访问BServlet
    • 因此对于重定向来说,发送了两次请求,一次是 /webapproot/a,另一次是/webapproot/b

以上所描述的是使用原生Servlet API来完成转发和重定向。在Spring MVC中是如何转发和重定向的呢?

4.2 forward

在Spring MVC中默认就是转发的方式,我们之前所写的程序,都是转发的方式。只不过都是转发到Thymeleaf的模板文件xxx.html上。

那么,在Spring MVC中如何转发到另一个Controller上呢?可以使用Spring MVC的forward

1、代码实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Controller
public class ForwardController {

@RequestMapping("/a")
public String toA() {
// 返回的是一个逻辑视图名称
//return "a";

// 采用SpringMVC的转发方式跳转到 /b
// 转发的时候,格式有特殊要求: return "forward:下一个资源的路径";
// 这个就不是逻辑视图名称了。
return "forward:/b"; // 创建InternalResourceView对象。
}

@RequestMapping("/b")
public String toB() {
// 返回的是一个逻辑视图名称
return "b"; // 创建ThymeleafView对象。
}
}

2、视图页面:

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>b</title>
</head>
<body>
<h1>Page B!!!</h1>
</body>
</html>

3、启动服务器,浏览器地址栏上输入:http://localhost:8080/springmvc/a

通过测试,可以顺利的完成转发,转发是一次请求,可以看到地址栏上的地址没有发生改变

源码剖析

我们来跟踪一下源码,看看以上程序执行过程中,创建了几个视图对象,分别是什么?

转发请求创建InternalResourceView对象

转发请求创建ThymeleafView对象

通过源码的跟踪得知:整个请求处理过程中,一共创建了两个视图对象

  • InternalResourceView
  • ThymeleafView

这说明转发底层创建的视图对象是:InternalResourceView

思考

既然会创建InternalResourceView,应该会对应一个视图解析器呀(InternalResourceViewResolver)?但是我在springmvc.xml文件中只配置了ThymeleafViewResolver,并没有配置InternalResourceViewResolver呀?这是为什么?

这是因为forward: 后面的不是逻辑视图名,而是一个请求路径。因此转发是不需要视图解析器的。😁另外,转发使用的是InternalResourceView,也说明了转发是内部资源的跳转。(Internal是内部的意思,Resource是资源的意思。)

4.3 redirect

redirect是专门完成重定向效果的。和forward语法类似,只需要将之前的 return "forward:/b"修改为 return "redirect:/b"即可。

1、Controller代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Controller
public class IndexController {

@RequestMapping("/a")
public String toA(){
return "redirect:/b";
}

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

2、视图页面:

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>b</title>
</head>
<body>
<h1>Page B!!!</h1>
</body>
</html>

3、启动服务器,浏览器地址栏上输入:http://localhost:8080/springmvc/a

请求a重定向到b

可见,重定向是两次请求,地址栏上的地址发生了改变

源码剖析

可以看一下源码,在重定向的时候,Spring MVC创建哪个视图对象?

重定向请求创建RedirectView对象

重定向请求创建ThymeleafView对象

通过断点调试可以看出,当重定向的时候,SpringMVC会创建一个重定向视图对象:**RedirectView**。这个视图对象也是SpringMVC框架内置的。

另外可以看出重定向之后的第二次请求创建的视图对象就是ThymeleafView了。

PS:从springmvc应用重定向到springmvc2应用(跨域),语法是:

1
2
3
4
@RequestMapping("/a")
public String a(){
return "redirect:http://localhost:8080/springmvc2/b";
}

可以自行测试一下!!!

5. <mvc:view-controller>

<mvc:view-controller> 配置用于将某个请求映射到特定的视图上,即指定某一个 URL 请求到一个视图资源的映射,使得这个视图资源可以被访问。它相当于是一个独立的处理程序,不需要编写任何 Controller,只需要指定 URL 和对应的视图名称就可以了。

一般情况下,<mvc:view-controller> 配置可以替代一些没有业务逻辑的 Controller,例如首页、错误页面等。当用户访问配置的 URL 时,框架将直接匹配到对应的视图,而无需再经过其他控制器的处理。

<mvc:view-controller> 配置的格式如下:

1
<mvc:view-controller path="/如何访问该页面" view-name="对应的逻辑视图名称" />

其中:

  • path:被映射的 URL 路径。
  • view-name:对应的逻辑视图名称。

例如,配置首页的映射:

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

上述配置将会匹配上访问应用程序的根路径,如:http://localhost:8080/。当用户在浏览器中访问该根路径时,就会直接渲染名为 index 的视图。

PS:当你使用了 <mvc:view-controller> 配置,会让你整个项目中所有的注解全部失效,你需要使用以下的配置来再次开启注解。

6. <mvc:annotation-driven/>

在SpringMVC中,如果在springmvc.xml文件中配置了 <mvc:view-controller>,就需要同时在springmvc.xml文件中添加如下配置:

1
<mvc:annotation-driven/>

该配置的作用是:启用Spring MVC的注解。

如果没有以上的配置,Controller就无法访问到。访问之前的Controller会发生 404 问题。

7. 访问静态资源

一个项目可能会包含大量的静态资源,比如:css、js、images等。

由于我们DispatcherServlet的url-pattern配置的是/,之前我们说过,这个/代表的是除jsp请求之外的所有请求,也就是说访问应用中的静态资源,也会走DispatcherServlet,这会导致404错误,无法访问静态资源,如何解决,两种方案:

  • 使用默认 Servlet 处理静态资源
  • 使用 mvc:resources 标签配置静态资源处理

这两种方式都可以,自行选择。

7.1 使用默认Servlet处理静态资源

首先需要在springmvc.xml文件中添加以下配置,开启 默认Servlet处理静态资源 功能:

1
2
3
4
5
<!-- 开启注解驱动 -->
<mvc:annotation-driven />

<!--开启默认Servlet处理-->
<mvc:default-servlet-handler>

然后在web.xml文件中指定什么样的路径走其他Servlet:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<servlet>
<servlet-name>default</servlet-name>
<servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
<init-param>
<param-name>debug</param-name>
<param-value>0</param-value>
</init-param>
<init-param>
<param-name>listings</param-name>
<param-value>false</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>

以上配置url-pattern使用的也是/,和DispatcherServlet一样。表示的含义是:同一个请求路径,先走DispatcherServlet,如果找不到则走默认的Servlet。

默认的 Servlet 类中的代码已经由 Tomcat 服务器提供了实现,一般不需要开发者自己编写。在上面的示例中,我们指定了 org.apache.catalina.servlets.DefaultServlet,则 Tomcat 服务器会自动将请求转发给该类处理。在处理时,该类会根据请求的 URL 去查询 Web 应用的静态资源(如 HTML、CSS、JavaScript 和图片等),并将其返回给用户。

告诉大家一个好消息,以上在web.xml文件中的配置我们也可以省略了,因为在Tomcat服务器中已经为我们提前配置好了,在CATALINA_HOME/conf/web.xml文件中,如下:

Tomcat自带的servlet配置项

Tomcat自带的servlet-mapping配置项

因此我们只需要在springmvc.xml文件中启用这个默认的Servlet即可:<mvc:default-servlet-handler>

7.2 使用 mvc:resources 标签配置静态资源

访问静态资源,也可以在springmvc.xml文件中添加如下的配置:

1
2
3
4
5
<!-- 开启注解驱动 -->
<mvc:annotation-driven />

<!-- 配置静态资源处理 -->
<mvc:resources mapping="/static/**" location="/static/" />

表示凡是请求路径是/static/开始的,都会去/static/目录下找该资源。

PS:要想使用 <mvc:resources> 配置,必须开启注解驱动 <mvc:annotation-driven />

附录:视图机制源码跟踪

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
public class DispatcherServlet extends FrameworkServlet {

// 前端控制器的核心方法,处理请求,返回视图,渲染视图,都是在这个方法中完成的。
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {


// 根据请求路径调用映射的处理器方法,处理器方法执行结束之后,返回逻辑视图名称
// 返回逻辑视图名称之后,DispatcherServlet会将 逻辑视图名称ViewName + Model,将其封装为ModelAndView对象。
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

// 这行代码的作用是处理视图
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
// 渲染页面(将模板字符串转换成html代码响应到浏览器)
render(mv, request, response);
}

protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
// 这个方法的作用是将 逻辑视图名称 转换成 物理视图名称 ,并且最终返回视图对象View
View view = resolveViewName(viewName, mv.getModelInternal(), locale, request);

// 真正的将模板字符串转换成HTML代码,并且将HTML代码响应给浏览器。(真正的渲染。)
view.render(mv.getModelInternal(), request, response);
}

protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,
Locale locale, HttpServletRequest request) throws Exception {
// 其实这一行代码才是真正起作用的:将 逻辑视图名称 转换成 物理视图名称 ,并且最终返回视图对象View
ViewResolver viewResolver; // 底层会创建一个ThymeleafViewResolver
// 如果使用的是Thymeleaf,那么返回的视图对象:ThymeleafView对象。
View view = viewResolver.resolveViewName(viewName, locale);
return view;
}

}


// 这是一个接口(负责视图解析的)
public interface ViewResolver { // 如果使用Thymeleaf,那么该接口的实现类就是:ThymeleafViewResolver
// 这个方法就是将:逻辑视图名称 转换成 物理视图名称 ,并且最终返回视图对象View
View resolveViewName(String viewName, Locale locale) throws Exception;
}

// 这是一个接口(负责将 模板字符串 转换成HTML代码,响应给浏览器)
public interface View {
void render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)
throws Exception;
}

/*
核心类:DispatcherServlet
核心接口1:ViewResolver(如果你使用的是Thymeleaf,那么底层会创建ThymeleafViewResolver对象)
核心接口2:View(如果你使用的是Thymeleaf,那么底层会创建ThymeleafView对象)

结论:如果你自己想实现属于自己的视图。你至少需要编写两个类,
一个类实现ViewResolver接口,实现其中的resolveViewName方法。
另一个类实现View接口,实现其中的render方法。
*/