# Spring Boot REST API 教程，第 2 部分：添加 GET 和 DELETE 端点

> For the complete documentation index, see [llms.txt](https://www.jetbrains.com.cn/help/idea/llms.txt).

* [1：创建并运行 Spring Boot 项目](spring-boot-tutorial-create-project.html)

* [2：添加 REST 端点]()

* [3：上传和下载文件](spring-boot-tutorial-file-upload-download.html)

* [4：在 H2 中存储数据并创建 JAR 软件包](spring-boot-tutorial-h2-database-jar.html)

从 [Spring Boot REST API 教程，第 1 部分：创建并运行项目](spring-boot-tutorial-create-project.html) 继续项目。 在本部分中，添加一个照片模型、一个将照片存储在内存中的服务，以及用于列出、检索和删除照片的端点。

Procedure: 创建 Photo 模型类

问候端点返回字符串，但照片端点需要处理结构化数据。  `Photo`  在 Java 中表示该数据。 先包含一个标识符和一个文件名称；在下一部分添加上传功能时，图像字节会随之到来。

1. 在  `model`  软件包中，创建一个名为  `Photo`  的类，其中包含一个标识符、一个文件名称和两个构造函数：

```JAVA
public class Photo {

    private String id;
    private String fileName;

    public Photo() {
    }

    public Photo(String id, String fileName) {
        this.id = id;
        this.fileName = fileName;
    }

}

```

双实参构造函数会创建下面使用的示例照片。 无实参构造函数会在下一部分添加上传的照片时使用。

2. 为两个字段生成访问器：将文本光标放在类中，按 `Alt+Insert` (Windows), `⌘ N` (macOS), `⌃ N` (IntelliJ IDEA Classic (macOS)), `⌘ N` (macOS System Shortcuts), `Alt+Insert` (XWin), `Alt+Insert` (GNOME), `Alt+Insert` (KDE), `Alt+Insert` (Emacs), `Alt+Insert` (Sublime Text), `⌘ N` (Sublime Text (macOS)), `Alt+Insert` (Visual Studio), `⌘ ⌃ N` (Visual Studio (macOS)), `Alt+Insert` (NetBeans), `Alt+Insert` (Eclipse), `⌘ N` (Eclipse (macOS))，选择   Getter 和 Setter  ，然后选择两个字段，并点击   OK  。

![Photo 类中打开的生成弹出窗口，已选择 Getter and Setter，用于为 id 和 fileName 字段创建访问器方法。](https://resources.jetbrains.com.cn/help/img/idea/2026.3/sb_generate_getter_setter.png)

Spring 使用 Jackson 将端点返回的照片转换为 JSON。 Jackson 通过 getter 读取照片属性，因此 JSON 响应包含  `id`  和  `fileName` 。 这些 setter 可用于在创建照片时分配这些值。

> **Tip:**
> 有关 IntelliJ IDEA 可以生成的所有内容，请参阅 [生成代码](generating-code.html)。

Procedure: 创建并注入照片服务

控制器将 HTTP 请求转换为调用，并将结果转换回响应。 创建一个服务来存储  `PhotosController`  将返回的照片。

在下一部分中，将添加第二个控制器来提供图像下载。 两个控制器都需要相同的照片。 如果为每个控制器提供各自的映射，应用程序中会留下两组单独的照片，因此两者都将使用同一个服务。

该服务也会成为放置非 HTTP 转换逻辑的位置，例如从上传的文件构建照片。 两个控制器都可以使用此服务，并且该服务可以从映射切换到数据库，而无需在任一控制器中放置数据库操作。

1. 在  `service`  软件包中，创建  `PhotosService` ：

```JAVA
@Service
public class PhotosService {

    private final Map<String, Photo> db = new HashMap<>();

    public PhotosService() {
        db.put("1", new Photo("1", "hello.jpg"));
    }

    public Collection<Photo> get() {
        return db.values();
    }

    public Photo get(String id) {
        return db.get(id);
    }

    public Photo remove(String id) {
        return db.remove(id);
    }

}

```

对  `映射` 、  `HashMap`  和  `集合`  使用  `java.util`  导入，并从 IntelliJ IDEA 建议的  `model`  软件包导入  `Photo` 。

`@Service`  会告知 Spring 在启动时创建一个实例，其方式与  `@RestController`  相同。 映射按标识符存储照片，因此检索或移除某一张照片时无需搜索列表。 它以一个示例  `hello.jpg`  开始，让端点有内容可返回。 这只是元数据，尚无图像文件。

2. 在  `PhotosController`  中，为服务添加一个字段和一个构造函数。 保留  `hello()`  方法：

```JAVA
private final PhotosService photosService;

public PhotosController(PhotosService photosService) {
    this.photosService = photosService;
}

```

代码中没有任何内容会调用此构造函数。 启动时，Spring 会发现  `PhotosController`  需要一个  `PhotosService` ，找到带有  `@Service`  注解的类，创建一个实例并将其传入。 这就是构造函数注入，并且会创建并共享一个实例，这使第二个控制器能够在下一部分看到相同的照片。

> **Note:**
> Spring 支持其他注入样式。 用  `@Autowired`  注解字段并去掉构造函数具有相同效果，并且允许在构造函数上使用  `@Autowired` ，但没有必要，因为单个构造函数会自动使用。
>
>
>
> 本教程全程使用构造函数注入，因为它允许字段为  `final` ，并使依赖项在签名中可见：没有服务就无法构造该类，因此它不能以半初始化状态存在。

Procedure: 为所有照片添加 GET 端点

模型和存储已就位。 第一个照片端点通过向服务请求所有已存储的照片并返回结果，将它们连接到浏览器。

1. 在  `PhotosController`  中，添加一个用于 GET 请求的方法，并将其映射到  `/photos` ：

```JAVA
@GetMapping("/photos")
public Collection<Photo> get() {
    return photosService.get();
}

```

2. 重启应用程序：点击 ![重新运行按钮](https://resources.jetbrains.com.cn/help/img/idea/2026.3/app.expui.run.rerun.svg)   重新运行  ，位置在 [运行](run-tool-window.html) 工具窗口中。 然后打开  `http://localhost:8080/photos` 。 浏览器会显示一个包含这张照片的 JSON 数组：

```JSON
[{"fileName":"hello.jpg","id":"1"}]

```

![localhost:8080/photos 上的浏览器显示一个 JSON 数组，其中包含一个照片对象，该对象的标识符为 1，文件名称为 hello.jpg。](https://resources.jetbrains.com.cn/help/img/idea/2026.3/sb_return_list_of_photos.png)

方法中没有任何内容显式创建 JSON。 它返回一个 Java 集合，并且由于该类是  `@RestController` ，Spring 会将返回值转换为响应。 生成的 getter 提供了出现在 JSON 数组中的照片属性。

Procedure: 为一张照片添加 GET 端点

1. 向  `PhotosController`  添加另一个  `get()`  方法：

```JAVA
@GetMapping("/photos/{id}")
public Photo get(@PathVariable String id) {
    Photo photo = photosService.get(id);
    if (photo == null) {
        throw new ResponseStatusException(HttpStatus.NOT_FOUND);
    }
    return photo;
}

```

`/photos/{id}`  中的大括号标识路径的变量部分。  `@PathVariable`  会将其传入具有相同名称的形参，因此对  `/photos/1`  的请求会查找标识符为  `1`  的照片。

当找不到照片时，服务返回  `null` 。 控制器通过抛出  `ResponseStatusException`  将这种缺失转换为  `404 Not Found` 。 这会将 HTTP 响应处理保留在控制器中。

2. 重启应用程序并打开  `http://localhost:8080/photos/1` ，即可看到作为 JSON 对象的照片。 打开  `http://localhost:8080/photos/2`  以检查 404 响应。 由于不存在具有此标识符的照片，浏览器会显示默认的 Spring Boot Whitelabel Error Page，并带有  `type=Not Found, status=404`  消息。

![Whitelabel Error Page 位于 localhost:8080/photos/2，显示 Not Found 错误和状态 404，因为不存在具有此标识符的照片。](https://resources.jetbrains.com.cn/help/img/idea/2026.3/sb_404_error.png)

Procedure: 添加 DELETE 端点

1. 向  `PhotosController`  添加一个用于 DELETE 请求的方法：

```JAVA
@DeleteMapping("/photos/{id}")
public void delete(@PathVariable String id) {
    Photo photo = photosService.remove(id);
    if (photo == null) {
        throw new ResponseStatusException(HttpStatus.NOT_FOUND);
    }
}

```

`@DeleteMapping`  在同一路径上处理不同的 HTTP 动词。 服务返回被移除的照片；如果该照片不存在，则返回  `null` ，控制器会将其转换为 404。

2. 重启应用程序并打开  `http://localhost:8080/photos` 。

打开浏览器开发者工具，并切换到   控制台   标签页。

为存在的照片发送 DELETE 请求：

```JAVASCRIPT
(async function deletePhoto(id) {
    await fetch("http://localhost:8080/photos/" + id, {
        method: "DELETE"
    })
})("1")

```

![浏览器开发者工具的控制台标签页，其中包含一个异步 fetch 调用，用于向 http://localhost:8080/photos/1 发送 DELETE 请求。](https://resources.jetbrains.com.cn/help/img/idea/2026.3/sb_console_delete_1.png)

重新加载  `http://localhost:8080/photos` 。 列表现在为空：  `[]` 。

![位于 http://localhost:8080/photos 的浏览器，在 DELETE 请求移除照片 1 后显示一个空 JSON 数组。](https://resources.jetbrains.com.cn/help/img/idea/2026.3/sb_localhost_empty_array.png)

[下一部分](spring-boot-tutorial-file-upload-download.html)

