【SpringMVC】(一)

SpringMVC简介

SpringMVC是Spring的一个后续产品,是Spring的一个子项目

基于原生的Servlet,通过了功能强大的DispatcherServlet,对请求和响应进行统一处理

什么是MVC

MVC是一种架构思想,将软件按照模型、视图、控制器来划分

M:Modedl,模型层,指工程中的JavaBean,作用是处理数据

JavaBean分为两类:

· 一类是实体类Bean,专门存储业务数据

· 一类是业务处理Bean,指Service和Dao对象,专门处理业务逻辑和数据库访问

V:View,视图层,指工程中的html和jsp页面,作用是与用户交互,展示数据

C:Controller,控制层,指工程中的servlet,作用是接收请求和响应浏览器

MVC的工作流程

用户通过视图层发送数据请求到服务器,在服务器中请求被Controller接收,Controller调用Model层处理请求,处理完毕将结果返回给Controller,Controller再根据请求处理的结果找到相应的View视图层,对数据进行渲染相应给浏览器。

开发环境

IDE:idea 2019.2

构建工具:maven3.5.4

服务器:tomcat7

Spring版本:5.3.1

创建Maven工程

①添加web模块

②设置打包方式为war

<packaging>war</packaging>

③引入依赖

<dependencies>
<!-- SpringMVC -->
<dependency>
<roupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.1</version>
</dependency>
<!-- 日志 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<!-- ServletAPI -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!-- Spring5和Thymeleaf整合包 -->
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
<version>3.0.12.RELEASE</version>
</dependency>
</dependencies>

导入依赖时会同时导入依赖所需要的依赖,如导入spring-webmvc时就导入了Spring所需的aop、beans、context等

scope provided /scope 标签表示服务器已经提供了该依赖,打包时不必再注入

配置web.xml

默认请求方式
<!--配置SpringMVC前端控制器,对浏览器发送的请求进行统一处理 -->
<servlet>
<servlet-name>SpringMVC</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>SpringMVC</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>

对名为SpringMVC的servlet进行注册,servlet和servlet-mapping的servlet-name的名称必须相同

匹配路径设置为SpringMVC的核心控制器能够处理的请求的请求路径

/表示除了.jsp之外的请求,可以匹配如/login、.html、css方式的请求路径,/*则表示包括.jsp的所有请求

为什么不能匹配jsp?

jsp被认为是一种特殊的servlet,需要服务器指定的servlet进行处理,因此.jsp结尾的请求路径不需要DispatcherServlet处理,否则SpringMVC将.jsp请求当做普通请求处理而不会找到相应的jsp页面

扩展配置方式

Maven要求配置文件都要在resource下,因此通过init-param(初始化前端控制器属性)设置SpringMVC配置文件(contextConfigLocation,上下文配置路径)的位置(value,对应resource的类路径)和名称(name),通过load-on-startup标签设置前端控制器DispatcherServlet的启动时间为服务器启动时(Servlet生命周期默认为第一次访问时才启动)

<!--配置SpringMVC前端控制器,对浏览器发送的请求进行统一处理 -->
<servlet>
<servlet-name>SpringMVC</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--配置SpringMVC配置文件的位置和名称 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:SpringMVC.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>

创建SpringMVC.xml配置文件

开启注解扫描
<context:component-scan base-package="com.hikaru.mvc.controller"></context:component-scan>
@Controller
public class HelloController {
}

这样控制器就能被IOC识别并注入

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

Order设置视图解析器的优先级

templateEngine为当前模板,其内部Bean表示的是当前解析视图的一种策略。视图名称加上视图前缀和视图后缀就可以得到要跳转的页面。

每当页面发生跳转的时候,视图名称符合当前条件,就会被视图解析器解析,得到相应的页面

对控制器设置请求映射注解 @RequestMapping

将当前的请求和控制器方法创建映射关系,不仅可以通过请求路径,而且可以通过请求方式、请求参数、请求报文等进行匹配

@Controller
public class HelloController {
@RequestMapping("/")
public String getIndex() {
return "index";
}
}

这里省略了value="/"

WEB-INFO下的页面浏览器是不能够访问的,也不能够通过重定向只能通过转发的方式

不允许多个控制器对同一个上下文地址进行映射

由于引入的日志依赖,控制台输出的日志信息:

13:53:35.733 [http-nio-8080-exec-1] DEBUG org.thymeleaf.TemplateEngine - [THYMELEAF] TEMPLATE ENGINE INITIALIZED
13:53:35.858 [http-nio-8080-exec-1] DEBUG org.springframework.web.servlet.DispatcherServlet - Completed 200 OK
13:53:36.105 [http-nio-8080-exec-4] DEBUG org.springframework.web.servlet.DispatcherServlet - GET "/springMVC/", parameters={}
13:53:36.106 [http-nio-8080-exec-4] DEBUG org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping - Mapped to com.hikaru.mvc.controller.HelloController#getIndex()
13:53:36.111 [http-nio-8080-exec-4] DEBUG org.springframework.web.servlet.DispatcherServlet - Completed 200 OK

可以看到

org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping - Mapped to com.hikaru.mvc.controller.HelloController#getIndex()

这里发生的映射关系:访问请求Mapped to控制器方法,且与注解表示的方法有关而与方法名无关

@RequestMapping注解位置

为了解决多个控制器对应同一个请求的情况,需要在控制器类上和方法上各添加@RequestMapping注解

@Controller
public class TestController {
@RequestMapping("/")
public String getIndex() {
return "index";
}
}
@Controller
public class TestController1 {
@RequestMapping("/")
public String getIndex1() {
return "index";
}
}

如上两个控制器对应同一个访问请求/,服务器日志信息:

Caused by: java.lang.IllegalStateException: Ambiguous mapping. Cannot map 'testController1' method
com.hikaru.controller.TestController1#getIndex1()
to { [/]}: There is already 'testController' bean method

@RequestMapping标识一个:设置映射请求的请求路径的初始信息

@RequestMapping标识一个方法:设置映射请求的请求路径的具体信息

在控制器TestController类上添加注解,则请求路径/会匹配控制器TestController1的控制器方法。

@Controller
@RequestMapping("/test")
public class TestController {
@RequestMapping("/")
public String getIndex() {
return "index";
}
}

或者在浏览器输入上下文路径(http://localhost:8080/springMVC/test/) 也能够通过控制器一匹配控制器方法/

@RequestMapping的属性
1 value

value值可以为数组,接收多个请求路径,value属性必不可少

2 method

method属性为请求的方式(get或post)

get方式的请求参数以 ?请求参数名=请求参数值&... 的方式拼接在地址后面,文件上传不能使用get方式

post方式请求参数放在请求体中,相较于post安全、数据量比较大但速度较慢

不写method属性则表明没有请求方式限制

@RequestMapping(
value = {"/success"},
method = {RequestMethod.GET}
)
public String success() {
return "success";
}

其中RequestMethod为枚举类,包含所有的请求方式

public enum RequestMethod {
GET,
HEAD,
POST,
PUT,
PATCH,
DELETE,
OPTIONS,
TRACE;
private RequestMethod() {
}
}
<a th:href="@{/test/success}">SUCCESS</a>
<form th:action="@{/test/success}" method="post">
<button type="submit">SUCCESS</button>
</form>

这种情况下,只有超链接get方式能够匹配请求

表单提交的post方式满足映射value属性,但是不满足method属性

3 params(了解)

@ RequestMapping注解的params属性通过请求的请求参数匹配请求映射,params属性是一个字符串数组,要求请求参数必须满足数组中的全部要求才能匹配成功,可以通过四种表达式设置请求参数和请求映射的匹配关系。

①"param":要求匹配映射所匹配的请求必须携带param请求参数

②"!param":要求匹配映射所匹配的请求不能携带param请求参数

③"param=value":要求匹配映射所匹配的请求必须携带值为value的param请求参数

④"param!=value":要求匹配映射所匹配的请求必须携带param请求参数但是不能为value

@RequestMapping(
value = {"/paramsAndHeaderTest"},
params = {"username=admin"}
)
public String paramsAndHeaderTest() {
return "success";
}
<a th:href="@{/test/paramsAndHeaderTest(username='admin')}">paramsAndHeaderTest</a>

thymeleaf会将上述解析为:/test/paramsAndHeaderTest?username=admin,也可以直接这样写,但是会报错

HTTP 400:请求参数不匹配

4 headers
@RequestMapping(
value = {"/paramsAndHeaderTest"},
params = {"username=admin"},
headers = {"Host=localhost:8080"}
)
public String paramsAndHeaderTest() {
return "success";
}

请求头参数为一个字符串键值对数组,请求头不匹配的话服务器会报HTTP404

@RequestMapping的常用的派生注解

使用派生注解可以不用声明method请求方式

@GetMapping
@PostMapping
@PutMapping
@DeleteMapping

但是目前浏览器只支持post和get方式,表单提交时,若使用put、delete则会按照默认请求方式get处理

@PostMapping(
value = {"/success"}
)
public String success() {
return "success";
}

get方式超链接会出现http405错误

SpringMVC支持ant风格的路径(模糊匹配)

?: 表示任意的单个字符

*: 表示任意的0个或多个字符

**: 表示任意的一层或多层目录

在使用**时,只能使用/ **/xxx的形式,否则会被当做多个字符

@RequestMapping(
value = {"/**/*"},
params = {"username=admin"},
headers = {"Host=localhost:8080"}
)
public String paramsAndHeaderTest() {
return "success";
}
<a th:href= "@{/test/qweqweqwewq/eqweqweqwe(username='admin')}">paramsAndHeaderTest</a>
SpringMVC支持路径中的占位符

原始方式:/deleteUser?id=1

rest方式:/deleteUser/1

SpringMVC路径占位符常用于restful风格中,当请求路径中的某些参数通过路径的方式传输到服务器中,就可以在相应的@ResquestMapping属性中通过占位符{xxx}表示传输过来的数据,再通过@PathVariable注解,将占位符表示的数据赋值给控制器方法的形参。

@RequestMapping(
value = {"/*/{username}"},
headers = {"Host=localhost:8080"}
)
public String paramsAndHeaderTest(@PathVariable("username")String username) {
System.out.println(username);
return "success";
}
<a th:href= "@{/test/1/admin}">paramsAndHeaderTest</a>

通过thymeleaf视图解析器访问指定的页面th:href="@{/target}"th:href="@{/target}"th:href="@{/target}"

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

thymeleaf会解析a标签的上下文路径(或者使用 /springMVC/target作为上下文路径 ,但是上下文可以修改耦合性不好,否则普通超链接地址只有浏览器或服务器解析的绝对路径),将请求发送给控制器,经请求映射的控制器方法得到返回值后再加上视图前缀和视图后缀,返回完整的页面地址

@Controller
public class HelloController {
@RequestMapping("/")
public String getIndex() {
return "index";
}
@RequestMapping("/target")
public String toTarget() {
return "target";
}
}

总结

浏览器发送请求,若请求符合前段控制器的url-pattern,该请求就会被前端控制器DispatcherServlet处理,前端控制器会读取SpringMVC核心配置文件(resource中的SpringMVC.xml)通过扫描组件找到控制器,将请求地址和控制器方法的@RequestMapping的value值进行匹配,如果匹配成功则说明该控制器方法是处理请求的方法。处理请求的方法需要返回一个字符串类型的视图名称,该视图名称会被视图解析器解析,加上视图前缀、后缀组成视图的路径,通过ThymeLeaf对视图进行渲染,最终转发到视图对应的页面。

SpringMVC获取请求参数

1 通过原生ServletAPI获取
<a th:href="@{/testServletAPI(username='admin', password=123456)}">get_param</a>
<form th:action="@{/testServletAPI}" method="post">
<input type="text" name="username">
<input type="password" name="password">
<input type="submit">
</form>
@RequestMapping("/testServletAPI")
public String testServletAPI(HttpServletRequest request) {
String username = request.getParameter("username");
String password = request.getParameter("password");
System.out.println("username:" + username + ",password:" + password);
return "success";
}

浏览器发送的请求会被RequestMapping注解所匹配,匹配成功后会由在web.xml注册的前端控制器DispatcherServlet调用匹配控制器的控制器方法,并且将前端控制器得到的数据注入控制器方法的参数中

2 通过控制器方法形参获取
<form th:action="@{/testServletAPI}" method="post">
<input type="text" name="username">
<input type="password" name="password">
<input type="checkbox" name="hobby" value="a">a<br/>
<input type="checkbox" name="hobby" value="b">b<br/>
<input type="checkbox" name="hobby" value="c">c<br/>
<input type="submit">
</form>
@RequestMapping("/testServletAPI")
public String testServletAPI(String username, String password, String[] hobby) {
System.out.println("username:" + username + ",password:" + password);
System.out.println(Arrays.toString(hobby));
return "success";
}

多个同名请求参数的情况可以使用一个String数组接收

或者使用一个String类型形参接收,这样里面的请求参数会用,分割组成一个String

@RequestParam注解设置控制器方法的形参
@RequestMapping("/testServletAPI")
public String testServletAPI(
@RequestParam(value = "user_name", required = false, defaultValue = "tod4") String username,
String password,
String[] hobby) {
System.out.println("username:" + username + ",password:" + password);
System.out.println(Arrays.toString(hobby));
return "success";
}
<form th:action="@{/testServletAPI}" method="post">
<input type="text" name="user_name">
<input type="password" name="password">
<input type="checkbox" name="hobby" value="a">a<br/>
<input type="checkbox" name="hobby" value="b">b<br/>
<input type="checkbox" name="hobby" value="c">c<br/>
<input type="submit">
</form>

value属性:value值对应请求的请求参数名,防止请求参数名和控制器方法名不一致导致服务器报错400参数不一致

required属性:默认为true,表示请求必须含有此请求参数,否则服务器报错400

defaultValue属性:无论required值true或false,也无论请求有没有此请求参数或者请求参数为空都为其赋初始值

@RequestHeader

@ RequestHeader 将请求头信息和构造器方法形参之间创建映射关系

@RequestMapping("/param")
public String param() {
return "test_param";
}
@RequestMapping("/testServletAPI")
public String testServletAPI(
@RequestParam(value = "user_name", required = false, defaultValue = "tod4") String username,
String password,
String[] hobby,
@RequestHeader(value="host123", required = true, defaultValue = "11111111") String host) {
System.out.println("username:" + username + ",password:" + password);
System.out.println(Arrays.toString(hobby));
System.out.println("host:" + host);
return "success";
}

@ RequestHeader 也有三种属性value、defaultValue、required,并且与RequestParam注解相同

@CookieValue创建Cookie和控制器方法参数之间的映射关系

控制器方法的getSession执行时会先查看请求报文中上是否含有JSESSIONID的Cookie,如果没有的话说明会话使第一次,会创建Session对象并将其放在服务器所维护的Map集合中,并创建一个键值对为JSESSIONID和一个随机序列的的Cookie作为响应报文返回给客户端。

Cookie存储于客户端(浏览器端),Session存储于服务器端所维护的Map集合中

@RequestMapping("/testServletAPI")
public String testServletAPI(HttpServletRequest request) {
HttpSession session = request.getSession();
return "success";
}

![](file://D:\资料\学习笔记\Java\SpringMVC\1.png?msec=1654150087933)

第一次会话服务器端没有检测到含有JSESSIONID的Cookie,便会创建Session并返回一个含有JSESSIONID和随机序列的Cookie,以后每次客户端再发送请求都会再发送这个JSESSIONID的Cookie

![](file://D:\资料\学习笔记\Java\SpringMVC\2.png?msec=1654150087905)

后面的请求的响应报文就不再有JSESSIONID的Cookie了

@RequestMapping("/testCookie")
public String testCookie(@RequestParam("user_name")String username,
@RequestParam("password")String password,
@RequestParam("hobby")String[] hobby,
@CookieValue("JSESSIONID")String cookie) {
System.out.println("username:" + username + ",password" + password);
System.out.println("hobby:" + Arrays.toString(hobby));
System.out.println("JSESSION:" + cookie);
return "success";
}

@ CookieValue也有三种属性value、defaultValue、required,并且与RequestParam注解相同

通过POJO获取参数

可以把控制器方法的参数设置为一个实体类型的形参,此时若浏览器传输的请求参数与实体类中的属性名一致,那么请求参数就会为此属性赋值

@RequestMapping("/testCookie")
public String testCookie(User user,
@CookieValue("JSESSIONID")String cookie) {
System.out.println(user.toString());
System.out.println("JSESSION:" + cookie);
return "success";
}
<form th:action="@{/testCookie}" method="post" style="border: black 1px">
<input type="text" name="username">
<input type="password" name="password">
<input type="checkbox" name="hobby" value="a">a<br/>
<input type="checkbox" name="hobby" value="b">b<br/>
<input type="checkbox" name="hobby" value="c">c<br/>
<input type="submit">
</form>

框架一般使用反射来创建实体类对象,反射使用的是实体类的无参构造器,这里要么不写构造器默认无参构造器,要么有参无参都要声明,不能只写有参构造器

CharacterEncodingFilter编码过滤器处理获取请求参数乱码的问题
1 get请求方式乱码:

get请求由于是把参数加到URL,所以乱码是由Tomcat对URL解析编码不支持中文造成的,可以在conf下的service.xml中修改URLEncoding,但是Tomcat8之后默认为UTF-8也就不需要修改了。

2 post请求乱码

原生Servlet是通过设置HttpServletRequest编码的方式,但是SpringMVC是先在控制器方法的形参中获取请求参数和HttpServletRequest,再通过HttpServletRequest修改编码方式的话对于以已经获取到的请求参数是没有作用的,因此需要在前端控制器DIspatcherServlet调用控制器方法前修改编码方式。

服务器三大组件的执行的时间顺序是:ServletContextListener、Filter最后才是Servlet,监听器Listener只是监听作用,因此可以使用过滤器Filter,当请求地址满足过滤器地址都会被Filter过滤,经FIlter处理之后再交给DIspatcherServlet。

过滤器Filter作为服务器的组件,和Servlet一样需要在web.xml中注册:

<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>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

匹配路径/*设置为包括jsp的所有请求

CharacterEncodingFilter部分源码:

@Nullable
private String encoding;
private boolean forceRequestEncoding;
private boolean forceResponseEncoding;
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String encoding = this.getEncoding();
if (encoding != null) {
if (this.isForceRequestEncoding() || request.getCharacterEncoding() == null) {
request.setCharacterEncoding(encoding);
}
if (this.isForceResponseEncoding()) {
response.setCharacterEncoding(encoding);
}
}
filterChain.doFilter(request, response);
}

其中属性encoding为编码方式,forceRequestEncoding为是否强制设置请求编码方式,forceResponseEncoding为是否强制设置响应编码方式,这些属性均可以在web.xml中进行初始化设置

下面的doFilterInternal方法可以看到,forceRequestEncoding为true或者请求编码为null时进行编码设置为encoding,forceResponseEncoding为true时会进行请求编码设置

域对象共享数据

servlet域对象范围太大不常使用

session域对象钝化活化

钝化:在会话过程中服务器关闭但是浏览器没有关闭,session过程是浏览器开启到浏览器关闭的过程与服务器关闭与否没有关系,因此会话仍然在继续,session中的数据就会经过序列化存储到磁盘上

活化:会话仍没有结束(浏览器没有关闭),此时服务器重启了,并且将钝化的文件内容重新读取到了session中的过程

Session域
1 使用ServletAPI向Session域添加共享数据
@RequestMapping("/testSessionScope")
public ModelAndView testSessionScope(HttpSession session,
@RequestParam("name") String name) {
ModelAndView mv = new ModelAndView();
mv.addObject("info", name);
session.setAttribute("info", "Hello,Session");
mv.setViewName("success");
return mv;
}
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>Success page</h1>
<hr>
<p th:text="${info}"></p>
<p th:text="${session.info}"></p>
</body>
</html>

JSESSIONID情况:

第一次点击超链接:

ResponseHeaders: JSESSIONID=B726AFACD887E8081984D1CA9D559FAB;

RequestHeaders: JSESSIONID=221AE584225F856A8F84E60ED96381BE;

第二次点击超链接

ResponseHeaders:

RequestHeaders: JSESSIONID=221AE584225F856A8F84E60ED96381BE;

③同理request域对象只在服务器启动时创建,在服务器关闭时销毁,因此使用的域对象都是同一个,因此能够共享数据

在选择域对象进行共享数据时,应选择能实现功能且范围最小的域对象

Request域
1 使用ServletAPI向request域中共享数据

request.setAttibute(String , Object)

@Controller
public class testScopeController {
@RequestMapping("/testScope")
public String testScope(HttpServletRequest request,
@RequestParam("name") String name) {
request.setAttribute("info", "Hello," + name);
return "success";
}
}
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>Index page</h1>
<hr>
<a th:href="@{/testScope(name='Hikaru')}">testScope</a>
</body>
</html>
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>Success page</h1>
<hr>
<p th:text="${info}"></p>
</body>
</html>
2 使用ModelAndView向request域对象共享数据
@RequestMapping("/testModelAndView")
public ModelAndView testModelAndView(@RequestParam("name") String name) {
ModelAndView mav = new ModelAndView();
//向请求域中添加共享数据
mav.addObject("info", "Hello," + name);
//设置试图,实现页面跳转
mav.setViewName("success");
return mav;
}
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>Index page</h1>
<hr>
<a th:href="@{/testScope(name='Hikaru')}">testScope</a><br>
<a th:href="@{/testModelAndView(name='Hikaru1')}">testModelAndView</a>
</body>
</html>

控制器方法执行完之后,DIspatcherServlet会将model(Request域添加信息)和view(视图层)信息封装成ModelAndView对象

3 使用Model向request域对象共享数据
4 使用Map向request域对象共享数据
@RequestMapping("/testModel")
public String testModel(@RequestParam("name") String name,
Model model) {
model.addAttribute("info", "Hello," + name);
return "success";
}
@RequestMapping("/testMap")
public String testMap(@RequestParam("name") String name,
Map<String, Object> map) {
map.put("info", "Hello" + name);
return "success";
}
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>Index page</h1>
<hr>
<a th:href="@{/testScope(name='Hikaru')}">testScope</a><br>
<a th:href="@{/testModelAndView(name='Hikaru1')}">testModelAndView</a><br>
<a th:href="@{/testModel(name='Hikaru2')}">testModel</a><br>
<a th:href="@{/testMap(name='Hikaru3')}">testMap</a><br>
</body>
</html>

需要注意的是,这些被添加到request对象域的共享数据的名称是相同的(info),但是值是不同的,这是因为每次点开的超链接哪怕是同一种都对应一个request,不同request域之间的共享数据自然不会相互影响

5 使用ModelMap向request域对象共享数据
※Model、Map以及MoelMap之间的关系

①其中Model、Map是接口,ModelMap是具体的类。

②三个类的实例化对象的class都是org.springframework.validation.support.BindingAwareModelMap

说明为三者形参赋值的对象是同一个,三者都可以通过一个类进行实例化

继承实现关系:

public interface Model
public interface Map
public class ModelMap extends LinkedHashMap<String, Object>
public class ExtendedModelMap extends ModelMap implements Model
public class BindingAwareModelMap extends ExtendedModelMap

所以BindingAwareModelMap可以为Model、Map、ModelMap赋值实例化。

Application(Servlet域)

对应整个工程范围

@RequestMapping("/testApplicationScope")
public String testApplicationScope(HttpSession session) {
ServletContext application = session.getServletContext();
application.setAttribute("info", "ApplicationScope");
return "success";
}
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>Success page</h1>
<hr>
<p th:text="${info}"></p>
<p th:text="${session.info}"></p>
<p th:text="${application.info}"></p>
</body>
</html>

SpringMVC的视图

SpringMVC的视图是View接口,视图的作用是渲染数据,将模型Model中的数据展示给用户

SpringMVC视图的种类有很多,默认有转发和重定向视图

此外,当工程引入JSTL依赖,转发视图自动转换为JSTL视图

若使用的视图技术是ThymeLeaf,在SpringMVC的配置文件中配置了Thymeleaf视图解析器,由视图解析器解析后得到的是ThymeLeaf视图

转发视图
1 ThymeLeaf视图

当控制器方法中的视图名称没有任何的前缀时,此时视图名称会被视图解析器解析,将视图名称拼接视图前缀和试图后缀,然后通过转发的方式进行跳转。

2 foward: InternalResourceView

在视图名称前面添加forward前缀,可以实现转发请求到其他控制器方法

@RequestMapping("/testApplicationScope")
public String testApplicationScope(HttpSession session) {
ServletContext application = session.getServletContext();
application.setAttribute("info", "ApplicationScope");
return "success";
}
@RequestMapping("/testInternalResourceView")
public String testInternalResourceView() {
return "forward:/testApplicationScope";
}

日志:

DispatcherServlet - GET "/springMVC_3_war_exploded/testInternalResourceView", parameters={}
mvc.method.annotation.RequestMappingHandlerMapping - Mapped to com.hikaru.controller.testScopeController#testInternalResourceView()
view.InternalResourceView - View [InternalResourceView], model {}
view.InternalResourceView - Forwarding to [/testApplicationScope]
DispatcherServlet - "FORWARD" dispatch for GET "/springMVC_3_war_exploded/testApplicationScope", parameters={}
mvc.method.annotation.RequestMappingHandlerMapping - Mapped to com.hikaru.controller.testScopeController#testApplicationScope(HttpSession)

由日志文件中可以看出这个过程中创建了两个视图:一个是testInternalResourceView方法中的转发视图,另一个是testApplicationScope的ThymeLeaf视图

重定向视图 RedirectView
※转发和重定向的区别

转发和重定向都是servlet中最常用的浏览器响应、页面跳转方式,都会将转发或者重定向的地址响应给浏览器。

转发有一次浏览器请求,第二次是发生在服务器内部的请求,所以地址栏还是第一次发生请求的地址

重定向有两次请求,第一次是访问Servlet的请求,第二次是访问重定向页面的请求,最终地址栏地址是重定向的地址

转发可以获取请求域的共享数据,重定向是两次请求,不能获取请求域中的共享数据

转发可以访问WEB-INF下的资源,重定向不可以,但是重定向可以访问工程外部资源

@RequestMapping("/testInternalResourceView")
public String testInternalResourceView() {
return "forward:/testApplicationScope";
}
@RequestMapping("/testRedirectView")
public String testRedirectView() {
return "redirect:/testInternalResourceView";
}
<a th:href="@{/testInternalResourceView}">testInternalResourceView</a><br>
<a th:href="@{/testRedirectView}">testRedirectView</a><br>

两个地址栏的最终地址均是testInternalResourceView

SpringMVC视图控制器 mvc:view-controller标签

当控制器方法只用来实现页面跳转,即只需要设置视图名称时,可以使用MVC视图控制器来代替该控制器方法

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

当MVC中设置了任何一个视图控制器时,所有的控制器方法都会失效,因此需要手动开启MVC注解驱动

SpringMVC的视图解析器

posted @   Tod4  阅读(98)  评论(0编辑  收藏  举报
   
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起