REST全称是Representational State Transfer,中文意思是表述(编者注:通常译为表征)性状态转移。 它首次出现在2000年Roy Fielding的博士论文中,Roy Fielding是HTTP规范的主要编写者之一。 他在论文中提到:“我这篇文章的写作目的,就是想在符合架构原理的前提下,理解和评估以网络为基础的应用软件的架构设计,得到一个功能强、性能好、适宜通信的架构。REST指的是一组架构约束条件和原则。” 如果一个架构符合REST的约束条件和原则,我们就称它为RESTful架构。
具体说,就是 HTTP 协议里面,四个表示操作方式的动词:GET、POST、PUT、DELETE。
它们分别对应四种基本操作:GET 用来获取资源,POST 用来新建资源,PUT 用来更新资源,DELETE 用来删除资源。
REST 风格提倡 URL 地址使用统一的风格设计,从前到后各个单词使用斜杠分开,不使用问号键值对方式携带请求参数,而是将要发送给服务器的数据作为 URL 地址的一部分,以保证整体风格的一致性。
操作 | 传统方式 | REST风格 |
---|---|---|
查询操作 | getUserById?id=1 | user/1–>get请求方式 |
保存操作 | saveUser | user–>post请求方式 |
删除操作 | deleteUser?id=1 | user/1–>delete请求方式 |
更新操作 | updateUser | user–>put请求方式 |
由于浏览器只支持发送get和post方式的请求,那么该如何发送put和delete请求呢?
SpringMVC 提供了 HiddenHttpMethodFilter 帮助我们将 POST 请求转换为 DELETE 或 PUT 请求
HiddenHttpMethodFilter 处理put和delete请求的条件:
a>当前请求的请求方式必须为post
b>当前请求必须传输请求参数_method
满足以上条件,HiddenHttpMethodFilter 过滤器就会将当前请求的请求方式转换为请求参数_method的值,因此请求参数_method的值才是最终的请求方式
在web.xml中注册HiddenHttpMethodFilter
<filter>
<filter-name>HiddenHttpMethodFilterfilter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilterfilter-class>
filter>
<filter-mapping>
<filter-name>HiddenHttpMethodFilterfilter-name>
<url-pattern>/*url-pattern>
filter-mapping>
注:
目前为止,SpringMVC中提供了两个过滤器:CharacterEncodingFilter和HiddenHttpMethodFilter
在web.xml中注册时,必须先注册CharacterEncodingFilter,再注册HiddenHttpMethodFilter
原因:
- 在 CharacterEncodingFilter 中通过 request.setCharacterEncoding(encoding) 方法设置字符集的
- request.setCharacterEncoding(encoding) 方法要求前面不能有任何获取请求参数的操作
- 而 HiddenHttpMethodFilter 恰恰有一个获取请求方式的操作:
String paramValue = request.getParameter(this.methodParam);
和传统 CRUD 一样,实现对员工信息的增删改查。
public class Employee {
private Integer id;
private String lastName;
private String email;
//1 male, 0 female
private Integer gender;
}
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import com.atguigu.mvc.bean.Employee;
import org.springframework.stereotype.Repository;
@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", "[email protected]", 1));
employees.put(1002, new Employee(1002, "E-BB", "[email protected]", 1));
employees.put(1003, new Employee(1003, "E-CC", "[email protected]", 0));
employees.put(1004, new Employee(1004, "E-DD", "[email protected]", 0));
employees.put(1005, new Employee(1005, "E-EE", "[email protected]", 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);
}
}
功能 | URL 地址 | 请求方式 |
---|---|---|
访问首页√ | / | GET |
查询全部数据√ | /employee | GET |
删除√ | /employee/2 | DELETE |
跳转到添加数据页面√ | /toAdd | GET |
执行保存√ | /employee | POST |
跳转到更新数据页面√ | /employee/2 | GET |
执行更新√ | /employee | PUT |
<mvc:view-controller path="/" view-name="index"/>
DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8" >
<title>Titletitle>
head>
<body>
<h1>首页h1>
<a th:href="@{/employee}">访问员工信息a>
body>
html>
@RequestMapping(value = "/employee", method = RequestMethod.GET)
public String getEmployeeList(Model model){
Collection<Employee> employeeList = employeeDao.getAll();
model.addAttribute("employeeList", employeeList);
return "employee_list";
}
DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Employee Infotitle>
<script type="text/javascript" th:src="@{/static/js/vue.js}">script>
head>
<body>
<table border="1" cellpadding="0" cellspacing="0" style="text-align: center;" id="dataTable">
<tr>
<th colspan="5">Employee Infoth>
tr>
<tr>
<th>idth>
<th>lastNameth>
<th>emailth>
<th>genderth>
<th>options(<a th:href="@{/toAdd}">adda>)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>
<a class="deleteA" @click="deleteEmployee" th:href="@{'/employee/'+${employee.id}}">deletea>
<a th:href="@{'/employee/'+${employee.id}}">updatea>
td>
tr>
table>
body>
html>
<form id="delete_form" method="post">
<input type="hidden" name="_method" value="delete"/>
form>
引入vue.js
<script type="text/javascript" th:src="@{/static/js/vue.js}">script>
删除超链接
<a class="deleteA" @click="deleteEmployee" th:href="@{'/employee/'+${employee.id}}">deletea>
通过vue处理点击事件
<script type="text/javascript">
var vue = new Vue({
el:"#dataTable",
methods:{
//event表示当前事件
deleteEmployee:function (event) {
//通过id获取表单标签
var delete_form = document.getElementById("delete_form");
//将触发事件的超链接的href属性为表单的action属性赋值
delete_form.action = event.target.href;
//提交表单
delete_form.submit();
//阻止超链接的默认跳转行为
event.preventDefault();
}
}
});
script>
@RequestMapping(value = "/employee/{id}", method = RequestMethod.DELETE)
public String deleteEmployee(@PathVariable("id") Integer id){
employeeDao.delete(id);
return "redirect:/employee";
}
<mvc:view-controller path="/toAdd" view-name="employee_add">mvc:view-controller>
DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Add Employeetitle>
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="add"><br>
form>
body>
html>
@RequestMapping(value = "/employee", method = RequestMethod.POST)
public String addEmployee(Employee employee){
employeeDao.save(employee);
return "redirect:/employee";
}
<a th:href="@{'/employee/'+${employee.id}}">updatea>
@RequestMapping(value = "/employee/{id}", method = RequestMethod.GET)
public String getEmployeeById(@PathVariable("id") Integer id, Model model){
Employee employee = employeeDao.get(id);
model.addAttribute("employee", employee);
return "employee_update";
}
DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Update Employeetitle>
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"><br>
form>
body>
html>
@RequestMapping(value = "/employee", method = RequestMethod.PUT)
public String updateEmployee(Employee employee){
employeeDao.save(employee);
return "redirect:/employee";
}
购买链接:
当当
京东
《Spring Cloud 微服务快速上手》介绍了当下主流的属于Spring生态的微服务框架,它继承了Spring Boot的优点,开发部署都非常简单。本书内容全面,介绍了微服务架构的发展历程,包含Spring Cloud Netflix 和 Spring Cloud Alibaba的组件,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等。在解读核心组件的实现原理的同时,配以案例进行实践。本书内容包含微服务架构和云原生架构,读者在掌握微服务之后,可以进一步掌握云原生知识。
Spring Cloud 系列技术更新迭代的速度非常快, 一直以来它的版本都是采用英国伦敦地铁站的命 名方式, 不过从 2020 年年底开始, 它抛弃了这种命名方式, 采用了日历化的版本命名方式。与此同 时, Spring Cloud 移除了很多旧的组件,如 netflix-archaius 、netflix-concurrency-limits 、netflix-core、 netflix-hystrix 、netflix-ribbon 等,几乎移除了Netflix 公司除了 Eureka 之外的所有组件,当然被移除 的组件都有对应的替代方案,这些都需要我们去学习。
关于作者和本书
笔者从事软件开发工作距今已有 10 年时间, 先后参与过电子政务、移动医疗、车联网、网约车 等业务。从 2019 年开始, 笔者从程序员转为讲师。此后, 笔者接触到了大量的不同年龄、不同工作 经验、不同背景的学生,从而慢慢总结出了一套让技术赋能业务的教学方式,可以让学生从解决方 案的角度去学习技术。
笔者从 2015 年开始接触微服务, 到现在已经有 6 年时间, 其间经历了 Spring Cloud 技术的大发 展,也利用 Spring Cloud 技术为公司解决了许多业务和技术问题。后来经过不断的总结,发现很多 问题产生的根源是对软件原理和对框架设计的不了解导致的。所以在编写这本书时,笔者从业务场 景、设计思路、落地实现、原理源码等几个方面来进行讲解,希望能给读者带来一些启发。
本书将主流的微服务解决方案基本都融合在一起。例如, 注册中心既包括 Netflix 的 Eureka,也 包括 Alibaba 的 Nacos,还包括 HashiCorp 的 Consul,让大家在技术选型中有一个横向的对比, 可以 结合自己的业务有更多的选择。另外,本书也提供了很多分布式的解决方案,如分布式锁和分布式 事务,可以让大家通过对本书的学习,对微服务技术栈有一个整体的认识,同时能将所学技能应用 在生产环境中。
第 1 章 微服务概述
1.1 单体架构
1.2 集群架构
1.3 微服务架构
1.4 微服务特性
1.5 微服务实践参考
1.6 微服务的缺点
1.7 Spring Cloud 简介
1.8 小结 第 2 章 微服务注册中心
2.1 为什么要有注册中心
2.2 注册中心的设计思路
2.2.1 注册中心的存储结构
2.2.2 注册中心需要具备的操作
2.3 Eureka 的使用
2.3.1 创建注册中心服务端 Eureka Server
2.3.2 创建客户端
2.3.3 Eureka Server 高可用搭建
2.3.4 Eureka Server 端用户认证
2.3.5 自我保护机制
2.3.6 多网卡选择
2.3.7 Eureka Server 源码解析
2.3.8 Eureka Client 源码解析
2.4 Nacos 的使用
2.4.1 搭建单节点 Nacos Server
2.4.2 创建 Nacos Client
2.4.3 高可用 Nacos Server 搭建
2.5 Consul 的使用
2.5.1 搭建单节点 Consul Server
2.5.2 创建 Consul Client
2.5.3 高可用 Consul Server 搭建
2.6 小结 第 3 章 服务调用
3.1 生产环境中的微服务架构
3.2 RestTemplate 调用
3.2.1 RESTful 风格介绍
3.2.2 RestTemplate 实战
3.2.3 RestTemplate 源码解析
3.2.4 负载均衡
3.2.5 自定义配置负载均衡
3.2.6 Ribbon 源码解析
3.3 OpenFeign 调用
3.3.1 OpenFeign 的基础使用
3.3.2 自定义 URL
3.3.3 自定义 OpenFeign 的配置
3.3.4 Feign 源码解析
3.4 小结 第 4 章 服务的熔断、降级和限流
4.1 熔断和降级的应用场景
4.2 熔断和降级的使用
4.2.1 RestTemplate 中熔断和降级的使用
4.2.2 OpenFeign 中熔断和降级的使用
4.3 自定义熔断配置
4.4 限流
4.4.1 计数器(固定窗口)算法
4.4.2 滑动时间窗口算法
4.4.3 漏桶限流算法
4.4.4 令牌桶限流算法
4.5 Sentinel 熔断和限流实战
4.5.1 Sentinel 控制台安装
4.5.2 Sentinel 在程序中的配置
4.5.3 Sentinel 流控规则
4.6 小结 第 5 章 配置中心
5.1 配置中心应用场景
5.2 配置中心的设计思路
5.2.1 配置存储
5.2.2 配置的属性
5.2.3 配置服务
5.3 Spring Cloud 配置中心的使用
5.3.1 在 Git 上创建配置
5.3.2 创建配置的服务端
5.3.3 创建配置的客户端
5.3.4 配置的手动刷新
5.3.5 配置的自动刷新
5.3.6 在 MySQL 上创建配置 .
5.3.7 配置内容对称加密
5.3.8 配置内容非对称加密
5.3.9 配置中心安全认证
5.3.10 高可用配置中心
5.4 Nacos 配置中心使用
5.4.1 Nacos 配置中心的基本使用
5.4.2 Nacos 配置扩展
5.4.3 Nacos 模型管理
5.5 总结 第 6 章 服务网关
6.1 网关 Gateway 的基本使用
6.1.1 微服务搭建 passenger-api
6.1.2 Gateway 网关搭建 cloud-gateway
6.1.3 Java 类加载器层级结构
6.1.4 Java 双亲委派机制原理
6.1.5 Java ClassLoader 类的原理
6.1.6 Java URLClassLoader 类的原理
6.1.7 Java 双亲委派机制的打破
6.1.8 Java 自定义类加载器
6.2 路由断言使用
6.2.1 Path 路由断言
6.2.2 Query 路由断言
6.2.3 Method 路由断言
6.2.4 Header 路由断言
6.2.5 自定义路由断言
6.3 过滤器的使用
6.3.1 添加请求头过滤器
6.3.2 移除请求头过滤器
6.3.3 状态码设置
6.3.4 重定向设置
6.3.5 过滤器源码
6.4 全局过滤器
6.5 小结 第 7 章 链路追踪
7.1 链路追踪的设计思路
7.2 链路追踪的使用
7.3 追踪原理分析
7.4 可视化链路追踪
7.5 消息队列收集链路追踪
7.6 小结 第 8 章 服务监控
8.1 Spring Boot Admin 的使用
8.2 监控内容介绍
8.3 认证保护
8.4 服务监听邮件通知
8.5 服务监听钉钉通知
8.6 小结 第 9 章 分布式锁解决方案
9.1 业务场景
9.2 单机 JVM 锁
9.2.1 系统架构与核心代码
9.2.2 JMeter 安装与配置
9.2.3 压力测试
9.2.4 单机 JVM 锁的问题
9.3 分布式锁思路分析
9.4 MySQL 分布式锁
9.5 Redis 分布式锁
9.5.1 死锁问题
9.5.2 过期时间问题
9.5.3 Redisson 框架使用
9.5.4 Redis 单节点问题
9.5.5 红锁
9.5.6 Redis 做分布式锁的终极问题
9.6 Zookeeper 分布式锁
9.6.1 Zookeeper 节点类型
9.6.2 Zookeeper 分布式锁原理
9.6.3 Zookeeper 结合 MySQL 乐观锁
9.6.4 Zookeeper 分布式锁代码实现
9.7 小结 第 10 章 分布式事务解决方案
10.1 分布式事务业务场景
10.2 分布式事务思路分析
10.3 X/Open 分布式事务模型
10.4 两阶段提交协议
10.4.1 两阶段提交协议的过程
10.4.2 两阶段提交协议的缺点
10.5 三阶段提交协议
10.5.1 三阶段提交协议的过程
10.5.2 两阶段提交协议和三阶段提交协议的区别
10.6 CAP 定理和 BASE 理论
10.6.1 CAP 定理
10.6.2 BASE 理论
10.7 TCC 分布式事务解决方案
10.7.1 TCC 方案
10.7.2 TCC 方案的异常处理
10.8 可靠消息终一致性方案
10.8.1 可靠消息终一致性问题分析
10.8.2 本地消息事件表方案
10.8.3 RocketMQ 事务消息方案
10.9 RocketMQ 安装部署
10.10 RocketMQ 事务消息实战
10.10.1 生产者 producer
10.10.2 消费者 consumer
10.11 Seata 分布式事务解决方案
10.12 Seata AT 模式实战
10.12.1 启动注册中心
10.12.2 下载安装 Seata
10.12.3 搭建订单服务
10.12.4 搭建库存服务
10.12.5 测试
10.13 Seata TCC 模式实战
10.13.1 订单服务
10.13.2 库存服务
10.13.3 测试
10.14 努力通知方案
10.14.1 什么是努力通知方案
10.14.2 努力通知方案实战
10.15 小结 第 11 章 微服务鉴权认证安全设计
11.1 鉴权认证常见的场景及解决方案
11.1.1 单体应用
11.1.2 微服务应用
11.2 OAuth 2.0 介绍
11.3 OAuth 2.0 实战
11.4 JWT 使用
11.4.1 JWT 的介绍
11.4.2 JWT 的实践
11.4.3 JWT 的使用场景
11.5 小结