使用 SpringBoot 构建一个RESTful API
背景
这么简单的事情也值得写一篇文章?
去年搞一个项目时候,因为参与的人员越来越多,很快就暴露出一个问题:大家每个人都有自己的一套(代码)风格。考虑团队协同作战,就提议制订一下相关的规范,把大家的步调搞得一致一些,也有利于项目的推进效率。执行过程中出现了特别大的分歧(意料之中),其中争议最大的就是 Http API 这部分,主要发现两个问题:
- 很多同学认为 RESTful 风格的 API 就是好的(先进的),可是为什么好,说不上来?
- 很多同学认为自己知道 RESTful 风格的 API 是什么样子的,可是坐下来一聊,发现每个人的都不一样,谁都说服不了对方?
最后因为项目周期关系,没有在这个事情上面多纠结,以业务优先为幌子,大家先按自己的风格推进项目,在实践的过程中逐步建立规范。
后来 Review 代码的时候,也发现一个很有意思的现象:即使对于同一位同学,他所写出来的 RESTful 风格的 API 实际也是不一致的,主要表现两个现象:
- 类似的业务场景,API Url和参数的设计明显不同,这位同学也说不上原因,可能和写具体 API 时的心情有关系;
- 有一些业务场景,API 的设计已经明显偏离 RESTful,这位同学用“不这么写,业务逻辑没法儿实现”来搪塞;
和多位同学沟通之后,我得出一个结论:让这些自己以为自己很懂 RESTful 的同学按照自己的理解制订一套规范,用于约束什么样的场景应该如何设计 API,实际上是做不到的。
这么流行的东西,应该是标准化(大家的共识)程度很高的,为什么还会有这种现象?直到最近几天看到一篇 文章:
REST stands for “representational state transfer,” described by Roy Fielding in his dissertation. Sadly, that dissertation is not widely read, and so many people have their own idea of what REST is, leading to a lot of confusion and disagreement.
大意就是,REST 是一篇老外的论文(2000年的时候)里提出来的;但是呢,这篇论文应该很多人没有实际看过,大家都是根据网络上的“只言片语”来理解的,所以导致了很多的曲解和不一致(表示无法更认同)。
论文 确实有点儿长,估计很少有人真的会去看(包括我自己),这里推荐大家看一篇源自微软的文章,讨论的内容很全面,基本可以作为 RESTful API 入门和实践的指南。
本文的后续章节以该篇文章为基础,主要讲述如何使用 SpringBoot 构建 RESTful API 的若干关键技术点,已具备相关经验的同学可以忽略这部分内容。
创建 SpringBoot 项目/模块
使用 Idea 创建 Maven 项目(Project),名称:SpringBoot;再创建一个模块,名称:api,用于构建 RESTful API,这里仅列出 pom.xml。
SpringBoot pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>tech.exchange</groupId>
<artifactId>springboot</artifactId>
<packaging>pom</packaging>
<version>0.1</version>
<modules>
<module>api</module>
</modules>
<properties>
<spring-boot.version>2.6.1</spring-boot.version>
<maven-compiler-plugin.version>3.8.1</maven-compiler-plugin.version>
<maven-assembly-plugin.version>3.3.0</maven-assembly-plugin.version>
<java.version>17</java.version>
<encoding>UTF-8</encoding>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring-boot.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>${encoding}</encoding>
</configuration>
</plugin>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<version>${maven-assembly-plugin.version}</version>
<configuration>
<descriptors>
<!--suppress UnresolvedMavenProperty -->
<descriptor>
${maven.multiModuleProjectDirectory}/src/assembly/package.xml
</descriptor>
</descriptors>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
简要介绍一下 pom.xml 各个部分的含义:
groupId/artifactId/packaging/version
<groupId>tech.exchange</groupId>
<artifactId>springboot</artifactId>
<packaging>pom</packaging>
<version>0.1</version>
用于声明项目的组织、名称、打包方式和版本。
modules
<modules>
<module>api</module>
</modules>
用于声明项目(多模块项目)内的多个模块,这里仅包含一个模块:api。
properties
<properties>
......
</properties>
用于声明项目 pom.xml 中可能多次使用或需要统一设置的属性(值),比如 SpringBoot 版本号。
dependencyManagement/dependencies
<dependencyManagement>
<dependencies>
......
</dependencies>
</dependencyManagement>
用于声明项目需要使用的依赖(Jar)名称和版本,项目的模块只需要声明具体使用的依赖名称即可,版本由项目统一指定。
build/plugins
<build>
<plugins>
......
</plugins>
</build>
用于声明项目或模块构建(编译、打包或其它)需要使用的插件。
api pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>tech.exchange</groupId>
<artifactId>springboot</artifactId>
<version>0.1</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>api</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>
artifactId
<artifactId>api</artifactId>
用于声明模块名称。
dependencies/dependency
<dependencies>
<dependency>
......
</dependency>
</dependencies>
用于声明模块具体需要使用的依赖(Jar)。
创建 RESTful API 应用
创建一个名称为 Main 的类:
package tech.exchange.springboot.api;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author yurun
*/
@SpringBootApplication
public class Main {
public static void main(String[] args) {
SpringApplication.run(Main.class, args);
}
}
@SpringBootApplication
@SpringBootApplication 是三个注解(Annotation)的合集:
@SpringBootApplication = @Configuration + @EnableAutoConfiguration + @ComponentScan
它的作用就是告诉 Spring 以 Main 为入口,自动装载各种各样的 Bean(组件或配置)到 容器 中,最终形成一个 SpringBoot 应用,用以接收和响应外部请求。
这些 Bean 来源于三个方面:
@Configuration
Tags the class as a source of bean definitions for the application context.
装载 Main 中我们自定义的 Beans(本文示例中没有包含自定义 Bean)。
@EnableAutoConfiguration
Tells Spring Boot to start adding beans based on classpath settings, other beans, and various property settings.
根据类路径、其它 Beans或属性配置装载需要的 Beans,比如:示例中包含依赖 spring-boot-starter-web,会自动装载 web 相关的 Beans。
@ComponentScan
Tells Spring to look for other components, configurations, and services in the tech/exchange/springboot/api package。
扫描包(Package)路径:tech/exchange/springboot/api,装载包下面的 Beans。
SpringApplication.run
SpringApplication.run(Main.class, args);
启动 SpringBoot 应用,默认情况下会看到如下输出:
2021-12-02 14:44:15.537 INFO 58552 --- [ main] tech.exchange.springboot.api.Main : Starting Main using Java 17.0.1 on bogon with PID 58552 (/Users/yurun/workspace/tech-exchange/springboot/api/target/classes started by yurun in /Users/yurun/workspace/tech-exchange/springboot)
2021-12-02 14:44:15.538 INFO 58552 --- [ main] tech.exchange.springboot.api.Main : No active profile set, falling back to default profiles: default
2021-12-02 14:44:16.160 INFO 58552 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
2021-12-02 14:44:16.170 INFO 58552 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2021-12-02 14:44:16.170 INFO 58552 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.55]
2021-12-02 14:44:16.213 INFO 58552 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2021-12-02 14:44:16.214 INFO 58552 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 633 ms
2021-12-02 14:44:16.436 INFO 58552 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2021-12-02 14:44:16.443 INFO 58552 --- [ main] tech.exchange.springboot.api.Main : Started Main in 1.209 seconds (JVM running for 1.584)
其中,8080 (http) 表示应用实例端口号为8080,支持 HTTP 请求。
Rest Controller
目前,应用还是一个 空 的应用,无法实际接收任何请求或响应。SpringBoot 中 HTTP 请求或响应需要通过 Controller 实现,一个 Controller 内可以支持(包含)一个或多个 HTTP 请求或响应的实现,也就是一个或多个 API 的实现。
package tech.exchange.springboot.api.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author yurun
*/
@RestController
@RequestMapping("/hello")
public class HelloController {
}
@RestController
@RestController 用于标识 HelloController 是一个 Controller,SpringBoot 应用启动后会被自动装载到容器中。
@RequestMapping
HelloController 相当于是一个 API 的集合,内部可包含多个 API 的具体实现,如:
/hello/a1
/hello/a2
/hello/a3
......
可以使用 @RequestMapping 统一标识这些 API 请求路径的父路径,如:/hello,内部的 API 请求路径无需再包含此父路径,使用 /a1、/a2、/a3 即可。
Get/Post/Put/Patch/Delete
目前,HelloController 还是一个 空 的 Controller,不包含任何 API 的实现。
RESTfull API 共涉及 5 种 请求类型:Get/Post/Put/Patch/Delete,3种参数类型:请求路径参数、请求参数和请求体参数。每一种请求类型模拟实现一个 API,用于演示 API 的实现过程,以及每一种参数类型的使用方式。
Get
在 HelloController 中添加方法(Method) get:
@GetMapping("/get/{name1}")
public String get(@PathVariable String name1, @RequestParam(defaultValue = "name2dv") String name2) {
return "hello " + name1 + " " + name2;
}
@GetMapping
@GetMapping 用于标识方法 get 仅响应 HTTP GET 请求,且请求路径为 /hello/get/{name1},其中 {name1} 为请求路径参数名称,实际请求时需要替换为具体的参数值,如:value1。
@PathVariable
@PathVariable String name1
@PathVariable 用于标识请求方法参数,接收请求路径参数。假设请求路径为 /hello/value1,执行请求时参数值 value1 会被传递给请求方法 get 的参数 name1。
请求路径参数默认为必填项(不支持修改),发起请求时必须填写,否则请求会失败。
@RequestParam
@RequestParam(defaultValue = "name2dv") String name2
@RequestParam 用于标识请求方法参数,接收请求参数。假设请求路径为 /hello?name2=value2,执行请求时参数值 value2 会被传递给请求方法 get 的参数 name2;假设请求路径为 /hello,执行请求时请求方法 get 的参数 name2 会被设置为默认值(defaultValue) name2dv。
请求参数默认为必填项(可以通过注解属性 required 修改),发起请求时必须填写;如果有设置 defaultValue,则发起请求时可以不填写,使用默认值代替,否则请求会失败。
调用示例
请求:curl http://localhost:8080/hello/get/value1
响应:hello value1 name2dv
请求:curl http://localhost:8080/hello/get/value1?name2=value2
响应:hello value1 value2
请求路径参数和请求方法参数的参数名称需要保持一致,如不一致,需要通过注解属性额外指定(下同);
发起请求时填写的参数类型需要和请求方法声明的参数类型兼容(下同);
请求路径参数和请求方法参数可以使用零个或多个(下同);
Get 场景中请求体参数应用场景不多,本文不予讨论。
Post
Post 中请求路径参数和请求参数的使用方式与 Get 相同,不再赘述,仅实现请求体参数的使用方式。请求体参数的使用方式与 HTTP Request Header Content-Type 的具体值有关,本文仅讨论最常用的类型:application/json。
发起请求时需要使用 JSON 传递请求体参数,请求方法需要通过 类 接收请求体参数值。
@PostMapping("/post")
public String post(@RequestBody PostParams params) {
return "hello " + params.getName1() + " " + params.getName2();
}
@PostMapping
@PostMapping 用于标识方法 post 仅响应 HTTP POST 请求,且请求路径为 /hello/post。
@RequestBody
@RequestBody PostParams params
@RequestBody 用于标识请求方法参数,接收请求体参数(JSON)。
假设请求体参数:
{"name1": "value1", "name2": "value2"}
需要创建一个类用于接收体参数:
public class PostParams {
private String name1;
private String name2 = "name2dv";
public String getName1() {
return name1;
}
public void setName1(String name1) {
this.name1 = name1;
}
public String getName2() {
return name2;
}
public void setName2(String name2) {
this.name2 = name2;
}
}
执行请求时会将每个 JSON 字段按名称分别赋值给 类 实例(param)字段。如果 name1 不存在于 JSON 中,则 name1 为 null;如果 name2 不存在于 JSON 中,则 name2 为 name2dv;如果 JSON 中的某些字段不存在于 类 中,这些字段将会被忽略。
调用示例
请求:curl -H "Content-Type:application/json" -X POST --data '{"name1": "value1", "name2": "value2"}' http://localhost:8080/hello/post
响应:hello value1 value2
请求:curl -H "Content-Type:application/json" -X POST --data '{}' http://localhost:8080/hello/post
响应:hello null name2dv
请求:curl -H "Content-Type:application/json" -X POST --data '{"name1": "value1", "name3": "value2"}' http://localhost:8080/hello/post
响应:hello value1 name2dv
请求体参数仅能为零个或一个(下同);
请求体参数字段类型需要和类字段类型兼容(下同);
请求路径参数、请求参数和请求体参数可以混合使用(下同)。
Put
@PutMapping("/put")
public String put(@RequestBody PostParams params) {
return "hello " + params.getName1() + " " + params.getName2();
}
@PutMapping
@PutMapping 用于标识方法 put 仅响应 HTTP PUT 请求,且请求路径为 /hello/put。
其余内容同上,不再赘述。
Patch
@PatchMapping("/patch")
public String patch(@RequestBody PostParams params) {
return "hello " + params.getName1() + " " + params.getName2();
}
@PatchMapping
@PatchMapping 用于标识方法 patch 仅响应 HTTP PATCH 请求,且请求路径为 /hello/patch。
其余内容同上,不再赘述。
Delete
@DeleteMapping("/delete")
public String delete(@RequestBody PostParams params) {
return "hello " + params.getName1() + " " + params.getName2();
}
@DeleteMapping
@DeleteMapping 用于标识方法 delete 仅响应 HTTP DELETE 请求,且请求路径为 /hello/delete。
其余内容同上,不再赘述。
结语
本文介绍了一篇讲述 RESTfull API 的文章,并以此为基础,演示了一个使用 SpringBoot 构建 RESTfull API 应用的完整过程,核心的配置和注解也给出了说明,希望对大家有帮助。
附
https://github.com/tech-exchange/springboot/blob/master/api/src/main/java/tech/exchange/springboot/api/controller/HelloController.java
https://github.com/tech-exchange/springboot/blob/master/api/src/main/java/tech/exchange/springboot/api/controller/PostParams.java
使用 SpringBoot 构建一个RESTful API的更多相关文章
- 基于SpringBoot开发一个Restful服务,实现增删改查功能
前言 在去年的时候,在各种渠道中略微的了解了SpringBoot,在开发web项目的时候是如何的方便.快捷.但是当时并没有认真的去学习下,毕竟感觉自己在Struts和SpringMVC都用得不太熟练. ...
- [译]Spring Boot 构建一个RESTful Web服务
翻译地址:https://spring.io/guides/gs/rest-service/ 构建一个RESTful Web服务 本指南将指导您完成使用spring创建一个“hello world”R ...
- 使用python的Flask实现一个RESTful API服务器端
使用python的Flask实现一个RESTful API服务器端 最近这些年,REST已经成为web services和APIs的标准架构,很多APP的架构基本上是使用RESTful的形式了. 本文 ...
- Spring MVC 中使用 Swagger2 构建动态 RESTful API
当多终端(WEB/移动端)需要公用业务逻辑时,一般会构建 RESTful 风格的服务提供给多终端使用. 为了减少与对应终端开发团队频繁沟通成本,刚开始我们会创建一份 RESTful API 文档来记录 ...
- Spring Boot 构建一个 RESTful Web Service
1 项目目标: 构建一个 web service,接收get 请求 http://localhost:8080/greeting 响应一个json 结果: {"id":1,&qu ...
- 用 Go 快速开发一个 RESTful API 服务
何时使用单体 RESTful 服务 对于很多初创公司来说,业务的早期我们更应该关注于业务价值的交付,而单体服务具有架构简单,部署简单,开发成本低等优点,可以帮助我们快速实现产品需求.我们在使用单体服务 ...
- 使用python的Flask实现一个RESTful API服务器端[翻译]
最近这些年,REST已经成为web services和APIs的标准架构,很多APP的架构基本上是使用RESTful的形式了. 本文将会使用python的Flask框架轻松实现一个RESTful的服务 ...
- 一个Restful Api的访问控制方法
最近在做的两个项目,都需要使用Restful Api,接口的安全性和访问控制便成为一个问题,看了一下别家的API访问控制办法. 新浪的API访问控制使用的是AccessToken,有两种方式来使用该A ...
- 实现一个 RESTful API 服务器
RESTful 是目前最为流行的一种互联网软件结构.因为它结构清晰.符合标准.易于理解.扩展方便,所以正得到越来越多网站的采用. 什么是 REST REST(REpresentational Stat ...
随机推荐
- HCNP Routing&Switching之BGP邻居建立条件、优化和认证
前文我们了解了BGP相关概念.AS相关概念以及BGP邻居类型.基础配置等,相关回顾请参考https://www.cnblogs.com/qiuhom-1874/p/15370838.html:今天我们 ...
- Flask的环境配置
Flask django是大而全,提供所有常用的功能 flask是小而精,只提供核心功能 环境配置 为了防止 django和 flask环境相互冲突,可以使用 虚拟环境分割开 pip instal ...
- python 类中的公有属性 私有属性 实例属性
class parent(): i=1 __j=2 class child(parent): m=3 __n=4 def __init__(self,age,name): self.age=age s ...
- javascript-原生-闭包
1.变量的作用域 前提:这里只全部都通过var创建的变量或对象 1.全局变量:函数外创建变量 var x=10; function test(){ alert("全局变量在test函数中&q ...
- 更好的 java 重试框架 sisyphus 的 3 种使用方式
回顾 我们前面学习了 更好的 java 重试框架 sisyphus 入门简介 更好的 java 重试框架 sisyphus 配置的 2 种方式介绍 更好的 java 重试框架 sisyphus 背后的 ...
- CentOS 文本编辑器
目录 1.Nano 1.1.基础命令 1.2.快捷操作 1.3.配置文件 2.Vim 2.1.四大模式 2.2.基础命令 2.3.标准操作 2.4.高级操作 2.5.配置文件 Linux 终端的文本编 ...
- 【数据结构与算法Python版学习笔记】图——基本概念及相关术语
概念 图Graph是比树更为一般的结构, 也是由节点和边构成 实际上树是一种具有特殊性质的图 图可以用来表示现实世界中很多有意思的事物,包括道路系统.城市之间的航班.互联网的连接,甚至是计算机专业的一 ...
- django 中的hello word 开心,通过申请博客了,,发个随笔庆祝一下~~~~~~~
django 中的hello word! 准备:[pymsql,pycharm,django3.0.7] >>>终端中:django-admin.py startproject [项 ...
- MySQL:提高笔记-2
MySQL:提高笔记-2 学完基础的语法后,进一步对 MySQL 进行学习,第一篇为:MySQL:提高笔记-1,这是第二篇内容 说明:这是根据 bilibili 上 黑马程序员 的课程 mysql入门 ...
- Gitflow branch与Docker image tag命名冲突怎么办?
谷歌还是比必应要好用一点. 在前公司,我根据主流的git flow 给团队搭建了一套devops流程,运行在 docker & k8s上. 在现代devops流程中,一般推荐使用git分支名或 ...