Spring Mvc 文件上传(MultipartFile )—官方原版
一、创建应用程序类
要启动Spring Boot MVC应用程序,首先需要一个启动器。在这个示例中,已经添加了spring-boot-starter thymelaf和spring-boot-starter web作为依赖项。要使用Servlet容器上传文件,您需要注册一个MultipartConfigElement类(在web.xml中为<multipart-config>)。多亏了Spring Boot,一切都可以自动配置!
1、引入依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency>
2、创建UploadingFilesApplication
开始使用此应用程序所需的只是以下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);}}
二、创建文件上传控制器
最初的应用程序已经包含了一些类来处理在磁盘上存储和加载上传的文件。它们都位于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查找当前上载文件的列表,并将其加载到Thymelaf模板中。它使用MvcUriComponentsBuilder计算到实际资源的链接。
-
GET/files/{filename}:加载资源(如果存在),并使用Content-Disposition响应标头将其发送到浏览器进行下载。
-
POST/:处理由多部分组成的消息文件,并将其提供给StorageService进行保存。
您需要提供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 模板
以下Thymelaf模板(来自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范围的消息。
-
允许用户上传文件的表单。
-
从后端提供的文件列表。
四、调整文件上传限制
配置文件上载时,设置文件大小限制通常很有用。想象一下,试图处理5GB的文件上传!使用Spring Boot,我们可以通过一些属性设置来调整其自动配置的MultipartConfigElement。
将以下财产添加到现有财产设置中(在src/main/resources/application.nenenebc属性中):
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,这意味着 的总请求大小不能超过 128KB。multipart/form-data
五、运行应用程序
您想要一个上传文件的目标文件夹,因此需要增强Spring Initializer创建的基本UploadingFilesApplication类,并添加Boot CommandLineRunner以在启动时删除并重新创建该文件夹。以下列表(来自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是一个方便的注释,它添加了以下所有内容:
@配置:将类标记为应用程序上下文的bean定义的源。
@EnableAutoConfiguration:告诉SpringBoot开始基于类路径设置、其他bean和各种属性设置添加bean。例如,如果spring-webmvc在类路径上,则此注释将应用程序标记为web应用程序并激活关键行为,例如设置DispatcherServlet。
@ComponentScan:告诉Spring在com/example包中查找其他组件、配置和服务,让它找到控制器。
main()方法使用Spring Boot的SpringApplication.run()方法来启动应用程序。您注意到没有一行XML吗?也没有web.xml文件。这个web应用程序是100%纯Java的,您不需要配置任何管道或基础设施。
六、测试
有多种方法可以在我们的应用程序中测试这一特定功能。下面的列表(来自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.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());}}