目录
RESTful架构风格
1、RESTful概述
2、RESTful的六大原则
3、RESTful的实现
4、HiddenHttpMethodFilter
RESTful风格的CRUD
1、环境搭建
2、功能需求
3、功能:访问首页
4、功能:查询所有数据
5、功能:删除一条数据
6、功能:添加一条数据
SpringMVC处理静态资源
1、tomcat自己的web.xml
2、关于DefaultServlet
RESTful架构风格
1、RESTful概述
什么是REST
REST:
Re
presentationalS
tateT
ransfer,表述层资源状态转移。(表现层:视图+控制器)它是在2000年,由Http协议的主要编写者Roy Fielding提出的,他觉得所有人都在胡乱使用Http,违背了他的设计思想。他就站出来,规定了一个web应用的,功能强、性能好、适宜通信的架构。这就是REST,
它指的是一组架构约束条件和原则
。如果一个架构符合REST的约束条件和原则,我们就称它为
RESTful架构风格
。REST本身并没有创造新的技术、组件或服务,简单来说,REST的目的就是设立规定,
促使人们更好地使用Http协议
。理论上REST架构风格并不是绑定在HTTP上,只不过目前HTTP是唯一与REST相关的实例。不遵守REST规范,也能编写出web应用,这就类似于不使用设计模式也能开发程序一样。但基本都在使用。
资源
项目最终会部署到服务器上,被访问。
服务器中,万物皆资源。每个资源都是服务器上的一个可命名的抽象概念。
与面向对象的设计理念类似,
资源是以名词为核心来组织的,而不是方法
。资源可以是一个文件,一张数据库表,或是更抽象的概念,比如一个视图,一个参数之类。
一个资源可以由一个或多个URI来标识。
URI
URI既是资源的名称,也是资源在web上的地址。客户端通过URI与资源进行交互。
REST 是面向资源的,这个概念非常重要,而资源是通过 URI 进行暴露。
URI 的设计只要负责把资源通过合理方式暴露出来就可以了。对资源的操作与它无关,操作是通过 HTTP动词来体现,所以REST 通过 URI 暴露资源时,会强调不要在 URI 中出现动词。
资源的状态表述
资源的表述,指某个资源在某个特定时刻的状态的描述。这种状态,可以在客户端-服务器之间交换。
资源的表述可以有多种格式,例如HTML、XML、JSON、纯文本、图片、音视频等。
统一资源在请求-响应方向的表述,通常使用不同的格式
资源状态表述的转移
状态转移,指在客户端-服务器端之间转移 资源状态的表述。
通过转义和操作资源的表述,来实现间接操作资源的目的。
2、RESTful的六大原则
Roy Fielding在提出RESTful时,也阐述了REST架构的6大原则。他希望所有web应用都符合这六个特征,相当于是web应用的设计原则
如果某个服务违反了其他任意一项准则,严格意义上不能称之为RESTful风格。
1、C-S架构
数据的存储在Server端,Client端只需使用就行。两端彻底分离的好处使client端代码的可移植性变强,Server端的拓展性变强。两端单独开发,互不干扰。
2、无状态
http请求本身就是无状态的,基于C-S架构,客户端的每一次请求都必须带有充分的信息,才能够让服务端识别。
服务端能够根据请求的各种参数,无需保存客户端的状态,将响应正确返回给客户端。
无状态的好处:无状态的特征大大提高的服务端的健壮性和可拓展性。
无状态的坏处:每次请求必须携带身份状态信息,造成传输数据的冗余。但影响很细微,可以忽略
3、统一资源接口
这个才是REST架构的核心,统一的接口对于RESTful服务非常重要。客户端只需要关注实现接口就可以,接口的可读性加强,使用人员方便调用。
4、一致的数据格式
5、系统分层
客户端通常无法表明自己是直接还是间接与端服务器进行连接,分层时同样要考虑安全策略。
6、可缓存
管理得当的缓存会部分地或完全地除去客户端和服务端之间的交互,进一步改善性能和延展性。
3、RESTful的实现
REST 风格提倡 URL 地址使用统一的风格设计
,从前到后各个单词使用斜杠分开,不使用问号键值对方式携带请求参数
,而是将要发送给服务器的数据作为 URL 地址的一部分
,以保证整体风格的一致性。
具体说,就是 HTTP 协议里面,四个表示操作方式的动词:GET、POST、PUT、DELETE。 它们分别对应四种基本操作:GET 用来获取资源,POST 用来新建资源,PUT 用来更新资源,DELETE 用来删除资源
。
操作 | 传统方式 | REST风格 |
---|---|---|
查询操作 | getUserById?id=1 | user/1 --> get请求方式 |
保存操作 | saveUser | user --> post请求方式 |
删除操作 | deleteUser?id=1 | user/1 --> delete请求方式 |
更新操作 | updateUser | user --> put请求方式 |
4、HiddenHttpMethodFilter
由于浏览器只支持发送get和post方式的请求,那么该如何发送put和delete请求呢?
SpringMVC 提供了 HiddenHttpMethodFilter 帮助我们
将 POST 请求转换为 DELETE 或 PUT 请求
HiddenHttpMethodFilter如何使用?
HiddenHttpMethodFilter 处理put和delete请求的条件:
- 当前请求的
请求方式必须为post
- 当前请求
必须传输请求参数_method
满足以上条件,HiddenHttpMethodFilter 过滤器就会将当前请求的请求方式转换为请求参数_method的值,因此
请求参数_method的值才是最终的请求方式
在web.xml中注册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>
注意,在web.xml中注册时,必须先注册CharacterEncodingFilter,再注册HiddenHttpMethodFilter
即先注册字符编码过滤器,再注册Http隐藏方法过滤器
HiddenHttpMethodFilter需要获取请求方式,也属于请求参数,那就必须在设置好字符编码之后再去获取。
在前端页面中,使用隐藏域传递请求参数_method
<form th:action="@{/user}" method="post">修改用户信息
<input type="hidden" name="_method" value="put"/>
用户名:<input type="test" name="username"/>
密码:<input type="password" name="password"/>
<input type="submit" value="提交">
</form><br>
此时,点击提交按钮,发出的就是一个put类型的请求
RESTful风格的CRUD
1、环境搭建
创建实体类
public class Employee {
private Integer id;
private String lastName;
private String email;
//1 male, 0 female
private Integer gender;
...
}
创建DAO,使用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", "aa@163.com", 1));
employees.put(1002, new Employee(1002, "E-BB", "bb@163.com", 1));
employees.put(1003, new Employee(1003, "E-CC", "cc@163.com", 0));
employees.put(1004, new Employee(1004, "E-DD", "dd@163.com", 0));
employees.put(1005, new Employee(1005, "E-EE", "ee@163.com", 1));
}
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);
}
}
@Repository 持久层组件标记
创建Controlle
public class EmployeeController {
private EmployeeDAO employeeDAO;
}
创建私有成员变量DAO,后续使用依赖注入
2、功能需求
功能 | URL 地址 | 请求方式 |
---|---|---|
访问首页 | / | GET |
查询全部数据 | /employee | GET |
删除 | /employee/2 | DELETE |
跳转到添加数据页面 | /toAdd | GET |
执行保存 | /employee | POST |
跳转到更新数据页面 | /employee/2 | GET |
执行更新 | /employee | PUT |
使用RESTful风格,对相同资源的不同操作,请求地址相同,不同请求方式对应不同的操作。
3、功能:访问首页
由于只是页面跳转的功能,可以使用view-controller标签配置。
<mvc:view-controller path="/" view-name="index"/>
<mvc:annotation-driven />
首页的前端页面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h3>RESTful风格的员工CRUD系统</h3>
<a th:href="@{/employee}">查看所有员工信息</a>
</body>
</html>
4、功能:查询所有数据
分析
不使用ajax异步访问,就只能先获取数据,再转发到其他页面来展示。
传参流程
首页点击超链接,被Spring前端控制器拦截,匹配到控制器方法;
控制器方法中,执行DAO的相应方法,将结果存入request域对象中,并转发到结果页面;
结果页面上,使用视图模板引擎获取域对象中的相关数据,并展示。
控制器方法
@GetMapping("/employee")
public String getEmployeeList(Model model){
Collection<Employee> employeeList = employeeDAO.getAll();
model.addAttribute("employeeList", employeeList);
return "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" cellspacing="0" cellpadding="0" style="text-align: center">
<tr>
<th colspan="5">全部员工信息</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 href="">Delete</a>
<a href="">Update</a>
</td>
</tr>
</table>
</body>
</html>
效果
5、功能:删除一条数据
分析
删除操作,需要使用DELETE请求方式,则需要在Delete超链接上,绑定一个post表单的提交事件,该表单使用隐藏域,设置_method为DELETE
点击删除超链接,需要传递该行的员工id,
动态获取数据并拼接
。拼接有两种方法
:
<!--方法一-->
<a th:href="@{/employee/}+${employee.id}">Delete</a>
<!--方法二-->
<a th:href="@{'/employee/'+${employee.id}}">Delete</a>
步骤
- 创建发送delete请求方式的form表单
- 给delete超链接绑定事件
- 引入vue.js
- 给delete超链接绑定单击事件
- 通过vue处理单击事件
- 编写控制器方法,获取前端请求携带的id信息,调用DAO,根据id删除该数据
创建发送delete请求方式的form表单
<form id="deleteForm" method="post">
<input type="hidden" name="_method" value="delete"/>
</form>
引入vue.js
<script type="text/javascript" th:src="@{/static/js/vue.js}"></script>
需要在webapp下创建static/js目录,放入vue.js
给delete超链接绑定单击事件
<a @click="deleteEmployee" th:href="@{'/employee/'+${employee.id}}">Delete</a>
这里做了两件事情:
- 给超链接绑定了单击事件,方法名称为deleteEmployee
- 拼接了请求路径,用RESTful风格传参,请求路径包含该行的id值
通过vue处理单击事件
<script type="text/javascript">
var vue = new Vue({
el:"#dataTable",
methods:{
deleteEmployee:function (event){
//获取deleteForm这个表单元素
var deleteForm = document.getElementById("deleteForm");
//获取当前触发事件的href属性,即employee/1001
deleteForm.action = event.target.href;
//提交表单
deleteForm.submit();
//取消默认行为,阻止submit跳转页面
event.preventDefault();
}
}
});
</script>
注意
如果不做配置,vue.js无法被获取到,单击事件也就不起作用。
因为
SpringMVC默认无法访问静态资源
。vue.js会被前端控制器 / 拦截到,显然没有它对应的控制器方法,返回了404需要在SpringMVC配置文件中,引入
默认的servlet
,开放对静态资源的访问<mvc:default-servlet-handler/>静态资源在访问时,会先被前端控制器进行处理,如果前端控制器找不到对应的请求映射,就会交给默认的servlet来处理。如果默认的servlet找到了相对应的资源,就访问资源。如果找不到,就返回404。
控制器方法
@DeleteMapping("/employee/{id}")
public String delEmployee(@PathVariable Integer id){
employeeDAO.delete(id);
return "redirect:/employee";
}
6、功能:添加一条数据
在前端页面,设置添加员工的入口
<th>options(<a th:href="@{/toAdd}">添加员工</a>)</th>
效果
点击“添加员工”,跳转到添加员工的页面。用view-controller实现
<mvc:view-controller path="/toAdd" view-name="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"/>男
<input type="radio" name="gender" value="0"/>女<br>
<input type="submit" value="提交"/>
<input type="reset" value="重置"/>
</form>
</body>
</html>
样式
控制器方法
@GetMapping("/employee/{id}")
public String getEmployeeById(@PathVariable Integer id, Model model){
//根据id获取员工对象
Employee employee = employeeDAO.get(id);
//将员工对象存入request域中,用于数据回显
model.addAttribute("employee", employee);
return "employee_update";
}
@PutMapping("/employee")
public String updateEmployee(Employee employee){
employeeDAO.save(employee);
return "redirect:/employee";
}
修改的前端页面
<!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.lastName}"/><br>
gender:<input type="radio" name="gender" value="1" th:field="${employee.gender}"/>男
<input type="radio" name="gender" value="0" th:field="${employee.gender}"/>女<br>
<input type="submit" value="提交"/>
<input type="reset" value="重置"/>
</form>
</body>
</html>
- 注意这里实现勾选框 回显的方式
th:field="${employee.gender}"可用于单选框或复选框的回显
若单选框的value和employee.gender的值一致,则添加checked="checked"属性
2. 注意,需要使用隐藏域传递id,达到覆盖原有数据的目的
SpringMVC处理静态资源
传统的web项目中,负责处理静态资源的是“默认的Servlet”
1、tomcat自己的web.xml
存放位置
tomcat目录下的conf目录内。
tomcat的web.xml,和工程中web.xml的关系
tomcat的web.xml文件,是全局配置,作用于部署在tomcat的所有web应用。
web应用中的web.xml,只针对于自身。
如果当前应用内的web.xml配置,与tomcat的web.xml配置产生了冲突,则以web应用的配置为准。
2、关于DefaultServlet
tomcat的web.xml中,注册了DefaultServlet
<servlet>
<servlet-name>default</servlet-name>
<servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
<init-param>
<param-name>debug</param-name>
<param-value>0</param-value>
</init-param>
<init-param>
<param-name>listings</param-name>
<param-value>false</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
DefaultServlet的url-pattern是 / 说明访问所有资源(除了jsp)时,都会进入DefaultServlet。
在SpringMVC中,DefaultServlet和DispacherServlet产生了冲突。冲突时,以当前工程中的配置为准,也就是DispacherServlet。
就是说,所有的请求都会被DispacherServlet处理。
但DispacherServlet处理请求的方式是,将请求地址与控制器方法的映射路径匹配,直到找到相对应的请求映射。但这种方式无法访问静态资源。例如需要访问vue.js资源,而此路径并没有相对应的控制器方法,所以会报404,找不到该资源。
而DefaultServlet可以找到vue.js这个请求的资源。
所以SpringMVC默认不能访问静态资源,必须手动开启对静态资源的访问
,在核心配置中进行如下的配置:
<!--开启对静态资源的访问-->
<mvc:default-servlet-handler/>
但此时,所有的请求都将被DefaultServlet处理,则只能访问到静态资源,请求映射将全部失效
。
所以,还需要配置开启注解驱动的标签
:
<!--开启mvc注解驱动-->
<mvc:annotation-driven>
同时配置这两个标签,就能实现,
请求先被DispacherServlet处理,如果找不到,再被DefaultServlet处理
。
关于控制台信息
如果不添加日志功能,只有DispacherServlet的处理结果会被输出,而DefaultServlet的结果不会显示。