本指南将指导您完成创建可以接收 HTTP 多文件上传服务器应用程序的过程。
你要构建什么
您将创建一个接受文件上传的 Spring Boot Web 应用程序。您还将构建一个简单的 HTML 界面来上传测试文件。
你需要什么
大约 15 分钟
最喜欢的文本编辑器或 IDE
JDK 1.8 或更高版本
Gradle 4+ 或 Maven 3.2+
您还可以将代码直接导入 IDE:
使用 STS 构建 / 导入入门指南
使用 IntelliJ IDEA 导入入门指南
如何完成本指南
与大多数 Spring 入门指南一样,您可以从头开始并完成每个步骤,或者您可以绕过您已熟悉的基本设置步骤。无论哪种方式,您最终都会使用工作代码。
要从头开始,请继续使用 Gradle 构建。
要跳过基础知识,请执行以下操作:
下载并解压缩本指南的源存储库,或使用 Git 克隆它:
git clone https://github.com/spring-guides/gs-uploading-files.git
进入 gs-uploading-files/initial
跳转到创建 Application 类。
完成后,可以根据 ggs-uploading-files/complete 中的代码检查结果。
使用 Gradle 构建
首先,设置一个基本的构建脚本。在使用 Spring 构建应用程序时,您可以使用任何您喜欢的构建系统,但此处包含了使用 Gradle 和 Maven 所需的代码。如果您不熟悉这两者,请参阅使用 Gradle 构建 Java 项目或使用 Maven 构建 Java 项目。
创建目录结构
在您选择的项目目录中,创建以下子目录结构; 例如,在 *nix 系统上使用 mkdir -p src/main/java/hello:
└── src
└── main
└── java
└── hello
创建 Gradle 构建文件
下面是最初的 Gradle 构建文件。
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath(“org.springframework.boot:spring-boot-gradle-plugin:2.0.3.RELEASE”)
}
}
apply plugin: ‘java’
apply plugin: ‘eclipse’
apply plugin: ‘org.springframework.boot’
apply plugin: ‘io.spring.dependency-management’
bootJar {
baseName = ‘gs-uploading-files’
version = ‘0.1.0’
}
repositories {
mavenCentral()
}
sourceCompatibility = 1.8
targetCompatibility = 1.8
dependencies {
compile(“org.springframework.boot:spring-boot-starter-web”)
compile(“org.springframework.boot:spring-boot-starter-thymeleaf”)
testCompile(“org.springframework.boot:spring-boot-starter-test”)
}
在 Spring Boot gradle plugin 提供了许多便捷的功能:
它收集类路径上的所有 jar 并构建一个可运行的“über-jar”,这使得执行和传输服务更加方便。
它搜索 public static void main() 标记为可运行类的方法。
它提供了一个内置的依赖项解析器,它设置版本号以匹配 Spring Boot 依赖项。您可以覆盖任何您希望的版本,但它将默认为 Boot 的所选版本集。
使用 Maven 构建
首先,设置一个基本的构建脚本。在使用 Spring 构建应用程序时,您可以使用任何您喜欢的构建系统,但此处包含了使用 Maven 所需的代码。如果您不熟悉 Maven,请参阅使用 Maven 构建 Java 项目。
创建目录结构
在您选择的项目目录中,创建以下子目录结构; 例如,在 *nix 系统上使用 mkdir -p src/main/java/hello:
└── src
└── main
└── java
└── hello
pom.xml
<?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>2.0.3.RELEASE</version>
</parent>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<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 gradle plugin 提供了许多便捷的功能:
它收集类路径上的所有 jar 并构建一个可运行的“über-jar”,这使得执行和传输服务更加方便。
它搜索 public static void main() 标记为可运行类的方法。
它提供了一个内置的依赖项解析器,它设置版本号以匹配 Spring Boot 依赖项。您可以覆盖任何您希望的版本,但它将默认为 Boot 的所选版本集。
使用 IDE 构建
阅读如何将本指南直接导入使用 STS 构建 / 导入入门指南。
阅读使用 IntelliJ IDEA 导入入门指南中如何使用本指南。
创建一个 Application 类
要启动 Spring Boot MVC 应用程序,我们首先需要一个启动器; spring-boot-starter-thymeleaf 和 spring-boot-starter-web 已经添加为依赖关系。要使用 Servlet 容器上传文件,您需要注册一个 MultipartConfigElement 类(在 web.xml 中 **)。感谢 Spring Boot,一切都是自动配置的!
您开始使用此应用程序所需的只是以下 Application 类。
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 中使用它们。
src/main/java/hello/FileUploadController.java
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 / 适用于处理多部分消息 file 并将其提供给 StorageService 保存
在生产场景中,您更有可能将文件存储在临时位置,数据库或 Mongo 的 GridFS 之类的 NoSQL 存储中。最好不要使用内容加载应用程序的文件系统。
您需要为控制器提供与 StorageService 存储层(例如文件系统)交互的控件。界面是这样的:
src/main/java/hello/storage/StorageService.java
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 模板是上传文件以及显示已上传内容的一个很好的例子。
src/main/resources/templates/uploadForm.html
<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 范围的消息。
允许用户上传文件的表单
从后端提供的文件列表
调整文件上传限制
配置文件上传时,设置文件大小限制通常很有用。想象一下尝试处理 5GB 文件上传!使用 Spring Boot,我们可以 MultipartConfigElement 使用一些属性设置调整其自动配置。
将以下属性添加到现有属性设置:
src/main/resources/application.properties
spring.servlet.multipart.max-file-size=128KB
spring.servlet.multipart.max-request-size=128KB
spring.http.multipart.enabled=false
多部分设置受限制如下:
spring.http.multipart.max-file-size 设置为 128KB,意味着总文件大小不能超过 128KB。
spring.http.multipart.max-request-size 设置为 128KB,表示 a 的总请求大小 multipart/form-data 不能超过 128KB。
构建可执行的 JAR
虽然可以将此服务打包为传统的 WAR 文件以部署到外部应用程序服务器,但下面演示的更简单的方法创建了一个独立的应用程序。您将所有内容打包在一个可执行的 JAR 文件中,由一个好的旧 Java main() 方法驱动。在此过程中,您使用 Spring 的支持将 Tomcat servlet 容器嵌入为 HTTP 运行时,而不是部署到外部实例。
您还需要一个目标文件夹来上传文件,所以让我们增强基本 Application 类并添加一个 Boot CommandLineRunner,它在启动时删除并重新创建该文件夹:
src/main/java/hello/Application.java
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。
通常你会添加 @EnableWebMvc 一个 Spring MVC 应用程序,但 Spring Boot 会在类路径上看到 spring-webmvc 时自动添加它。这会将应用程序标记为 Web 应用程序并激活关键行为,例如设置 DispatcherServlet。
@ComponentScan 告诉 Spring 在包中寻找其他组件,配置和服务,允许它找到控制器。
main() 方法使用 Spring Boot 的 SpringApplication.run() 方法来启动应用程序。您是否注意到没有一行 XML?也没有 web.xml 文件。此 Web 应用程序是 100%纯 Java,您无需处理配置任何管道或基础结构。
构建可执行的 JAR
您可以使用 Gradle 或 Maven 从命令行运行该应用程序。或者,您可以构建一个包含所有必需依赖项,类和资源的可执行 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 target/gs-uploading-files-0.1.0.jar
上面的过程将创建一个可运行的 JAR。您也可以选择构建经典 WAR 文件。
它运行接收文件上传的服务器端部分。显示记录输出。该服务应在几秒钟内启动并运行。
在服务器运行时,您需要打开浏览器并访问 http://localhost:8080/ 以查看上传表单。选择一个(小)文件并按“Upload”,您应该从控制器中看到成功页面。选择一个太大的文件,你会得到一个丑陋的错误页面。
然后,您应该在浏览器窗口中看到类似的内容:
You successfully uploaded <name of your file>!
测试您的应用程序
在我们的应用程序中有多种方法可以测试此特定功能。这是一个利用 MockMvc 的示例,因此不需要启动 Servlet 容器:
src/test/java/hello/FileUploadTests.java
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 使用 Servlet 容器本身的 MockMultipartFile 交互。
概要
恭喜!您刚刚编写了一个使用 Spring 处理文件上传的 Web 应用程序。