[Spring Guides] 上传文件
- 本指南将引导您完成创建可以接收HTTP多部分文件上传的服务器应用程序的过程。
- 您将创建一个接受文件上传的Spring Boot Web应用程序。您还将构建一个简单的HTML界面来上传测试文件。
使用Maven构建项目
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework</groupId>
<artifactId>gs-uploading-files</artifactId>
<version>0.1.0</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.7.RELEASE</version>
</parent>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
创建应用程序类
要启动Spring Boot MVC应用程序,我们首先需要一个starter;spring-boot-starter-thymeleaf
和spring-boot-starter-web
已经被添加为依赖关系。要使用Servlet容器上传文件,您需要注册一个MultipartConfigElement
类(在web.xml中为<multipart-config>
)。Spring Boot将一切都为您自动配置。
package hello;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
作为自动配置Spring MVC的一部分,Spring Boot将创建一个MultipartConfigElement
bean,并使其准备好进行文件上传。
创建文件上传控制器
初始应用程序已经包含几个类来处理在磁盘上存储和加载上传的文件;它们都位于之后创建的 hello.storage
包中。我们将在FileUploadController
中使用他们。
package hello;
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 hello.storage.StorageFileNotFoundException;
import hello.storage.StorageService;
@Controller
public 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().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();
}
}
这个类用@Controller注释
这样Spring MVC可以将它收集起来并寻找路由。每个方法都使用@GetMapping
或@PostMapping
进行标记,以将路径和HTTP操作绑定到特定的Controller操作。
在这种情况下:
-
GET /
从StorageService查找上传的文件列表,并将其加载到Thymeleaf模板中。它使用MvcUriComponentsBuilder
计算到实际资源的链接 -
GET / files / {filename}
加载资源(如果存在),并使用“Content-Disposition”响应头将资源发送到浏览器进行下载 -
POST /
适用于处理多部分消息文件,并将其交给StorageService
进行保存
在生产场景中,您更有可能将文件存储在临时位置,数据库,或者可能是诸如Mongo的GridFS之类的NoSQL存储。最好不要用内容加载应用程序的文件系统。
您将需要为控制器提供一个StorageService用以与存储层(例如文件系统)进行交互:
package hello.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模板是上传文件的一个很好的例子,并显示了上传的内容。
<html xmlns:th="http://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 messages(个人译作闪现范围消息)。
- 一个允许用户上传文件的表单
- 从后端提供的文件列表
调整文件上传限制
配置文件上传时,通常设置文件大小的限制很有用。试着想象一下处理一个5GB的文件上传,使用Spring Boot,我们可以使用某些属性设置来调整自动配置的MultipartConfigElement。
将以下属性添加到现有属性设置中:
src/main/resources/application.properties
spring.http.multipart.max-file-size=128KB
spring.http.multipart.max-request-size=128KB
多部分设置受到以下限制:
-
spring.http.multipart.max-file-size
设置为128KB,意思是总文件大小不能超过128KB。 -
spring.http.multipart.max-request-size
设置为128KB,意味着multipart / form-data
的总请求大小不能超过128KB。
编写应用程序执行主体
您还需要一个目标文件夹来上传文件,所以让我们增强基本的Application类,并添加一个Boot CommandLineRunner,它在启动时删除并重新创建该文件夹:
package hello;
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 hello.storage.StorageProperties;
import hello.storage.StorageService;
@SpringBootApplication
@EnableConfigurationProperties(StorageProperties.class)
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Bean
CommandLineRunner init(StorageService storageService) {
return (args) -> {
storageService.deleteAll();
storageService.init();
};
}
}
@SpringBootApplication
是一个方便的注释,它添加以下所有内容:
-
@Configuration
将类标记为应用程序上下文的bean定义的源。 -
@EnableAutoConfiguration
指示Spring Boot根据类路径设置,其他bean和各种属性设置开始添加bean。 - 通常,您将为Spring MVC应用程序添加
@EnableWebMvc
,但是当Spring类在类路径中看到spring-webmvc时,Spring Boot会自动添加它。这将应用程序标记为Web应用程序,并激活诸如设置DispatcherServlet等关键行为。 -
@ComponentScan
告诉Spring在hello包中查找其他组件,配置和服务,允许它找到控制器。
测试你的应用程序
在我们的应用程序中有多种方法来测试这个特定的功能。以下是使用MockMvc的一个示例,所以不需要启动Servlet容器:
package hello;
import java.nio.file.Paths;
import java.util.stream.Stream;
import org.hamcrest.Matchers;
import org.junit.Test;
import org.junit.runner.RunWith;
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.context.junit4.SpringRunner;
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.result.MockMvcResultMatchers.header;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import hello.storage.StorageFileNotFoundException;
import hello.storage.StorageService;
@RunWith(SpringRunner.class)
@AutoConfigureMockMvc
@SpringBootTest
public 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(fileUpload("/").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());
}
}
在这些测试中,我们使用各种模拟器来设置与Controller和StorageService
的交互,也可以使用MockMultipartFile
来使用Servlet容器本身。
作为集成测试的示例,请查看FileUploadIntegrationTests
类。