1. RESTful风格介绍
RESTful架构风格是目前最流行的一种架构风格,它机构清晰、符合标准、易于理解、扩展方便,所以在Web开发中经常被使用。
REST,全称是Representational State Transfer,译作“表现层状态转化”,在 2000 年 Roy Fielding 的博士论文中首次被提出。面向资源是REST明显的特征,对于同一个资源的一组不同的操作。资源是服务器 上一个可命名的抽象概念,资源是以名词为核心来组织的,首先关注的是名词。REST要求必须通过统一的接口来对资源执行各种操作。对于每个资源只能执行一组有限的操作。
详细介绍参照RESTful百度百科
2. RESTful常用接口规范
简单介绍一下安全性和幂等,下面会提及:
- 安全性 :不会改变资源状态,可以理解为只读的;
- 幂等性 :执行1次和执行N次,对资源状态改变的效果是等价的。
2.1 GET
安全且幂等
获取表示
变更时获取表示(缓存)
状态码 | 表示的状态 |
---|---|
200(OK) | 已在响应中发出 |
204(无内容) | 资源有空 |
301(Moved Permanently) | 资源的URI已被更新 |
303(See Other) | 其他(如,负载均衡) |
304(not modified) | 资源未更改(缓存) |
400 (bad request) | 指代坏请求(如,参数错误) |
404 (not found) | 资源不存在 |
406 (not acceptable) | 服务端不支持所需表示 |
500 (internal server error) | 通用错误响应 |
503 (Service Unavailable) | 服务端当前无法处理请求 |
2.2POST
不安全且不幂等
使用服务端管理的(自动产生)的实例号创建资源
创建子资源
部分更新资源
如果没有被修改,则不过更新资源(乐观锁)
状态码 | 表示的状态 |
---|---|
200(OK) | 现有资源已被更改 |
201(created) | 新资源被创建 |
202(accepted) | 已接受处理请求但尚未完成(异步处理) |
301(Moved Permanently) | 资源的URI已被更新 |
303(See Other) | 其他(如,负载均衡) |
400 (bad request) | 指代坏请求(如,参数错误) |
404 (not found) | 资源不存在 |
406 (not acceptable) | 服务端不支持所需表示 |
409 (conflict) | 通用冲突 |
412(Precondition Failed) | 前置条件失败(如执行条件更新时的冲突) |
415 (unsupported media type) | 接受到的表示不受支持 |
500 (internal server error) | 通用错误响应 |
503 (Service Unavailable) | 服务端当前无法处理请求 |
2.3 PUT
不安全但幂等
用客户端管理的实例号创建一个资源
通过替换的方式更新资源
如果未被修改,则更新资源(乐观锁)
状态码 | 表示的状态 |
---|---|
200(OK) | 现有资源已被更改 |
201(created) | 新资源被创建 |
301(Moved Permanently) | 资源的URI已被更新 |
303(See Other) | 其他(如,负载均衡) |
400 (bad request) | 指代坏请求(如,参数错误) |
404 (not found) | 资源不存在 |
406 (not acceptable) | 服务端不支持所需表示 |
409 (conflict) | 通用冲突 |
412(Precondition Failed) | 前置条件失败(如执行条件更新时的冲突) |
415 (unsupported media type) | 接受到的表示不受支持 |
500 (internal server error) | 通用错误响应 |
503 (Service Unavailable) | 服务端当前无法处理请求 |
2.4 DELETE
不安全但幂等
删除资源
状态码 | 表示的状态 |
---|---|
200(OK) | 现有资源已被更改 |
301(Moved Permanently) | 资源的URI已被更新 |
303(See Other) | 其他(如,负载均衡) |
400 (bad request) | 指代坏请求(如,参数错误) |
404 (not found) | 资源不存在 |
409 (conflict) | 通用冲突 |
500 (internal server error) | 通用错误响应 |
503 (Service Unavailable) | 服务端当前无法处理请求 |
3.传统风格与RESTful风格简单对比
操作 | 传统风格URL | method | ------------- | RESTful风格URL | method |
---|---|---|---|---|---|
增加 | /addUser?name=xxx | POST | /users | POST | |
删除 | /deleteUser?id=1 | GET | /user/1 | DELETE | |
修改 | /updateUser?id=123&name=test | POST | /users/123 | PUT | |
查询 | /listUser | GET | /users | GET | |
指定查询 | /getUser?id=456 | GET | /users/456 | GET |
4.项目实战
4.1 新建数据
创建数据库
create database restful;
创建表
use restful;
CREATE TABLE user (
id int(11) NOT NULL AUTO_INCREMENT,
name varchar(30),
PRIMARY KEY (id)
) DEFAULT CHARSET=UTF8;
添加数据备用
insert into user values(null,'user 1');
insert into user values(null,'user 2');
insert into user values(null,'user 3');
insert into user values(null,'user 4');
insert into user values(null,'user 5');
insert into user values(null,'user 6');
4.2 构建项目
这次项目用springboot+mybatis+mysql
全工程构造:
开始新建工程:
项目信息如下
勾选Web模板
4.2.1 修改pom.xml文件,添加如下依赖;
<!-- servlet依赖. -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
</dependency>
<!-- tomcat的支持.-->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
</dependency>
<!-- mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.15</version><!--我装的是8.0.15版本-->
</dependency>
<!-- mybatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.3</version>
</dependency>
<!--添加PageHelper的支持,用于分页-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>4.1.6</version>
</dependency>
4.2.2 修改application.properties文件
spring.mvc.view.prefix=/WEB-INF/jsp/
spring.mvc.view.suffix=.jsp
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/restful?useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true
spring.datasource.username=root
spring.datasource.password=666666
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
注意:我用得是Mysql8.0 所以驱动类是 com.mysql.cj.jdbc.Driver如果是其他版本请改用com.mysql.jdbc.Driver
4.2.3 新建一个PageHelperConfig 类用于配置
package com.eknaij.springbootrestful.config;
import com.github.pagehelper.PageHelper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Properties;
@Configuration
public class PageHelperConfig {
@Bean
public PageHelper pageHelper() {
PageHelper pageHelper = new PageHelper();
Properties p = new Properties();
p.setProperty("offsetAsPageNum", "true"); //offsetAsPageNum:设置为true时,会将RowBounds第一个参数offset当成pageNum页码使用.
p.setProperty("rowBoundsWithCount", "true"); //rowBoundsWithCount:设置为true时,使用RowBounds分页会进行count查询.
p.setProperty("reasonable", "true");//reasonable:启用合理化时,如果pageNum<1会查询第一页,如果pageNum>pages会查询最后一页。
pageHelper.setProperties(p);
return pageHelper;
}
}
4.2.4 新建一个User,只包含id和name
package com.eknaij.springbootrestful.pojo;
public class User {
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
4.2.5 新建一个UserMapper接口
package com.eknaij.springbootrestful.mapper;
import com.eknaij.springbootrestful.pojo.User;
import org.apache.ibatis.annotations.*;
import java.util.List;
@Mapper
public interface UserMapper {
@Select("select * from user ")
List<User> findAll();
@Insert(" insert into user ( name ) values (#{name}) ")
public int save(User user);
@Delete(" delete from user where id= #{id} ")
public void delete(int id);
@Select("select * from user where id= #{id} ")
public User get(int id);
@Update("update user set name=#{name} where id=#{id} ")
public int update(User user);
}
4.2.6 新建controller类
package com.eknaij.springbootrestful.controller;
import com.eknaij.springbootrestful.mapper.UserMapper;
import com.eknaij.springbootrestful.pojo.User;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
@Controller
public class UserController {
@Autowired
UserMapper userMapper;
@RequestMapping("/listUser")
//在参数里接受当前是第几页 start ,以及每页显示多少条数据 size。 默认值分别是0和5。
public String listUser(Model model, @RequestParam(value = "start", defaultValue = "0") int start,
@RequestParam(value = "size", defaultValue = "5") int size) throws Exception {
//根据start,size进行分页
PageHelper.startPage(start,size);
List<User> cs=userMapper.findAll();
PageInfo<User> page = new PageInfo<>(cs);
model.addAttribute("page", page);
return "listUser";
}
@RequestMapping("/addUser")
public String addUser(User c) throws Exception {
userMapper.save(c);
return "redirect:listUser";
}
@RequestMapping("/deleteUser")
public String deleteUser(User c) throws Exception {
userMapper.delete(c.getId());
return "redirect:listUser";
}
@RequestMapping("/updateUser")
public String updateUser(User c) throws Exception {
userMapper.update(c);
return "redirect:listUser";
}
@RequestMapping("/editUser")
public String getUser(int id, Model m) throws Exception {
User c= userMapper.get(id);
m.addAttribute("c", c);
return "editUser";
}
}
4.2.7 新建视图listUser.jsp(采用传统的方法)
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<div align="center">
</div>
<div style="width:500px;margin:20px auto;text-align: center">
<table align='center' border='1' cellspacing='0'>
<tr>
<td>id</td>
<td>name</td>
<td>编辑</td>
<td>删除</td>
</tr>
<c:forEach items="${page.list}" var="c" varStatus="st">
<tr>
<td>${c.id}</td>
<td>${c.name}</td>
<td><a href="editUser?id=${c.id}">编辑</a></td>
<td><a href="deleteUser?id=${c.id}">删除</a></td>
</tr>
</c:forEach>
</table>
<br>
<div>
<a href="?start=1">[首 页]</a>
<a href="?start=${page.pageNum-1}">[上一页]</a>
<a href="?start=${page.pageNum+1}">[下一页]</a>
<a href="?start=${page.pages}">[末 页]</a>
</div>
<br>
<form action="addUser" method="post">
name: <input name="name"> <br>
<button type="submit">提交</button>
</form>
</div>
4.2.8 新建一个editUser.jsp用于修改信息:
<%@ page language=“java” contentType=“text/html; charset=UTF-8”
pageEncoding=“UTF-8” isELIgnored=“false”%>
<form action="updateUser" method="post">
name: <input name="name" value="${c.name}"> <br>
<input name="id" type="hidden" value="${c.id}">
<button type="submit">提交</button>
</form>
4.2.9 浏览器启动测试
运行项目,浏览器输入http://localhost:8080/listUser
,可以看到如下信息:
CRUD操作可以用,接下来我们修改成RESTful风格
5. 修改成RESTful风格
5.1 修改listUser.jsp
- 增加:把form表单的 action修改为"users"
将Usercontroller中addUser方法的@RequestMapping("/addUser")改成@PostMapping("/users"),跟前面表单的action对应
- 删除:由于一个页面中只能用一个submit按钮提交一个form表单,所以这里我们采用JS进行提交,原理上是一样的,相当于把action改成了users/${c.id},把method改成了DELETE
修改删除的超链接:
在后面添加一个用于提交删除的表单:
然后新建JS文件夹,导入JS:
在JSP中导入文件<script type="text/javascript" src="js/jquery.min.js"></script>
编写JS,如下
<script type="text/javascript" src="js/jquery.min.js"></script>
<script type="text/javascript">
/*将post method 改变为delete*/
$(function(){
$(".delete").click(function(){
var href=$(this).attr("href");
$("#formdelete").attr("action",href).submit();
return false;
})
})
</script>
注意:一般实际开发中删除要进行二次确认,在此为了方便便省略了
将Usercontroller中deleteUser方法的@RequestMapping("/deleteUser")改成 @DeleteMapping("/users/{id}")
-
指定查询:url修改为users/id
将Usercontroller中getUser方法的@RequestMapping("/editUser")改成@GetMapping("/users/{id}") -
修改:
将editUser.jsp页面form表单页面的action改成users/id的形式,方法为PUT:
由于form表单的method没有PUT方法 所以我们用一个隐藏的input修改method方法为PUT
将Usercontroller中updateUser方法的@RequestMapping("/updateUser")改成@PutMapping("/users/{id}")
最终Usercontroller类如图:
红框中的为已修改后的内容
6. 测试RESTful风格
由于之前我们进行了修改,所以重新启动项目后,浏览器输入的url为http://localhost:8080/users
,运行正确就可以看到和之前一样的界面:
如果CRUD操作都没有问题就OK了
7.总结
RESTful风格与传统风格大同小异,关键的地方要注意
- 前端视图表单提交的action要与后端相对应
- 不同的操作使用的注解不一样,下面汇总为一个表格方便对比:
操作 | 使用的注解 |
---|---|
增加 | @PostMapping("") |
删除 | @DeleteMapping("") |
修改 | @PutMapping("") |
查询 | @GetMapping("") |
本文源码:点击前往