SpringMVC六:RESTful

RESTful

一、概述
  1. 是一种软件框架的风格,风格就是格式

  2. Representational State Transfer:表现层资源状态转移

  3. 表现层:Representational

    指视图和控制层,即前端的视图页面和后端的控制层

  4. 资源

    通俗理解:学习了服务器之后,把当前的web工程放到服务器的过程叫做部署。部署后,web工程中的内容在服务器中叫做资源,即万物即资源。比如一个类、一个html页面、一个js脚本、一个图片都可以叫做资源。

    官方解释:资源是一种看待服务器的方式,即将服务器看作是很多离散的资源组成。每个资源都是服务器上一个可命名的抽象概念。因为资源是一个抽象的概念,所以它不仅仅能代表服务器文件系统中的一个文件、或数据库中一张数据表等具体的东西,而是可以将资源设计的要多抽象有多抽象,只要想象力允许而客户端应用开发者能够理解。与面向对象设计类似,资源是以名词为核心来组织的,首先关注的是名词。一个资源可以由一个或多个URL来标识。URL既是资源的名称,也是资源在Web上的地址。对某个资源感兴趣的客户端应用,可以通过资源的URL与其进行交互

  5. 状态

    资源的状态就是资源当前的一个表现形式,即资源的表述。资源的表述可以由多种格式,如HTML/XML/JSON/音频/图片等,也可通过协商机制来确定。

  6. 转移

    在客户端和服务端之间转移代表资源状态的表述。比如浏览器发送一个请求到服务器,本着请求-响应原则,这时服务器就要将其对应的资源转移到当前的客户端

  7. 引入RESTful的原因

    目前,我们在访问一个资源的时候,方式五花八门。比如获取一个用户的信息,使用URL(请求路径),在请求路径中就表示了要干什么,访问的资源时什么。请求路径可以写成getUserById 或 selectUserById 或 findUserById 或 chaxunUserById等等

  8. 引入RESTful的目的

    统一形式:当我们把内容部署到服务器之后,万物皆资源,所以对于某一资源的各种操作都对应同一个请求路径。

  9. RESTful的四种操作

    四种基本操作:GET 用来获取资源,POST 用来新建资源,PUT 用来更新(修改)资源,DELETE用来删除资源。

  10. RESTful中的URL地址的风格统一

    从前都后各个单词用斜杠分开,不使用问号键值对方式携带请求参数,而是将请求参数作为url地址的一部分发送给服务器,以保证整体风格的一致性

    操作传统方式RESTful风格
    查询getUserById?id=1user/1 get请求方式
    增加saveUseruser post请求方式
    删除deleteUserById?id=1user/1 delete请求方式
    更新updateUseruser put请求方式
二、HiddenHttpMethodFilter源码解析
  1. 要实现对资源的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>
    
  2. 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操作实现
  1. 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 />
    
  2. 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>
    
  3. 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 +
                    '}';
        }
    }
    
    
  4. 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);
        }
    }
    
    
  5. 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";
        }
    }
    
四、删除过程需要注意的几点
  1. 点击删除,要绑定一个事件,该事件的执行在Vue中进行

  2. 在Vue中要通过 el 设置作用范围

  3. 在Vue中要通过methods,在点击事件内绑定form表单,进行delete请求转换

  4. 重要

    ① 要取消超链接l的默认操作 event.preventDefault();

    ② 在springMVC.xml内开启默认Servlet。给默认Servlet的开启原先在Tomcat服务器config文件内的web.xml

    ​ 中已经设置。但我们在配置springMVC.xml时,将全部请求的处理权交给了DispatcherServlet,因为就近

    ​ 原则,所以默认Servlet就被屏蔽掉了,故要在springMVC.xml内重新开启。

    ​ 当DispatcherServlet遇到无法处理的请求,默认Servlet就会进行处理

    ③ 添加了 /static/js/vue.js文件后,要通过maven对引入文件进行重新打包

    在这里插入图片描述

五、错误
  1. 在vue中的元素标签写错了
    <script type="text/javascript">
    	var vue = new Vue({
        <!-- 确定Vue的作用范围 -->
        el:"#dataTable"
        }
    </script>
    
    <table id="dateTable"></table>
    
    导致的错误

    在这里插入图片描述

    在这里插入图片描述

  2. Vue内不要使用多行注释,否则会引发格式错误
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

e_nanxu

感恩每一份鼓励-相逢何必曾相识

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值