1. REST 简介
REST(Representational State Transfer),表现形式状态转换,它是一种软件架构风格。
当要表示一个网络资源的时候,可以使用两种方式:
- 传统风格资源描述形式
http://localhost/user/getById?id=1
查询 id 为 1 的用户信息
http://localhost/user/saveUser
保存用户信息 - REST风格描述形式
http://localhost/user/1
查询 id 为 1 的用户信息
http://localhost/user
保存用户信息
传统方式一般是一个请求 url 对应一种操作,这样做不仅麻烦,也不安全,因为会程序的人读了请求 url 地址,就大概知道该 url 实现的是什么操作。
查看 REST 风格的描述,会发现请求地址变简单了,并且只看请求 URL 并不能轻易猜出该 URL 的具体功能。
所以 REST 的优点有:
- 隐藏资源的访问行为,无法通过地址得知对资源是何种操作。
- 书写简化。
但是问题也随之而来,一个相同的 url 地址既可以是新增也可以是修改或者查询,该如何区分该请求到底是什么操作呢?
按照 REST 风格访问资源时使用请求动作区分对资源进行了何种操作:
请求的方式比较多,但是比较常用的就 4 种,分别是 GET、POST、PUT、DELETE。不同的请求方式代表不同的操作类型:
- 发送 GET 请求是用来做查询
- 发送 POST 请求是用来做新增
- 发送 PUT 请求是用来做修改
- 发送 DELETE 请求是用来做删除
上述行为是约定方式,约定不是规范,可以打破,所以称REST风格,而不是REST规范。可以不这样做,但不建议。
描述模块的名称通常使用复数,也就是加 “s”。这样表示此类资源,而非单个资源,例如:users、books、accounts…
根据 REST 风格对资源进行访问称为 RESTful。
在开发过程中,大多都遵从 REST 风格来访问后台服务,所以可以说以后都是基于 RESTful 来进行开发。
2. RESTful 入门案例
@PathVariable
注解绑定路径参数与处理器方法形参间的关系,要求路径参数名与形参名一一对应。
public class BookController {
//用postman发post请求,请求路径:http://localhost/books
@RequestMapping(value = "/books",method = RequestMethod.POST)
@ResponseBody
public String save(@RequestBody Book book){
System.out.println("book save..." + book);
return "{'module':'book save'}";
}
//用postman发delete请求,请求路径:http://localhost/books/1
//请求路径中的“1”传给id
@RequestMapping(value = "/books/{id}",method = RequestMethod.DELETE)
@ResponseBody
//@PathVariable:把请求路径中的变量值传给形参
public String delete(@PathVariable Integer id){
System.out.println("book delete..." + id);
return "{'module':'book delete'}";
}
//用postman发put请求,请求路径:http://localhost/books
@RequestMapping(value = "/books",method = RequestMethod.PUT)
@ResponseBody
public String update(@RequestBody Book book){
System.out.println("book update..." + book);
return "{'module':'book update'}";
}
//用postman发get请求,请求路径:http://localhost/books/1
@RequestMapping(value = "/books/{id}",method = RequestMethod.GET)
@ResponseBody
//@PathVariable:把请求路径中的变量值传给形参
public String getById(@PathVariable Integer id){
System.out.println("book getById..." + id);
return "{'module':'book getById'}";
}
//用postman发get请求,请求路径:http://localhost/books
@RequestMapping(value = "/books",method = RequestMethod.GET)
@ResponseBody
public String getAll(){
System.out.println("book getAll...");
return "{'module':'book getAll'}";
}
}
@RequestBody、@RequestParam、@PathVariable 三个注解之间的区别和应用分别是什么?
区别:
- @RequestParam 用于接收 url 地址传参或表单传参。
- @RequestBody 用于接收 json 数据。
- @PathVariable 用于接收路径参数,使用 {参数名称} 描述路径参数。
应用:
- 后期开发中,发送请求参数超过 1 个时,以 json 格式为主,@RequestBody 应用较广。
- 如果发送非 json 格式数据,选用 @RequestParam 接收请求参数。
- 采用 RESTful 进行开发,当参数数量较少时,例如 1 个,可以采用 @PathVariable 接收请求路径变量,通常用于传递 id 值。
3. RESTful 快速开发
在前面基础上,
- 可以把相同的路径前缀写在类上,即:把
@RequestMapping("/books")
提到类上。 - 由于之前每个方法上都有
@ResponseBody
注解,所以可以统一写在类上。
得到如下代码:
@Controller
@ResponseBody
@RequestMapping("/books")
public class BookController {
//用postman发post请求,请求路径:http://localhost/books
@RequestMapping(method = RequestMethod.POST)
public String save(@RequestBody Book book){
System.out.println("book save..."+ book);
return "{'module':'book save'}";
}
//用postman发delete请求,请求路径:http://localhost/books/1
//请求路径中的“1”传给id
@RequestMapping(value = "/{id}",method = RequestMethod.DELETE)
//@PathVariable:把请求路径中的变量值传给形参
public String delete(@PathVariable Integer id){
System.out.println("book delete..." + id);
return "{'module':'book delete'}";
}
//用postman发put请求,请求路径:http://localhost/books
@RequestMapping(method = RequestMethod.PUT)
public String update(@RequestBody Book book){
System.out.println("book update..." + book);
return "{'module':'book update'}";
}
//用postman发get请求,请求路径:http://localhost/books/1
@RequestMapping(value = "/{id}",method = RequestMethod.GET)
//@PathVariable:把请求路径中的变量值传给形参
public String getById(@PathVariable Integer id){
System.out.println("book getById..." + id);
return "{'module':'book getById'}";
}
//用postman发get请求,请求路径:http://localhost/books
@RequestMapping(method = RequestMethod.GET)
public String getAll(){
System.out.println("book getAll...");
return "{'module':'book getAll'}";
}
}
在上面代码的基础上,@Controller
和@ResponseBody
注解可以用一个注解@RestController
替代。
请求动作也可以用更简单的注解表示。
// @Controller
// @ResponseBody
@RestController
@RequestMapping("/books")
public class BookController {
//用postman发post请求,请求路径:http://localhost/books
// @RequestMapping(method = RequestMethod.POST)
@PostMapping
public String save(@RequestBody Book book){
System.out.println("book save..."+ book);
return "{'module':'book save'}";
}
//用postman发delete请求,请求路径:http://localhost/books/1
//请求路径中的“1”传给id
// @RequestMapping(value = "/{id}",method = RequestMethod.DELETE)
@DeleteMapping("/{id}")
//@PathVariable:把请求路径中的变量值传给形参
public String delete(@PathVariable Integer id){
System.out.println("book delete..." + id);
return "{'module':'book delete'}";
}
//用postman发put请求,请求路径:http://localhost/books
// @RequestMapping(method = RequestMethod.PUT)
@PutMapping
public String update(@RequestBody Book book){
System.out.println("book update..." + book);
return "{'module':'book update'}";
}
//用postman发get请求,请求路径:http://localhost/books/1
// @RequestMapping(value = "/{id}",method = RequestMethod.GET)
@GetMapping("/{id}")
//@PathVariable:把请求路径中的变量值传给形参
public String getById(@PathVariable Integer id){
System.out.println("book getById..." + id);
return "{'module':'book getById'}";
}
//用postman发get请求,请求路径:http://localhost/books
// @RequestMapping(method = RequestMethod.GET)
@GetMapping
public String getAll(){
System.out.println("book getAll...");
return "{'module':'book getAll'}";
}
}
上面的代码去掉注释后:
@RestController
@RequestMapping("/books")
public class BookController {
//用postman发post请求,请求路径:http://localhost/books
@PostMapping
public String save(@RequestBody Book book){
System.out.println("book save..."+ book);
return "{'module':'book save'}";
}
//用postman发delete请求,请求路径:http://localhost/books/1
//请求路径中的“1”传给id
@DeleteMapping("/{id}")
//@PathVariable:把请求路径中的变量值传给形参
public String delete(@PathVariable Integer id){
System.out.println("book delete..." + id);
return "{'module':'book delete'}";
}
//用postman发put请求,请求路径:http://localhost/books
@PutMapping
public String update(@RequestBody Book book){
System.out.println("book update..." + book);
return "{'module':'book update'}";
}
//用postman发get请求,请求路径:http://localhost/books/1
@GetMapping("/{id}")
//@PathVariable:把请求路径中的变量值传给形参
public String getById(@PathVariable Integer id){
System.out.println("book getById..." + id);
return "{'module':'book getById'}";
}
//用postman发get请求,请求路径:http://localhost/books
@GetMapping
public String getAll(){
System.out.println("book getAll...");
return "{'module':'book getAll'}";
}
}
4. 基于 RESTful 的页面数据交互
4.1 需求分析
需求一:图片列表查询,从后台返回数据,将数据展示在页面上。
需求二:新增图书,将新增图书的数据传递到后台,并在控制台打印。
说明:此次案例的重点是在 SpringMVC 中使用 RESTful 实现前后台交互,所以并没有和数据库进行交互,所有数据使用假数据来完成开发。
4.2 后台接口开发
@RestController
@RequestMapping("/books")
public class BookController {
//保存图书
@PostMapping
public String save(@RequestBody Book book){
System.out.println("保存图书:"+book);
return "{'module':'save book success'}";
}
//查询所有图书
@GetMapping
public List<Book> getAll(){
System.out.println("查询所有图书...");
List<Book> bookList = new ArrayList<>();
Book book1 = new Book();
book1.setType("计算机");
book1.setName("SpringMVC入门教程");
book1.setDescription("小试牛刀");
bookList.add(book1);
Book book2 = new Book();
book2.setType("计算机");
book2.setName("SpringMVC实战教程");
book2.setDescription("一代宗师");
bookList.add(book2);
return bookList;
}
}
postman 测试:
① 测试新增
② 测试查询
4.2 页面访问处理
(1) 拷贝静态页面:将资料\功能页面下的所有内容拷贝到项目的 webapp 目录下。
(2) 访问 pages 目录下的 books.html
打开浏览器输入http://localhost/pages/books.html
为什么会出现错误?
SpringMVC 拦截了静态资源,根据 /pages/books.html
去 controller 找对应的方法,找不到所以会报 404 的错误。
SpringMVC 为什么会拦截静态资源呢?
解决方案:SpringMVC 需要将静态资源放行。
//设置静态资源访问过滤,当前类需要设置为配置类,并被扫描加载
@Configuration
public class SpringMvcSupport extends WebMvcConfigurationSupport {
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
// 当访问/pages/...的时候,从/pages目录下查找内容
registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");
registry.addResourceHandler("/js/**").addResourceLocations("/js/");
registry.addResourceHandler("/css/**").addResourceLocations("/css/");
registry.addResourceHandler("/plugins/**").addResourceLocations("/plugins/");
}
}
该配置类在 config 目录下,SpringMVC 当前扫描的是 controller 包,所以该配置类还未生效,要想生效需要修改 SpringMvcConfig 配置类的扫描范围。
@Configuration
@ComponentScan({"com.itheima.controller","com.itheima.config"})
@EnableWebMvc//开启将JSON转换成对象的功能
public class SpringMvcConfig {
}
//或者
@Configuration
@ComponentScan("com.itheima")
@EnableWebMvc
public class SpringMvcConfig {
}
(3) 修改 books.html 页面
页面完整代码:
<!DOCTYPE html>
<html>
<head>
<!-- 页面meta -->
<meta charset="utf-8">
<title>SpringMVC案例</title>
<!-- 引入样式 -->
<link rel="stylesheet" href="../plugins/elementui/index.css">
<link rel="stylesheet" href="../plugins/font-awesome/css/font-awesome.min.css">
<link rel="stylesheet" href="../css/style.css">
</head>
<body class="hold-transition">
<div id="app">
<div class="content-header">
<h1>图书管理</h1>
</div>
<div class="app-container">
<div class="box">
<div class="filter-container">
<el-input placeholder="图书名称" style="width: 200px;" class="filter-item"></el-input>
<el-button class="dalfBut">查询</el-button>
<el-button type="primary" class="butT" @click="openSave()">新建</el-button>
</div>
<el-table size="small" current-row-key="id" :data="dataList" stripe highlight-current-row>
<el-table-column type="index" align="center" label="序号"></el-table-column>
<el-table-column prop="type" label="图书类别" align="center"></el-table-column>
<el-table-column prop="name" label="图书名称" align="center"></el-table-column>
<el-table-column prop="description" label="描述" align="center"></el-table-column>
<el-table-column label="操作" align="center">
<template slot-scope="scope">
<el-button type="primary" size="mini">编辑</el-button>
<el-button size="mini" type="danger">删除</el-button>
</template>
</el-table-column>
</el-table>
<div class="pagination-container">
<el-pagination
class="pagiantion"
@current-change="handleCurrentChange"
:current-page="pagination.currentPage"
:page-size="pagination.pageSize"
layout="total, prev, pager, next, jumper"
:total="pagination.total">
</el-pagination>
</div>
<!-- 新增标签弹层 -->
<div class="add-form">
<el-dialog title="新增图书" :visible.sync="dialogFormVisible">
<el-form ref="dataAddForm" :model="formData" :rules="rules" label-position="right" label-width="100px">
<el-row>
<el-col :span="12">
<el-form-item label="图书类别" prop="type">
<el-input v-model="formData.type"/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="图书名称" prop="name">
<el-input v-model="formData.name"/>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<el-form-item label="描述">
<el-input v-model="formData.description" type="textarea"></el-input>
</el-form-item>
</el-col>
</el-row>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible = false">取消</el-button>
<el-button type="primary" @click="saveBook()">确定</el-button>
</div>
</el-dialog>
</div>
</div>
</div>
</div>
</body>
<!-- 引入组件库 -->
<script src="../js/vue.js"></script>
<script src="../plugins/elementui/index.js"></script>
<script type="text/javascript" src="../js/jquery.min.js"></script>
<script src="../js/axios-0.18.0.js"></script>
<script>
var vue = new Vue({
el: '#app',
data:{
dataList: [],//当前页要展示的分页列表数据
formData: {},//表单数据
dialogFormVisible: false,//增加表单是否可见
dialogFormVisible4Edit:false,//编辑表单是否可见
pagination: {},//分页模型数据,暂时弃用
},
//钩子函数,VUE对象初始化完成后自动执行
created() {
this.getAll();
},
methods: {
// 重置表单
resetForm() {
//清空输入框
this.formData = {};
},
// 弹出添加窗口
openSave() {
this.dialogFormVisible = true;
this.resetForm();
},
//添加
saveBook () {
axios.post("/books",this.formData).then((res)=>{
});
},
//主页列表查询
getAll() {
axios.get("/books").then((res)=>{
this.dataList = res.data;
});
},
}
})
</script>
</html>