SpringMVC:概述、控制器方法、RESTful风格范例
概述
MVC模式:
- 一种软件设计规范(架构模式),将业务逻辑、数据、显示分离的方法来组织代码,降低视图与业务逻辑的耦合 - Model:数据模型,提供要展示的数据。例如JavaBean组件(包含数据和行为)——模型数据查询和模型数据更新等功能- JavaBean:一个遵循特定写法的Java类,其它程序可以通过反射实例化JavaBean对象- 必须具有一个无参的构造函数
- 属性必须私有化
- 私有化的属性必须通过public类型的方法暴露给其它程序(get、set)
 
- JavaBean分为两类: - 实体类Bean:存储业务数据,例如Student、Emp、User等
- 业务处理Bean:处理业务逻辑和数据访问(Service 和 Dao)
 
 
- JavaBean:一个遵循特定写法的Java类,其它程序可以通过反射实例化JavaBean对象
- View:负责模型的展示,一般指用户界面
- Controller:接收用户请求并委托给模型处理(查询数据、改变数据状态),把模型返回的数据返回给视图——取得表单数据、调用业务逻辑、转向指定页面
 
- Model:数据模型,提供要展示的数据。例如JavaBean组件(包含数据和行为)——模型数据查询和模型数据更新等功能
- 工作流程:用户通过视图层发送请求到服务器,在服务器中请求被Controller接收,Controller 调用相应的Model层处理请求,处理完毕将结果返回到Controller,Controller再根据请求处理的结果 找到相应的View视图,渲染数据后响应给浏览器 
- 基于Servlet的MVC模式   - 模型:一个或者多个JavaBean对象
- 视图:一个或者多个JSP页面。JSP主要使用html和javabean来显示数据
- 控制器:一个或者多个Servlet对象,将请求转发给处理业务逻辑的JavaBean,将处理结果放到JavaBean中,输出给视图显示- JSP:一种Java servlet,主要用于实现Java web应用程序的用户界面部分
- Servlet:运行在 Web 服务器或应用服务器上的程序,作为请求和数据库(其他应用程序)的中间层(所有 Servlet 功能都是通过一个名为Servlet的接口向外暴露的,HttpServlet 抽象类实现了 Servlet 接口的很多常用功能)
 
 
- MVC框架的任务: - 将url映射到java类或java类的方法
- 封装用户提交的数据
- 处理请求–调用相关的业务处理–封装响应数据
- 渲染响应的数据(JSP/HTML等)
 
SpringMVC
- Spring 为表述层开发提供的一整套的解决方案 
- 以请求为驱动,可配置,包含多种视图技术——SpringMVC不关心使用的视图技术 
- 三层架构: - 表述层(或表示层,Web层)、业务逻辑层(Service)、数据访问层(持久层,Dao)
- 表述层包括前台页面和后台 servlet,即展示层和控制层,展示层负责结果的展示,控制层负责接收请求——通过前端控制器DispatcherServlet,对请求和响应进行统一处理
- 业务层:事务放到业务层来控制
- 数据访问层:包括数据层(即数据库)和数据访问层,和数据库交互
- 表述层:SpringMVC;业务层:Spring;持久层:MyBatis
 
- DispatcherServlet:前端控制器,配置在web.xml文件中 - 协调和组织不同组件完成请求处理并返回响应工作
- 继承自 HttpServlet,是 SpringMVC 的入口,所有的请求都通过它
- 根据自己定义的具体规则拦截匹配的请求,分发到目标 Controller 来处理。初始化 DispatcherServlet时,框架在web应用程序WEB-INF目录中寻找一个名为[servlet名称]-servlet.xml的文件,并定义相关的Beans,重写在全局中定义的任何Beans
 
- HandlerMapping:映射处理器,通过配置文件或注解找到Web请求路径对应的Handler 
- Handler:处理器,开发人员编写,处理器中完成业务逻辑功能 
- HandlerAdapter:处理器适配器。Handler有多种实现类型,适配器模式将具有类似功能但无共同父类的类定义统一的访问接口。HandlerAdapter是SpringMVC中Handler的访问接口 
- ViewResolver:视图解析器。解析多种视图(JSP等) 
- ModelAndView:封装了Model和View对象 
- 请求处理的流程:     - Tomcat 启动,对 DispatcherServlet 实例化,调用 DispatcherServlet 的 init() 初始化,初始化中完成:对 web.xml 中初始化参数的加载;建立 WebApplicationContext(SpringMVC的IOC容器);进行组件的初始化
- 客户端发出请求,Tomcat 接收这个请求,如果匹配 DispatcherServlet 在 web.xml 中配置的映射路径,Tomcat 将请求转交给 DispatcherServlet 处理
- DispatcherServlet 从 SpringMVC 的 IOC 容器中取出所有 HandlerMapping 实例(每个实例对应一个 HandlerMapping 接口的实现类)并遍历,每个 HandlerMapping 会根据请求信息,通过相应实现类中的方式去找到处理该请求的 Handler(执行程序,如 Controller 中的方法),并且将这个 Handler 与一堆 HandlerInterceptor (拦截器)封装成一个 HandlerExecutionChain 对象。一旦有一个 HandlerMapping 可以找到 Handler则退出遍历
- DispatcherServlet 取出 HandlerAdapter 组件,根据已经找到的 Handler,从所有HandlerAdapter 中找到可以处理该 Handler 的 HandlerAdapter 对象
- 执行 HandlerExecutionChain 中所有拦截器的 preHandler(),再利用 HandlerAdapter 执行 Handler ,执行完成得到 ModelAndView,再依次调用拦截器的 postHandler();
- 利用 ViewResolver 将 ModelAndView 或是 Exception (可解析成 ModelAndView) 解析成 View,View 会调用 render() 再根据 ModelAndView 中的数据渲染出页面;
- 最后依次调用拦截器的 afterCompletion(),请求结束
 
基本范例
- 导入依赖: - 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- <!--打包为web项目,此时会自动创建web.xml--> 
 <packaging>war</packaging>
 <dependencies>
 <!-- SpringMVC -->
 <dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring-webmvc</artifactId>
 <version>5.2.14.RELEASE</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整合包 -->
 <!--Thymeleaf: 一个服务器模板引擎。和Vue相比,Vue能够实现前后端完全分离,SpringMVC + Thymeleaf不是前后端完全分离的-->
 <dependency>
 <groupId>org.thymeleaf</groupId>
 <artifactId>thymeleaf-spring5</artifactId>
 <version>3.0.12.RELEASE</version>
 </dependency>
 </dependencies>- Thymeleaf是服务器端的模板引擎,在服务器端获取模板和数据,生成结果输出给浏览器呈现结果。和Vue等前端框架相比,Thymeleaf没有实现前后端完全分离
 
- 配置web.xml - 通过IDEA的Project Structure导入Web项目结构   
- 配置web.xml: - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 <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">
 <servlet>
 <servlet-name>SpringMVC_demo</servlet-name>
 <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
 <!-- 通过初始化参数指定SpringMVC配置文件的位置和名称 -->
 <init-param>
 <!-- contextConfigLocation为固定值 -->
 <param-name>contextConfigLocation</param-name>
 <param-value>classpath:SpringMVC_demo.xml</param-value>
 </init-param>
 <!--启动过程中有大量的初始化操作要做,将启动控制DispatcherServlet的初始化时间提前到服务器启动时,而非第一次请求时-->
 <load-on-startup>1</load-on-startup>
 </servlet>
 <servlet-mapping>
 <servlet-name>SpringMVC_demo</servlet-name>
 <url-pattern>/</url-pattern>
 </servlet-mapping>
 </web-app>
- 标签中使用 - /和- /*的区别- /所匹配的请求可以是/login或.html或.js或.css方式的请求路径,不能匹配.jsp请求路径的请求,避免在访问jsp页面时该请求被DispatcherServlet处理
- /*能够匹配所有请求。在使用过滤器时,若需要对所有请求进行过滤,需要使用- /*
 
 
- 创建请求控制器: - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17- package Controller; 
 import org.springframework.stereotype.Controller;
 import org.springframework.web.bind.annotation.RequestMapping;
 public class HelloController {
 
 public String protal(){
 //将逻辑视图返回
 return "index";
 }
 
 public String hello(){
 return "success";
 }
 }- 由于SpringMVC已经封装了servlet,因此不需要显式地继承servlet接口,所有的请求都已经通过web.xml中配置的DispatcherServlet接收和分发,请求的具体处理需要通过请求控制器(通过@Controller注解来表明该Pojo为一个请求控制器)来进行处理
- 处理请求的方法需要返回字符串类型的视图名称,该视图名称会被视图解析器解析,加上前缀和后缀组成视图的路径。通过Thymeleaf对视图进行渲染,最终转发到视图所对应页面——由于这里是在服务器端渲染,因此是转发。重定向的实现见后文
 
- 配置SpringMVC.xml:该文件在DispatcherServlet初始化时自动加载,因此该文件需要在固定的位置、有固定的名字 - 默认的位置:WEB-INF下;默认名称:<servlet-name>-servlet.xml(servlet-name为web.xml中<servlet-name>的值,上面的结果为 - SpringMVC_demo-servlet.xml)
- 可以通过web.xml中 - <init-param>标签进行配置,重新指定它的位置和名称(根据上面的表述,应当是resources下的SpringMVC_demo.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
 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
 <beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:mvc="http://www.springframework.org/schema/mvc"
 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
 http://www.springframework.org/schema/mvc
 http://www.springframework.org/schema/mvc/spring-mvc.xsd">
 <!--扫描控制层组件-->
 <context:component-scan base-package="Controller"></context:component-scan>
 <!-- 配置Thymeleaf视图解析器 -->
 <bean id="viewResolver" class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
 <property name="order" value="1"/>
 <property name="characterEncoding" value="UTF-8"/>
 <property name="templateEngine">
 <bean class="org.thymeleaf.spring5.SpringTemplateEngine">
 <property name="templateResolver">
 <bean class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver">
 <!-- 视图前缀 -->
 <!--模板的位置-->
 <property name="prefix" value="/WEB-INF/templates/"/>
 <!-- 视图后缀 -->
 <property name="suffix" value=".html"/>
 <property name="templateMode" value="HTML5"/>
 <property name="characterEncoding" value="UTF-8"/>
 </bean>
 </property>
 </bean>
 </property>
 </bean>
 <!--访问静态资源,例如图片、视频、jsp等-->
 <mvc:default-servlet-handler/>
 <!-- 开启mvc注解驱动 -->
 <mvc:annotation-driven>
 <mvc:message-converters>
 <!-- 处理响应中文内容乱码 -->
 <bean class="org.springframework.http.converter.StringHttpMessageConverter">
 <property name="defaultCharset" value="UTF-8"/>
 <property name="supportedMediaTypes">
 <list>
 <value>text/html</value>
 <value>application/json</value>
 </list>
 </property>
 </bean>
 </mvc:message-converters>
 </mvc:annotation-driven>
 </beans>
- <mvc:default-servlet-handler/>:在SpringMVC上下文中定义一个org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler,它会对进入DispatcherServlet的URL进行筛查,如果发现是静态资源请求,就将该请求转由Web服务器(Tomcat)默认的Servlet处理,如果不是静态资源请求,才由DispatcherServlet继续处理——静态资源访问是通过url直接去定位资源,中间不需要繁琐的解析操作
- 如果是多个模板路径,则为如下的配置。此时,WEB-INF下有多个模板文件夹:templates和templates/RequestMapping - 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- <!-- 配置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">
 <!--这里引入多个模板路径,因此需要用templateResolvers!Controller中返回的字符串照常即可-->
 <property name="templateResolvers">
 <set>
 <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"/>
 <property name="checkExistence" value="true"/>
 </bean>
 
 <bean class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver">
 <!-- 视图前缀 -->
 <!--模板的位置-->
 <property name="prefix" value="/WEB-INF/templates/RequestMapping/"/>
 <!-- 视图后缀 -->
 <property name="suffix" value=".html"/>
 <property name="templateMode" value="HTML5"/>
 <property name="characterEncoding" value="UTF-8"/>
 <property name="checkExistence" value="true"/>
 </bean>
 </set>
 </property>
 </bean>
 </property>
 </bean>
 
- 配置Tomcat(预先安装tomcat8.5)   - /Demo1_war_exploded:工程的上下文路径   
 
- 使用SpringMVC必须配置的三大件: 处理器映射器、处理器适配器、视图解析器。通常只需要手动配置视图解析器,处理器映射器和处理器适配器只需要开启注解驱动即可 
RequestMapping注解
- 将请求和处理请求的控制器方法关联起来,建立映射关系。SpringMVC 接收到指定的请求则找相应的控制器方法处理这个请求
注解的位置
- 标识一个类:设置映射请求的请求路径的初始信息 
- 标识一个方法:设置映射请求的请求路径的具体信息 
- 下例的请求路径为 - /requestMapping/location- 1 
 2
 3
 4
 5
 6
 7
 8
 public class TestRequestMapping {
 
 public String testLocation() {
 // 测试在类上标注@RequestMapping
 return "testLocation";
 }
 }
注解的属性
- @RequestMapping注解的value属性: - 通过请求地址匹配请求映射——value属性是一个字符串类型的数组,表示该请求映射能够匹配多个请求地址所对应的请求 
- 这表明多个请求会由一个控制器方法处理 - 1 
 2
 3
 4
 5
 public String testValue() {
 // 测试@RequestMapping的Value属性
 return "testValue";
 }
 
- @RequestMapping注解的method属性: - 通过请求的请求方式(get或post)匹配请求映射 
- method属性是一个RequestMethod类型数组,该请求映射能够匹配多种请求方式的请求。若当前请求的地址满足value但请求方式不满足method,则浏览器报错 405:Request method ‘POST’ not supported 
- SpringMVC提供@RequestMapping的派生注解 - 处理get请求的映射:@GetMapping
- 处理post请求的映射:@PostMapping
- 处理put请求的映射:@PutMapping
- 处理delete请求的映射:@DeleteMapping
 
- 若要发送put和delete请求,则需要通过spring提供的过滤器HiddenHttpMethodFilter(见后文RESTful) - 1 
 2
 3
 4
 5
 6
 7- //<form th:action="@{/requestMapping/method}" method="post"> <!--提交时跳转到该页面--> 
 // <input type="submit">
 //</form>
 public String testMethod() {
 return "testMethod";
 }
 
- @RequestMapping注解的params属性: - 通过请求的请求参数匹配请求映射 
- params属性是一个字符串类型的数组,通过四种表达式设置请求参数和请求映射的匹配关系 - “param”:匹配的请求必须携带param请求参数
- “!param”:匹配的请求必须不能携带param请求参数
- “param=value”:匹配的请求必须携带param请求参数,且param=value
- “param!=value”:匹配的请求必须携带param请求参数,且param!=value
 
- 若不满足params属性,则页面报错400 - 1 
 2
 3
 4
 5
 6- //<a th:href="@{/requestMapping/params(username='admin',password=123456)}">测试@RequestMapping的params属性</a><br> 
 public String testParams() {
 // 必须包含username(不包含为!username),password必须不是123456
 return "testParams";
 }
 
- @RequestMapping注解的headers属性: - 通过请求的请求头信息匹配请求映射
- headers属性是一个字符串类型的数组,通过四种表达式设置请求头信息和请求映射的匹配关系- “header”:匹配的请求必须携带header请求头信息
- “!header”:匹配的请求必须不能携带header请求头信息
- “header=value”:匹配的请求必须携带header请求头信息,且header=value
- “header!=value”:匹配的请求必须携带header请求头信息,且header!=value
 
- 若请求满足@RequestMapping注解的value和method属性,但不满足headers属性,页面显示404错误,资源未找到
 
- ant风格的路径: - ?:任意的单个字符
- *:任意的0个或多个字符
- **:任意层数的任意目录
 
- 路径中的占位符: - 原始方式:/deleteUser?id=1;REST方式:/deleteUser/1 
- 请求某些数据通过路径的方式传输到服务器中,可以在@RequestMapping注解的value属性中通过占位符 - {xxx}表示传输的数据。通过@PathVariable注解,将占位符所表示的数据赋给控制器方法的形参- 1 
 2
 3
 4
 5
 6- //<a th:href="@{/requestMapping/placeholder/1/admin}">测试路径中的占位符-->/testRest</a><br> 
 public String testRest( String id, String username){
 System.out.println("id:"+id+",username:"+username);
 return "testPlaceholder";
 }
 
获取参数
通过ServletAPI获取
- 将HttpServletRequest作为控制器方法的形参,该参数为封装了当前请求报文的对象 - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14- /*<form th:action="@{/params/servletApi}" method="post"> 
 用户名:<input type="text" name="username"><br>
 密码:<input type="password" name="password"><br>
 <input type="submit" value="登录"><br>
 </form>*/
 public String testServletApi(HttpServletRequest request) {
 // 测试ServletAPI获取请求参数
 // 获取request对象
 HttpSession session = request.getSession();
 String username = request.getParameter("username"); // 参数名为index.html中相应的字段名
 String password = request.getParameter("password");
 return "testServletApi";
 }
通过控制器方法的形参获取请求参数
- 5个方式 
- 形参和请求参数同名 - 1 
 2
 3
 4
 5
 6
 7
 public String testParams(String username, String password) {
 // 测试使用方法参数获取请求参数
 // 要求形参名 = 请求参数名
 System.out.println(username + " " + password);
 return "testParams";
 }- 若有多个同名的请求参数,形参设置为字符串数组 / 字符串- 字符串数组:数组中包含了每一个数据
- 字符串:每个数据之间使用逗号拼接
 
 
- 若有多个同名的请求参数,形参设置为字符串数组 / 字符串
- @RequestParam:请求参数和形参创建映射关系 - 属性: - value:指定为形参赋值的请求参数名
- required:设置是否必须传输此请求参数,默认值为true;若没有传输该请求参数,且没有设置defaultValue属性,则页面报错400
- defaultValue:当value所指定的请求参数没有传输或传输的值为””时,设置为默认值
 - 1 
 2
 3
 4
 5
 6
 7
 8
 public String testRequestParam( String name,
 String password) {
 // @RequestParam获取请求参数
 // 要求形参名 = 请求参数名
 System.out.println(name + " " + password);
 return "testRequestParam";
 }
 
- @RequestHeader:请求头信息和形参创建映射关系,属性同上(获取请求头里的字段信息),用得很少 - 1 
 2
 3
 4
 5
 6
 public String testHeader( String refer) {
 // RequestHeader获取请求参数
 System.out.println(refer);
 return "testHeader";
 }
- @CookieValue:cookie数据和形参创建映射关系,属性同上(获取cookie里的字段信息),用得很少 - 1 
 2
 3
 4
 5
 6
 public String testCookie( String sessionId) {
 // 测试RequestHeader获取请求参数
 System.out.println(sessionId);
 return "testCookie";
 }
- 通过POJO获取请求参数:形参设置一个POJO,若请求参数的参数名和实体类的属性名一致,则属性被赋值为请求参数值——注意,是属性(有get、set方法的),而不是成员变量 - 1 
 2
 3
 4
 5
 6
 7
 public String testPojo(User user) {
 // 测试Pojo获取请求参数
 // username, password
 System.out.println(user);
 return "testPojo";
 }
请求参数的乱码问题
- 使用SpringMVC提供的编码过滤器CharacterEncodingFilter(必须在web.xml中注册) - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18- <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>forceEncoding</param-name>
 <param-value>true</param-value>
 </init-param>
 </filter>
 <filter-mapping>
 <filter-name>CharacterEncodingFilter</filter-name>
 <url-pattern>/*</url-pattern>
 </filter-mapping>
 
 <servlet>...
- 处理编码的过滤器一定要配置到其他过滤器之前 
域共享数据
- 域对象:主要用在web应用中,负责存储数据(通俗的讲,这个对象本身可以存储一定范围内的所有数据) - 可以在不同的Servlet中进行数据传递的对象称为域对象
- 域对象的数据以key-value形式存储
- 都有如下方法:- setAttribute(name, value):存储数据
- getAttribute(name)
- removeAttribute(name)
 
- 四个域- page(PageContextImpl):当前jsp页面范围内有效
- request(HttpServletRequest):一次请求响应范围有效,同一客户端的不同请求,无法获取域对象中的值
- session(HttpSession):一次会话范围有效,同一客户端在一次会话内的多个请求,都可以获取到session域内的值;可以看成一次浏览器关闭
- application(ServletContext):一次应用程序范围;可以看成一次服务器关闭
 
 
- 使用ServletAPI向request域对象共享数据:略,只需要设置一个形参HttpServletRequest request即可 
- 使用ModelAndView向request域对象共享数据:官方推荐 - Model:向request域共享数据;View:设置逻辑视图,实现页面跳转 
- 实际上,之前的控制器方法返回逻辑视图(字符串)之后,都会被封装为ModelAndView对象 - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 public class TestScope {
 
 public ModelAndView testModelAndView() {
 // ModelAndView向request域共享数据
 ModelAndView mav = new ModelAndView();
 //向request域域共享数据
 mav.addObject("testScope", "hello, ModelAndView");
 //设置视图,实现页面跳转
 mav.setViewName("testModelAndView");
 return mav;
 }
 }
- ModelAndView中的key-value对,将用于在相应View模板(这里为testModelAndView.html,模板的具体路径需要加上SpringMVC.xml中定义的前缀和后缀)中填充相应位置,进行渲染 - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 <html lang="en" xmlns:th="http://www.thymeleaf.org">
 <head>
 <meta charset="UTF-8">
 <title>Scope_MandV</title>
 </head>
 <body>
 <h1>Scope_ModelAndView</h1>
 <p th:text="${testScope}"></p>
 </body>
 </html>
 
- 使用map向request域对象共享数据:key-value以相同方式被渲染;这里需要设置形参为 Map 类型 - 1 
 2
 3
 4
 5
 6
 public String testMap(Map<String, Object> map){
 // Map向request域对象共享数据
 map.put("testMap", "hello, Map");
 return "testMap";
 }
- 使用ModelMap向request域对象共享数据:这里需要设置形参为 ModelMap 类型 - 1 
 2
 3
 4
 5
 6
 public String testModelMap(ModelMap modelMap){
 // ModelMap向request域对象共享数据
 modelMap.addAttribute("testModelMap", "hello, ModelMap");
 return "testModelMap";
 }
- Model、ModelMap、Map的关系:Model、ModelMap、Map类型的参数本质上都是 BindingAwareModelMap 类型 - 为什么ModelAndView可以new,但Model、Map、ModelMap需要为形式参数? 
- 向session域共享数据(直接用serlvet的api),此时相应模板位置为 - <p th:text="${session.testSessionScope}"></p>- 1 
 2
 3
 4
 5
 6
 public String testSession(HttpSession session){
 // 向session域共享数据
 session.setAttribute("testSessionScope", "hello,session");
 return "testSession";
 }- 注意session的钝化和活化
 
- 向application域共享数据(直接用serlvet的api),此时相应模板位置为 - <p th:text="${application.testApplicationScope}"></p>- 1 
 2
 3
 4
 5
 6
 7
 public String testApplication(HttpSession session){
 // 向application域共享数据
 ServletContext application = session.getServletContext();
 application.setAttribute("testApplicationScope", "hello,application");
 return "testApplication";
 }
- 以上的返回值,都会被封装到一个ModelAndView对象中 
SpringMVC的视图
- SpringMVC中的视图是View接口,渲染数据,将Model中的数据展示给用户
- 若使用的视图技术为Thymeleaf,在SpringMVC的配置文件中配置了Thymeleaf的视图解析器,通过该解析器解析得到的是ThymeleafView
ThymeleafView
- 当控制器方法中设置的视图名称没有任何前缀时,会被SpringMVC配置文件中的视图解析器解析,最终路径为视图前缀 + 视图名称 + 视图后缀,通过转发的方式实现跳转 - 1 
 2
 3
 4
 public String testThymeleafView(){
 return "testThymeleaf";
 }
转发视图
- SpringMVC中默认的转发视图是InternalResourceView 
- 当控制器方法中所设置的视图名称以”forward:”为前缀时,创建InternalResourceView视图。该视图名称不会被SpringMVC配置文件中的视图解析器解析,而将前缀”forward:”去掉,剩余部分作为最终路径,通过转发的方式实现跳转 - 1 
 2
 3
 4
 public String testInternalResourceView(){
 return "forward:/test/model";
 }
重定向视图
- SpringMVC中默认的重定向视图是RedirectView 
- 当控制器方法中所设置的视图名称以”redirect:”为前缀时,创建RedirectView视图。该视图视图名称不会被SpringMVC配置文件中的视图解析器解析,而将前缀”redirect:”去掉,剩余部分作为最终路径,通过重定向的方式实现跳转 
- 这里重定向的时候,会自动为最终路径添加上下文路径(localhost:8080/…/) - 1 
 2
 3
 4
 public String testRedirectView(){
 return "redirect:/test/model";
 }
- 这里转发视图和重定向视图的返回值,为一个新的请求路径(url) 
视图控制器
- 如果一个控制器方法只是用来实现页面跳转,即只需要返回视图名称,没有其他业务逻辑时,可以在SpringMVC配置文件中view-controller标签来替代 - 1 
 2
 3
 4
 5- <!-- 
 path:设置处理的请求地址
 view-name:设置请求地址所对应的视图名称
 -->
 <mvc:view-controller path="/" view-name="index"></mvc:view-controller>
- 注意,此时其他控制器的请求映射将全部失效,需要在SpringMVC配置文件中再添加开启mvc注解驱动的标签: - <mvc:annotation-driven />
小结
- 这四章所记录的内容都与控制器方法相关。RequestMapping决定了使用哪一个控制器方法,传入参数使得用户填入的表单数据能够发送到服务器,域共享数据使得服务器的处理结果能够返回到用户页面进行渲染,SpringMVC视图则记录了不同的页面跳转方式(转发、重定向)
RESTful
RESTful
- REST:Representational State Transfer,表现层资源状态转移。一种看待服务器的方式 
- 将服务器看作是由很多离散的资源(图片、数据库的表等)组成,一个资源由一个或多个URI来标识。URI是资源的名称,也是资源在Web上的地址。客户端应用通过资源的URI与其进行交互 
- 资源的表述:一段对于资源在某个特定时刻状态的描述。可以在客户端-服务器端之间转移(交换) - 可以有多种格式,例如HTML/XML/JSON/纯文本/图片/视频/音频等
- 资源的表述格式可以通过协商机制来确定
 
- 状态转移:在客户端和服务器端之间转移(transfer)代表资源状态的表述。通过转移和操作资源的表述,间接实现操作资源 
- HTTP 协议里四个表示操作方式的动词:GET、POST、PUT、DELETE - 分别对应四种基本操作:GET获取资源、POST新建资源、PUT更新资源、DELETE删除资源
- REST 风格提倡 URL 地址使用统一的风格设计,从前到后各个单词使用斜杠分开,不使用问号键值对的方式携带请求参数,而将发给服务器的数据作为 URL 地址的一部分
- 不去考虑到底对资源做什么操作,只关注请求什么资源(即,请求路径是什么)
   
HiddenHttpMethodFilter
- 浏览器只支持发送get和post方式的请求 
- SpringMVC 提供 HiddenHttpMethodFilter 将 POST 请求转换为 DELETE 或 PUT 请求 
- HiddenHttpMethodFilter 处理 put 和 delete 请求的条件: - 当前请求的请求方式必须为post
- 当前请求必须传输请求参数 _method
 
- HiddenHttpMethodFilter 过滤器将当前请求的请求方式转换为请求参数 - _method的值,即- _method是最终的请求方式
- 在web.xml中注册HiddenHttpMethodFilter - 1 
 2
 3
 4
 5
 6
 7
 8
 9- <!--注册HiddenHttpMethodFilter--> 
 <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>- 1 
 2
 3
 4- <form th:action="@{/user}" method="post"> 
 <input type="submit" value="登录"><br>
 <input type="hidden" name="_method" value="put"><br>
 </form>
- 必须先注册 CharacterEncodingFilter,因为 CharacterEncodingFilter 通过 request.setCharacterEncoding(encoding) 设置字符集,该方法要求前面不能有任何获取请求参数的操作,而 HiddenHttpMethodFilter 有一个获取参数 - _method的操作
范例
- 配置: - web.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
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 <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">
 <!--配置springMVC的编码过滤器-->
 <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>forceEncoding</param-name>
 <param-value>true</param-value>
 </init-param>
 </filter>
 <filter-mapping>
 <filter-name>CharacterEncodingFilter</filter-name>
 <url-pattern>/*</url-pattern>
 </filter-mapping>
 <!--注册HiddenHttpMethodFilter-->
 <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>
 <init-param>
 <param-name>contextConfigLocation</param-name>
 <param-value>classpath:SpringMVC.xml</param-value>
 </init-param>
 <load-on-startup>1</load-on-startup>
 </servlet>
 <servlet-mapping>
 <servlet-name>SpringMVC</servlet-name>
 <url-pattern>/</url-pattern>
 </servlet-mapping>
 </web-app>
- 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
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 <beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:mvc="http://www.springframework.org/schema/mvc"
 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
 http://www.springframework.org/schema/mvc
 http://www.springframework.org/schema/mvc/spring-mvc.xsd">
 <context:component-scan base-package="Controller"/>
 <context:component-scan base-package="Dao"/>
 <!-- 配置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">
 <!--这里引入多个模板路径,因此需要用templateResolvers!Controller中返回的字符串照常即可-->
 <property name="templateResolvers">
 <set>
 <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"/>
 <property name="checkExistence" value="true"/>
 </bean>
 </set>
 </property>
 </bean>
 </property>
 </bean>
 <!--使用默认的servlet处理静态资源-->
 <!--当前dispatcherServlet的urlpattern为/,无法处理静态资源-->
 <!--因此需要此配置,此时请求会先由dispatcherServlet处理-->
 <mvc:default-servlet-handler />
 <!--开启注解驱动-->
 <mvc:annotation-driven />
 <!--视图控制器-->
 <!--<mvc:view-controller path="/" view-name="index"/>-->
 </beans>
 
- 实体类 - 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- package Pojo; 
 public class Emp {
 private Integer id;
 private String name;
 private String email;
 private Integer gender; //male:1, female:0
 public Emp() {}
 public Emp(Integer id, String name, String email, Integer gender) {
 this.id = id;
 this.name = name;
 this.email = email;
 this.gender = gender;
 }
 public void setId(Integer id) {
 this.id = id;
 }
 public void setName(String name) {
 this.name = name;
 }
 public void setEmail(String email) {
 this.email = email;
 }
 public void setGender(Integer gender) {
 this.gender = gender;
 }
 public Integer getId() {
 return id;
 }
 public String getName() {
 return name;
 }
 public String getEmail() {
 return email;
 }
 public Integer getGender() {
 return gender;
 }
 
 public String toString() {
 return "Emp{" +
 "id=" + id +
 ", name='" + name + '\'' +
 ", email='" + email + '\'' +
 ", gender=" + gender +
 '}';
 }
 }
- Dao与模拟数据(这里模拟数据放到Dao中,而非数据库中的表)。SpringMVC的配置文件中,要添加对Dao、Controller包的注解扫描!(见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
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43- package Dao; 
 import org.springframework.stereotype.Repository;
 import java.util.*;
 import Pojo.Emp;
 public class EmpDao {
 private static Map<Integer, Emp> employees = null;
 static{
 employees = new HashMap<Integer, Emp>();
 employees.put(1001, new Emp(1001, "E-AA", "aa@163.com", 1));
 employees.put(1002, new Emp(1002, "E-BB", "bb@163.com", 1));
 employees.put(1003, new Emp(1003, "E-CC", "cc@163.com", 0));
 employees.put(1004, new Emp(1004, "E-DD", "dd@163.com", 0));
 employees.put(1005, new Emp(1005, "E-EE", "ee@163.com", 1));
 }
 private static Integer initId = 1006;
 public void save(Emp employee){
 if(employee.getId() == null){
 employee.setId(initId++);
 }
 employees.put(employee.getId(), employee);
 }
 public Collection<Emp> getAll(){
 return employees.values();
 }
 public Emp get(Integer id){
 return employees.get(id);
 }
 public void delete(Integer id){
 employees.remove(id);
 }
 }
- 查询: - Controller: - 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- package Controller; 
 import Dao.EmpDao;
 import Pojo.Emp;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Controller;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RequestMethod;
 import org.springframework.web.servlet.ModelAndView;
 import java.util.*;
 public class DemoController {
 
 private EmpDao empDao;
 
 public String protal() {
 return "index";
 }
 // 查询数据
 
 public ModelAndView getAllData() {
 Collection<Emp> empList = empDao.getAll();
 ModelAndView mav = new ModelAndView();
 mav.addObject("empList", empList);
 mav.setViewName("showAllEmps");
 return mav;
 }
 }
- Template: - index.html - 1 
 2
 3
 4
 5
 6
 7
 8
 <html lang="en" xmlns:th="http://www.thymeleaf.org">
 <head>
 <meta charset="UTF-8">
 <title>首页</title>
 </head>
 <body>
 <a th:href="@{/employee}">查询所有员工数据</a><br>
 
 
- showAllEmps.html(引入css,优化页面——css为静态资源,因此需要在SpringMVC配置文件中添加 - <mvc:default-servlet-handler />,静态资源请求由defaultServlet处理,而”/static/css/index_work.css“为WEB-INF同级的路径)- 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
 <html lang="en" xmlns:th="http://www.thymeleaf.org">
 <head>
 <meta charset="UTF-8">
 <title>EmpList</title>
 <link rel="stylesheet" th:href="@{/static/css/index_work.css}">
 </head>
 <body>
 <table border="1" cellpadding="0" cellspacing="0" style="text-align: center;" id="dataTable">
 <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="@{/addEmp}">add</a>)</th>
 </tr>
 <tr th:each="employee : ${empList}">
 <td th:text="${employee.id}"></td>
 <td th:text="${employee.name}"></td>
 <td th:text="${employee.email}"></td>
 <td th:text="${employee.gender}"></td>
 <td>
 <a th:href="@{'/employee/'+${employee.id}}">delete</a>-->
 <a th:href="@{'/employee/'+${employee.id}}">update</a>-->
 </td>
 </tr>
 </table>
 </body>
 </html>
- SpringMVC的配置文件添加默认servlet配置,用于处理静态资源: - 1 
 2
 3
 4- <!--使用默认的servlet处理静态资源--> 
 <!--当前dispatcherServlet的urlpattern为/,无法处理静态资源-->
 <!--因此需要此配置,此时请求会先由dispatcherServlet处理-->
 <mvc:default-servlet-handler />

- 添加: - 转入添加页面: - 1 
 2
 3
 4
 5
 6
 7- <!--index.html--> 
 <a th:href="@{/addEmp}">添加数据</a><br>
 <!--showAllEmps.html-->
 ...
 <th>options(<a th:href="@{/addEmp}">add</a>)</th>
 ...- 1 
 2
 3
 4
 public String addDataRedirect() {
 return "addEmp";
 }
- 添加的控制器逻辑: - 1 
 2
 3
 4
 5
 6
 public String addData(Emp employee) {
 empDao.save(employee);
 
 return "redirect:/employee";
 }
- 添加的页面: - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 <html lang="en" xmlns:th="http://www.thymeleaf.org">
 <head>
 <meta charset="UTF-8">
 <title>添加数据</title>
 <link rel="stylesheet" th:href="@{/static/css/index_work.css}">
 </head>
 <body>
 <form th:action="@{/employee}" method="post">
 name:<input type="text" name="name"><br>
 email:<input type="text" name="email"><br>
 gender:<input type="radio" name="gender" value="1">male
 <input type="radio" name="gender" value="0">female<br>
 <input type="submit" value="添加数据"><br>
 </form>
 </body>
 </html>
 
- 更新: - 转入更新页面(index.html) - 1 - <a th:href="@{'/employee/'+${employee.id}}">update</a>--> 
- 更新的控制器逻辑 - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 public ModelAndView updateIndex( Integer id) {
 Emp update = empDao.get(id);
 ModelAndView mav = new ModelAndView();
 mav.addObject("emp", update);
 mav.setViewName("updateEmp");
 return mav;
 }
 
 public String updateData(Emp employee) {
 empDao.save(employee);
 
 return "redirect:/employee";
 }
- 更新页面 - 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
 <html lang="en" xmlns:th="http://www.thymeleaf.org">
 <head>
 <meta charset="UTF-8">
 <title>更新数据</title>
 <link rel="stylesheet" th:href="@{/static/css/index_work.css}">
 </head>
 <body>
 <tr>
 <th>id</th>
 <th>lastName</th>
 <th>email</th>
 <th>gender</th>
 </tr>
 <tr>
 <td th:text="${emp.id}"></td>
 <td th:text="${emp.name}"></td>
 <td th:text="${emp.email}"></td>
 <td th:text="${emp.gender}"></td>
 </tr>
 <hr>
 <form th:action="@{/employee}" method="post">
 <input type="hidden" name="_method" value="put"><br>
 <input type="hidden" name="id" th:value="${emp.id}"><br>
 name:<input type="text" name="name" th:value="${emp.name}" ><br>
 email:<input type="text" name="email" th:value="${emp.email}"><br>
 gender:<input type="radio" name="gender" value="1">male
 <input type="radio" name="gender" value="0">female<br>
 <input type="submit" value="更新数据"><br>
 </form>
 </body>
 </html>
 
- 删除: - 每点击一次超链接,就要自动提交一次表单(用vue实现) - 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- <div id="app"> 
 ...
 <td>
 <a @click="deleteEmployee()" th:href="@{'/employee/'+${employee.id}}">delete</a>
 <a th:href="@{'/employee/'+${employee.id}}">update</a>-->
 </td>
 ...
 </table>
 <form method="post">
 <input type="hidden" name="_method" value="delete">
 </form>
 </div>
 <script type="text/javascript" th:src="@{/static/js/vue.js}"></script>
 <script type="text/javascript">
 var vue = new Vue({
 methods:{
 deleteEmployee(){
 //获取form表单
 var form = document.getElementsByTagName("form")[0];
 //将超链接的href属性值赋值给form表单的action属性
 form.action = event.target.href; //event.target表示当前触发事件的标签
 //表单提交
 form.submit();
 //阻止超链接的默认行为
 event.preventDefault();
 }
 }
 });
 </script>
- 删除的控制器逻辑: - 1 
 2
 3
 4
 5
 6
 7- // 删除数据 
 public String deleteIndex( Integer id) {
 System.out.println(id);
 empDao.delete(id);
 return "redirect:/employee";
 }
 
- 文件结构:   
 
        