本指南将引导您完成创建可接收 HTTP 多部分文件上载的服务器应用程序的过程。
您将构建什么
您将创建一个接受文件上传的 Spring 引导 Web 应用程序。您还将构建一个简单的 HTML 界面来上传测试文件。
你需要什么
- 约15分钟
- 最喜欢的文本编辑器或 IDE
- JDK 1.8或以后
- 格拉德尔 4+或梅文 3.2+
- 您也可以将代码直接导入到 IDE 中:
- 弹簧工具套件 (STS)
- 智能理念
- VSCode
如何完成本指南
像大多数春天一样入门指南,您可以从头开始并完成每个步骤,也可以绕过您已经熟悉的基本设置步骤。无论哪种方式,您最终都会得到工作代码。
要从头开始,请继续从 Spring 初始化开始.
要跳过基础知识,请执行以下操作:
- 下载并解压缩本指南的源存储库,或使用吉特:git clone https://github.com/spring-guides/gs-uploading-files.git
- 光盘成gs-uploading-files/initial
- 跳转到创建应用程序类.
完成后,您可以根据 中的代码检查结果。gs-uploading-files/complete
从 Spring 初始化开始
你可以使用这个预初始化项目,然后单击生成以下载 ZIP 文件。此项目配置为适合本教程中的示例。
手动初始化项目:
如果您的 IDE 集成了 Spring Initializr,则可以从 IDE 完成此过程。
您也可以从 Github 分叉项目,然后在 IDE 或其他编辑器中打开它。
创建应用程序类
要启动 Spring Boot MVC 应用程序,您首先需要一个启动器。在此示例中,并且已添加为依赖项。要使用 Servlet 容器上传文件,您需要注册一个类(该类将在 web.xml 中)。多亏了Spring Boot,一切都是自动为您配置的!spring-boot-starter-thymeleafspring-boot-starter-webMultipartConfigElement<multipart-config>
要开始使用此应用程序,您只需要以下类(来自):UploadingFilesApplicationsrc/main/java/com/example/uploadingfiles/UploadingFilesApplication.java
package com.example.uploadingfiles;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplicationpublic class UploadingFilesApplication { public static void main(String[] args) { SpringApplication.run(UploadingFilesApplication.class, args); }}作为自动配置 Spring MVC 的一部分,Spring Boot 将创建一个 bean 并准备好上传文件。MultipartConfigElement
创建文件上传控制器
初始应用程序已经包含一些类来处理在磁盘上存储和加载上传的文件。它们都位于包中。您将在新的 .以下清单(来自 )显示了文件上载控制器:com.example.uploadingfiles.storageFileUploadControllersrc/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;@Controllerpublic class FileUploadController { private final StorageService storageService; @Autowired public 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:.+}") @ResponseBody public 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(); }}该类带有注释,以便Spring MVC可以拾取它并查找路线。每个方法都使用 or 进行标记,以将路径和 HTTP 操作绑定到特定的控制器操作。FileUploadController@Controller@GetMapping@PostMapping
在这种情况下:
- GET /:从中查找上传文件的当前列表,并将其加载到百里香叶模板中。它使用 计算指向实际资源的链接。StorageServiceMvcUriComponentsBuilder
- GET /files/{filename}:加载资源(如果存在)并将其发送到浏览器以使用响应标头下载。Content-Disposition
- POST /:处理多部分消息并将其提供给 进行保存。fileStorageService
在生产方案中,您更有可能将文件存储在临时位置、数据库或 NoSQL 存储(例如蒙戈的网格FS).最好不要在应用程序的文件系统中加载内容。
您需要提供 ,以便控制器可以与存储层(例如文件系统)进行交互。以下清单(来自 )显示了该接口:StorageServicesrc/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 在其中写入闪存范围消息.
- 允许用户上载文件的表单。
- 从后端提供的文件列表。
调整文件上传限制
配置文件上传时,设置文件大小限制通常很有用。想象一下,尝试处理 5GB 的文件上传!使用 Spring Boot,我们可以使用一些属性设置来调整其自动配置。MultipartConfigElement
将以下属性添加到现有属性设置(在 中):src/main/resources/application.properties
spring.servlet.multipart.max-file-size=128KBspring.servlet.multipart.max-request-size=128KB多部分设置的约束如下:
- spring.servlet.multipart.max-file-size设置为 128KB,表示总文件大小不能超过 128KB。
- spring.servlet.multipart.max-request-size设置为 128KB,这意味着 的总请求大小不能超过 128KB。multipart/form-data
运行应用程序
你想要一个要将文件上传到的目标文件夹,所以你需要增强 Spring Initializr 创建的基本类,并添加一个引导以在启动时删除并重新创建该文件夹。以下清单(来自 )显示了如何执行此操作:UploadingFilesApplicationCommandLineRunnersrc/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); } @Bean CommandLineRunner init(StorageService storageService) { return (args) -> { storageService.deleteAll(); storageService.init(); }; }}@SpringBootApplication是一个方便的注释,它添加了以下所有内容:
- @Configuration:将类标记为应用程序上下文的 Bean 定义源。
- @EnableAutoConfiguration:告诉 Spring 引导根据类路径设置、其他 bean 和各种属性设置开始添加 bean。例如,如果 在类路径上,则此注释会将应用程序标记为 Web 应用程序并激活关键行为,例如设置 .spring-webmvcDispatcherServlet
- @ComponentScan:告诉 Spring 在包中查找其他组件、配置和服务,让它找到控制器。com/example
该方法使用 Spring Boot 的方法启动应用程序。您是否注意到没有一行 XML?也没有文件。此 Web 应用程序是 100% 纯 Java,您无需处理配置任何管道或基础结构。main()SpringApplication.run()web.xml
构建可执行的 JAR
您可以使用 Gradle 或 Maven 从命令行运行应用程序。您还可以构建一个包含所有必需依赖项、类和资源的可执行 JAR 文件并运行该文件。通过构建可执行 jar,可以轻松地在整个开发生命周期中跨不同环境等将服务作为应用程序进行交付、版本控制和部署。
如果使用 Gradle,则可以使用 .或者,您可以使用 JAR 文件生成 JAR 文件,然后运行该文件,如下所示:./gradlew bootRun./gradlew build
java -jar build/libs/gs-uploading-files-0.1.0.jar如果使用 Maven,则可以使用 运行应用程序。或者,您可以使用 JAR 文件生成 JAR 文件,然后运行该文件,如下所示:./mvnw spring-boot:run./mvnw clean package
java -jar target/gs-uploading-files-0.1.0.jar此处描述的步骤将创建一个可运行的 JAR。你也可以构建经典 WAR 文件.
它运行接收文件上传的服务器端部分。将显示日志记录输出。该服务应在几秒钟内启动并运行。
在服务器运行时,您需要打开浏览器并访问以查看上传表单。选择一个(小)文件,然后按上传。应会看到控制器中的成功页面。如果您选择的文件太大,您将获得一个丑陋的错误页面。http://localhost:8080/
然后,您应该在浏览器窗口中看到类似于以下内容的行:
“您已成功上传<文件名>!”
测试您的应用程序
有多种方法可以在我们的应用程序中测试此特定功能。下面的清单(来自)显示了一个示例,该示例使用,因此不需要启动 servlet 容器:src/test/java/com/example/uploadingfiles/FileUploadTests.javaMockMvc
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.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@SpringBootTestpublic class FileUploadTests { @Autowired private MockMvc mvc; @MockBean private StorageService storageService; @Test public 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"))); } @Test public 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") @Test public void should404WhenMissingFile() throws Exception { given(this.storageService.loadAsResource("test.txt")) .willThrow(StorageFileNotFoundException.class); this.mvc.perform(get("/files/test.txt")).andExpect(status().isNotFound()); }}在这些测试中,您可以使用各种模拟来设置与控制器的交互,以及通过使用 与 Servlet 容器本身的交互。StorageServiceMockMultipartFile
有关集成测试的示例,请参见类(位于 中)。FileUploadIntegrationTestssrc/test/java/com/example/uploadingfiles
总结
祝贺!您刚刚编写了一个使用 Spring 来处理文件上传的 Web 应用程序。