RESTful
一、概述
-
是一种软件框架的风格,风格就是格式
-
Representational State Transfer:表现层资源状态转移
-
表现层:Representational
指视图和控制层,即前端的视图页面和后端的控制层
-
资源
通俗理解:学习了服务器之后,把当前的web工程放到服务器的过程叫做部署。部署后,web工程中的内容在服务器中叫做资源,即万物即资源。比如一个类、一个html页面、一个js脚本、一个图片都可以叫做资源。
官方解释:资源是一种看待服务器的方式,即将服务器看作是很多离散的资源组成。每个资源都是服务器上一个可命名的抽象概念。因为资源是一个抽象的概念,所以它不仅仅能代表服务器文件系统中的一个文件、或数据库中一张数据表等具体的东西,而是可以将资源设计的要多抽象有多抽象,只要想象力允许而客户端应用开发者能够理解。与面向对象设计类似,资源是以名词为核心来组织的,首先关注的是名词。一个资源可以由一个或多个URL来标识。URL既是资源的名称,也是资源在Web上的地址。对某个资源感兴趣的客户端应用,可以通过资源的URL与其进行交互。
-
状态
资源的状态就是资源当前的一个表现形式,即资源的表述。资源的表述可以由多种格式,如HTML/XML/JSON/音频/图片等,也可通过协商机制来确定。
-
转移
在客户端和服务端之间转移代表资源状态的表述。比如浏览器发送一个请求到服务器,本着请求-响应原则,这时服务器就要将其对应的资源转移到当前的客户端
-
引入RESTful的原因
目前,我们在访问一个资源的时候,方式五花八门。比如获取一个用户的信息,使用URL(请求路径),在请求路径中就表示了要干什么,访问的资源时什么。请求路径可以写成getUserById 或 selectUserById 或 findUserById 或 chaxunUserById等等
-
引入RESTful的目的
统一形式:当我们把内容部署到服务器之后,万物皆资源,所以对于某一资源的各种操作都对应同一个请求路径。
-
RESTful的四种操作
四种基本操作:GET 用来获取资源,POST 用来新建资源,PUT 用来更新(修改)资源,DELETE用来删除资源。
-
RESTful中的URL地址的风格统一
从前都后各个单词用斜杠分开,不使用问号键值对方式携带请求参数,而是将请求参数作为url地址的一部分发送给服务器,以保证整体风格的一致性
操作 传统方式 RESTful风格 查询 getUserById?id=1 user/1 get请求方式 增加 saveUser user post请求方式 删除 deleteUserById?id=1 user/1 delete请求方式 更新 updateUser user put请求方式
二、HiddenHttpMethodFilter源码解析
-
要实现对资源的PUT和DELETE操作,需要在web.xml中配置HiddenHttpMethodFilter
<!-- 3.配置隐藏过滤器 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>
-
HiddenHttpMethodFilter源码
public class HiddenHttpMethodFilter extends OncePerRequestFilter { // 1.属性 private static final List<String> ALLOWED_METHODS; public static final String DEFAULT_METHOD_PARAM = "_method"; private String methodParam = "_method"; // 2.过滤方法 protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { // 2.1 将拦截到的请求赋值给自建的请求变量 HttpServletRequest requestToUse = request; // 2.2 对类型为POST的请求进行操作 if ("POST".equals(request.getMethod()) && request.getAttribute("javax.servlet.error.exception") == null) { // 2.3 提取request作用域中键名为"_method"的值 String paramValue = request.getParameter(this.methodParam); // 2.4 如果"_method"对应的值不为空 if (StringUtils.hasLength(paramValue)) { // 2.5 将该值全部大写 String method = paramValue.toUpperCase(Locale.ENGLISH); /* 2.6 如果全部大写后的该值如果包含在静态代码块ALLOWED_METHODS的列表中, 即:PUT/DELETE/PATCH 之中的一个 */ if (ALLOWED_METHODS.contains(method)) { /* 2.7 妙用之笔: 调用静态内部类中的包装方法,将request和method包装到 HttpMethodRequestWrapper对象内,该类最终实现的是ServletRequest 接口,故可将该对象赋值给requestToUser,同时也将request和method包 装到了该引用变量中 */ requestToUse = new HiddenHttpMethodFilter.HttpMethodRequestWrapper(request, method); } } } filterChain.doFilter((ServletRequest)requestToUse, response); } // 3.静态代码块,为ALLOWED_METHODS赋值 static { ALLOWED_METHODS = Collections.unmodifiableList(Arrays.asList(HttpMethod.PUT.name(), HttpMethod.DELETE.name(), HttpMethod.PATCH.name())); } // 4.静态内部类 private static class HttpMethodRequestWrapper extends HttpServletRequestWrapper { private final String method; public HttpMethodRequestWrapper(HttpServletRequest request, String method) { // 4.1 两级父类后,request的类型为ServletRequest super(request); this.method = method; } public String getMethod() { return this.method; } } }
静态内部类的两级父类
// 第一级父类 public class HttpServletRequestWrapper extends ServletRequestWrapper implements HttpServletRequest { public HttpServletRequestWrapper(HttpServletRequest request) { super(request); } } // 第二级父类 public class ServletRequestWrapper implements ServletRequest { private ServletRequest request; public ServletRequestWrapper(ServletRequest request) { if (request == null) { throw new IllegalArgumentException("Request cannot be null"); } else { this.request = request; } } }
三、RESTful操作实现
-
springMVC.xml
<!-- 3.开启mvc注解驱动 --> <mvc:annotation-driven/> <!-- 4.视图控制器 --> <!-- 4.1 通过url直接跳转到index.html页面 --> <mvc:view-controller path="/" view-name="index"></mvc:view-controller> <mvc:view-controller path="/toAdd" view-name="employee_insert"></mvc:view-controller> <!-- 5.开启默认Servlet,开启对静态资源的访问 静态资源被访问时,首先会被DispatcherServlet前端控制器进行处理, 如果找不到相对应的请求映射,就会转到默认Servlet进行处理 --> <mvc:default-servlet-handler />
-
html
2.1 主页index.html
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>首页</title> </head> <body> <a th:href="@{/employee}">查询所有列表</a> </body> </html>
2.2 - 展示页面employee_list.html
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>查询展示</title> </head> <body> <table id="dataTable" border="1" cellpacing="0" cleepadding="0" style="text-align: center"> <tr> <th>id</th> <th>lastName</th> <th>email</th> <th>gender</th> <!-- 跳转到一个有form表单的添加页面,可在springMVC中直接配置跳转 --> <th>options(<a th:href="@{/toAdd}">add</a>)</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> <!-- RESTful的另一种写法:<a th:href="@{/employee/}+${employee.id}">delete</a> 创建点击事件@click --> <a @click="deleteEmployee" th:href="@{'/employee/'+${employee.id}}">delete</a> <a th:href="@{'/employee/' + ${employee.id}}">update</a> </td> </tr> </table> <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> <!-- 通过Vue进行动作 --> <script type="text/javascript"> var vue = new Vue({ <!-- 确定Vue的作用范围 --> el:"#dataTable", methods:{ deleteEmployee:function (event) { var deleteForm = document.getElementById("deleteForm"); deleteForm.action = event.target.href; deleteForm.submit(); event.preventDefault(); } } }) </script> </body> </html> <!-- 处理事件,通过Vue控制form表单的提交 event:表示当前的事件 document.getElementById():在当前文件内通过id获取对应的元素,即获取form表单 deleteForm.action = event.target.href; 指定form表单的action,通过event指定到事件的href deleteForm.submit(); 提交表单 event.preventDefault(); 取消超链接的默认行为 html页面中有一些默认行为: ① 超链接:点击超链接后,就算绑定有事件,也会跳转页面 ② submit:如果绑定有事件,点击了提交后,会先执行事件,后提交表单 -->
2.3 - 增加页面employee_insert.html
<!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="测试PUT"> </form> </body> </html>
2.4 - 修改页面employee_update.html
包括页面回显功能 th:value th:field
<!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/> 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="测试update"> </form> </body> </html>
-
pojo实体类
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 + '}'; } }
-
dao
package com.atguigu.mvc.dao; import com.atguigu.mvc.bean.Employee; import org.springframework.stereotype.Repository; import java.util.*; /** * @author e_n * @version 1.0.0 * @ClassName EmployeeDao.java * @Description * @CreateTime 2022/05/07 00:16 */ @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); } }
-
controller
@Controller public class EmployeeController { @Autowired private EmployeeDao employeeDao; // 查询所有列表,首页展示 @GetMapping("/employee") public String getAll(Model model) { Collection<Employee> employeeList = employeeDao.getAll(); // 通过model进行request范围内的域对象共享 model.addAttribute("employeeList",employeeList); return "employee_list"; } // 删除 @DeleteMapping( "/employee/{id}") public String deleteById(@PathVariable("id") Integer id) { employeeDao.delete(id); return "redirect:/employee"; } // 增加 @PostMapping("/employee") // 通过实体类获取请求参数 public String addEmployee(Employee employee) { employeeDao.save(employee); return "redirect:/employee"; } // PUT -1 @GetMapping("/employee/{id}") public String updateEmployee(@PathVariable("id") Integer id,Model model) { Employee employee = employeeDao.get(id); model.addAttribute("employee",employee); return "employee_update"; } // PUT -2 @PutMapping("/employee") public String putEmployee(Employee employee) { employeeDao.save(employee); return "redirect:/employee"; } }
四、删除过程需要注意的几点
-
点击删除,要绑定一个事件,该事件的执行在Vue中进行
-
在Vue中要通过 el 设置作用范围
-
在Vue中要通过methods,在点击事件内绑定form表单,进行delete请求转换
-
重要
① 要取消超链接l的默认操作 event.preventDefault();
② 在springMVC.xml内开启默认Servlet。给默认Servlet的开启原先在Tomcat服务器config文件内的web.xml
中已经设置。但我们在配置springMVC.xml时,将全部请求的处理权交给了DispatcherServlet,因为就近
原则,所以默认Servlet就被屏蔽掉了,故要在springMVC.xml内重新开启。
当DispatcherServlet遇到无法处理的请求,默认Servlet就会进行处理
③ 添加了 /static/js/vue.js文件后,要通过maven对引入文件进行重新打包