本指南将引导您完成创建可以接收 HTTP 多部分文件上传的服务器应用程序的过程。
你将建造什么
您将创建一个接受文件上传的 Spring Boot Web 应用程序。您还将构建一个简单的 HTML 界面来上传测试文件。
你需要什么
- 约15分钟
- 最喜欢的文本编辑器或 IDE
- JDK 1.8或更高版本
- Gradle 4+或Maven 3.2+
- 您还可以将代码直接导入 IDE:
- 弹簧工具套件 (STS)
- IntelliJ IDEA
如何完成本指南
像大多数 Spring入门指南一样,您可以从头开始并完成每个步骤,也可以绕过您已经熟悉的基本设置步骤。无论哪种方式,您最终都会得到工作代码。
要从头开始,请继续从 Spring Initializr 开始。
要跳过基础知识,请执行以下操作:
- 下载并解压本指南的源代码库,或使用Git克隆它:
git clone https://github.com/spring-guides/gs-uploading-files.git
- 光盘进入
gs-uploading-files/initial
- 继续创建应用程序类。
完成后,您可以对照中的代码检查结果gs-uploading-files/complete
。
从 Spring Initializr 开始
您可以使用这个预先初始化的项目并单击 Generate 下载 ZIP 文件。此项目配置为适合本教程中的示例。
手动初始化项目:
- 导航到https://start.spring.io。该服务提取应用程序所需的所有依赖项,并为您完成大部分设置。
- 选择 Gradle 或 Maven 以及您要使用的语言。本指南假定您选择了 Java。
- 单击Dependencies并选择Spring Web和Thymeleaf。
- 单击生成。
- 下载生成的 ZIP 文件,该文件是根据您的选择配置的 Web 应用程序的存档。
如果您的 IDE 具有 Spring Initializr 集成,您可以从您的 IDE 完成此过程。
你也可以从 Github 上 fork 项目并在你的 IDE 或其他编辑器中打开它。
创建应用程序类
要启动 Spring Boot MVC 应用程序,首先需要一个启动器。在此示例中,spring-boot-starter-thymeleaf
并且spring-boot-starter-web
已作为依赖项添加。要使用 Servlet 容器上传文件,您需要注册一个MultipartConfigElement
类(<multipart-config>
在 web.xml 中)。感谢 Spring Boot,一切都是为您自动配置的!
开始使用此应用程序所需的只是以下UploadingFilesApplication
类(来自src/main/java/com/example/uploadingfiles/UploadingFilesApplication.java
):
package com.example.uploadingfiles;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class UploadingFilesApplication {
public static void main(String[] args) {SpringApplication.run(UploadingFilesApplication.class, args);}
}复制
作为自动配置 Spring MVC 的一部分,Spring Boot 将创建一个MultipartConfigElement
bean 并为文件上传做好准备。
创建文件上传控制器
初始应用程序已经包含一些类来处理在磁盘上存储和加载上传的文件。它们都位于com.example.uploadingfiles.storage
包装中。您将在新的FileUploadController
. 以下清单(来自src/main/java/com/example/uploadingfiles/FileUploadController.java
)显示了文件上传控制器:
package com.example.uploadingfiles;
import java.io.IOException;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import com.example.uploadingfiles.storage.StorageFileNotFoundException;
import com.example.uploadingfiles.storage.StorageService;
@Controller
public class FileUploadController {
private final StorageService storageService;
@Autowiredpublic FileUploadController(StorageService storageService) {this.storageService = storageService;}
@GetMapping("/")public String listUploadedFiles(Model model) throws IOException {
model.addAttribute("files", storageService.loadAll().map(
path -> MvcUriComponentsBuilder.fromMethodName(FileUploadController.class,"serveFile", path.getFileName().toString()).build().toUri().toString()).collect(Collectors.toList()));
return "uploadForm";}
@GetMapping("/files/{filename:.+}")@ResponseBodypublic ResponseEntity<Resource> serveFile(@PathVariable String filename) {
Resource file = storageService.loadAsResource(filename);return ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION,"attachment; filename=\"" + file.getFilename() + "\"").body(file);}
@PostMapping("/")public String handleFileUpload(@RequestParam("file") MultipartFile file,RedirectAttributes redirectAttributes) {
storageService.store(file);
redirectAttributes.addFlashAttribute("message","You successfully uploaded " + file.getOriginalFilename() + "!");
return "redirect:/";}
@ExceptionHandler(StorageFileNotFoundException.class)public ResponseEntity<?> handleStorageFileNotFound(StorageFileNotFoundException exc) {return ResponseEntity.notFound().build();}
}复制
该类FileUploadController
带有注释,@Controller
以便 Spring MVC 可以拾取它并查找路由。每个方法都被标记@GetMapping
或@PostMapping
将路径和 HTTP 操作绑定到特定的控制器操作。
在这种情况下:
-
GET /
:从 中查找当前上传文件的列表StorageService
并将其加载到 Thymeleaf 模板中。它通过使用 计算到实际资源的链接MvcUriComponentsBuilder
。 -
GET /files/{filename}
:加载资源(如果存在)并使用Content-Disposition
响应头将其发送到浏览器进行下载。 -
POST /
:处理多部分消息file
并将其提供给StorageService
保存。
在生产场景中,您更有可能将文件存储在临时位置、数据库或 NoSQL 存储(例如Mongo 的 GridFS)中。最好不要在应用程序的文件系统中加载内容。
您将需要提供一个StorageService
以便控制器可以与存储层(例如文件系统)进行交互。以下清单(来自src/main/java/com/example/uploadingfiles/storage/StorageService.java
)显示了该界面:
package com.example.uploadingfiles.storage;
import org.springframework.core.io.Resource;
import org.springframework.web.multipart.MultipartFile;
import java.nio.file.Path;
import java.util.stream.Stream;
public interface StorageService {
void init();
void store(MultipartFile file);
Stream<Path> loadAll();
Path load(String filename);
Resource loadAsResource(String filename);
void deleteAll();
}复制
创建 HTML 模板
以下 Thymeleaf 模板(来自src/main/resources/templates/uploadForm.html
)显示了如何上传文件并显示已上传内容的示例:
<html xmlns:th="https://www.thymeleaf.org">
<body>
<div th:if="${message}"><h2 th:text="${message}"/></div>
<div><form method="POST" enctype="multipart/form-data" action="/"><table><tr><td>File to upload:</td><td><input type="file" name="file" /></td></tr><tr><td></td><td><input type="submit" value="Upload" /></td></tr></table></form></div>
<div><ul><li th:each="file : ${files}"><a th:href="${file}" th:text="${file}" /></li></ul></div>
</body>
</html>复制
该模板包含三个部分:
- Spring MVC 在其中写入flash-scoped message顶部的可选消息。
- 允许用户上传文件的表单。
- 从后端提供的文件列表。
调整文件上传限制
配置文件上传时,设置文件大小限制通常很有用。想象一下尝试处理 5GB 文件上传!MultipartConfigElement
使用 Spring Boot,我们可以使用一些属性设置来调整它的自动配置。
将以下属性添加到现有属性设置(在 中src/main/resources/application.properties
):
spring.servlet.multipart.max-file-size=128KB
spring.servlet.multipart.max-request-size=128KB复制
多部分设置的约束如下:
-
spring.servlet.multipart.max-file-size
设置为 128KB,这意味着总文件大小不能超过 128KB。 -
spring.servlet.multipart.max-request-size
设置为 128KB,这意味着 a 的总请求大小multipart/form-data
不能超过 128KB。
运行应用程序
您需要一个目标文件夹来上传文件,因此您需要增强UploadingFilesApplication
Spring Initializr 创建的基本类并添加一个 BootCommandLineRunner
以在启动时删除并重新创建该文件夹。以下清单(来自src/main/java/com/example/uploadingfiles/UploadingFilesApplication.java
)显示了如何执行此操作:
package com.example.uploadingfiles;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import com.example.uploadingfiles.storage.StorageProperties;
import com.example.uploadingfiles.storage.StorageService;
@SpringBootApplication
@EnableConfigurationProperties(StorageProperties.class)
public class UploadingFilesApplication {
public static void main(String[] args) {SpringApplication.run(UploadingFilesApplication.class, args);}
@BeanCommandLineRunner init(StorageService storageService) {return (args) -> {
storageService.deleteAll();
storageService.init();};}
}复制
@SpringBootApplication
是一个方便的注释,它添加了以下所有内容:
-
@Configuration
: 将类标记为应用程序上下文的 bean 定义源。 -
@EnableAutoConfiguration
:告诉 Spring Boot 根据类路径设置、其他 bean 和各种属性设置开始添加 bean。例如,如果spring-webmvc
位于类路径上,则此注释将应用程序标记为 Web 应用程序并激活关键行为,例如设置DispatcherServlet
. -
@ComponentScan
: 告诉 Spring 在包中查找其他组件、配置和服务com/example
,让它找到控制器。
该main()
方法使用 Spring Boot 的SpringApplication.run()
方法来启动应用程序。您是否注意到没有一行 XML?也没有web.xml
文件。这个 Web 应用程序是 100% 纯 Java,您不必处理任何管道或基础设施的配置。
构建一个可执行的 JAR
您可以使用 Gradle 或 Maven 从命令行运行应用程序。您还可以构建一个包含所有必要依赖项、类和资源的单个可执行 JAR 文件并运行它。构建可执行 jar 可以在整个开发生命周期、跨不同环境等中轻松地将服务作为应用程序交付、版本化和部署。
如果您使用 Gradle,则可以使用./gradlew bootRun
. 或者,您可以使用构建 JAR 文件./gradlew build
,然后运行 JAR 文件,如下所示:
java -jar build/libs/gs-uploading-files-0.1.0.jar
如果您使用 Maven,则可以使用./mvnw spring-boot:run
. 或者,您可以使用构建 JAR 文件,./mvnw clean package
然后运行该 JAR 文件,如下所示:
java -jar 目标/gs-uploading-files-0.1.0.jar
此处描述的步骤创建了一个可运行的 JAR。您还可以构建经典的 WAR 文件。
它运行接收文件上传的服务器端部分。显示记录输出。该服务应在几秒钟内启动并运行。
在服务器运行的情况下,您需要打开浏览器并访问http://localhost:8080/
以查看上传表单。选择一个(小)文件,然后按Upload。您应该会从控制器中看到成功页面。如果你选择的文件太大,你会得到一个丑陋的错误页面。
然后,您应该会在浏览器窗口中看到类似于以下内容的行:
“您成功上传了<文件名>!”
测试您的应用程序
有多种方法可以在我们的应用程序中测试此特定功能。以下清单(来自src/test/java/com/example/uploadingfiles/FileUploadTests.java
)显示了一个示例,MockMvc
它不需要启动 servlet 容器:
package com.example.uploadingfiles;
import java.nio.file.Paths;
import java.util.stream.Stream;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.test.web.servlet.MockMvc;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.then;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.fileUpload;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import com.example.uploadingfiles.storage.StorageFileNotFoundException;
import com.example.uploadingfiles.storage.StorageService;
@AutoConfigureMockMvc
@SpringBootTest
public class FileUploadTests {
@Autowiredprivate MockMvc mvc;
@MockBeanprivate StorageService storageService;
@Testpublic void shouldListAllFiles() throws Exception {
given(this.storageService.loadAll()).willReturn(Stream.of(Paths.get("first.txt"), Paths.get("second.txt")));
this.mvc.perform(get("/")).andExpect(status().isOk()).andExpect(model().attribute("files",Matchers.contains("http://localhost/files/first.txt","http://localhost/files/second.txt")));}
@Testpublic void shouldSaveUploadedFile() throws Exception {MockMultipartFile multipartFile = new MockMultipartFile("file", "test.txt","text/plain", "Spring Framework".getBytes());this.mvc.perform(multipart("/").file(multipartFile)).andExpect(status().isFound()).andExpect(header().string("Location", "/"));
then(this.storageService).should().store(multipartFile);}
@SuppressWarnings("unchecked")@Testpublic void should404WhenMissingFile() throws Exception {
given(this.storageService.loadAsResource("test.txt")).willThrow(StorageFileNotFoundException.class);
this.mvc.perform(get("/files/test.txt")).andExpect(status().isNotFound());}
}复制
在这些测试中,您使用各种模拟来设置与控制器的交互以及StorageService
与 Servlet 容器本身的交互,方法是使用MockMultipartFile
.
有关集成测试的示例,请参见FileUploadIntegrationTests
类(位于 中src/test/java/com/example/uploadingfiles
)。
概括
恭喜!您刚刚编写了一个使用 Spring 处理文件上传的 Web 应用程序。