REST 服务可用于实现两个应用之间的通讯,包括 Web 应用中的客户端和服务器之间,移动应用与后端服务之间,或两个后端服务之间。

10.1 使用 REST 服务在应用之间交换数据

REST端点是应用程序通过 Web 协议公开服务的方式,因此也称为 Web 服务。

在 Spring 中,REST 端点仍然是映射到 HTTP 方法和路径的控制器操作。但对于 REST 服务, Spring MVC 调度器 servlet 不会查找视图,服务器在 HTTP 响应中直接向客户端返回控制器操作的返回内容。

REST 端点需注意以下问题:

  • 如果控制器的操作需要很长时间才能完成,对端点的 HTTP 调用可能会超时并中断通信。
  • 不建议在一次调用中发送大量数据(例如几兆字节),可能会导致调用超时并中断通信。
  • 端点上过多的并发调用可能导致应用失败。
  • REST 端点调用可能因为网络原因而失败。

总之,要考虑对失效情况的处理。作者推荐了J. J. Geewax的API Design
Patterns (Manning, 2021)一书。

10.2 实现 REST 端点

Spring 在 REST 端点后面使用相同的 Spring MVC 机制。

看一下示例sq-ch10-ex1。本例非常简单,没有HTML文件,只有一个控制类HelloController :

@Controller
public class HelloController {@GetMapping("/hello")@ResponseBodypublic String hello() {return "Hello!";}@GetMapping("/ciao")@ResponseBodypublic String ciao() {return "Ciao!";}
}

注解@Controller和@GetMapping上一章都讲过了,唯一新的注解是@ResponseBody。它的作用是告知调度 servlet,控制器的操作不会返回视图名称,而是直接在 HTTP 响应中发送数据。

本例和后续示例需要安装postman:
在这里插入图片描述
或者也可以命令行工具cURL(Ciao是意大利语的Hello):

$ curl http://localhost:8080/hello
Hello!
$ curl -X GET http://localhost:8080/ciao
Ciao!

示例sq-ch10-ex2和上例的效果完全一样,只不过使用注解@RestController,他是@Controller 和 @ResponseBody 的组合。

@RestController
public class HelloController {@GetMapping("/hello")public String hello() {return "Hello!";}@GetMapping("/ciao")public String ciao() {return "Ciao!";}
}

10.3 管理 HTTP 响应

HTTP 响应是指后端应用根据客户端请求将数据返回给客户端的方式,包含以下数据:

  • 响应头:响应中的短数据片段(通常不超过几个字)
  • 响应正文:返回的大量数据
  • 响应状态

此处建议阅读附录C:HTTP简介。

10.3.1 将对象作为响应主体发送

示例sq-ch10-ex3和上例非常类似,只不过返回值从字符串变为对象(此对象也称为DTO,即data transfer object)。

看一下控制类:

@RestController
public class CountryController {@GetMapping("/france")public Country france() {Country c = Country.of("France", 67);return c;}@GetMapping("/all")public List<Country> countries() {Country c1 = Country.of("France", 67);Country c2 = Country.of("Spain", 47);return List.of(c1,c2);}
}

应用运行如下:

$ curl http://localhost:8080/all
[{"name":"France","population":67},{"name":"Spain","population":47}]$ curl http://localhost:8080/france
{"name":"France","population":67}

返回的对象为JSON格式。使用 REST 端点时,JSON 是最常见的对象表示方式(当然你也可以用XML或YAML)。此处建议阅读附录D:使用 JSON 格式。

10.3.2 设置响应状态和标头

在某些情况下,会要求自定义 HTTP 响应状态,其最简单、最常用的方法是使用 ResponseEntity 类。Spring 提供的这个类允许您指定 HTTP 响应的主体、状态和标头。详见示例sq-ch10-ex4

model类和上例一样,控制类变为如下:

@RestController
public class CountryController {@GetMapping("/france")public ResponseEntity<Country> france() {Country c = Country.of("France", 67);return ResponseEntity.status(HttpStatus.ACCEPTED).header("continent", "Europe").header("capital", "Paris").header("favorite_food", "cheese and wine").body(c);}
}

程序运行如下:

$ curl -v http://localhost:8080/france
* Host localhost:8080 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
*   Trying [::1]:8080...
* Connected to localhost (::1) port 8080
* using HTTP/1.x
> GET /france HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/8.12.1
> Accept: */*
>
< HTTP/1.1 202
< continent: Europe
< capital: Paris
< favorite_food: cheese and wine
< Content-Type: application/json
< Transfer-Encoding: chunked
< Date: Sun, 03 Aug 2025 13:02:47 GMT
<
{"name":"France","population":67}* Connection #0 to host localhost left intact

其中202对应HttpStatus.ACCEPTED。

postman运行输出如下:
在这里插入图片描述

10.3.3 在端点级别管理异常

在很多情况下,我们会使用异常来指示特定情况,其中一些与业务逻辑相关。在这种情况下,您可能需要在 HTTP 响应中设置一些详细信息,以告知客户端发生的具体情况。管理异常的方法之一是在控制器的操作中捕获异常,并使用ResponseEntity 类,在发生异常时发送不同的响应配置。

参见示例sq-ch10-ex5。来看一下控制类的主体部分:

@PostMapping("/payment")public ResponseEntity<?> makePayment() {try {PaymentDetails paymentDetails = paymentService.processPayment();return ResponseEntity.status(HttpStatus.ACCEPTED).body(paymentDetails);} catch (NotEnoughMoneyException e) {ErrorDetails errorDetails = new ErrorDetails();errorDetails.setMessage("Not enough money to make the payment.");return ResponseEntity.badRequest().body(errorDetails);}}

运行输出如下:

$ curl -X POST http://localhost:8080/payment
{"message":"Not enough money to make the payment."}

上述错误处理方法虽然可行,但在更复杂的应用程序中,将异常管理分离处理会更方便。这样可以减少重复代码,因为有时同一个异常可能被多个端点复用。其次,当你需要理解特定情况的工作原理时,知道在一个地方找到所有异常逻辑会更方便。因此,推荐使用 REST 控制器建议,这是一个可以拦截控制器操作抛出的异常并根据拦截到的异常应用自定义逻辑的切面。

在示例sq-ch10-ex6中,控制类PaymentController不再进行异常处理,因此代码大大简化:

@RestController
public class PaymentController {private final PaymentService paymentService;public PaymentController(PaymentService paymentService) {this.paymentService = paymentService;}@PostMapping("/payment")public ResponseEntity<PaymentDetails> makePayment() {PaymentDetails paymentDetails = paymentService.processPayment();return ResponseEntity.status(HttpStatus.ACCEPTED).body(paymentDetails);}
}

错误处理的工作由advice类ExceptionControllerAdvice 专门负责:

@RestControllerAdvice
public class ExceptionControllerAdvice {@ExceptionHandler(NotEnoughMoneyException.class)public ResponseEntity<ErrorDetails> exceptionNotEnoughMoneyHandler() {ErrorDetails errorDetails = new ErrorDetails();errorDetails.setMessage("Not enough money to make the payment.");return ResponseEntity.badRequest().body(errorDetails);}
}

输出和上例一样。

10.4 使用请求主体从客户端获取数据

前面已经使用HTTP 请求参数从客户端到服务器传输少量数据,传输大量数据则可以用HTTP 请求正文(request body)。

要使用请求正文,只需使用 @RequestBody 注解控制器操作的参数即可。默认情况下,Spring 会假定您使用 JSON 来表示注解的参数,并尝试将 JSON 字符串解码为参数类型的实例。如果无法将 JSON 格式的字符串解码为该类型,应用将返回状态为“400 Bad Request”的响应。

参见示例sq-ch10-ex7。核心代码为控制类:

@RestController
public class PaymentController {private static Logger logger =Logger.getLogger(PaymentController.class.getName());@PostMapping("/payment")public ResponseEntity<PaymentDetails> makePayment(@RequestBody PaymentDetails paymentDetails) {logger.info("Received payment " + paymentDetails.getAmount());return ResponseEntity.status(HttpStatus.ACCEPTED).body(paymentDetails);}
}

程序输出如下:

$ curl -X POST http://127.0.0.1:8080/payment -d '{"amount": 1000}' -H "Content-Type: application/json"
{"amount":1000.0}

HTTP GET也支持请求正文,详见RFC 7231。

总结

  • 表述性状态转移 (REST) Web 服务是在两个应用程序之间建立通信的一种简单方法。
  • 在 Spring 应用中,Spring MVC 机制支持 REST 端点的实现。您需要使用 @ResponseBody 注解来指定方法直接返回响应主体,或者将 @Controller 注解替换为 @RestController 来实现 REST 端点。如果您不使用上述任何一种注解,调度器 Servlet 将假定控制器的方法返回的是视图名称,并尝试查找该视图。
  • 您可以让控制器的操作直接返回 HTTP 响应主体,并依赖 Spring 默认的 HTTP 状态行为。
  • 您可以通过让控制器的操作返回 ResponseEntity 实例来管理 HTTP 状态和标头。
  • 管理异常的一种方法是直接在控制器的操作级别处理它们。这种方法将处理异常的逻辑与特定的控制器操作耦合在一起。有时,使用这种方法会导致代码重复,最好避免这种情况。
  • 您可以直接在控制器的操作中管理异常,或者使用 REST 控制器建议类来分离控制器的操作抛出异常时执行的逻辑。
  • 端点可以通过 HTTP 请求中的请求参数、路径变量或 HTTP 请求正文从客户端获取数据。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/94227.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/94227.shtml
英文地址,请注明出处:http://en.pswp.cn/bicheng/94227.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SYBASE ASE、Oracle、MySQL/MariaDB、SQL Server及PostgreSQL在邮件/短信发送功能上的全面横向对比报告

以下是对SYBASE ASE、Oracle、MySQL/MariaDB、SQL Server及PostgreSQL在邮件/短信发送功能上的全面横向对比报告&#xff08;截至2025年8月最新版本&#xff09;&#xff0c;涵盖技术实现、配置复杂度、适用场景及权威评测&#xff1a;​​一、邮件发送能力对比​​​​1. Orac…

服务器与客户端

目录 一、服务器&#xff08;Server&#xff09; 核心特点 常见类型 二、客户端&#xff08;Client&#xff09; 核心特点 常见类型 客户端与服务器的交互流程 补充&#xff1a;与 “对等网络&#xff08;P2P&#xff09;” 的区别 C/S模式 一、C/S 模式的核心原理 …

GaussDB 并发自治事务数达到最大值处理案例

1 业务背景自治事务&#xff08;Autonomous Transactions&#xff09;是一种高级特性&#xff0c;允许你在一个事务中执行另一个独立的事务。这种机制特别有用&#xff0c;尤其是在需要在一个事务中执行多个操作但又不想因为其中一个操作失败而影响整个事务的场景。2 业务影响在…

【传奇开心果系列】Flet分页自定义组件CustomPaginationComponent封装版自定义模板

Flet分页自定义组件CustomPaginationComponent封装版自定义模板一、效果展示GIF动图二、应用场景三、特色说明四、源码下载地址一、效果展示GIF动图 二、应用场景 图片浏览应用&#xff1a; 用户可以通过分页组件浏览多张图片&#xff0c;每点击一次“上一页”或“下一页”按钮…

数据安全——39页解读数字化转型大数据安全基础培训方案【附全文阅读】

适应人群为企业数据安全管理人员、IT 运维人员、数字化转型决策者、网络安全工程师及关注大数据安全的从业人员。主要内容围绕数字化转型中大数据安全展开,核心包括基础概念(信息、数据与大数据的定义及区别,大数据 4V 特点与来源);安全风险(企业面临的数据资产管理缺失、…

week3-[二维数组]小方块

week3-[二维数组]小方块 题目描述 如果四个数 a,b,c,da,b,c,da,b,c,d 可以分成两组&#xff0c;每组两个数&#xff0c;满足每组里面的两个数一样&#xff0c;那么称这四个数是好的。 比如&#xff0c;2,5,2,52,5,2,52,5,2,5 是好的&#xff0c;因它满足两组&#xff1a;222 与…

Swift 项目结构详解:构建可维护的大型应用

Swift 项目结构详解&#xff1a;构建可维护的大型应用一、基础结构&#xff08;推荐新手使用&#xff09;二、组件化结构&#xff08;企业级应用推荐&#xff09;层级架构&#xff1a;MVVM Coordinator路由实现&#xff08;Coordinator模式&#xff09;三、通用组件实现DI&…

【实时Linux实战系列】基于实时Linux的数字转换器设计

在现代电子系统中&#xff0c;数字转换器&#xff08;如模数转换器ADC和数模转换器DAC&#xff09;扮演着至关重要的角色。它们负责将模拟信号转换为数字信号&#xff0c;或将数字信号转换为模拟信号&#xff0c;从而实现信号的数字化处理和传输。在实时系统中&#xff0c;如工…

FastTracker:实时准确的视觉跟踪

摘要 https://arxiv.org/pdf/2508.14370 传统的多目标跟踪(MOT)系统主要设计用于行人跟踪&#xff0c;通常对其他物体类别的泛化能力有限。本文提出了一种能够处理多种物体类型的通用跟踪框架&#xff0c;特别强调在复杂交通场景中的车辆跟踪。所提出的1方法包含两个关键组件&a…

国产轻量级桌面GIS软件Snaplayers从入门到精通(20)

国产轻量级桌面GIS软件Snaplayers实操&#xff1a;打开图层并显示属性信息1、根据数据格式选择图层文件2、加载图层到地图中&#xff0c;并在左侧显示图层的属性表格3、属性表格分页显示Snaplayers研发团队承诺&#xff1a;国产轻量级桌面GIS软件Snaplayers永久免费并持续更新

快速入门flask应用(从入门到实战)

目录 前言&#xff1a; 了解一些网络通信的概念 什么是网络通信&#xff1a; 当我们访问一个网址的时候发生了什么&#xff1a; 1. 解析 URL&#xff1a;明确访问目标 2. DNS 域名解析&#xff1a;将 “名字” 转为 “地址” 3. 建立连接&#xff1a;TCP 三次握手&#x…

C++/QT 开发技能树详解

一、 编程语言 (C)1. C基础语法&#xff08;数据类型、模板、命名空间&#xff09;是什么&#xff1a; 这是构建C程序的基石。数据类型定义了变量存储的数据种类和大小&#xff1b;模板允许编写与数据类型无关的通用代码&#xff1b;命名空间用于避免大型项目中的名称冲突。如何…

Java多线程进阶-死锁与面试题解析

文章目录Java多线程进阶&#xff1a;死锁与面试题解析一、并发编程的噩梦——死锁1. 什么是死锁&#xff1f;四个缺一不可的条件2. 如何避免死锁&#xff1f;从破坏循环等待开始二、并发编程面试题全景解析1. 锁与同步机制2. CAS 与原子操作3. JUC 工具与线程池4. 线程安全集合…

ZYNQ启动流程——ZYNQ学习笔记11

ZYNQ SoC 的启动由片上的 BootROM 开始。片上 BootROM 是 ZYNQ 芯片上的一块非易失性存储器&#xff0c;它包含了 ZYNQ 所支持的配置器件的驱动&#xff0c; 而且里面的代码是不可修改的。 BootROM 中的代码首先会在片外的非易失性存储器中寻找一个头文件&#xff0c; 头文件里…

C++利用CerateProcess创建WPF进程并通过命名管道通讯

引言原因是我需要在C程序中调用另外一个WPF窗体打开或则关闭&#xff0c;进程之前通过通讯协议进行交互。由于使用不同语言开发&#xff0c;两者都比较复杂不方便重写&#xff0c;最方便的方法就是使用进程间通信&#xff0c;WPF窗体应用程序根据消息进行Show/Hide/Exit操作。函…

Seaborn数据可视化实战

1. Seaborn基础与实践&#xff1a;数据可视化的艺术 2. Seaborn入门&#xff1a;环境搭建与基础操作 3. Seaborn基础图表绘制入门 4. Seaborn数据可视化基础&#xff1a;从内置数据集到外部数据集的应用 5. Seaborn颜色与样式定制教程 6. Seaborn数据可视化入门&#xff1a;绘制…

BIM+写实数字孪生落地实战指南

&#x1f31f; 正文 在智慧城市与工业4.0的浪潮中&#xff0c;BIM与数字孪生的深度碰撞正在重塑建筑的生命周期。基于Revit&#xff08;RVT&#xff09;模型构建的超写实数字孪生体&#xff0c;不仅实现物理空间的毫米级镜像&#xff0c;更通过实时数据驱动&#xff0c;赋予建…

[Git] 如何拉取 GitHub 仓库的特定子目录

作为开发者&#xff0c;我们经常遇到只需要克隆大型仓库中某个子目录的场景。 Git 本身并不支持直接克隆子目录&#xff0c;但通过一些技巧可以实现类似效果。本文将介绍几种实用的方法&#xff0c;帮助获取目标代码。 为什么需要局部拉取&#xff1f; 节省时间和带宽&#xff…

修复Simulink到UE5丢包时被控船体的残影问题

提问 simulink 有一个和UE5协同的模块&#xff0c;叫做Simulation 3D Scence Configuration&#xff0c;还有一个发送来自simulink到UE5数据的模块叫做Simulation 3D Message。 现在遇到的问题是&#xff0c;这两个模块的优先级设置是正确的&#xff0c;且sample time都设置为0…

嵌入式第三十五课!!Linux下的网络编程

一、目的网络编程的目的实际上也是进程通信的一种方式&#xff0c;不过它可以在不同的主机上进行通信&#xff1b;二、需要解决的问题1. 主机与主机之间物理层面必须互联互通。指的是参与通信的计算机&#xff08;主机&#xff09;需要通过物理设备建立连接&#xff08;光纤、网…