域对象共享数据
使用servletAPI(向request域对象共享数据)
使用ModelAndView(向request域对象共享数据)
测试:
使用Model(向request域对象共享数据)
使用Map(向request域对象共享数据)
使用ModelMap(向request域对象共享数据)
Model,Map,ModelMap的关系
通过反射获取方法形参中三者的类型均为
class org.springframework.validation.support.BindingAwareModelMap
继承关系如下
这个类可用来创建三者的实例化对象
源码解析:控制器方法执行后都会返回统一的ModelAndView对象
向session域共享数据
向application域共享数据
SpringMVC的视图
SpringMVC中的视图时View接口,视图的作用渲染数据,将模型Model中的数据展示给用户
SpringMVC视图的种类很多,默认有转发视图InternalResourceView和重定向视图RedirectView
当工程引入jstl的依赖,转发视图会自动转换为jstlView
若使用视图技术为Thymeleaf,在SpringMVC的配置文件中配置了Thymeleaf的视图解析器,由此视图解析器解析之后所得到的是ThymeleafView
ThymeleafView
当控制器方法中所设置的视图名称没有任何前缀时,此时的视图名称会被SpringMVC配置文件中所配置的视图解析器解析,视图名称拼接视图前缀和视图后缀所得到的最终路径,会通过转发的方式实现跳转。
转发视图
SpringMVC中默认的转发视图是InternalResourceView
SpringMVC中创建转发视图的情况:
当控制器方法中所设置的视图名称以“ forward:”为前缀时,创建InternalResourceView视图,此时的视图名称不会被SpringMVC配置文件所配置的视图解析器解析,而是会将前缀“forward:”去掉,剩余部分作为最终路径通过转发的方式实现跳转
重定向视图
SpringMVC中默认的重定向视图时RedirectView
当控制器方法中所设置的视图名称以“redirect:”为前缀时,创建RedirectView视图,此时的视图名称不会被SpringMVC配置文件所配置的视图解析器解析,而是会将前缀“redirect:”去掉,剩余部分作为最终路径通过重定向的方式实现跳转
视图控制器view-controller
springMVC.xml
配置了view-controller之后,控制器中所有的请求映射全部失效,需要开启MVC注解驱动
视图解析器InternalResourceViewResolver
RESTFul
简介
是一种软件架构的风格(格式)
REST:Representational State Transfer,表现层资源状态转移
资源
是一种看待服务器的方式,即将服务器看作是由很多离散的资源组成。每个资源是服务器上一个可命名的抽象概念,所以它不仅仅能代表服务器文件系统中的一个文件,数据库中的一张表等等具体的东西,可以将资源设计的要多抽象有多抽象。只要想象力允许而且客户端应用开发者能够理解。与面向对象设计类似,资源是以名词为核心来组织的,首先关注的是名词。一个资源可以由一个或多个URI来标识。URI既是资源的名称,也是资源在Web服务器上的地址,对某个资源感兴趣的客户端应用,可以通过资源的URI与其进行交互。
资源的表述
资源的表述是一段对于资源在某个特定时刻的状态的描述。可以在客户端-服务器之间转移(交换)。资源的表述可以有多种格式,例如HTML/XML/JSON/纯文本/图片/视频/音频等等。资源的表述格式可以通过协商机制来确定,请求-响应方向的表述通常使用不同的格式。
状态转移
状态转移说的是:在客户端和服务器端之间转移(transfer)代表资源的表述,通过转移和操作资源的表述,来间接实现操作资源的目的。
RESTFul的实现
具体说,就是HTTP协议里面,四个表示操作方式的动词:GET、POST、PUT、DELETE。
它们分别对应四种基础操作:GET用来获取资源,POST用来新建资源,PUT用来更新资源,DELETE用来删除资源。
REST风格提倡的是地址使用统一的风格设计,从前到后各个单词用斜杠分开,不使用问号键值对方式携带请求参数,而是将要发生给服务器的数据作为URL地址的一部分,以保证整体风格的一致性。
使用RESTFul模拟操作用户资源(get和post请求)
@Controller
public class UserController {
/**
* 使用RESTFul模拟用户资源的增删改查
* /users GET 查询所有用户信息
* /user/1 GET 根据用户id查询所有用户信息
* /user POST 添加用户信息
* /user/1 DELETE 删除用户信息
* /user PUT 更新用户信息
*/
@RequestMapping(value = "/users", method = RequestMethod.GET)
public String getAllUser() {
System.out.println("查询所有用户信息");
return "success";
}
@RequestMapping(value = "/user/{id}", method = RequestMethod.GET)
public String getUserById() {
System.out.println("根据id查询用户信息");
return "success";
}
@RequestMapping(value = "/user", method = RequestMethod.POST)
public String insertUser(String username, String password) {
System.out.println("添加用户,username:" + username + "password:" + password);
return "success";
}
}
测试
HiddenHttpMethodFilter处理PUT和DELETE请求方式
编码过滤器和HiddenHttpMethodFilter的顺序:编码过滤器顺序在前。
查看源码
模拟PUT和DELETE请求
但是一般删除都是超链接,需要绑定单击事件,取消默认跳转,并提交表单。
RESTFul案例
和传统CRUD一样,实现对员工信息的增删改查
准备工作
Employee实体类
public class Employee {
private Integer id;
private String lastName;
private String email;
private Integer gender;
public Employee() {
}
public Employee(Integer id, String lastName, String email, Integer gender) {
this.id = id;
this.lastName = lastName;
this.email = email;
this.gender = gender;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Integer getGender() {
return gender;
}
public void setGender(Integer gender) {
this.gender = gender;
}
@Override
public String toString() {
return "Employee{" +
"id=" + id +
", lastName='" + lastName + '\'' +
", email='" + email + '\'' +
", gender=" + gender +
'}';
}
}
EmployeeDao
@Repository
public class EmployeeDao {
private static Map<Integer, Employee> employees = null;
static {
employees = new HashMap<Integer, Employee>();
employees.put(1001, new Employee(1001, "HUI", "111.@qq.com", 1));
employees.put(1002, new Employee(1002, "DSA", "321.@qq.com", 0));
employees.put(1003, new Employee(1003, "SSD", "133.@qq.com", 0));
employees.put(1004, new Employee(1004, "FSA", "123.@qq.com", 1));
employees.put(1005, new Employee(1005, "GFS", "153.@qq.com", 0));
}
private static Integer initId = 1006;
public void save(Employee employee) {
if (employee.getId() == null) {
employee.setId(initId++);
}
employees.put(employee.getId(), employee);
}
public Collection<Employee> getAll() {
return employees.values();
}
public Employee get(Integer id) {
return employees.get(id);
}
public void delete(Integer id) {
employees.remove(id);
}
}
功能实现
查看全部员工信息
删除功能
注意:超链接路径
新建文件夹
在页面中引入vue.js,并为超链接添加单击事件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" xmlns:th="http://www.thymeleaf.org">
<title>员工信息</title>
</head>
<body>
<table id="dataTable" border="1" cellspacing="0" cellpadding="0" style="text-align: center;">
<tr>
<th colspan="5">Employee Info</th>
</tr>
<tr>
<th>id</th>
<th>lastName</th>
<th>email</th>
<th>gender</th>
<th>options</th>
</tr>
<tr th:each="employee:${employeeList}">
<td th:text="${employee.id}"></td>
<td th:text="${employee.lastName}"></td>
<td th:text="${employee.email}"></td>
<td th:text="${employee.gender}"></td>
<td>
<a @click="deleteEmployee" th:href="@{/employee/}+${employee.id}">delete</a>
<a href="">update</a>
</td>
</tr>
</table>
<form id="deleteForm" method="post">
<input type="hidden" name="_method" value="delete">
</form>
<script type="text/javascript" th:src="@{/static/js/vue.min.js}"></script>
<script type="text/javascript">
var vue = new Vue({
el: "#dataTable",
methods: {
deleteEmployee: function (event) {
var deleteForm = document.getElementById("deleteForm");
//将触发点击事件的超链接的href属性赋值给表单的action
deleteForm.action = event.target.href;
//提交表单
deleteForm.submit();
//取消超链接默认行为
event.preventDefault();
}
}
});
</script>
</body>
</html>
测试,依然报错405
是由于当前工程打包的结果没有static目录,需要重新打包
但是此时还是不行,因为此时访问静态资源,是由DispatcherServlet处理的。
配置springMVC.xml
配置之后,先由springMVC处理请求映射,如果没找到将当前请求交给默认Servlet处理。
实现添加功能
employee_add.html
测试
添加成功!
修改功能
employee_update.html
复选框内容会自动与value值进行比较,一致则勾选
完成
HttpMessageConverter
HttpMessageConverter,报文信息转换器,将请求报文转换为Java对象,或将Java对象转换为响应报文,HttpMessageConvert提供了两个注解和两个类型:@RequestBody,@ResponseBody
RequestEntity,ResponseEntity
@RequestBody
可以获取请求体,需要在控制器方法设置一个形参,使用@RequestBody进行标识,当前请求的请求体就会为当前注解所标识的形参赋值
RequestEntity
RequestEntity封装请求报文的一种类型,需要在控制器方法的形参在设置该类型的形参,当前请求的请求报文就会赋值给该形参,可以通过getHeaders()获取请求头信息,通过getBody()获取请求体信息
输出结果
requestHeader:[host:"localhost:8080", connection:"keep-alive", content-length:"57", cache-control:"max-age=0", sec-ch-ua:"" Not A;Brand";v="99", "Chromium";v="100", "Microsoft Edge";v="100"", sec-ch-ua-mobile:"?0", sec-ch-ua-platform:""Windows"", upgrade-insecure-requests:"1", origin:"http://localhost:8080", user-agent:"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36 Edg/100.0.1185.50", accept:"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", sec-fetch-site:"same-origin", sec-fetch-mode:"navigate", sec-fetch-user:"?1", sec-fetch-dest:"document", referer:"http://localhost:8080/springMVC_demo4/", accept-encoding:"gzip, deflate, br", accept-language:"zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6", cookie:"Idea-6aaab5b3=171a0ae7-91ce-403b-9084-01fc843ac0d3", Content-Type:"application/x-www-form-urlencoded;charset=UTF-8"]
requestBody:username=%E5%A4%A7%E8%8B%8F%E6%89%93144&password=153435hj
@ResponseBody
用于标识一个控制器方法,可以将该方法的返回值直接作为响应报文的响应体响应到浏览器
SpringMVC处理json
第一步:导入jackson依赖
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.1</version>
</dependency>
第二步:在SpringMVC的核心配置文件中开启mvc的注解驱动,此时在HandlerAdaptor中会自动装配一个消息转换器:MappingJackson2HttpMessageConvert,可以将响应到浏览器的Java对象转换为Json格式的字符串
第三步:在处理器方法上使用@ResponseBody注解进行标识
第四步:将Java对象直接作为控制器方法的放回值返回,就会自动转换为Json格式的字符串
SpringMVC处理ajax
导入所需资源
<div id="app">
<a @click="testAxios" th:href="@{/testAxios}">springMVC处理ajax</a>
</div>
<script type="text/javascript" th:src="@{/static/js/axios.min.js}"></script>
<script type="text/javascript" th:src="@{/static/js/vue.min.js}"></script>
<script type="text/javascript">
var vue = new Vue({
el: "#app",
methods: {
testAxios: function (event) {
axios({
method: "post",
url: event.target.href,
params: {
username: "admin",
password: "123456"
}
}).then(function (response) {
alert(response.data);
});
event.preventDefault();
}
}
});
</script>
@RestController注解
@RestController注解是springMVC提供的一个复合注解,标识在控制器的类上,就相当于为类添加了@Controller注解,并且为其中的每个方法添加了@ResponseBody注解
ResponseEntity
用于控制器方法的返回值类型,该控制器的方法的返回值就是响应到浏览器的响应报文
文件上传和下载
文件下载
使用ResponseEntity实现下载文件的功能
@RequestMapping("/testDownLoad")
public ResponseEntity<byte[]> testResponseEntity(HttpSession session) throws IOException {
//获取ServletContext对象
ServletContext context = session.getServletContext();
//获取服务器中文件的真实路径
String realPath = context.getRealPath("/static/img/vs2.jpg");
//创建输入流
FileInputStream is = new FileInputStream(realPath);
//创建字节数组
byte[] bytes = new byte[is.available()];
//将流读入到字节数组中
is.read(bytes);
//创建HttpHeader对象设置响应头信息
MultiValueMap<String, String> headers = new HttpHeaders();
//设置要下载方式以及下载文件的名字
headers.add("Content-Disposition", "attachment;filename=1.jpg");
//设置响应状态码
HttpStatus statusCode = HttpStatus.OK;
//创建ResponseEntity对象
ResponseEntity<byte[]> responseEntity = new ResponseEntity<>(bytes, headers, statusCode);
//关闭输入流
is.close();
return responseEntity;
}
文件上传
引入依赖
配置SpringMVC的文件上传解析器
<!--配置文件上传解析器,将上传的文件封装为MultipartFile-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"></bean>
代码
@RequestMapping("/testUp")
public String testUp(MultipartFile photo, HttpSession session) throws IOException {
//获取上传的文件的文件名
String filename = photo.getOriginalFilename();
//解决文件的重名问题
//获取上传的文件的后缀名
String suffixName = filename.substring(filename.lastIndexOf("."));
//将UUID作为文件名
String uuid = UUID.randomUUID().toString();
//将uuid和后缀名拼接后的结果作为最终的文件名
filename = uuid + suffixName;
//通过ServletContext获取服务器中photo目录的路径
ServletContext context = session.getServletContext();
String photoPath = context.getRealPath("photo");
File file = new File(photoPath);
//判断photoPath所对应路径是否存在
if (!file.exists()) {
file.mkdir();
}
String finalPath = photoPath + File.separator + filename;
photo.transferTo(new File(finalPath));
return "success";
}