当前位置: 首页 > news >正文

【Spring Boot】REST与RESTful详解,基于Spring Boot的RESTful API实现

文章目录

  • 前言
  • 一、REST与RESTful
    • 1.1 背景
    • 1.2 REST风格
    • 1.3 RESTful
    • 1.4 使用REST的优缺点
    • 1.5 小结
  • 二、Spring Boot中的RESTful落地指南
    • 2.1项目构建
    • 2.2 RESTful服务搭建
      • 2.2.1 资源路由定义
      • 2.2.2 控制器方法的HTTP语义定义
      • 2.2.3 状态码的返回
      • 2.2.4 返回状态码的工程化
    • 2.3 RESTful服务示例
      • 2.3.1 HttpGet
      • 2.3.2 HttpPost
      • 2.3.3 HttpPut
      • 2.3.4 HttpPatch
      • 2.3.5 HttpDelete
  • 总结


前言

提起WebAPI的开发,REST风格的API是不得不提及的一个话题。REST风格的WebAPI也是目前最流行的一种WebAPI风格,除了REST风格外,还有通过WebService或者WCF开发的基于SOAP协议的WebAPI,不过这些都在时代的潮流中或被沉淀,或又通过新的面貌重新进入我们的视野。

REST是一种基于 HTTP 协议的软件架构风格,而RESTful则是基于这种风格的实践。本文将详尽的介绍REST,并且通过Spring Boot开发RESTful WebAPI。


一、REST与RESTful

1.1 背景

REST(Representational State Transfer),这是一种基于HTTP协议的软件架构风格。由计算机科学家Roy Fielding在2000年的博士论文中提出(Roy博士也是 HTTP 协议的主要设计者之一)。

REST的理念的提出也是源自Web本身的设计原则(如URI、HTTP方法),目的是在让Web架构更符合其初衷,通过HTTP的语义来使用HTTP协议。

REST的提出是建立在对早期WebAPI服务协议的一种精简,开发过WebService的朋友应该能明细感觉到,这种基于SOAP协议,利用XML报文的形式传递数据的方式用起来很 “重” 。而且因为API风格大多使用RPC这种面向过程的方式,仅仅把HTTP当成传输数据的通道,并不关心HTTP谓词。而是通过方法实现HTTP语义,比如/Project/GetAll、/Project/GetByld?id=7、/Project/Update、/Project/DeleteByld/7。

RPC这种通过相应的业务驱动的方式,比较自然,但是未充分利用HTTP语义,容易导致API语义不清晰。并且随着业务的更改与扩张,大量的代码需要修改,版本管理复杂扩展性较差。

聊完RPC这种"面向过程的风格”,接下来我们来对比了解下遵循REST风格设计的Web API ——RESTful。

1.2 REST风格

REST是一种基于HTTP协议的软件架构风格,它使用URL表示资源,使用HTTP方法(GET、POST、PUT、DELETE)表示对资源的操作。本质上是一组设计原则。

  • 按照HTTP的语义来使用HTTP协议
  • 将资源作为核心抽象,通过不同HTTP语义实现对资源的操作
  • 无状态通信,每个请求独立
  • 支持多种数据格式

1.3 RESTful

RESTful是遵循REST架构风格的API实现。一个API被称为RESTful,意味着它严格遵守REST原则。我们可以这样简单的总结一下RESTful

  • 使用名词而非动词命名URL(如/users而非/getAllUsers),URL用于资源的定位
  • 服务器端要通过状态码来反映资源获取的结果(如200成功、201新增成功、403没有权限,404 未找到)

1.4 使用REST的优缺点

按照HTTP的语义来使用HTTP协议的这种约定大于配置的方式的,确实能节约不少开发上的工作,但是也变相要求开发人员对REST原则更了解、并且有更多的设计能力。以下分别总结几点优缺点。

  • REST优点
    • 通过URL对资源定位,语义更清晰通过HTTP谓词表示不同的操作,接口自描述。
    • 可以对GET、PUT、DELETE请求进行重试(分布式架构里的中间网关服务器,自动重发)
    • 可以用GET请求做缓存(依据幂等性质,安全的设置缓存)
    • 通过HTTP状态码反映服务器端的处理结果统一错误处理机制
    • 网关等可以分析请求处理结果(网关服务器解析API响应,通过请求结果判断系统是否健壮)
  • REST缺点
    • 真实系统中的资源非常复杂,难以优雅表达。很难清晰地进行资源的划分,对技术人员的业务和技术水平要求高。
    • 安全性依赖外部机制,认证授权需依赖JWT等外部方案,增加复杂性。
    • 状态管理不足,客户端维护更多状态。

1.5 小结

REST总体而言是一种偏学术化的概念,但实际工作生产中,面对复杂的情况,切记不要抱着学术化的概念固步自封。灵活的选择和裁剪REST(比方说URL定位结合QueryString查询数据),更有利于我们在实际工作中的落地REST。

二、Spring Boot中的RESTful落地指南

2.1项目构建

  1. 打开idea,基于Spring Boot脚手架成标准的项目模板在这里插入图片描述
  2. 添加相应的依赖
    在这里插入图片描述
  3. pojo层定于统一返回类型Result类与Student学生属性类
    统一的结果返回类型
package org.araby.restful.pojo;import lombok.Data;@Data
public class Result<T> {private T data;private Integer code;private String message;public static<T> Result<T> success(T data){Result<T> result = new Result<T>();result.setData(data);result.setCode(1);result.setMessage("success");return result;}public static<T> Result<T> success(){return success(null);}public static<T> Result<T> error(T data){Result<T> result = new Result<T>();result.setData(data);result.setCode(-1);result.setMessage("error");return result;}public static<T> Result<T> error(){return error(null);}
}

Student 类

package org.araby.restful.pojo;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDate;@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {public Integer id;public String name;public Integer age;public String sex;public String classNo;public LocalDate createTime;public LocalDate updateTime;
}
  1. mapper层定于StudentMapper,基于MyBatis实现和数据库的交互
package org.araby.restful.mapper;import org.apache.ibatis.annotations.*;
import org.araby.restful.pojo.Student;import java.util.List;@Mapper
public interface StudentMapper {@Select("select id, name, age, sex, class_no, create_time, update_time from student")List<Student> findAll();@Select("select id, name, age, sex, class_no, create_time, update_time from student where id = #{id}")Student findById(Integer id);@Delete("delete from student where id = #{id}")Integer deleteById (Integer id);@Insert("insert into student(name, age, sex, class_no,create_time, update_time) values(#{name}, #{age}, #{sex}, #{classNo},#{createTime},#{updateTime})")Integer insert(Student student);@Update("update student set name = #{name}, age = #{age}, sex = #{sex}, class_no = #{classNo} ,update_time = #{updateTime} where id = #{id}")Integer update(Student student);}
  1. service层定于StudentService,封装业务逻辑
package org.araby.restful.service;import org.araby.restful.pojo.Student;
import java.util.List;public interface StudentService {List<Student> findAll();Student findById(Integer id);Boolean deleteById(Integer id);Boolean insert(Student student);Boolean update(Student student);Boolean patchStudent(Integer id, Student updateStudent);
}
package org.araby.restful.service.impl;import org.araby.restful.mapper.StudentMapper;
import org.araby.restful.pojo.Student;
import org.araby.restful.service.StudentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.util.List;@Service
public class StudentServiceImpl implements StudentService {private  final StudentMapper studentMapper;@Autowiredpublic StudentServiceImpl( StudentMapper studentMapper) {this.studentMapper = studentMapper;}@Overridepublic List<Student> findAll() {return studentMapper.findAll();}@Overridepublic Student findById(Integer id) {return studentMapper.findById(id);}@Overridepublic Boolean deleteById(Integer id) {return  studentMapper.deleteById(id) > 0;}@Overridepublic Boolean insert(Student student) {student.setCreateTime(java.time.LocalDate.now());student.setUpdateTime(java.time.LocalDate.now());return studentMapper.insert(student) > 0;}@Overridepublic Boolean update(Student student) {student.setUpdateTime(java.time.LocalDate.now());return studentMapper.update(student) > 0;}@Overridepublic Boolean patchStudent(Integer id, Student updateStudent) {Student existingStudent = studentMapper.findById(id);if (existingStudent == null) {return false;}if (updateStudent.getName() != null) {existingStudent.setName(updateStudent.getName());}if (updateStudent.getAge() != null) {existingStudent.setAge(updateStudent.getAge());}if (updateStudent.getSex() != null) {existingStudent.setSex(updateStudent.getSex());}if (updateStudent.getClassNo() != null) {existingStudent.setClassNo(updateStudent.getClassNo());}existingStudent.setUpdateTime(java.time.LocalDate.now());return studentMapper.update(existingStudent) > 0;}}
  1. controller层定于StudentRESTfulController
@RestController
public class StudentRESTfulController {}

至此,准备工作全部完成,下面开始搭建RESTful的服务。

2.2 RESTful服务搭建

2.2.1 资源路由定义

@RestController
public class StudentRESTfulController {private final StudentServiceImpl studentService;@Autowiredpublic StudentRESTfulController(StudentServiceImpl studentService) {this.studentService = studentService;}
}

在Spring Boot中,控制器专注于接口的,需要用@RestController注解修饰。 这个注解本身是是个组合注解,内部包含@Controller和@ResponseBody。前者是向Spring标记本类是请求处理器,并且需要作为bean自动扫描注册到bean容器里。后者是将Controller内的所有控制器方法的返回值,序列化成JSON作为响应返回。

如果想给控制器里每个控制器方法添加一个统一的访问路径,可借助RequestMapping,传入访问路径

@RequestMapping("/api")
@RestController
public class StudentRESTfulController {

2.2.2 控制器方法的HTTP语义定义

在Spring Boot中,通过对控制器方法前添加诸如@GetMapping,@PostMapping,@PutMapping,@PatchMapping,@DeleteMapping。注解内部也可以定义路由参数,比如下面示例里的"{id}"是路由模板中的占位符,表示该方法需要接收一个名为id的参数,且该参数会从URL中提取

@GetMapping("/students")public Result<List<Student>> findAll() {@GetMapping("/students/{id}")public Result<Student> findById(@PathVariable(required = true) Integer id) {@DeleteMapping("/students")public Result<Void> deleteById( Integer id) {

值得注意的控制器方法参数可以值得为必须传入,通过PathVariable注解的required = true属性

HTTP方法

HTTP操作类型幂等性典型场景
GET读取获取单个资源(/api/students/1)或资源列表(/api/students)
POST创建创建新资源
PUT全量更新通过提供全部字段,完整替换资源
PATCH部分更新增量更新资源,修改部分属性
DELETE删除删除资源

2.2.3 状态码的返回

标注的REST风格是完全按照HTTP的语义来使用HTTP协议,也就是说返回结果里也要按照HTTP协议返回相应的状态码。

Spring Boot提供了ResponseEntity,我们可以通过设置ResponseEntity的status来决定放回的响应状态码,让我们开发能够方便的返回各种状态码。body内就是本身的返回类型。

@GetMapping("/students/{id}")public ResponseEntity<Result<Student>>findById(@PathVariable(required = true) Integer id) {Student student = studentService.findById( id);if (student == null){return ResponseEntity.status(HttpStatus.NOT_FOUND) .body(Result.error(student)); }else{return ResponseEntity.ok(Result.success(student));}}

查询一个不存在的结果,响应结果是404

在这里插入图片描述

一般是在全局异常拦截器里返回对应的响应状态码,这里仅作演示用。

成功状态码

状态码ResponseEntity.status含义典型场景
200 OKHttpStatus.OK请求成功,返回数据GET 请求返回资源列表或单个资源
201 CreatedHttpStatus.CREATED资源创建成功POST 请求创建新资源
202 AcceptedHttpStatus.ACCEPTED请求已接受但未完成处理异步操作(如后台任务处理),需在响应头中包含 Location 指向状态查询 URI
204 No ContentHttpStatus.OK请求成功但无返回内容PUT/PATCH/DELETE 操作成功后(无需返回数据)

客户端错误状态码

状态码ResponseEntity.status含义典型场景
400 Bad RequestHttpStatus.BAD_REQUEST请求参数无效或格式错误模型验证失败或请求体格式错误
401 UnauthorizedHttpStatus.UNAUTHORIZED未认证未登录
403 ForbiddenHttpStatus.FORBIDDEN已认证但权限不足比如JWT Token过期失效
404 Not FoundHttpStatus.NOT_FOUND资源不存在请求的 ID 对应的资源不存在(如 /api/users/999)
405 Method Not AllowedHttpStatus.METHOD_NOT_ALLOWED指定 URI 不支持该 HTTP 方法对 /api/users 用 DELETE(通常 DELETE 应针对单个资源)
409 ConflictHttpStatus.CONFLICT请求冲突(如唯一约束冲突)创建或更新资源时版本冲突(乐观锁)
415Unsupported Media TypeHttpStatus.UNSUPPORTED_MEDIA_TYPE不支持的请求格式(如 Content-Type 错误)发送 application/xml 但 API 仅支持 application/json

服务器错误状态码

状态码ResponseEntity.status含义典型场景
500 Internal Server ErrorHttpStatus.INTERNAL_SERVER_ERROR服务器内部错误服务器内部执行出现重大错误
501 Not ImplementedHttpStatus.NOT_IMPLEMENTED方法未实现API 端点尚未实现(如占位方法)
503 Service UnavailableHttpStatus.SERVICE_UNAVAILABLE服务不可用(临时过载或维护)服务器暂时无法处理请求(如限流、数据库维护)

2.2.4 返回状态码的工程化

HTTP的状态码并不适合用来表示业务层面的错误码,它是一个用来表示技术层面信息的状态码。工作中我们也能经常发现仅仅是凭借状态码,很难详细的将一个错误完整的描述清楚。

就比如404 Not Found这个状态码。请求一个不存在的URL和对于一个已存在的URL查询出一个不存在的结果都会返回404 Not Found。这其实就有一个业务和技术错误耦合在一起,导致仅仅凭借状态码难以描述清楚。

一种建议是业务方面的错误使用200返回,但是在报文内部定义指定的错误码和错误信息进行补充。

并且对于实际生成项目中来说,给每一个方法手动重复指定响应状态码是愚蠢的,还是需要通过一个全局异常拦截器来统一处理,通过注册自定义的异常处理逻辑里来返回响应状态码

2.3 RESTful服务示例

2.3.1 HttpGet

GetMapping特性的内部可以指定参数,比如下面示例的"{id}" 是路由模板中的占位,表示该方法需要接收一个名为id的参数,且该参数会从URL中提取。用@PathVariable注解修饰模板占位参数,name里的值便是模板占位符

    @GetMapping("/students")public Result<List<Student>> findAll() {List<Student> students = studentService.findAll();return Result.success(students);}@GetMapping("/students/{id}")public Result<Student> findById(@PathVariable(name = "id") Integer id) {Student students = studentService.findById( id);return Result.success(students);}

2.3.2 HttpPost

    @PostMapping("/students")public Result<Void> insert(@RequestBody Student student) {if ( studentService.insert(student)){return Result.success();}else {return Result.error();}}

2.3.3 HttpPut

HttpPut是全量更新,需要传入完整对象。这里用@RequestBody的注解修饰参数,表明该数据从请求body中获取。

@PutMapping("/students")public Result<Void> update(@RequestBody Student student) {if(studentService.update(student)){return Result.success();}else {return Result.error();}}

2.3.4 HttpPatch

增量更新,不一定是幂等。比方说累加器。

    @PatchMapping("/students/{id}")public Result<Void> patchStudent(@PathVariable Integer id, // 路径参数:学生ID(必选)@RequestBody Student updateStudent // 请求体:仅含需要修改的字段){if (studentService.patchStudent(id, updateStudent)){return Result.success();}else {return Result.error();}}

2.3.5 HttpDelete

    @DeleteMapping("/students/{id}")public Result<Void> deleteById(@PathVariable Integer id) {if(studentService.deleteById(id)){return Result.success();}else {return Result.error();}}

总结

本文介绍了REST架构风格及RESTful API的概念,阐述了其基于HTTP语义的设计原则,并结合Spring Boot演示了RESTful API的具体实现,包括路由设计、HTTP方法映射及状态码规范等。

http://www.dtcms.com/a/586092.html

相关文章:

  • 2025年渗透测试面试题总结-234(题目+回答)
  • Z-Wave物联网网关开发专用 SDK - Unify
  • 3g微网站北京住房和城乡建设官方网站
  • 济南一哥网站建设网站建设明薇通网络服务好
  • 弱函数 vs 回调函数:本质区别解析
  • 2024年 国内养老机构及床位数据
  • 建设部标准规范网站wordpress防止采集
  • 4.3 IP: Internet Protocol
  • 一般购物网站项目深圳做网站比较好天涯
  • 网站建设的重点难点企业简介 网站建设
  • PostgreSQL 19新特性之GROUP BY ALL
  • 技能补全之Python AES GCM 加密存储
  • NCP13992 CS 分压计算
  • MySQL事务:如何保证ACID?MVCC到底如何工作?
  • 240-基于Python的医疗疾病数据可视化分析系统
  • 网站开发合同管辖权异议商务网站建设实训心得体会
  • 【CUDA笔记】01-入门简介
  • 教育网站制作方案php大型网站开发书籍
  • 深圳网站建设_请到中投网络wordpress 获取ip
  • Swift 6.2 列传(第五篇):方法键路径的 “通脉奇功”
  • 【网络系列】Tracing Header
  • AI时代,我们该如何学Python?《AIGC高效编程:Python从入门到高手》
  • 连云港公司网站优化服务做静态网站的开题报告
  • 【STL——常用排序、拷贝与替换算法】
  • 网站 做实名认证吗建设银行 网站无法打开
  • S2B2C系统推荐|商淘云:以全链路数字化能力重构产业生态的深度实践
  • 北京企业网站建设推荐服装设计公司排名
  • iis7 网站用户权限超融合系统
  • 机器学习实践项目(二)- 房价预测增强篇 - 模型训练与评估:从多模型对比到小网格微调
  • 物联网协议全景图