本次讲的内容是,使用thymeleaf模板上传文件,spring在后台做处理。springboot默认的模板引擎是thymeleaf,需要掌握一下。
** 程序结构**
└── src
└── main
└── java
└── hello
pom.xml文件
4.0.0
org.springframework
gs-uploading-files
0.1.0
org.springframework.boot
spring-boot-starter-parent
2.1.6.RELEASE
1.8
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-thymeleaf
org.springframework.boot
spring-boot-starter-test
test
org.springframework.boot
spring-boot-maven-plugin
Spring Boot将会你做如下的事:
为了启动一个Spring Boot MVC应用程序,我们首先需要一个启动器,这里spring-boot-starter-thymeleaf
和 spring-boot-starter-web
已经添加作为依赖项。要在servlet容器中上载文件,
需要注册MultipartConfigElement类(在web.xml中为
)。由于Spring Boot,一切都是自动配置!
开始使用此应用程序所需的只是以下应用程序类。
src/main/java/hello/Application.java
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);
}
}
作为自动配置SpringMVC的一部分,SpringBoot将创建一个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 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 注释的,所以SpringMVC可以提取它并查找路由。每个方法都用@GetMapping 或@PostMapping标记,将路径和HTTP操作绑定到特定的控制器。
在这个例子中:
GET / 查找从StorageService上传的文件的当前列表,并将其加载到一个Thymeleaf模板中。它使用MvcUriComponentsBuilder映射到实际资源的链接
GET /files/{filename} 加载资源(如果存在),并使用"Content-Disposition"响应头将其发送到浏览器以便进行下载
POST / 用于处理多媒体文件,并将其交给StorageService进行保存。
在生产场景中,您更可能将文件存储在临时位置、数据库或NoSQL存储区(如Mongo的GridFS)。最好不要文件和文字内容放在一个服务器上。
您需要为控制器提供一个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 loadAll();
Path load(String filename);
Resource loadAsResource(String filename);
void deleteAll();
}
示例应用程序中有接口的示例实现。如果你想节省时间,你可以复制粘贴它。
创建一个简单的HTML模板
src/main/resources/templates/uploadForm.html
模板由三部分组成:
在顶部的可选消息,SpringMVC在其中编写一个flash范围的消息。
通过Spring引导,我们可以使用一些属性设置来调整其自动配置的MultipartConfigElement。
把下面加到你的配置文件:
src/main/resources/application.properties
spring.servlet.multipart.max-file-size=128KB
spring.servlet.multipart.max-request-size=128KB
spring.http.multipart.max-file-size
是设置为128KB,总文件大小不能超过128KB的意思。
spring.http.multipart.max-request-size
128KB的意思是设置为一个请求的大小,multipart/form-data
能超过128KB。
使程序运行
希望将文件上载到一个目标文件夹,因此,让我们增强基本应用程序类,并添加一个 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包含如下注解:
** 运行你的程序 **
STS下(Maven可参考前面文章):右键-选择Run as-Spring Boot App
服务器端部分接收上传的文件,并显示日志输出。
当服务器运行时,您需要打开浏览器并访问 http://localhost:8080/
查看上载表单。选择一个(小)文件并按“Upload”,您应该可以从控制器看到成功页面。选择一个太大的文件,你会得到一个的错误页面。
然后,您应该在浏览器窗口中看到类似的内容:
您成功上传了xxxx!
测试你的应用程序
在我们的应用程序中有多种方法可以测试这个功能性。下面是一个利用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());
}
}
在这些测试中,我们使用各种模拟来设置与控制器和StorageService的交互,也使用FileUploadIntegrationTests设置与servlet容器本身的交互。
有关集成测试的示例,请查看FileUploadIntegrationTests类。
我利用业余时间,翻译了Spring官网的例子,方便中文不好的同学,将陆续发到头条上,欢迎大家关注,也可以上我个人BLOG:itmanclub.com
,上面有已经翻译过的。