1. Введение
В этом кратком руководстве мы узнаем, как загрузить файл с помощью Spring Boot.
Давайте посмотрим, как это сделать через form submission.
2. Почему загрузка файлов важна?
Uploading files — очень распространённый сценарий использования в современных web applications, например, attaching documents в forms или uploading profile pictures. Несмотря на простоту понимания, корректная работа с файлами сопряжена с рядом сложностей. Например, открыв приложение без ограничений на file size, который пользователи могут загружать, мы рискуем перегрузить наше приложение или исчерпать доступное storage. Поэтому перед тем, как принять любой файл, нам нужно validate его type и content и handle failures gracefully.
Итак, в нашем приложении мы воспользуемся опциями, предлагаемыми Spring Boot, чтобы сделать его более надёжным, установив ограничение на file size и предоставляя обратную связь, чтобы пользователь понимал, что происходит.
3. Upload Flow
Ниже показан sequence diagram, который наглядно демонстрирует взаимодействия между User, Browser и нашим приложением Spring Boot:
sequenceDiagram
participant User as User
participant Browser as Browser
participant Controller as UploadController
participant Filesystem as File System
User->>Browser: Selects file and submits form
Browser->>Controller: POST /upload (multipart/form-data)
Controller->>Controller: Check if file is empty
alt File is valid
Controller->>Filesystem: Save file bytes to disk
Controller-->>Browser: Redirect to / with success message
else File is invalid
Controller-->>Browser: Redirect to / with error message
end
Browser->>User: Display upload status message
4. Конфигурация Maven
Давайте начнём с добавления наших dependencies. Убедимся, что используем последние версии spring-boot-starter-web и spring-boot-starter-thymeleaf.
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>3.4.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
<version>3.4.2</version>
</dependency>
</dependencies>
Для нового проекта нам лучше начать с Spring Initializr и включить следующие зависимости:
Spring Web: MVC-фреймворк и встроенный TomcatThymeleaf: Современный server-side Java template engine для web и standalone environments
5. Реализация File Upload
Давайте приступим к реализации нашего приложения.
5.1 Upload Form в Thymeleaf
Сначала мы создадим Thymeleaf template, в котором будет базовая form с file input и button для submit the form на наш endpoint.
Нам нужно создать файл file-upload.html в папке templates внутри папки resources приложения:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/mini.css/3.0.1/mini-default.min.css">
</head>
<body>
<div class="container">
<header>
<h2>File Upload</h2>
</header>
<main>
<form action="#" th:action="@{/upload}" method="post" enctype="multipart/form-data">
<div class="input-group vertical">
<input type="file" name="file" required>
<button type="submit">Upload</button>
</div>
</form>
<div th:if="${message}" class="alert"
th:classappend="${message.contains('Success') ? 'alert-success' : 'alert-error'}"
th:text="${message}"></div>
</main>
</div>
</body>
</html>
enctype=multipart/form-data, и это очень распространённая ошибка. Без него browser не будет отправлять файл как multipart data, и upload не будет работать так, как ожидается.После form мы добавили placeholder для отображения сообщений.
В нашем шаблоне мы используем mini.css, чтобы нам не приходилось заниматься стилизацией приложения, но при этом избежать стандартного внешнего вида браузера.
Вот и всё по стороне front-end.
5.2. Настройка хранилища
Далее нам нужно решить, где сохранять файлы, которые загружают наши пользователи. Давайте добавим свойство files.upload-dir в файл application.properties:
server.port=8090
files.upload-dir=./uploadedfiles
Это будет место, куда мы будем сохранять файлы.
5.3 Контроллер загрузки файлов
Теперь мы реализуем controller с именем FileUploadController, который будет предоставлять endpoint для отдачи созданного нами template в браузер:
@Controller
public class FileUploadController {
@GetMapping("/")
public String fileUpload() {
return "file-upload";
}
}
Нам просто нужно убедиться, что строка string, которую мы return, соответствует имени шаблона template, который мы создали ранее.
Далее, в том же контроллере мы создадим POST /upload endpoint для обработки запросов на загрузку файлов.
Этот endpoint будет использовать MultipartFile в качестве request parameter, чтобы получить файл. MultipartFile — это интерфейс, который расширяет InputStreamSource:
@Value("${files.upload-dir}")
private String uploadDir;
@PostMapping("/upload")
public String handleUpload(
@RequestParam("file") MultipartFile file,
RedirectAttributes redirectAttributes
) {
if (file.isEmpty()) {
redirectAttributes.addFlashAttribute("message", "Please select a file");
return "redirect:/";
}
try {
String filename = StringUtils.cleanPath(file.getOriginalFilename());
Path destination = Paths.get(uploadDir, filename);
Files.createDirectories(destination.getParent());
file.transferTo(destination);
redirectAttributes.addFlashAttribute("message", "Success: " + filename + " (" + file.getSize() + " bytes)");
return "redirect:/";
} catch (IOException e) {
redirectAttributes.addFlashAttribute("message", "Error: " + e.getMessage());
return "redirect:/";
}
}
Коротко: сначала мы получаем имя файла из входного объекта file. Затем мы создаём папки, необходимые для сохранения в destination, используя StringUtils.cleanPath, чтобы предотвратить simple path traversal attacks. Затем мы сохраняем входящие данные файла в destination и отправляем сообщение об успехе. В случае неудачи мы соответствующим образом уведомляем пользователя, чтобы он понимал, что происходит.
Также в контроллере мы внедрили директорию загрузки с помощью @Value и реализовали метод @PostMapping, который принимает MultipartFile вместе с объектом RedirectAttributes. Интерфейс RedirectAttributes в Spring MVC используется для передачи данных во время редиректа. В частности, мы использовали addFlashAttribute, чтобы временно сохранить сообщение в сессии, чтобы оно было доступно после редиректа при обработке шаблона, что позволяет отобразить корректную обратную связь.
StringUtils.cleanPath не следует полагаться в security context. Мы рекомендуем использовать другие механизмы для предотвращения path-traversal issues.Теперь, если мы запустим приложение с помощью mvn spring-boot:run и перейдём по адресу localhost:8090, мы должны увидеть нашу форму и сможем загружать небольшие файлы.
6. Обработка больших файлов
Теперь мы можем upload small files без каких-либо проблем. Но если file size превышает 1MB (the default limit), наше application выйдет из строя, и browser может отобразить error. Если мы хотим позволить users загружать более крупные files, мы можем настроить более высокий threshold.
6.1 Настройка Maximum File Size
Чтобы настроить максимально допустимый размер файла, мы изменяем свойства spring.servlet.multipart.max-file-size и spring.servlet.multipart.max-request-size в application.properties:
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=11MB
Этого само по себе недостаточно. Нам также нужно настроить наш servlet container так, чтобы он не прерывал соединение, если входящий запрос превысит его default maximum size value до того, как сработает Spring’s multipart handling.
Наше приложение на Spring Boot по умолчанию использует embedded Tomcat. Мы можем настроить его на приём более крупных (или фактически неограниченных по размеру) POST requests, чтобы проверки размера выполнялись исключительно Spring’s multipart configuration:
server.tomcat.max-http-form-post-size=-1
server.tomcat.max-swallow-size=-1
Теперь, если мы попытаемся загрузить файлы размером более 10MB, Spring MVC предотвратит загрузку, но поведение по умолчанию всё ещё может привести к необработанной странице ошибки (например, Whitelabel Error Page с ошибкой 500, связанной с MaxUploadSizeExceededException). Если мы хотим настроить это поведение, чтобы взаимодействие в UI было более плавным, мы можем добавить собственную обработку ошибок.
6.2 Пользовательская обработка ошибок
За кулисами Spring MVC выбрасывает MaxUploadSizeExceededException, когда файл превышает настроенный лимит размера. Мы можем создать @ControllerAdvice, чтобы глобально перехватывать MultipartException и корректно обрабатывать эти ошибки. Мы перехватываем MultipartException, потому что MaxUploadSizeExceededException расширяет его, что позволяет нам также обрабатывать другие возможные ошибки, связанные с multipart:
@ControllerAdvice
public class FileUploadExceptionHandler {
@ExceptionHandler(MultipartException.class)
public String handleSizeExceeded(RedirectAttributes attrs) {
attrs.addFlashAttribute("message",
"File exceeds size limit!");
return "redirect:/";
}
}
Настройка @ControllerAdvice позволяет централизованно обрабатывать exceptions в нескольких controllers и возвращать user-friendly error messages или выполнять redirect с соответствующей обратной связью.
7. Советы для Production Environments
Хотя в этом руководстве мы объясняем простой file upload, существует несколько дополнительных соображений для production environments, которые здесь не рассматриваются.
Во-первых, мы не ограничиваем type загружаемых файлов. Мы могли бы решить это с помощью простой file extension check или реализовать более глубокую валидацию, такую как проверка MIME type или сканирование на viruses and malware.
В production environments, где мы ожидаем large files, стоит рассмотреть использование streaming approach, чтобы не загружать весь файл в memory на server. Альтернативно, стоит рассмотреть использование chunked uploads с client-side, когда файл разбивается на меньшие части, загружается по отдельности и затем собирается на server-side.
8. Заключение
В этой статье мы научились реализовывать file uploads в Spring Boot, используя простой HTML form, Thymeleaf и Spring MVC controller. Мы также узнали, как configure limits для large files и как customize the error handling process для size exceptions.
Мы разместили полный код в репозитории на GitHub.


