【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"; }

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

后面的请求的响应报文就不再有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注解驱动
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步