猿蜕变9——一文搞定SpringMVC的RESTFul套路

时间:2022-07-22
本文章向大家介绍猿蜕变9——一文搞定SpringMVC的RESTFul套路,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

看过之前的蜕变系列文章,相信你对springMVC有了一定的认识。对springMVC的Interceptor拦截器,也有了一定的认识。今天我们来开启新讨论,讲一讲springMVC对那一种休闲风的支付——RestFul。

都0202年了,再不会RestFul的URL风格的东西,你都不好意思出门了。Roy Fielding提出论文都20年了,这种清爽的,含有意义的URL几乎所有的有点逼格的站点都在跟风靠拢。

REST的英文全称是——Representational StateTransfer,中文含义是表现层状态传输,目前主流的Web服务交互方案中,REST相比于SOAP(Simple Object Access protocol,简单对象访问协议)以及XML-RPC更加简单明了,无论是对URL的处理还是对Payload的编码,REST都倾向于用更加简单轻量的方法设计和实现。

值得注意的是REST并没有一个明确的标准,而更像是一种设计的风格。RESTful是一种网络应用程序的设计风格和开发方式,基于HTTP,可以使用XML格式定义或JSON格式定义(就目前而言基本上是JSON的天下了)。rest是一种架构风格,跟编程语言无关,跟平台无关,RESTFUL特点包括:

1、每一个URI代表1种资源;

2、客户端使用GET、POST、PUT、DELETE4个表示操作方式的动词对服务端资源进行操作:GET用来获取资源,POST用来新建资源(也可以用于更新资源),PUT用来更新资源,DELETE用来删除资源;

3、通过操作资源的表现形式来操作资源;

4、资源的表现形式是XML或者HTML;

5、客户端与服务端之间的交互在请求之间是无状态的,从客户端到服务端的每个请求都必须包含理解请求所必需的信息

RESTful对url要求非常严格,要求每一个URI都表示一个资源。我们看看下面两个URL:

127.0.0.1/xxx.do?id=1

127.0.0.1/xxx/1

127.0.0.1/xxx/1是属于RESTful风格的,而127.0.0.1/xxx.do?id=1就不是RESTful风格了。

使用RESTful架构,你的应用结构就变成了下图所描述的一样:

注意噢,你的应用更多的是使用JSON数据格式返回数据共其他应用使用,你就是其他应用的数据源!

项目开发引入RESTful架构,利于团队并行开发。在RESTful架构中,将多数HTTP请求转移到前端服务器上,降低服务器的负荷,使视图获取后端模型失败也能呈现。但RESTful架构却不适用于所有的项目,当项目比较小时无需使用RESTful架构,项目变得更加复杂。

接下来我们就开始学习怎样再Spring MVC中使用RESTFul的架构风格。在这之前我们先了了解下Spring MVC中和RESTFul相关的一个Annotation:

@RequestBody restful风格的请求数据是使用json格式,此时我们在要接收请求参数的javabean前面添加@RequestBody就可以将请求的数据赋值到相应的bean属性中。

@GetMapping 该注解用来替代RequestMapping,特点是@GetMapping只处理get方式的请求。

@PostMapping 该注解用来替代RequestMapping,特点是@PostMapping只处理post方式的请求。

@PutMapping 该注解用来替代RequestMapping,特点是@PutMapping只处理put方式的请求。

@DeleteMapping 该注解用来替代RequestMapping,特点是@DeleteMapping只处理delete方式的请求。

以上注解就是在restful架构风格中spring mvc常用的注解,下面我们来完成一个restful风格的例子。

前端和后端的数据传输都使用json格式了,所以需要引入json相关的依赖之前已经讲过了,这里就不多讲了。要实现restful风格,还需要修改web.xml文件里面的中央控制器的url匹配方式,不能是*.do之类的了,需要要改成/。

<?xml version="1.0" encoding="UTF-8"?>
<web-appxmlns="http://xmlns.jcp.org/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaeehttp://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
     version="3.1">
 
<!--字符编码过滤器-->
<filter>
   <filter-name>characterEncodingFilter</filter-name>
   <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
 
    <!--指定字符编码-->
    <init-param>
       <param-name>encoding</param-name>
       <param-value>utf-8</param-value>
    </init-param>
 
    <!--强制指定字符编码,即如果在request中指定了字符编码,那么也会为其强制指定当前设置的字符编码-->
    <init-param>
       <param-name>forceEncoding</param-name>
       <param-value>true</param-value>
    </init-param>
</filter>
<filter-mapping>
   <filter-name>characterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
 
<!-- 注册spring MVC中央控制器 -->
<servlet>
   <servlet-name>springMVC</servlet-name>
    <!-- spring MVC中的核心控制器 -->
   <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
       <param-name>contextConfigLocation</param-name>
       <param-value>classpath:springmvc.xml</param-value>
    </init-param>
   <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
   <servlet-name>springMVC</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>
 
</web-app>

我们之前将前端控制器的路径设置为”/”,所有的请求都会通过前端控制器转发,这样静态资源的访问也会被当作controller的请求,所以我们同样需要做一些处理。

除了需要在springmvc的配置文件中添加静态资源,我们还需要设置json格式的字符编码,否则可能会在响应时出现乱码。

springMVC使用的StringHttpMessageConverter默认字符编码是ISO_8859_1,这样就会导致响应头中出现Content-Type: text/plain;charset=ISO-8859-1,即使你使用了spring mvc中自带的编码过滤器也会出现乱码问题,因为在字符编码过滤器中没有设置响应的Content-Type,所以最好在配置文件中设置json格式的字符编码为UTF-8。



<?xmlversion="1.0" encoding="UTF-8"?>
<beansxmlns="http://www.springframework.org/schema/beans"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
      xmlns:context="http://www.springframework.org/schema/context"
      xmlns:mvc="http://www.springframework.org/schema/mvc"
      xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/mvchttp://www.springframework.org/schema/mvc/spring-mvc.xsd">
 
 
    <!--解决返回json数据乱码问题-->
    <beanid="stringHttpMessageConverter"
         class="org.springframework.http.converter.StringHttpMessageConverter">
        <propertyname="supportedMediaTypes">
            <list>
               <value>text/plain;charset=UTF-8</value>
               <value>application/json;charset=UTF-8</value>
            </list>
        </property>
    </bean>
    <mvc:annotation-driven>
        <mvc:message-convertersregister-defaults="true">
            <beanclass="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
                <propertyname="supportedMediaTypes">
                    <list>
                       <value>text/html;charset=UTF-8</value>
                        <value>application/json</value>
                       <value>application/xml;charset=UTF-8</value>
                    </list>
                </property>
                <propertyname="features">
                    <list>
                        <!-- 默认的意思就是不配置这个属性,配置了就不是默认了 -->
                        <!-- 是否输出值为null的字段 ,默认是false-->
 
                       <value>WriteMapNullValue</value>
 
                       <value>WriteNullNumberAsZero</value>
                       <value>WriteNullListAsEmpty</value>
                       <value>WriteNullStringAsEmpty</value>
                       <value>WriteNullBooleanAsFalse</value>
                       <value>WriteDateUseDateFormat</value>
 
                    </list>
                </property>
            </bean>
        </mvc:message-converters>
    </mvc:annotation-driven>
 
 
    <!--静态资源-->
    <mvc:resourcesmapping="/images/**" location="/images/" />
    <mvc:resourcesmapping="/js/**" location="/js/" />
    <mvc:resourcesmapping="/css/**" location="/css/" />
    <mvc:resourcesmapping="/html/**" location="/html/" />
 
    <!-- 注册组件扫描器 -->
    <context:component-scanbase-package="com.pz.web.study.springmvc.*"/>
    <!-- 视图解析器 -->
    <bean class="org.springframework.web.servlet.view.BeanNameViewResolver"/>
 
    <!--内部视图解析器-->
    <beanclass="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix"value="/jsp/"/>
        <property name="suffix"value=".jsp"/>
    </bean>
 
</beans>

本文针对SpringMVC的讲解,暂时不需要数据库方面的操作,因此我们编写一个DataUtil.java工具类来提供数据支持。下面是rest风格的controller的写法,一个rest风格的url中是不能包含动词的(当然你写动词了也不会出问题),因为在rest风格眼中,互联网中的任何一个资源都是一个事物。

package com.pz.web.study.springmvc;
 
import java.time.LocalDate;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
 
public class DataUtil {
 
 
    private staticHashMap<String, User> dataMap = new HashMap<>();
 
    //mock data初始化
 
    static{
        User user1 = newUser("pangzi", "13188888888", "北京",LocalDate.of(2010, 10, 10));
        User user2 = newUser("matou", "13866666666", "上海",LocalDate.of(2014, 11, 11));
        User user3 = newUser("xxxxxx", "13799999999", "广州",LocalDate.of(2012, 12, 12));
 
       dataMap.put("1", user1);
       dataMap.put("2", user2);
       dataMap.put("3", user3);
    }
 
    /**
     * 查找全部数据
     * @return
     */
    public staticHashMap<String, User> findAll(){
        return dataMap;
    }
 
    /**
     * 根据id查找用户
     * @param id
     * @return
     */
    public static UserfindById(String id){
        returndataMap.get(id);
    }
 
    /**
     * 创建用户
     * @param user
     * @throws Exception
     */
    public static voidcreate(User user) throws Exception{
        //遍历map找到key的最大值
       Set<Map.Entry<String, User>> entries = dataMap.entrySet();
       Iterator<Map.Entry<String, User>> iterator =entries.iterator();
        int max = 3;
        while(iterator.hasNext()) {
           Map.Entry<String, User> next = iterator.next();
            int i =Integer.parseInt(next.getKey());
            if (i >max) {
                max = i;
            }
        }
 
        //将最大值做自增运算,然后作为key放入map中
       dataMap.put(++max+"", user);
    }
 
    /**
     * 更新用户
     * @param id
     * @param user
     */
    public static voidupdate(String id, User user) throws Exception {
        dataMap.put(id,user);
    }
 
    /**
     * 根据id删除用户
     * @param id
     * @throws Exception
     */
    public static voiddelete(String id) throws Exception {
       dataMap.remove(id);
    }
}

package com.pz.web.study.springmvc;
 
import com.alibaba.fastjson.JSON;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
 
import java.util.HashMap;
 
@RestController
public class RestFulController {
    /**
     * 查找所有用户
     *
     * @return
     * @throws Exception
     */
 
   @GetMapping("/users")
    public String findAll()throws Exception {
 
        HashMap<String,User> allUser = DataUtil.findAll();
 
        returnJSON.toJSONString(allUser);
    }
 
    /**
     * 根据id查找
     *
     * @param id
     * @return
     * @throws Exception
     */
 
   @GetMapping("/users/{id}")
    public StringfindById(@PathVariable String id) throws Exception {
        User user =DataUtil.findById(id);
 
 
        returnJSON.toJSONString(user);
    }
 
 
    /**
     * 新增
     *
     * @param user
     * @return
     */
 
   @PostMapping("/users")
    public Stringcreate(@RequestBody User user) {
 
        try {
           DataUtil.create(user);
           System.out.println(user.getName());
        } catch (Exceptione) {
           e.printStackTrace();
            returnJSON.toJSONString("fail");
        }
 
        returnJSON.toJSONString("success");
    }
 
 
    /**
     * 更新
     *
     * @param id
     * @param user
     * @return
     */
 
   @PutMapping("/users/{id}")
    public Stringupdate(@PathVariable String id, @RequestBody User user) {
        try {
           DataUtil.update(id, user);
        } catch (Exceptione) {
           e.printStackTrace();
            returnJSON.toJSONString("fail");
        }
        returnJSON.toJSONString("success");
    }
 
    /**
     * 删除
     *
     * @param id
     * @return
     */
 
   @DeleteMapping("/users/{id}")
    public Stringdelete(@PathVariable String id) {
 
        try {
           DataUtil.delete(id);
        } catch (Exception e) {
           e.printStackTrace();
            returnJSON.toJSONString("fail");
        }
        returnJSON.toJSONString("success");
    }
}

其实rest风格的前端可以完全不用使用jsp了,关于这一点,在之前的猿进化系列17——实战之一文学会前后端分离套路中已经讨论得淋漓尽致了。

接下来在webapp目录下创建html文件夹: user_list.html:

<!DOCTYPE html>
<html>
<head>
    <metacharset="utf-8">
    <metahttp-equiv="X-UA-Compatible" content="IE=edge">
    <metaname="viewport" content="width=device-width,initial-scale=1">
   <title>用户列表</title>
    <linkhref="../css/bootstrap.css" rel="stylesheet">
</head>
<body>
<div class="container theme-showcase"role="main">
    <divid="msg"></div>
    <divclass="page-header">
        <inputtype="text" id="user-id" name="id"placeholder="请输入id">
        <buttonid="query" type="button" class="btn btn-smbtn-primary">查询</button>
        <buttonid="add" type="button" class="btn btn-smbtn-success">添加</button>
    </div>
    <divclass="row">
        <divclass="">
            <tableclass="table table-striped">
               <thead>
                <tr>
                   <th>编号</th>
                   <th>姓名</th>
                   <th>手机</th>
                   <th>生日</th>
                   <th>地址</th>
                   <th>操作</th>
               </tr>
               </thead>
                <tbodyid="tbody">
                   <tr>
                        <td>
 
                       </td>
                   </tr>
               </tbody>
            </table>
        </div>
    </div>
</div>
<scriptsrc="../js/jquery-3.3.1.min.js"></script>
<script src="../js/bootstrap.js"></script>
<script>
    $(function () {
        //查找
        getAll();
 
        //绑定删除按钮的点击事件
       $("#tbody").on("click", ".btn-info",function () {
            var flag =confirm("是否删除?");
            if(flag){
               deleteUser(this);
            }
        });
 
        //绑定修改按钮的点击事件
       $("#tbody").on("click", ".btn-warning",function () {
            var userId =this.parentNode.parentNode.firstChild.innerHTML;
           $(location).attr('href', '/html/user_update.html?id='+userId);
 
        });
 
        //绑定添加按钮的点击事件
       $("#add").click(function () {
           $(location).attr('href', '/html/user_add.html');
        });
 
       $("#query").click(function () {
            var userId =$("#user-id").val();
            if(userId !=""){
                getUserById(userId);
            }else{
                getAll();
            }
 
        });
 
    });
 
 
    functiongetUserById(userId) {
        $.ajax({
           url:"/users/"+userId,
           type:"get",
           dataType:"json",
           success:function (result) {
                var  dataTR =
                   "<tr>" +
                   "<td>" + userId + "</td>" +
                   "<td>" + result.name + "</td>" +
                   "<td>" + result.phone + "</td>" +
                    "<td>" +result.birthday + "</td>" +
                   "<td>" + result.address + "</td>" +
                   "<td>" +
                   "<button type='button' class='btn btn-sm btn-info' >删除</button>"+
                   "<button type='button' class='btn btn-sm btn-warning' >修改</button>"+
                   "</td>" +
                   "</tr>";
 
               $("#tbody").html(dataTR);
            }
        });
    }
 
 
    //查找
    function getAll() {
        $.ajax({
            url:"/users",
           type:"get",
            dataType:"json",
            success:function (result) {
               
                var dataTR= "";
               $.each(result, function (index, value) {
                    dataTR+=
                       "<tr>" +
                       "<td>" + index + "</td>" +
                       "<td>" + value.name + "</td>" +
                       "<td>" + value.phone + "</td>" +
                       "<td>" + value.birthday + "</td>" +
                       "<td>" + value.address + "</td>" +
                       "<td>" +
                       "<button type='button' class='btn btn-sm btn-info' >删除</button>"+
                       "<button type='button' class='btn btn-sm btn-warning' >修改</button>"+
                       "</td>" +
                       "</tr>";
                });
 
               $("#tbody").html(dataTR);
            }
        });
    }
 
    //删除
    functiondeleteUser(obj) {
        var userId =obj.parentNode.parentNode.firstChild.innerHTML;
 
        $.ajax({
           url:"/users/"+userId,
           type:"delete",
           dataType:"json",
           success:function (result) {
                getAll();
                console.log(result)
                if(result== "success"){
                    //提示信息
                    varmsg = '<div class="alert alert-info" role="alert">删除成功!</div>';
                   $('#msg').html(msg);
 
                   setTimeout(function(){
                       $('#msg').empty();
                   },2000);
                }else{
                    //提示信息
                    varmsg = '<div class="alert alert-warning" role="alert">删除失败!</div>';
                   $('#msg').html(msg);
 
                   setTimeout(function(){
                       $('#msg').empty();
                   },2000);
                }
 
            }
 
        });
    }
</script>
</body>
</html>

user_add.html:

<!DOCTYPE html>
<html>
<head>
    <metacharset="utf-8">
    <metahttp-equiv="X-UA-Compatible" content="IE=edge">
    <metaname="viewport" content="width=device-width,initial-scale=1">
   <title>新增用户</title>
    <linkhref="../css/bootstrap.css" rel="stylesheet">
</head>
<body>
<div></div>
<div>
    <divid="msg"></div>
    <formid="user-form" style="max-width: 330px;padding: 15px;margin: 0auto;">
        <divclass="form-group">
            <labelfor="name">姓名:</label>
            <inputtype="text" id="name"name="name">
        </div>
        <divclass="form-group">
            <labelfor="phone">手机:</label>
            <inputtype="text" id="phone"name="phone">
        </div>
        <divclass="form-group">
            <labelfor="birthday">生日:</label>
            <inputtype="date" id="birthday"name="birthday">
        </div>
        <divclass="form-group">
            <labelfor="address">地址:</label>
            <input type="text"class="form-control" id="address"name="address">
        </div>
 
        <buttontype="button" class="btn btn-sm btn-primary">添加</button>
    </form>
</div>
</body>
<scriptsrc="../js/jquery-3.3.1.min.js"></script>
<script src="../js/bootstrap.js"></script>
<script>
    $(function () {
       $(".btn-primary").click(function () {
 
            var jsonForm=$('#user-form').serializeArray();
 
            var jsonData ={};
 
           $.each(jsonForm,function (i,v) {
                jsonData[v.name] = v.value;
            });
 
            var params =JSON.stringify(jsonData);
 
           $.ajax({
              url:"/users",
              type:"post",
              data:params,
              dataType:"json",
              contentType:"application/json",
              success:function (result) {
                   if(result == "success"){
                      $(location).attr('href', '/html/user_list.html');
                   }else{
                       //提示信息
                       varmsg = '<div class="alert alert-warning" role="alert">添加失败!</div>';
                      $('#msg').html(msg);
 
                      setTimeout(function(){
                          $('#msg').empty();
                       },2000);
                   }
 
               }
           });
        });
    });
</script>
</html>

user_update.html:

<!DOCTYPE html>
<html>
<head>
    <metacharset="utf-8">
    <metahttp-equiv="X-UA-Compatible" content="IE=edge">
    <metaname="viewport" content="width=device-width,initial-scale=1">
   <title>修改用户</title>
    <linkhref="../css/bootstrap.css" rel="stylesheet">
</head>
<body>
<div></div>
<div>
    <formid="user-form" style="max-width: 330px;padding: 15px;margin: 0auto;">
        <inputtype="hidden" id="id">
        <divclass="form-group">
            <labelfor="name">姓名:</label>
            <inputtype="text" id="name"name="name">
        </div>
        <divclass="form-group">
            <labelfor="phone">手机:</label>
            <inputtype="text" id="phone"name="phone">
        </div>
        <divclass="form-group">
            <labelfor="birthday">生日:</label>
            <input type="date"class="form-control" id="birthday"name="birthday">
        </div>
        <divclass="form-group">
            <labelfor="address">地址:</label>
            <inputtype="text" id="address"name="address">
        </div>
 
        <buttontype="button" class="btn btn-sm btn-primary">修改</button>
    </form>
</div>
</body>
<scriptsrc="../js/jquery-3.3.1.min.js"></script>
<script src="../js/bootstrap.js"></script>
<script>
    $(function () {
 
        //从url中获取携带的参数
        var userId =location.search.split("=");
 
       $("#userId").val(userId[1]);
 
        //查询要修改的数据
        $.ajax({
           url:"/users/"+userId[1],
           type:"get",
           dataType:"json",
           success:function (result) {
                $("#name").val(result.name);
               $("#phone").val(result.phone);
               $("#address").val(result.address);
               $("#birthday").val(result.birthday);
            }
        });
 
 
       $(".btn-primary").click(function () {
 
            var jsonForm=$('#user-form').serializeArray();
 
            var jsonData ={};
 
           $.each(jsonForm,function (i,v) {
               jsonData[v.name] = v.value;
            });
 
            var params = JSON.stringify(jsonData);
 
           $.ajax({
              url:"/users/"+userId[1],
              type:"put",
              data:params,
              dataType:"json",
              contentType:"application/json",
               success:function(result) {
                   if(result == "success"){
                      $(location).attr('href', '/html/user_list.html');
                   }else{
                       //提示信息
                       varmsg = '<div class="alert alert-warning" role="alert">添加失败!</div>';
                      $('#msg').html(msg);
 
                      setTimeout(function(){
                          $('#msg').empty();
                      },2000);
                   }
 
               }
           });
        });
    });
</script>
</html>

以上示例就是基于spring mvc的restful架构风格。