← Назад к вопросам

Как бы реализовал возвращение файла в Spring MVC

2.0 Middle🔥 161 комментариев
#Spring Framework

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

# Возвращение файла в Spring MVC

Возвращение файла в Spring MVC — это частая задача при разработке REST API или веб-приложений. Рассмотрю несколько подходов и выберу оптимальный.

Основной подход через ResponseEntity с FileSystemResource

Это самый элегантный и рекомендуемый способ для большинства случаев:

@RestController
@RequestMapping("/api/files")
public class FileController {
    
    @GetMapping("/{filename}")
    public ResponseEntity<Resource> downloadFile(@PathVariable String filename) throws IOException {
        Path filePath = Paths.get("/data/files/" + filename);
        
        if (!Files.exists(filePath)) {
            return ResponseEntity.notFound().build();
        }
        
        Resource resource = new FileSystemResource(filePath);
        
        return ResponseEntity.ok()
                .header(HttpHeaders.CONTENT_DISPOSITION, 
                        "attachment; filename=\"" + filename + "\"")
                .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_OCTET_STREAM_VALUE)
                .contentLength(Files.size(filePath))
                .body(resource);
    }
}

Для потокового возвращения больших файлов

Если файл очень большой, используй InputStreamResource:

@GetMapping("/download/{filename}")
public ResponseEntity<InputStreamResource> streamLargeFile(@PathVariable String filename) throws IOException {
    Path filePath = Paths.get("/data/files/" + filename);
    
    InputStream inputStream = Files.newInputStream(filePath);
    InputStreamResource resource = new InputStreamResource(inputStream);
    
    return ResponseEntity.ok()
            .header(HttpHeaders.CONTENT_DISPOSITION, 
                    "attachment; filename=\"" + filename + "\"")
            .contentType(MediaType.APPLICATION_PDF) // или APPLICATION_OCTET_STREAM
            .body(resource);
}

ByteArrayResource для файлов из памяти

Если файл хранится в памяти или генерируется на лету:

@GetMapping("/generate-report")
public ResponseEntity<ByteArrayResource> generateReport() throws IOException {
    byte[] reportData = generateReportData(); // твоя логика
    
    ByteArrayResource resource = new ByteArrayResource(reportData);
    
    return ResponseEntity.ok()
            .header(HttpHeaders.CONTENT_DISPOSITION, 
                    "attachment; filename=\"report.pdf\"")
            .contentType(MediaType.APPLICATION_PDF)
            .contentLength(reportData.length)
            .body(resource);
}

Инлайновое отображение вместо скачивания

Иногда нужно открыть файл в браузере, а не скачать:

@GetMapping("/preview/{filename}")
public ResponseEntity<Resource> previewFile(@PathVariable String filename) throws IOException {
    Resource resource = new FileSystemResource(Paths.get("/data/files/" + filename));
    
    return ResponseEntity.ok()
            .header(HttpHeaders.CONTENT_DISPOSITION, 
                    "inline; filename=\"" + filename + "\"")
            .contentType(MediaType.IMAGE_JPEG) // или IMAGE_PNG
            .body(resource);
}

Практические соображения

Валидация пути: Всегда проверяй, что пользователь не может получить доступ к файлам вне разрешённой директории (path traversal attack):

Path basePath = Paths.get("/data/files").toRealPath();
Path filePath = basePath.resolve(filename).toRealPath();

if (!filePath.startsWith(basePath)) {
    throw new SecurityException("Access denied");
}

Правильное имя файла: Используй URLEncoder.encode() для имён с кириллицей и спецсимволами:

String encodedFilename = URLEncoder.encode(filename, StandardCharsets.UTF_8);
header(HttpHeaders.CONTENT_DISPOSITION, 
    "attachment; filename*=UTF-8\"\"" + encodedFilename + "\"");

Закрытие ресурсов: Spring автоматически закроет InputStream при отправке ответа, но если используешь классические параметры методов, убедись в try-with-resources.

Выбор подхода

  • FileSystemResource — для обычных файлов на диске (80% случаев)
  • InputStreamResource — для больших файлов или потоковых данных
  • ByteArrayResource — для данных в памяти или генерируемого контента
  • UrlResource — для удалённых файлов

В большинстве проектов первый вариант обеспечивает оптимальный баланс простоты, производительности и безопасности.