使用 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 ...
随机推荐
- Java(1)开发环境配置及第一个程序Hello World
作者:季沐测试笔记 原文地址:https://www.cnblogs.com/testero/p/15201468.html 博客主页:https://www.cnblogs.com/testero ...
- python flask1
以这个服务端代码为例,简单了解一下flask的运用. 1.app = Flask(__name__)记住就好了 2.@app.route("/")记住就好了:注意括号里的是调用这个 ...
- 是兄弟就来摸鱼 Scrum Meeting 博客汇总
是兄弟就来摸鱼 Scrum Meeting 博客汇总 一.Alpha阶段 第一次Scrum meeting 第二次Scrum meeting 第三次Scrum meeting 第四次Scrum mee ...
- 【二食堂】Beta - 发布声明
Beta - 发布声明 新功能 在Beta阶段,图谱方面的新功能有:自定义关系的添加与删除.实体查找.实体名称的修改.实体之间关系的修改.新增了项目创建与删除功能,此外还增加了好友系统,可以实现好友的 ...
- 6月4日 Scrum Meeting
日期:2021年6月4日 会议主要内容概述:讨论账单功能模块,讨论账单前后端接口. 一.进度情况 组员 负责 两日内已完成的工作 后两日计划完成的工作 工作中遇到的困难 徐宇龙 后端 账单数据界面 设 ...
- IDEA + maven 零基础构建 java agent 项目
200316-IDEA + maven 零基础构建 java agent 项目 Java Agent(java 探针)虽说在 jdk1.5 之后就有了,但是对于绝大多数的业务开发 javaer 来说, ...
- [BZOJ4399]魔法少女LJJ----------线段树进阶
感谢线段树进阶,给了我重新做人的机会.---------------某不知名OIer,Keen_z Description 题目描述 在森林中见过会动的树,在沙漠中见过会动的仙人掌过后,魔法少女LJJ ...
- 生产环境部署springcloud微服务启动慢的问题排查
今天带来一个真实案例,虽然不是什么故障,但是希望对大家有所帮助. 一.问题现象: 生产环境部署springcloud应用,服务部署之后,有时候需要10几分钟才能启动成功,在开发测试环境则没有这个问题. ...
- stm32直流电机驱动与测速学习总结
通过实验发现,定时器的一个通道控制一个pwm信号. 在正式开始之前也可以参考这个视频学习资料 (stm32直流电机驱动) http://www.makeru.com.cn/live/1392_1218 ...
- C语言编程基础有网盘资料哦
刚开始看STM32的库函数,会有很多疑惑,例如指针怎么用,结构体跟指针怎么配合,例如函数的参数有什么要求,如何实时更新IO口的数据等.如果重新进行C语言的学习,那么要学很久才能够系统地认识.本文则将比 ...