REST 以及 Spring 对 REST 的支持

下图是学习 REST 风格接口,然后通过 SpringMVC 实现的一个课程管理系统的效果图。 那么 RESTful 是什么呢?其实简言之看 Url 就知道要什么、看 Http method 就知道干什么、看 Http status code 就知道结果如何。

大家都知道 “古代” 网页是前端后端融在一起的,比如之前的 PHP,JSP 等。在之前的桌面时代问题不大,但是近年来移动互联网的发展,各种类型的 Client 层出不穷,RESTful 可以通过一套统一的接口为 Web,iOS 和 Android 提供服务。另外对于广大平台来说,比如 Facebook platform,微博开放平台,微信公共平台等,它们不需要有显式的前端,只需要一套提供服务的接口,于是 RESTful 更是它们最好的选择。

REST 风格概述

REST 风格的服务呢:REST(Representational State Transfer),中文可以叫表述性状态转移!

如你所见,RESTful 其实是为了只提供一套统一的接口,让其他任何需要访问资源的设备或者服务都从这个同一的接口写数据,取数据!

REST 本质上是使用 URL 来访问资源种方式。URL 就是我们平常使用的请求地址了,其中包括两部分:请求方式与请求路径,比较常见的请求方式是 GET 与 POST,但在 REST 中又提出了几种其它类型的请求方式,汇总起来有六种:GET、POST、PUT、DELETE、HEAD、OPTIONS。尤其是前四种,正好与 CRUD(Create-Retrieve-Update-Delete,增删改查)四种操作相对应,例如,GET(查)、POST(增)、PUT(改)、DELETE(删),这正是 REST 与 CRUD 的异曲同工之妙!需要强调的是,REST 是 “面向资源” 的,这里提到的资源,实际上就是我们常说的领域对象,在系统设计过程中,我们经常通过领域对象来进行数据建模。

REST 是一个 “无状态” 的架构模式,因为在任何时候都可以由客户端发出请求到服务端,最终返回自己想要的数据,当前请求不会受到上次请求的影响。也就是说,服务端将内部资源发布 REST 服务,客户端通过 URL 来访问这些资源,这不就是 SOA 所提倡的 “面向服务” 的思想吗?所以,REST 也被人们看做是一种 “轻量级” 的 SOA 实现技术,因此在企业级应用与互联网应用中都得到了广泛应用。

REST 描述了一个架构样式的互联系统(如 Web 应用程序)。REST 约束条件作为一个整体应用时,将生成一个简单、可扩展、有效、安全、可靠的架构。由于它简便、轻量级以及通过 HTTP 直接传输数据的特性,RESTful Web 服务成为基于 SOAP 服务的一个最有前途的替代方案。用于 web 服务和动态 Web 应用程序的多层架构可以实现可重用性、简单性、可扩展性和组件可响应性的清晰分离。开发人员可以轻松使用 Ajax 和 RESTful Web 服务一起创建丰富的界面,相信这句话还是很能说明问题的!

REST 实战

这个练手小项目就是正如上图所示,是个课程管理的项目,功能是查询课程,添加课程,修改课程,删除课程,CURD 全齐了(为了说明主要问题,Dao 层直接通过一个 HashMap 模拟了),下面把代码摆出来,摆代码之前先说说这个小项目的注意点:

  • 发一个重定向请求,注意格式问题: return"redirect:/getAll";
  • PostMapping 用于增删改查的
  • GetMapping 用于增删改查的
  • PutMapping 用于增删改查的
  • DeleteMapping 用于增删改查的
  • 写 jsp 的时候不要忘记 EL 表达式的支持
  • 引入 css 文件的时候,要么选择网络连接引入,要么配置 css 的 servlet 默认请求,否则会引入失败
  • 隐式 DELETE 请求和隐式 PUT 请求 <input type="hidden"name="_method"value="DELETE"/><input type="hidden"name="_method"value="PUT"/>
  • Button 做页面跳转的方式:<button type="button"class="btn btn-default"onclick ="window.location.href = 'getAdd'">

CourseController.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
@Controller
@RequestMapping ("/")
public class CourseController {
@Autowired
private CourseDao courseDao;

@RequestMapping (value = "/getAdd")
public ModelAndView getAdd(){
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName ("add");
return modelAndView;
}

/**
* 添加课程
*/
@PostMapping (value = "/add")
public String add(Course course){
courseDao.add (course);
// 发一个重定向请求
return "redirect:/getAll";
}

/**
* 查询全部课程信息
*/
@GetMapping (value = "/getAll")
public ModelAndView getAll(){
ModelAndView view = new ModelAndView();
view.setViewName ("index");
view.addObject ("courses", courseDao.getAll ());
return view;
}

/**
* 通过 id 查询课程
*/
@GetMapping (value = "/getById/{id}")
public ModelAndView getById(@PathVariable (value = "id") int id){
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName ("edit");
modelAndView.addObject ("course", courseDao.getById (id));
return modelAndView;
}

/**
* 修改课程
*/
@PutMapping (value = "/update")
public String update(Course course){
courseDao.update (course);
// 发一个重定向请求
return "redirect:/getAll";
}

/**
* 删除课程
*/
@DeleteMapping (value = "/delete/{id}")
public String update(@PathVariable (value = "id")int id){
courseDao.delete (id);
// 发一个重定向请求
return "redirect:/getAll";
}
}

Dao 层:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
@Repository
public class CourseDao {
private Map<Integer , Course> courses = new HashMap<>();
/**
* 新增课程
*/
public void add(Course course){
courses.put (course.getId (), course);
}

/**
* 查询全部
*/
public Collection<Course> getAll(){
return courses.values ();
}

/**
* 根据 id 查询
*/
public Course getById(int id){
return courses.get (id);
}

/**
* 修改课程
*/
public void update(Course course){
courses.put (course.getId (), course);
}

/**
* 删除课程
*/
public void delete(int id){
courses.remove (id);
}
}

JavaBean:
1
2
3
4
5
public class Course {
private int id;
private String name;
private double price;
}

web.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59

<web-app>
<display-name>Archetype Created Web Application</display-name>
<!-- 配置过滤器 -->
<filter>
<filter-name>hiddenHttpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>

<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<!-- 要使用的字符集,一般我们使用 UTF-8 (保险起见 UTF-8 最好)-->
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<!-- 是否强制设置 request 的编码为 encoding,默认 false,不建议更改 -->
<param-name>forceRequestEncoding</param-name>
<param-value>false</param-value>
</init-param>
<init-param>
<!-- 是否强制设置 response 的编码为 encoding,建议设置为 true,下面有关于这个参数的解释 -->
<param-name>forceResponseEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>


<filter-mapping>
<filter-name>hiddenHttpMethodFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

<servlet>
<servlet-name>springmvc</servlet-name>
<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>
</servlet>

<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.css</url-pattern>
</servlet-mapping>

<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>

springmvc.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!-- JSON 数据解析 -->
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
</bean>
</mvc:message-converters>
</mvc:annotation-driven>

<!-- 配置自动扫描 IOC 容器 -->
<context:component-scan base-package="cn.edu.xpu"/>

<!-- 配置视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- 前缀 -->
<property name="prefix" value="/"></property>
<!-- 配置后缀 -->
<property name="suffix" value=".jsp"></property>
</bean>

index.jsp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page isELIgnored="false" %>
<html>
<head>
<title>Title</title>
<!-- 最新版本的 Bootstrap 核心 CSS 文件 -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">

<!-- 可选的 Bootstrap 主题文件(一般不用引入) -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">

<!-- 最新的 Bootstrap 核心 JavaScript 文件 -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>

</head>
<body>

<div class="container">
<!-- 标题 -->
<div class="row">
<div class="col-md-12">
<h1> 课程管理 & lt;button type="button" class="btn btn-default"
onclick = "window.location.href = 'getAdd'"> 增加课程 & lt;/button></h1>
</div>
</div>
<!-- 显示表格数据 -->
<div class="row">
<div class="col-md-12">
<table class="table table-hover" id="emps_table">
<thead>
<tr>
<th>
<input type="checkbox" id="check_all"/>
</th>
<th> 编号 & lt;/th>
<th> 课程名 & lt;/th>
<th> 价格 & lt;/th>
<th> 编辑 & lt;/th>
<th> 删除 & lt;/th>
</tr>
</thead>
<tbody>
<c:forEach items="${courses}" var="course">
<tr>
<td><input type='checkbox' class='check_item'/></td>
<td>${course.id}</td>
<td>${course.name}</td>
<td>${course.price}</td>
<td>
<form action="${pageContext.request.contextPath}/getById/${course.id}" method="get">
<!-- 隐式 PUT 请求 -->
<input type="hidden" name="_method" value="PUT"/>
<button class="btn btn-primary btn-sm edit_btn">
<span class="glyphicon glyphicon-pencil"> 编辑 & lt;/span>
</button>
</form>
</td>
<td>
<form action="${pageContext.request.contextPath}/delete/${course.id}" method="post">
<!-- 隐式 DELETE 请求 -->
<input type="hidden" name="_method" value="DELETE"/>
<button class="btn btn-danger btn-sm delete_btn">
<span class="glyphicon glyphicon-trash"> 删除 & lt;/span>
</button>
</form>
</td>
</tr>
</c:forEach>
</tbody>
</table>
</div>
</div>

</div>
</body>
</html>

edit.jsp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page isELIgnored="false" %>
<html>
<head>
<title>Title</title>
<!-- 最新版本的 Bootstrap 核心 CSS 文件 -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">

<!-- 可选的 Bootstrap 主题文件(一般不用引入) -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">

<!-- 最新的 Bootstrap 核心 JavaScript 文件 -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
</head>
<body>
<div class="container">
<div class="row">
<form class="form-horizontal" role="form" action="${pageContext.request.contextPath}/update" method="post">
<div class="form-group">
<label class="col-sm-1 control-label"> 课程编号 & lt;/label>
<div class="col-sm-3">
<input type="text" value="${course.id}" readonly="readonly" class="form-control" name="id" placeholder=" 请输入商品名称 & quot;>
</div>
</div>
<div class="form-group">
<label class="col-sm-1 control-label"> 课程名称 & lt;/label>
<div class="col-sm-3">
<input type="text" value="${course.name}" class="form-control" name="name" placeholder=" 请输入商品价格 & quot;>
</div>
</div>
<div class="form-group">
<label class="col-sm-1 control-label"> 课程价格 & lt;/label>
<div class="col-sm-3">
<input type="text" value="${course.price}" class="form-control" name="price" placeholder=" 请输入商品价格 & quot;>
</div>
</div>
<div class="form-group">
<label class="col-sm-1 control-label"></label>
<div class="col-sm-3">
<input type="hidden" name="_method" value="PUT"/>
<button type="submit" class="btn btn-default" > 提交 & lt;/button>
</div>
</div>

</form>
</div>
</div>
</body>
</html>

add.jsp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page isELIgnored="false" %>
<html>
<head>
<title>Title</title>
<!-- 最新版本的 Bootstrap 核心 CSS 文件 -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">

<!-- 可选的 Bootstrap 主题文件(一般不用引入) -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">

<!-- 最新的 Bootstrap 核心 JavaScript 文件 -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
</head>
<body>


<div class="col-md-12">
<form class="form-horizontal" role="form" action="${pageContext.request.contextPath}/add" method="post">
<div class="form-group">
<label class="col-sm-1 control-label"> 课程编号 & lt;/label>
<div class="col-sm-3">
<input type="text" class="form-control" name="id" placeholder=" 请输入商品名称 & quot;>
</div>
</div>
<div class="form-group">
<label class="col-sm-1 control-label"> 课程名称 & lt;/label>
<div class="col-sm-3">
<input type="text" class="form-control" name="name" placeholder=" 请输入商品价格 & quot;>
</div>
</div>
<div class="form-group">
<label class="col-sm-1 control-label"> 课程价格 & lt;/label>
<div class="col-sm-3">
<input type="text" class="form-control" name="price" placeholder=" 请输入商品价格 & quot;>
</div>
</div>

<div class="form-group">

<label class="col-sm-1 control-label"></label>
<div class="col-sm-3">
<button type="submit" class="btn btn-default" > 提交 & lt;/button>
</div>
</div>
</form>
</div>
</body>
</html>

pom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
</dependency>
<!-- JSTL -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<!-- JSTL 实现包 -->
<dependency>
<groupId>org.apache.taglibs</groupId>
<artifactId>taglibs-standard-impl</artifactId>
<version>1.2.5</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>

<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.8</version>
</dependency>