SpringMVC学习笔记(二)
一、域对象共享数据
-
域对象
(1)Request:一次请求。
(2)Session:一次会话,浏览器开启到浏览器关闭。
假设我们在淘宝中用户过多的时候,虽然 session 没有下限,但 session 的数量就会不断增多。之后就会导致内存无法承受,此时就会有一些 session 长时间都没有活动。服务器启动时就会将这些很久没有活动的 session 放到硬盘上,让内存给空出来。就会使得很多的session被保存到硬盘上以此来空出内存。而即便之后需要再次访问 session,它就会再次从硬盘中将 session给放到内存中来使用。这样用户就不会感觉到自己掉线了。这个过程就是 session 的钝化和活化。
钝化:服务器关闭了,但浏览器未关闭会话还在继续,这个时候Session中的数据通过序列化保存到磁盘中。
活化:将磁盘中序列化保存的Session信息读取到Session中。
(3)ServletContext/Application:整个应用开启到关闭,服务器开启到服务器关闭。 -
共享数据的实现
(1)转发可以获取请求域中的数据,因为转发本质上是一次请求。
(2)会话共享数据:因为Cookie中保存了JSESSION_ID,只要有JSESSION_ID创建的Session永远是同一个
(3)ServletContext共享数据:因为ServletContext只会在服务器开启时创建一次,服务器关闭时销毁,调用的都是同一个对象。 -
域对象的使用选择
(1)查询列表,表单回显使用Request域。
(2)通常推荐选择能、实现功能的范围最小的域。
1、使用ServletAPI向request域对象共享数据
@RequestMapping("/testServletAPI")
public String testServletAPI(HttpServletRequest request){
request.setAttribute("testScope", "hello,servletAPI");
return "success";
}
(1)操作共享域数据的三种方法:setAttribute()、getAttribute()、removeAttribute()
(2)return"视图名",本质是转发
(3)th:text=“${内容}“中,
①要使用request域中的数据,直接写键名;
②要使用session域中的数据,写session.键名
③要使用servletContext域中的数据,写application.键名
(3)th:href =”@{内容}”,内容写请求地址
2、使用ModelAndView向request域对象共享数据
@RequestMapping("/testModelAndView")
public ModelAndView testModelAndView(){
/**
* ModelAndView有Model和View的功能
* Model主要用于向请求域共享数据
* View主要用于设置视图,实现页面跳转
*/
ModelAndView mav = new ModelAndView();
//向请求域共享数据
mav.addObject("testScope", "hello,ModelAndView");
//设置视图,实现页面跳转
mav.setViewName("success");
return mav;
}
3、使用Model向request域对象共享数据
@RequestMapping("/testModel")
public String testModel(Model model){
model.addAttribute("testScope","Hello,Model");
return "test";
}
4、使用map向request域对象共享数据
@RequestMapping("/testMap")
public String testMap(Map<String,Object> map){
map.put("testScope","Hello,Map");
return "test";
}
5、使用ModelMap向request域对象共享数据
@RequestMapping("/testModelMap")
public String testModelMap(ModelMap modelMap){
modelMap.addAttribute("testScope","Hello,ModelMap");
return "test";
}
6、Model、ModelMap、Map的关系
Model、ModelMap、Map类型的参数其实本质上都是 BindingAwareModelMap 类型的
public interface Model{}
public class ModelMap extends LinkedHashMap<String, Object> {}
public class ExtendedModelMap extends ModelMap implements Model {}
public class BindingAwareModelMap extends ExtendedModelMap {}
View是视图信息,Model是通过域传递的内容。
不管用的是什么方式,最终都会把内容封装到ModelAndView对象中。
7、向session域共享数据
使用原生Sevlet共享数据。
@RequestMapping("/testSession")
public String testSession(HttpSession httpSession){
httpSession.setAttribute("testSessionScope","Hello,Session");
return "test";
}
8、向application域共享数据
- 如何获取ServletContext对象
(1)JSP页面中的pageContext对象可以获取ServletContext对象
(2)request可以获取ServletContext对象
(3)servlet的init方法中有一个对象ServletConfig可以获得ServletContext对象
@RequestMapping("/testApplication")
public String testSerlvetContext(HttpSession httpSession){
ServletContext servletContext = httpSession.getServletContext();
servletContext.setAttribute("testApplicationScope","Hello,Application");
return "text";
}
- 各类域对象使用场景
(1)Request:列表、修改回显、错误信息
(2)Session:保存用户的登陆状态,Session默认30分钟后失效
二、SpringMVC的视图
SpringMVC中的视图是View接口,视图的作用渲染数据,将模型Model中的数据展示给用户
SpringMVC视图的种类很多,默认有InternalResourceView转发视图和RedirectView重定向视图
当工程引入jstl的依赖,转发视图会自动转换为JstlView
若使用的视图技术为Thymeleaf,在SpringMVC的配置文件中配置了Thymeleaf的视图解析器,由此视图解析器解析之后所得到的是ThymeleafView
1、ThymeleafView
当控制器方法中所设置的视图名称没有任何前缀时,此时的视图名称会被SpringMVC配置文件中所配置的视图解析器解析,视图名称拼接视图前缀和视图后缀所得到的最终路径,会通过转发的方式实现跳转。
@RequestMapping("/testHello")
public String testHello(){
return "hello";
}
2、转发视图
SpringMVC中默认的转发视图是InternalResourceView
SpringMVC中创建转发视图的情况:
当控制器方法中所设置的视图名称以"forward:"为前缀时,创建InternalResourceView视图,此时的视图名称不会被SpringMVC配置文件中所配置的视图解析器解析,而是会将前缀"forward:“去掉,剩余部分作为最终路径通过转发的方式实现跳转
例如"forward:/”,"forward:/employee
@RequestMapping("/testForward")
public String testForward(){
return "forward:/testHello";
}
(1)创建两次视图,第一次创建转发视图,第二次创建转发到的地址的视图。
(2)如果要转发到具体某一个html页面,请求地址不能写具体的页面,因为页面是thymeleaf动态渲染的,所有请求必须通过thymeleaf解析,写具体的页面就解析不了了,所以要通过转发到一个能处理thymeleaf页面的请求来跳转。
3、重定向视图
SpringMVC中默认的重定向视图是RedirectView
当控制器方法中所设置的视图名称以"redirect:"为前缀时,创建RedirectView视图,此时的视图名称不会被SpringMVC配置文件中所配置的视图解析器解析,而是会将前缀"redirect:“去掉,剩余部分作为最终路径通过重定向的方式实现跳转
例如"redirect:/”,“redirect:/employee”
由于重定向不能访问WEB-INF目录下的文件,而页面都存在在WEB-INF下,所以重定向的请求地址也不能写具体的路径,必须要写另一个请求的地址。
@RequestMapping("/testRedirect")
public String testRedirect(){
return "redirect:/testHello";
}
转发和重定向的区别
(1)转发是一次请求,重定向是两次请求,第一次访问servlet,第二次访问重定向地址
(2)转发地址不会改变,重定向地址会改变
(3)转发可以获取request域中的对象,重定向因为是两次请求所以不可以
(4)转发能访问WEB-INF目录下的资源,重定向不可以
(5)转发不能跨域,重定向可以跨域
4、视图控制器view-controller
当控制器方法中,仅仅用来实现页面跳转,即只需要设置视图名称时,可以将处理器方法使用view-controller标签进行表示。
是SpringMVC中的一个标签。
<!--
path:设置处理的请求地址
view-name:设置请求地址所对应的视图名称
-->
<mvc:view-controller path="/testView" view-name="success"></mvc:view-controller>
注:
当SpringMVC中设置任何一个view-controller时,其他控制器中的请求映射将全部失效,此时需
要在SpringMVC的核心配置文件中设置开启mvc注解驱动的标签:
<mvc:annotation-driven />
5、SpringMVC自带的视图解析器InternalResourceView
在SpringMVC.xml中配置
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/templates/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
三、RESTful
1、RESTful简介
REST:Representational State Transfer,表现层资源状态转移。
a>资源
资源是一种看待服务器的方式,即,将服务器看作是由很多离散的资源组成。每个资源是服务器上一个可命名的抽象概念。因为资源是一个抽象的概念,所以它不仅仅能代表服务器文件系统中的一个文件、数据库中的一张表等等具体的东西,可以将资源设计的要多抽象有多抽象,只要想象力允许而且客户端应用开发者能够理解。与面向对象设计类似,资源是以名词为核心来组织的,首先关注的是名词。一个资源可以由一个或多个URI来标识。URI既是资源的名称,也是资源在Web上的地址。对某个资源感兴趣的客户端应用,可以通过资源的URI与其进行交互。
b>资源的表述
资源的表述是一段对于资源在某个特定时刻的状态的描述。可以在客户端-服务器端之间转移(交换)。资源的表述可以有多种格式,例如HTML/XML/JSON/纯文本/图片/视频/音频等等。资源的表述格式可以通过协商机制来确定。请求-响应方向的表述通常使用不同的格式。
c>状态转移
状态转移说的是:在客户端和服务器端之间转移(transfer)代表资源状态的表述。通过转移和操作资源的表述,来间接实现操作资源的目的。
2、RESTful的实现
具体说,就是 HTTP 协议里面,四个表示操作方式的动词:GET、POST、PUT、DELETE。
它们分别对应四种基本操作:GET 用来获取资源,POST 用来新建资源,PUT 用来更新资源,DELETE用来删除资源。
REST 风格提倡 URL 地址使用统一的风格设计,从前到后各个单词使用斜杠分开,不使用问号键值对方式携带请求参数,而是将要发送给服务器的数据作为 URL 地址的一部分,以保证整体风格的一致性。
操作 | 传统方式 | REST风格 |
---|---|---|
查询操作 | getUserById?id=1 | user/1–>get请求方式 |
保存操作 | saveUser | user–>post请求方式 |
删除操作 | deleteUser?id=1 | user/1–>delete请求方式 |
更新操作 | updateUser | user–>put请求方式 |
3、HiddenHttpMethodFilter
由于浏览器只支持发送get和post方式的请求,那么该如何发送put和delete请求呢?
SpringMVC 提供了HiddenHttpMethodFilter帮助我们将 POST 请求转换为 DELETE 或 PUT 请求
HiddenHttpMethodFilter 处理put和delete请求的条件:
a>当前请求的请求方式必须为post
b>当前请求必须传输请求参数_method
- 源码分析
3.1 模拟PUT请求
(1)在web.xml中注册HiddenHttpMethodFilter
<!-- 配置HiddenHttpMethod过滤器 -->
<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>
(2)页面
<h2>测试put方法</h2>
<form th:action="@{/user}" method="post">
<input type="hidden" name="_method" value="put">
用户名:<input type="text" name="username"><br>
密码:<input type="password" name="password"><br>
<input type="submit" value="修改">
</form>
(3)Controller
@RequestMapping(value = "/user",method = RequestMethod.PUT)
public String updateUser(String username,String password){
System.out.println("PUT方法");
System.out.println("username:"+username+",password:"+password);
return "test";
}
3.2 和CharacterEncodingFilter的关系
目前为止,SpringMVC中提供了两个过滤器:CharacterEncodingFilter和HiddenHttpMethodFilter
在web.xml中注册时,必须先注册CharacterEncodingFilter,再注册HiddenHttpMethodFilter原因:
- 在 CharacterEncodingFilter 中通过 request.setCharacterEncoding(encoding) 方法设置字符集的
- request.setCharacterEncoding(encoding) 方法要求前面不能有任何获取请求参数的操作
- 而 HiddenHttpMethodFilter 恰恰有一个获取请求方式的操作:String paramValue = request.getParameter(this.methodParam)
四、RESTful案例
1、准备工作
- Employee实体类
package com.yuuto148.mvc.bean;
import org.springframework.stereotype.Component;
@Component
public class Employee {
private Integer id;
private String lastName;
private Integer gender;
private String email;
public Employee() {
}
public Employee(Integer id, String lastName, Integer gender, String email) {
this.id = id;
this.lastName = lastName;
this.gender = gender;
this.email = email;
}
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 Integer getGender() {
return gender;
}
public void setGender(Integer gender) {
this.gender = gender;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
@Override
public String toString() {
return "Employee{" +
"id=" + id +
", lastName='" + lastName + '\'' +
", gender=" + gender +
", email='" + email + '\'' +
'}';
}
}
- DAO
package com.yuuto148.mvc.dao;
import com.yuuto148.mvc.bean.Employee;
import org.springframework.stereotype.Repository;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
@Repository
public class EmployeeDao {
private static Map<Integer, Employee> employees = null;
static{
employees = new HashMap<Integer,Employee>();
employees.put(1001, new Employee(1001, "E-AA", 1, "aa@163.com"));
employees.put(1002, new Employee(1002, "E-BB", 1, "bb@163.com"));
employees.put(1003, new Employee(1003, "E-CC", 0, "cc@163.com"));
employees.put(1004, new Employee(1004, "E-DD", 0, "dd@163.com"));
employees.put(1005, new Employee(1005, "E-EE", 1, "ee@163.com"));
}
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);
}
}
2、功能清单
功能 | URL地址 | 请求方式 |
---|---|---|
查询所有人员 | /employee | GET |
删除 | /employee/2 | DELETE |
新增人员 | /employee | POST |
更新人员 | /employee | PUT |
具体功能:访问首页
a>配置view-controller
<mvc:view-controller path="/restIndex" view-name="restIndex"></mvc:view-controller>
b>创建页面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<a th:href="@{/employee}">查询所有人员</a>
</body>
</html>
具体功能:查询所有员工
a>控制器方法
@Controller
public class RestExampleController {
@Autowired
private EmployeeDao dao;
@RequestMapping(value = "/employee", method = RequestMethod.GET)
public String getEmployeeList(Model model){
Collection<Employee> employeeList = dao.getAll();
model.addAttribute("employeeList",employeeList);
return "employee_list";
}
}
b>employee_list页面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>员工信息</title>
</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>gender</th>
<th>email</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.gender}"></td>
<td th:text="${employee.email}"></td>
<td>
<a href="">delete</a>
<a href="">update</a>
</td>>
</tr>
</table>
</body>
</html>
具体功能:删除
a>创建处理delete请求方式的表单
<form id="deleteForm" method="post">
<input type="hidden" name="_method" value="delete">
</form>
b>删除超链接绑定点击事件
引入vue.js
<script type="text/javascript" th:src="@{/static/js/vue.js}"></script>
删除超链接
<a @click="deleteEmployee" th:href="@{'/employee/'+${employee.id}}">delete</a>
通过vue处理点击事件
<script type="text/javascript">
var vue = new Vue({
el:"#dataTable",
methods:{
deleteEmployee:function(event){
//根据id获取表单元素
var deleteForm = document.getElementById("deleteForm");
//将触发点击事件的超链接href赋值给表单
deleteForm.action = event.target.href;
//提交表单
deleteForm.submit();
//取消超链接的默认行为
event.preventDefault();
}
}
});
</script>
c>控制器方法
@RequestMapping(value = "/employee/{id}", method = RequestMethod.DELETE)
public String deleteEmployee(@PathVariable("id") Integer id){
dao.delete(id);
return "redirect:/employee";
}
mvc开启静态资源的访问
<mvc:default-servlet-handler></mvc:default-servlet-handler>
具体功能:添加员工
a>添加a标签
<th>options(<a th:href="@{/toAdd}">Add</a>)</th>
b>添加映射
<mvc:view-controller path="/toAdd" view-name="employee_add"></mvc:view-controller>
c>编写employee_add页面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>添加新员工</title>
</head>
<body>
<form th:action="@{/employee}" method="post">
lastName:<input type="text" name="lastName"><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="add"><br>
</form>
</body>
</html>
d>编写控制器方法
@RequestMapping(value = "/employee", method = RequestMethod.POST)
public String addEmployee(Employee employee){
dao.save(employee);
return "redirect:/employee";
}
具体功能:更新员工
a>添加超链接
<a th:href="@{'/employee/'+${employee.id}}">update</a>
b>控制器中配置跳转方法
@RequestMapping(value = "/employee/{id}", method = RequestMethod.GET)
public String getEmployeeById(@PathVariable("id") Integer id, Model model){
Employee employee = dao.get(id);
model.addAttribute("employee",employee);
return "employee_update";
}
c>编写employee_update页面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>更新人员信息</title>
</head>
<body>
<form th:action="@{/employee}" method="post">
<input type="hidden" name="_method" value="put">
<input type="hidden" name="id" th:value="${employee.id}">
lastName:<input type="text" name="lastName" th:value="${employee.lastName}"><br>
email:<input type="text" name="email" th:value="${employee.email}"><br>
<!--
th:field="${employee.gender}"可用于单选框或复选框的回显
若单选框的value和employee.gender的值一致,则添加checked="checked"属性
-->
gender:<input type="radio" name="gender" value="1" th:field="${employee.gender}">male
<input type="radio" name="gender" value="0" th:field="${employee.gender}">female<br>
<input type="submit" value="add"><br>
</form>
</body>
</html>
d>编写控制器代码
@RequestMapping(value = "/employee", method = RequestMethod.PUT)
public String updateEmployee(Employee employee){
dao.save(employee);
return "redirect:/employee";
}