ファイルのアップロード

このガイドでは、HTTP マルチパートファイルアップロードを受信できるサーバーアプリケーションを作成するプロセスについて説明します。

構築するもの

ファイルのアップロードを受け入れる Spring Boot Web アプリケーションを作成します。また、テストファイルをアップロードするための単純な HTML インターフェースを構築します。

必要なもの

本ガイドの完成までの流れ

ほとんどの Spring 入門ガイドと同様に、最初から始めて各ステップを完了するか、すでに慣れている場合は基本的なセットアップステップをバイパスできます。いずれにしても、最終的に動作するコードになります。

最初から始めるには、Spring Initializr から開始に進みます。

基本をスキップするには、次の手順を実行します。

完了したときは、gs-uploading-files/complete のコードに対して結果を確認できます。

Spring Initializr から開始

IDE を使用する場合はプロジェクト作成ウィザードを使用します。IDE を使用せずにコマンドラインなどで開発する場合は、この事前に初期化されたプロジェクトからプロジェクトを ZIP ファイルとしてダウンロードできます。このプロジェクトは、このチュートリアルの例に合うように構成されています。

プロジェクトを手動で初期化するには:

  1. IDE のメニューまたはブラウザーから Spring Initializr を開きます。アプリケーションに必要なすべての依存関係を取り込み、ほとんどのセットアップを行います。

  2. Gradle または Maven のいずれかと、使用する言語を選択してください。

  3. 依存関係をクリックし、Spring Web と Thymeleaf を選択します。

  4. 生成をクリックします。

  5. 結果の ZIP ファイルをダウンロードします。これは、選択して構成された Web アプリケーションのアーカイブです。

Eclipse や IntelliJ のような IDE は新規プロジェクト作成ウィザードから Spring Initializr の機能が使用できるため、手動での ZIP ファイルのダウンロードやインポートは不要です。
プロジェクトを GitHub からフォークして、IDE または他のエディターで開くこともできます。

アプリケーションクラスを作成する

Spring Boot MVC アプリケーションを起動するには、まずスターターが必要です。このサンプルでは、spring-boot-starter-thymeleaf と spring-boot-starter-webmvc がすでに依存関係として追加されています。サーブレットコンテナーを使用してファイルをアップロードするには、MultipartConfigElement クラス(web.xml では <multipart-config>)を登録する必要があります。Spring Boot のおかげで、すべてが自動構成されます!

このアプリケーションを開始するために必要なのは、次の UploadingFilesApplication クラスだけです。

Java
package com.example.uploadingfiles;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class UploadingFilesApplication {

  public static void main(String[] args) {
    SpringApplication.run(UploadingFilesApplication.class, args);
  }

}
Kotlin
package com.example.uploadingfiles

import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication

@SpringBootApplication
class UploadingFilesApplication

fun main(args: Array<String>) {
	runApplication<UploadingFilesApplication>(*args)
}

Spring MVC の自動構成の一環として、Spring Boot は MultipartConfigElement Bean を作成し、ファイルのアップロードの準備を整えます。

ファイルアップロードコントローラーを作成する

初期アプリケーションには、アップロードされたファイルのディスクへの保存と読み込みを処理するクラスがいくつかすでに含まれています。これらはすべて com.example.uploadingfiles.storage パッケージにあります。新しい FileUploadController ではこれらを使用します。以下のリストはファイルアップロードコントローラーを示しています。

Java
package com.example.uploadingfiles;

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 com.example.uploadingfiles.storage.StorageFileNotFoundException;
import com.example.uploadingfiles.storage.StorageService;

@Controller
public class FileUploadController {

  private final StorageService storageService;

  @Autowired
  public FileUploadController(StorageService storageService) {
    this.storageService = storageService;
  }

  @GetMapping("/")
  public String listUploadedFiles(Model model) {

    model.addAttribute("files", storageService.loadAll().map(
        path -> MvcUriComponentsBuilder.fromMethodName(FileUploadController.class,
            "serveFile", path.getFileName().toString()).build().toUri().toString())
        .collect(Collectors.toList()));

    return "uploadForm";
  }

  @GetMapping("/files/{filename:.+}")
  @ResponseBody
  public ResponseEntity<Resource> serveFile(@PathVariable String filename) {

    Resource file = storageService.loadAsResource(filename);

    if (file == null)
      return ResponseEntity.notFound().build();

    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();
  }

}
Kotlin
package com.example.uploadingfiles

import com.example.uploadingfiles.storage.StorageFileNotFoundException
import com.example.uploadingfiles.storage.StorageService
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.*
import org.springframework.web.multipart.MultipartFile
import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder
import org.springframework.web.servlet.mvc.support.RedirectAttributes
import java.util.stream.Collectors

@Controller
class FileUploadController(private val storageService: StorageService) {

  @GetMapping("/")
  fun listUploadedFiles(model: Model): String {
    model.addAttribute("files", storageService.loadAll().map { path ->
      MvcUriComponentsBuilder.fromMethodName(
        FileUploadController::class.java,
        "serveFile", path.fileName.toString()
      ).build().toUri().toString()
    }.collect(Collectors.toList()))

    return "uploadForm"
  }

  @GetMapping("/files/{filename:.+}")
  @ResponseBody
  fun serveFile(@PathVariable filename: String): ResponseEntity<Resource> {
    val file = storageService.loadAsResource(filename)
      ?: return ResponseEntity.notFound().build()

    return ResponseEntity.ok().header(
      HttpHeaders.CONTENT_DISPOSITION,
      "attachment; filename=\"${file.filename}\""
    ).body(file)
  }

  @PostMapping("/")
  fun handleFileUpload(@RequestParam("file") file: MultipartFile, 
             redirectAttributes: RedirectAttributes): String {
    storageService.store(file)
    redirectAttributes.addFlashAttribute(
      "message",
      "You successfully uploaded ${file.originalFilename}!")

    return "redirect:/"
  }

  @ExceptionHandler(StorageFileNotFoundException::class)
  fun handleStorageFileNotFound(exc: StorageFileNotFoundException): ResponseEntity<*> {
    return ResponseEntity.notFound().build<Any>()
  }
}

FileUploadController クラスには @Controller のアノテーションが付けられているため、Spring MVC はそれをピックアップしてルートを探すことができます。各メソッドは @GetMapping または @PostMapping でタグ付けされ、パスと HTTP アクションを特定のコントローラーアクションに結び付けます。

この場合:

  • GET /: StorageService からアップロードされたファイルの現在のリストを検索し、Thymeleaf テンプレートにロードします。MvcUriComponentsBuilder を使用して、実際のリソースへのリンクを計算します。

  • GET /files/{filename}: リソース(存在する場合)をロードし、Content-Disposition レスポンスヘッダーを使用してダウンロードするためにブラウザーに送信します。

  • POST /: マルチパートメッセージ file を処理し、保存のために StorageService に渡します。

本番シナリオでは、一時的な場所、データベース、NoSQL ストア(Mongo の GridFS (英語) など)にファイルを保存する可能性が高くなります。アプリケーションのファイルシステムにコンテンツをロードしないことをお勧めします。

コントローラーがストレージ層(ファイルシステムなど)とやり取りできるようにするには、StorageService を提供する必要があります。次のリストにそのインターフェースを示します。

Java
package com.example.uploadingfiles.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();

}
Kotlin
package com.example.uploadingfiles.storage

import org.springframework.core.io.Resource
import org.springframework.web.multipart.MultipartFile
import java.nio.file.Path
import java.util.stream.Stream

interface StorageService {

  fun init()

  fun store(file: MultipartFile)

  fun loadAll(): Stream<Path>

  fun load(filename: String): Path

  fun loadAsResource(filename: String): Resource?

  fun deleteAll()

}

ストレージサービスをサポートするには、次の 4 つのクラスも必要です。

Java
package com.example.uploadingfiles.storage;

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties("storage")
public class StorageProperties {

  /**
   * Folder location for storing files
   */
  private String location = "upload-dir";

  public String getLocation() {
    return location;
  }

  public void setLocation(String location) {
    this.location = location;
  }

}
Kotlin
package com.example.uploadingfiles.storage

import org.springframework.boot.context.properties.ConfigurationProperties

@ConfigurationProperties("storage")
data class StorageProperties(
  /**
   * Folder location for storing files
   */
  var location: String = "upload-dir"
)
Java
package com.example.uploadingfiles.storage;

public class StorageException extends RuntimeException {

  public StorageException(String message) {
    super(message);
  }

  public StorageException(String message, Throwable cause) {
    super(message, cause);
  }
}
Kotlin
package com.example.uploadingfiles.storage

open class StorageException(message: String, cause: Throwable? = null) :
  RuntimeException(message, cause)
Java
package com.example.uploadingfiles.storage;

public class StorageFileNotFoundException extends StorageException {

  public StorageFileNotFoundException(String message) {
    super(message);
  }

  public StorageFileNotFoundException(String message, Throwable cause) {
    super(message, cause);
  }
}
Kotlin
package com.example.uploadingfiles.storage

class StorageFileNotFoundException(message: String, cause: Throwable? = null) :
  StorageException(message, cause)
Java
package com.example.uploadingfiles.storage;

import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.stream.Stream;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.stereotype.Service;
import org.springframework.util.FileSystemUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

@Service
public class FileSystemStorageService implements StorageService {

  private final Path rootLocation;

  @Autowired
  public FileSystemStorageService(StorageProperties properties) {
        
        if(properties.getLocation().trim().length() == 0){
            throw new StorageException("File upload location can not be Empty."); 
        }

    this.rootLocation = Paths.get(properties.getLocation());
  }

  @Override
  public void store(MultipartFile file) {
    try {
      if (file.isEmpty()) {
        throw new StorageException("Failed to store empty file.");
      }
      Path destinationFile = this.rootLocation.resolve(
          Paths.get(file.getOriginalFilename()))
          .normalize().toAbsolutePath();
      if (!destinationFile.getParent().equals(this.rootLocation.toAbsolutePath())) {
        // This is a security check
        throw new StorageException(
            "Cannot store file outside current directory.");
      }
      try (InputStream inputStream = file.getInputStream()) {
        Files.copy(inputStream, destinationFile,
          StandardCopyOption.REPLACE_EXISTING);
      }
    }
    catch (IOException e) {
      throw new StorageException("Failed to store file.", e);
    }
  }

  @Override
  public Stream<Path> loadAll() {
    try {
      return Files.walk(this.rootLocation, 1)
        .filter(path -> !path.equals(this.rootLocation))
        .map(this.rootLocation::relativize);
    }
    catch (IOException e) {
      throw new StorageException("Failed to read stored files", e);
    }

  }

  @Override
  public Path load(String filename) {
    return rootLocation.resolve(filename);
  }

  @Override
  public Resource loadAsResource(String filename) {
    try {
      Path file = load(filename);
      Resource resource = new UrlResource(file.toUri());
      if (resource.exists() || resource.isReadable()) {
        return resource;
      }
      else {
        throw new StorageFileNotFoundException(
            "Could not read file: " + filename);

      }
    }
    catch (MalformedURLException e) {
      throw new StorageFileNotFoundException("Could not read file: " + filename, e);
    }
  }

  @Override
  public void deleteAll() {
    FileSystemUtils.deleteRecursively(rootLocation.toFile());
  }

  @Override
  public void init() {
    try {
      Files.createDirectories(rootLocation);
    }
    catch (IOException e) {
      throw new StorageException("Could not initialize storage", e);
    }
  }
}
Kotlin
package com.example.uploadingfiles.storage

import org.springframework.beans.factory.annotation.Autowired
import org.springframework.core.io.Resource
import org.springframework.core.io.UrlResource
import org.springframework.stereotype.Service
import org.springframework.util.FileSystemUtils
import org.springframework.web.multipart.MultipartFile
import java.io.IOException
import java.net.MalformedURLException
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import java.nio.file.StandardCopyOption
import java.util.stream.Stream

@Service
class FileSystemStorageService(properties: StorageProperties) : StorageService {

  private val rootLocation: Path

  init {
    if (properties.location.trim().isEmpty()) {
      throw StorageException("File upload location can not be Empty.")
    }
    this.rootLocation = Paths.get(properties.location)
  }

  override fun store(file: MultipartFile) {
    try {
      if (file.isEmpty) {
        throw StorageException("Failed to store empty file.")
      }
      val filename = file.originalFilename
        ?: throw StorageException("Failed to store file with null filename")

      val destinationFile = rootLocation.resolve(filename)
        .normalize().toAbsolutePath()

      // Security check - prevent directory traversal
      if (!destinationFile.parent.equals(rootLocation.toAbsolutePath())) {
        throw StorageException("Cannot store file outside current directory.")
      }

      file.inputStream.use { inputStream ->
        Files.copy(inputStream, destinationFile, StandardCopyOption.REPLACE_EXISTING)
      }
    } catch (e: IOException) {
      throw StorageException("Failed to store file ${file.originalFilename}", e)
    }
  }

  override fun loadAll(): Stream<Path> {
    return try {
      Files.walk(rootLocation, 1)
        .filter { path -> path != rootLocation }
        .map { path -> rootLocation.relativize(path) }
    } catch (e: IOException) {
      throw StorageException("Failed to read stored files", e)
    }
  }

  override fun load(filename: String): Path {
    return rootLocation.resolve(filename)
  }

  override fun loadAsResource(filename: String): Resource? {
    return try {
      val file = load(filename)
      val resource = UrlResource(file.toUri())
      if (resource.exists() || resource.isReadable) {
        resource
      } else {
        throw StorageFileNotFoundException("Could not read file: $filename")
      }
    } catch (e: MalformedURLException) {
      throw StorageFileNotFoundException("Could not read file: $filename", e)
    }
  }

  override fun deleteAll() {
    FileSystemUtils.deleteRecursively(rootLocation.toFile())
  }

  override fun init() {
    try {
      Files.createDirectories(rootLocation)
    } catch (e: IOException) {
      throw StorageException("Could not initialize storage", e)
    }
  }
}

HTML テンプレートの作成

次の Thymeleaf テンプレート(src/main/resources/templates/uploadForm.html から)は、ファイルをアップロードし、アップロードされたものを表示する方法の例を示しています。

<html xmlns:th="https://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>

このテンプレートには 3 つの部分があります。

ファイルアップロード制限の調整

ファイルアップロードを設定する際に、ファイルサイズに制限を設けることが有効な場合があります。5GB のファイルアップロードを処理しようとするとどうなるか想像してみてください。Spring Boot では、いくつかのプロパティ設定を行うことで、自動構成された MultipartConfigElement を微調整できます。

次のプロパティを既存のプロパティ設定(src/main/resources/application.properties 内)に追加します。

spring.servlet.multipart.max-file-size=128KB
spring.servlet.multipart.max-request-size=128KB

マルチパート設定は次のように制限されています。

  • spring.servlet.multipart.max-file-size は 128 KB に設定されており、ファイルの合計サイズは 128 KB を超えることはできません。

  • spring.servlet.multipart.max-request-size は 128KB に設定されます。つまり、multipart/form-data の合計リクエストサイズは 128KB を超えることはできません。

アプリケーションを更新する

ファイルをアップロードするターゲットフォルダーが必要なので、Spring Initializr が作成した基本の UploadingFilesApplication クラスを拡張し、起動時にそのフォルダーを削除して再作成する Boot CommandLineRunner クラスを追加する必要があります。その方法を以下に示します。

Java
package com.example.uploadingfiles;

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 com.example.uploadingfiles.storage.StorageProperties;
import com.example.uploadingfiles.storage.StorageService;

@SpringBootApplication
@EnableConfigurationProperties(StorageProperties.class)
public class UploadingFilesApplication {

  public static void main(String[] args) {
    SpringApplication.run(UploadingFilesApplication.class, args);
  }

  @Bean
  CommandLineRunner init(StorageService storageService) {
    return (args) -> {
      storageService.deleteAll();
      storageService.init();
    };
  }
}
Kotlin
package com.example.uploadingfiles

import com.example.uploadingfiles.storage.StorageProperties
import com.example.uploadingfiles.storage.StorageService
import org.springframework.boot.CommandLineRunner
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.boot.runApplication
import org.springframework.context.annotation.Bean

@SpringBootApplication
@EnableConfigurationProperties(StorageProperties::class)
class UploadingFilesApplication {

	@Bean
	fun init(storageService: StorageService) = CommandLineRunner {
		storageService.deleteAll()
		storageService.init()
	}
}

fun main(args: Array<String>) {
	runApplication<UploadingFilesApplication>(*args)
}

アプリケーションの実行

@SpringBootApplication は、次のすべてを追加する便利なアノテーションです。

  • @Configuration: アプリケーションコンテキストの Bean 定義のソースとしてクラスにタグを付けます。

  • @EnableAutoConfiguration: クラスパス設定、他の Bean、さまざまなプロパティ設定に基づいて Bean の追加を開始するよう Spring Boot に指示します。例: spring-webmvc がクラスパスにある場合、このアノテーションはアプリケーションに Web アプリケーションとしてフラグを立て、DispatcherServlet のセットアップなどの主要な動作をアクティブにします。

  • @ComponentScan: Spring に、com/example パッケージ内の他のコンポーネント、構成、サービスを探して、コントローラーを検出させるように指示します。

main() メソッドは、Spring Boot の SpringApplication.run() メソッドを使用してアプリケーションを起動します。XML が 1 行もないことに気付きましたか? web.xml ファイルもありません。この Web アプリケーションは 100% 純粋な Java であり、接続機能やインフラストラクチャの構成に対処する必要はありませんでした。

実行可能 JAR を構築する

コマンドラインから Gradle または Maven を使用してアプリケーションを実行できます。必要なすべての依存関係、クラス、リソースを含む単一の実行可能 JAR ファイルを構築して実行することもできます。実行可能な jar を構築すると、開発ライフサイクル全体、さまざまな環境などで、アプリケーションとしてサービスを簡単に提供、バージョン管理、デプロイできます。

Gradle を使用する場合、./gradlew bootRun を使用してアプリケーションを実行できます。または、次のように、./gradlew build を使用して JAR ファイルをビルドしてから、JAR ファイルを実行できます。

java -jar build/libs/gs-uploading-files-0.0.1-SNAPSHOT.jar

Maven を使用する場合、./mvnw spring-boot:run を使用してアプリケーションを実行できます。または、次のように、./mvnw clean package で JAR ファイルをビルドしてから、JAR ファイルを実行できます。

java -jar target/gs-uploading-files-0.0.1-SNAPSHOT.jar

ファイルのアップロードを受信するサーバー側のピースを実行します。ロギング出力が表示されます。サービスは数秒以内に起動して実行されるはずです。

サーバーが稼働している状態で、ブラウザーを開いて http://localhost:8080/ にアクセスし、アップロードフォームを表示する必要があります。(小さな)ファイルを選択してアップロードを押します。コントローラーから成功ページが表示されるはずです。大きすぎるファイルを選択すると、いエラーページが表示されます。

ブラウザーウィンドウに次のような行が表示されます。

「< ファイル名> のアップロードに成功しました!」

アプリケーションをテストする

アプリケーションでこの特定の機能をテストする方法は複数あります。次のリストは、サーブレットコンテナーを起動せずに MockMvc を使用する例を示しています。

Java
package com.example.uploadingfiles;

import java.nio.file.Paths;
import java.util.stream.Stream;

import org.hamcrest.Matchers;
import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
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.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart;
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 com.example.uploadingfiles.storage.StorageFileNotFoundException;
import com.example.uploadingfiles.storage.StorageService;

@SpringBootTest
@AutoConfigureMockMvc
public class FileUploadTests {

  @Autowired
  private MockMvc mvc;

  @MockitoBean
  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(multipart("/").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());
  }

}
Kotlin
package com.example.uploadingfiles

import com.example.uploadingfiles.storage.StorageFileNotFoundException
import com.example.uploadingfiles.storage.StorageService
import org.hamcrest.Matchers
import org.junit.jupiter.api.Test
import org.mockito.BDDMockito.given
import org.mockito.BDDMockito.then
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc
import org.springframework.mock.web.MockMultipartFile
import org.springframework.test.context.bean.override.mockito.MockitoBean
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.*
import java.nio.file.Paths
import java.util.stream.Stream

@SpringBootTest
@AutoConfigureMockMvc
class FileUploadTests(@Autowired private val mvc: MockMvc) {

  @MockitoBean
  private lateinit var storageService: StorageService

  @Test
  fun shouldListAllFiles() {
    given(storageService.loadAll())
      .willReturn(Stream.of(Paths.get("first.txt"), Paths.get("second.txt")))

    mvc.perform(get("/"))
      .andExpect(status().isOk)
      .andExpect(
        model().attribute(
          "files",
          Matchers.contains(
            "http://localhost/files/first.txt",
            "http://localhost/files/second.txt"
          )
        )
      )
  }

  @Test
  fun shouldSaveUploadedFile() {
    val multipartFile = MockMultipartFile(
      "file",
      "test.txt",
      "text/plain",
      "Spring Framework".toByteArray()
    )

    mvc.perform(multipart("/").file(multipartFile))
      .andExpect(status().isFound)
      .andExpect(header().string("Location", "/"))

    then(storageService).should().store(multipartFile)
  }

  @Test
  fun should404WhenMissingFile() {
    given(storageService.loadAsResource("test.txt"))
      .willThrow(StorageFileNotFoundException::class.java)

    mvc.perform(get("/files/test.txt"))
      .andExpect(status().isNotFound)
  }
}

これらのテストでは、さまざまなモックを使用して、コントローラーおよび StorageService だけでなく、MockMultipartFile を使用してサーブレットコンテナー自体との対話を設定します。

統合テストの例については、FileUploadIntegrationTests クラスを参照してください。

要約

おめでとう! Spring を使用してファイルのアップロードを処理する Web アプリケーションを作成しました。

関連事項

次のガイドも役立つかもしれません:

新しいガイドを作成したり、既存のガイドに貢献したいですか? 投稿ガイドラインを参照してください [GitHub] (英語) 。

すべてのガイドは、コード用の ASLv2 ライセンス、およびドキュメント用の帰属表示、NoDerivatives クリエイティブコモンズライセンス (英語) でリリースされています。

コードを入手する