SpringBoot 构建 RestAPI 应用
目录
Spring 是 Java 应用框架,SpringMVC 只是其中一个能提供 web 服务的模块。
SpringBoot 是 Spring 的常用模块封装,类似项目的脚手架。
RestAPI 是内容传输格式,客户端和浏览器约定用 JSON 格式交互数据,适用于后台接口开发,前后端分离。
本实验用 SpringBoot 的 MVC 整合模块开发一个 RestAPI 应用,Gradle 作为依赖管理工具。
实验工具版本说明
工具 | 版本 | 说明 |
---|---|---|
Gradle | 7.2 | 依赖管理工具 |
springboot gradle plugin | 2.4.8 | SpringBoot Gradle 插件 |
springboot starter web | 2.5.5 | SpringBoot web 模块整合 |
Java | 11 | 任意 Java 11 发行版,推荐 AdoptOpenJDK |
用 Gradle 新建一个 Java 应用项目
项目根目录执行 gradle init,Gradle 询问一些东西,然后生成项目基本结构.
> gradle init
# 询问项目类型: 实验选择 application
Select type of project to generate: 选择 application
# 询问开发语言: 实验选择 Java
Select implementation language:
# 询问是否同时开发库: 实验选择单应用项目 (no - only one application project)
Split functionality across multiple subprojects?: no - only one application project
# 询问构建语言: 实验选择 Groovy
Select build script DSL:
# 询问测试工具: 默认即可
Select test framework:
# 项目名称: 默认即可
Project name:
# 应用包名: 实验为 org.luo3.springrestdemo
Source package:
init 完毕,项目根目录会有这些文件:

gradle init 后的项目结构
用 ./gradlew tasks
可以查看所有可用的构建任务。
管理项目依赖
打开 app/build.gradle
。
添加 SpringBoot Gradle 插件
导入后就可以用 bootRun、bootJar 启动或打包应用了。
bootRun 和 run 的区别?
bootRun 是 SpringBoot Gradle 提供的,run 是 application 插件提供的。它们都可以读取
application { mainClass = XXX }
作为程序入口类。bootXXX 只是方便 Spring 应用打包,运行的任务,用这些任务可以省去很多麻烦的配置步骤。
在 plugins
处增加 SpringBoot Gradle 插件。
plugins {
// ...
// [添加] SpringBoot Gradle 插件
id "org.springframework.boot" version "2.4.8"
}
添加 SpringBoot Starter Web 依赖包
在 dependencies
处增加依赖。
dependencies {
// [增加] SpringBoot Starter Web 模块整合包
implementation 'org.springframework.boot:spring-boot-starter-web:2.5.5'
// ...
}
现在已经可以在 java 代码引用 spring 的依赖了。
入口类启动 Spring
Gradle init application 会自动生成一个 App.java
作为程序入口。
将程序入口改成用 Spring 启动:
@SpringBootApplication
标记入口类static void main
方法启动 Spring
@SpringBootApplication
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
在项目根目录执行 ./gradlew bootRun
即可启动 web 服务器。
./gradlew bootRun
默认端口 :8080
,因为没有 Controller,所以请求任意路径都会报 404。
> curl localhost:8080
{"timestamp":"2021-10-14T02:26:00.038+00:00","status":404,"error":"Not Found","path":"/"}
添加 MVC 控制器 Controller
在 App.java
同级目录新建控制器类 AppController.java
。
基本路由映射
// @RestController 标记为 RestAPI 控制器
@RestController
public class AppController {
// 基本 HTTP GET
@GetMapping("/hi")
public Map<String, String> handleBaseHTTPGET () {
return new HashMap<>(){{
put("msg", "hi");
}};
}
}
运行服务器,访问 localhost:8080/hi
# 测试基本 HTTP GET
curl -XGET localhost:8080/hi
# 结果 {"msg":"hi"}
@RestController
public class AppController {
// 处理 POST
// curl -X POST localhost:8080/post_hi
@PostMapping("/post_hi")
public Map<String, String> handlePost () {
return new HashMap<>(){{
put("msg", "hi from POST");
}};
}
// 接受多个 method,比如 GET, POST
// curl -X POST localhost:8080/getpost_hi
// curl -X GET localhost:8080/getpost_hi
// curl localhost:8080/getpost_hi
@RequestMapping(path = "/getpost_hi", method = {RequestMethod.GET, RequestMethod.POST})
public Map<String, String> handleGETPOST () {
return new HashMap<>(){{
put("msg", "hi from GET | POST");
}};
}
}
@RestController
@RequestMapping("/sub")
public class SubRouteController {
// 子路由,实际路径 /sub/hi
@GetMapping("/hi")
public Map<String, String> handleSubRoute () {
return new HashMap<>(){{
put("msg", "hi, sub route");
}};
}
}
# 测试子路由
curl -XGET localhost:8080/sub/hi
# 结果 {"msg":"hi, sub route"}
处理 Query (Param, search)
@RestController
public class AppController {
// URL Query
@GetMapping("/hello")
public Map<String, String> handleURLQuery (@RequestParam("name") String name) {
return new HashMap<>(){{
put("msg", "hi, " + name);
}};
}
}
运行效果
# 测试 URL Query
curl -XGET localhost:8080/hello?name=luo
# 返回 {"msg":"hi, luo"}
处理 JSON body
@RestController
public class AppController {
// JSON body
@PostMapping(
path = "/hello",
consumes = "application/json",
produces = "application/json"
)
public Map<String,Object> handleJSONBody (@RequestBody Map<String,Object> jsonObject) {
String name = jsonObject.get("name").toString();
return new HashMap<>(){{
put("msg", "welcome back, " + name);
}};
}
}
运行效果
# 测试 JSON 内容
curl -XPOST \
-H 'Content-Type: application/json' \
-d '{"name": "luo"}' \
localhost:8080/hello
# 结果 {"msg": "welcome back, luo"}
@RestController
public class AppController {
public static class Person {
// 私有成员
private String name;
private String nickname;
// getter
// 建议用 lombok 库,@Getter 自动生成成员 getter
public String getName() {
return name;
}
public String getNickname() {
return nickname;
}
// setter
// 建议用 lombok 库,@Setter 自动生成成员 setter
public void setName(String name) {
this.name = name;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
}
@PostMapping(
path = "/post",
consumes = "application/json",
produces = "application/json"
)
public Person post (@RequestBody Person personObject) {
return personObject; // 将请求的 JSON 原样返回
}
}
curl -X POST \
-H 'Content-Type: application/json' \
-d '{ "name": "guest" }' \
--url 'localhost:8080/post'
# 返回 {"name":"guest","nickname":null}
路径变量 (占位符)
@RestController
public class AppController {
// 占位符
@GetMapping("/profile/{slug}")
public Map<String, String> handlePathVariable (@PathVariable("slug") String slugValue) {
return new HashMap<>(){{
put("msg", "profile of user: " + slugValue);
}};
}
}
运行结果
# 测试占位符变量
curl -XGET localhost:8080/profile/luo
# 结果 {"msg": "profile of user: luo"}
直接拿整个请求
@RestController
public class AppController {
@GetMapping("/get_scheme")
public String rawRequest (HttpServletRequest request) {
return request.getScheme();
}
}
运行结果
curl 'http://localhost:8080/get_scheme'
// 返回 http
应用打包成 Jar
执行 bootJar
任务,会在 app/build/libs
目录生成 jar。
./gradlew bootJar
jar 可以独立执行
java -jar app.jar
附录与引用
Java 开发辅助库: lombok
getter,setter 在 SpringMVC 或其他数据交换情形经常需要用到,lombok 的一些库省去了一堆本来要手写的 getter, setter 代码。
class Bottle {
private int capacity;
private String name;
private boolean isPlastic;
// 手写 getter
public int getCapacity () { ... }
public int getName () { ... }
public int getIsPlastic () { ... }
// 手写 setter
public void setCapacity (int cap) { ... }
public void setName (String name) { ... }
public void setIsPlastic (boolean flag) { ... }
// 手写 toString
public String toString() { return "" + "" + ""; }
}
@Getter // 自动生成 getter
@Setter // 自动生成 setter
@ToString // 还可以生成 toString,拼接成员变量
class LombokBottle {
private int capacity; // 只要定义私有成员即可
private String name;
private boolean isPlastic;
}
但是 lombok 不用显式声明方法名称,Java 编译是肯定不给过的。像这样:
error: cannot find symbol
让 Java 无视这些 symbol 检查就可以了。
dependencies {
// lombok 依赖
implementation 'org.projectlombok:lombok:1.18.20'
// lombok 声明检查,加上后,编译通过
annotationProcessor 'org.projectlombok:lombok:1.18.20'
}
@ResponseBody 的作用 (StackOverflow)
看到很多项目的 RestAPI Controller 都能见到 @ResponseBody
。
public class Controller {
@GetMapping("/")
@ResponseBody
public String hi () { ... }
}
@ResponseBody
标记是告诉 Spring MVC,这个方法的返回值,直接编码放在 HTTP body 里面。