LoginSignup
82
91

More than 5 years have passed since last update.

Spring MVC ファイルのアップロード

Last updated at Posted at 2018-03-26

Spring MVCを使うと、Webブラウザからアップロードされたファイルを容易に受け取ることができます。
ここでは、基本的な実装方法から、少し掘り下げたファイルのアップロード方法を解説します。

基本的な実装方法

ファイルのアップロード画面を作成します。ここではテンプレートエンジンとしてThymeleafを使用します。
アップロードするファイルを選択するフィールドと、アップロードボタンがあるだけのシンプルな画面です。

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>File Upload</title>
</head>
<body>
    <form action="/sample/upload" th:action="@{/sample/upload}" method="post" enctype="multipart/form-data">
        <input type="file" name="upload_file" /><br />
        <button type="submit">upload</button>
    </form>
</body>
</html>

次にアップロードボタンをクリックしたときのsubmit先のコントローラーメソッドを作成します。

@PostMapping("upload")
public String upload(@RequestParam("upload_file") MultipartFile multipartFile, Model model) {
    model.addAttribute("originalFilename", multipartFile.getOriginalFilename());

    return "sample/result";
}

コントローラーの引数にMultipartFileを追加して、その引数に@RequestParamアノテーションを指定します。この@RequestParamアノテーションのvalue属性に、画面のアップロードするファイルを選択するフィールドのname属性の値を指定するところがポイントです。

<input type="file" name="upload_file" />
public String upload(@RequestParam("upload_file") MultipartFile multipartFile, ...) {

ファイルを選択するフィールドとのマッピング

先ほどのサンプルでは、画面のファイルを選択するフィールドのname属性と、コントローラーの引数のMultipartFileに指定した@RequestParamアノテーションでマッピングしました。
しかし、もう少し簡単にマッピングすることもできます。@RequestParamアノテーションを使用せず、画面のファイルを選択するフィールドのname属性と、コントローラーの引数のMultipartFileの変数名を同一にすることで、自動的にマッピングされます。

<input type="file" name="multipartFile" />
public String upload(MultipartFile multipartFile, ...) {

コントローラーの実装がシンプルになったことがわかります。

Formへのバインド

通常の入力フィールドと同様、ファイルを選択するフィールドもFormのメンバ変数にバインドすることができます。

まずは、Formクラスを作成します。lombokを使用して、アクセッサメソッドの実装を省略しています。

@Getter
@Setter
public class UploadForm {
    private MultipartFile multipartFile;
}

画面のファイルを選択するフィールドを、Formクラスのメンバ変数とマッピングさせるために、少し修正します。

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>File Upload</title>
</head>
<body>
    <form action="/sample/upload" th:action="@{/sample/upload}" method="post" enctype="multipart/form-data" th:object="${uploadForm}">
        <input type="file" th:field="*{multipartFile}" /><br />
        <button type="submit">upload</button>
    </form>
</body>
</html>

コントローラーはFormクラスを引数にするだけで、アップロードしたファイルを受け取ることができます。

@PostMapping("upload")
public String upload(UploadForm uploadForm, Model model) {
    model.addAttribute("originalFilename", uploadForm.getMultipartFile()
            .getOriginalFilename());

    return "sample/result";
}

アップロードファイルのバリデーション

通常の入力フィールドと同様、アップロードするファイルも検証が必要となるケースが多いでしょう。
受け取ったMultipartFileオブジェクトの検証処理をコントローラー内に実装してもよいのですが、検証処理と通常の処理は分離しておいたほうがソースの見通しがよくなります。

Bean Validationによる検証

Bean Validationにより、Formクラスのメンバ変数にアノテーションを指定することで、アップロードファイルの検証を実装してみます。
とはいえ、Bean Validationではアップロードファイル向けのバリデーションは提供されていません。

Bean ValidationはJSR(Java仕様要求)で定義されているものなので、Spring Frameworkの独自仕様であるMultipartFileによるファイルアップロードに対応していないのは仕方のないことでしょう。

Bean Validationでは、開発者が独自のカスタムバリデーションを実装する方法が用意されているので、この仕組みを利用してアップロードファイル向けのバリデーションを作成します。

ここでは、ファイルの指定を必須とする@FileRequiredアノテーションを作成します。

@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = FileRequiredValidator.class)
public @interface FileRequired {
    String message() default "{com.example.demo.validation.FileRequired.message}";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

    @Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @interface List {
        FileRequired[] value();
    }
}

次に、アノテーションに対応するバリデータークラスを作成します。検証処理自体は、このクラスに実装することになります。
アップロードファイルが指定されなかった場合、MultipartFileオブジェクトのgetOriginalFilename()メソッドで返却されるファイル名が空文字列("")になるので、このメソッドの返却値を検証に使用します。

public class FileRequiredValidator implements ConstraintValidator<FileRequired, MultipartFile> {
    @Override
    public void initialize(FileRequired constraint) {}

    @Override
    public boolean isValid(MultipartFile multipartFile, ConstraintValidatorContext context) {
        return multipartFile != null
                && !multipartFile.getOriginalFilename().isEmpty();
    }
}

ここまででカスタムバリデーションの実装は完了です。あとは、通常のBean Validationと同様の実装で、アップロードファイルの検証が可能です。
具体的には、Formのメンバ変数にバリデーションのアノテーションを指定します。

@FileRequired
private MultipartFile multipartFile;

次に、コントローラーのForm引数に@Validatedアノテーションを指定、BindingResultを追加し、バリデーションエラーがある場合の処理を追加します。

@PostMapping("upload")
public String upload(@Valid UploadForm uploadForm, BindingResult result, Model model) {
    if (result.hasErrors()) {
        return "sample/upload";
    }

通常のBean Validationと同様の実装で、アップロードファイルの検証ができました。
よくあるアップロードファイルの検証は、Bean Validationを用意しておくと検証処理の実装コストが軽減されます。
たとえば、次のようなBean Validationは再利用性が高いと思われます。

  • ファイルの指定必須。
  • ファイルが空(0バイト)でない。
  • ファイルの種類が特定のものである。

Spring Validatorによる検証

Bean Validationは再利用性が高い、汎用的な検証を実装して、Formクラスのメンバ変数ごとに設定しました。それに対し、Spring Validatorはフォーム単位で検証を実装します。
ここではSpring Validatorを使って、アップロードファイルの検証を実装します。

org.springframework.validation.Validatorインターフェースを実装したクラスに検証処理を実装します。

@Component
public class MultipartFileValidator implements Validator {
    @Override
    public boolean supports(Class<?> clazz) {
        return UploadForm.class.isAssignableFrom(clazz);
    }

    @Override
    public void validate(Object target, Errors errors) {
        UploadForm uploadForm = (UploadForm) target;
        if (uploadForm.getMultipartFile().getOriginalFilename().isEmpty()) {
            errors.rejectValue("multipartFile", "com.example.demo.validation.FileLinesLarge.message");
        }
    }
}

これだけでSpring Validatorの実装は完了です。あとは、コントローラークラスでSpring ValidatorでFormを検証するように設定します。

@Autowired
private MultipartFileValidator multipartFileValidator;

@InitBinder
private void initBinderFileBucket(WebDataBinder binder) {
    binder.setValidator(multipartFileValidator);
}

コントローラーのメソッドの変更は、Bean Validatorと同様です。

@PostMapping("upload")
public String upload(@Valid UploadForm uploadForm, BindingResult result, Model model) {
    if (result.hasErrors()) {
        return "sample/upload";
    }

ファイルアップロードの設定

Springでは、ファイルのアップロードに関するいくつかの設定項目を持っています。
ここでは、Spring Bootを使ったファイルアップロードの設定を解説します。

spring.servlet.multipart.max-file-size

アップロードするファイルごとの最大サイズを指定します。デフォルトは「1MB」です。数値の後に単位となる「MB」もしくは「KB」をつけて最大サイズを指定します。
1つのファイルの最大サイズであることに注意しましょう。例えば、「1MB」を指定した場合、「1MB」のファイルを10個アップロードすることはできても、「10MB」のファイルを1個アップロードすることはできません。

spring.servlet.multipart.max-file-size=50MB

このプロパティーのキー名はSpring Boot 2.0.0で変更されたものです。Spring Boot 2.0.0より前のバージョンを使用する場合は、以前のキー名「spring.http.multipart.max-file-size」を使用する必要があります。

spring.servlet.multipart.max-request-size

Multipartリクエスト全体の最大サイズを指定します。デフォルトは「10MB」です。数値の後に単位となる「MB」もしくは「KB」をつけて最大サイズを指定します。
この「最大サイズ」は、ファイルアップロードだけではなく、ほかの入力項目など、サーバに送信されるすべてのデータの合計の「最大サイズ」であることに注意しましょう。

spring.servlet.multipart.max-request-size=100MB

このプロパティーのキー名はSpring Boot 2.0.0で変更されたものです。Spring Boot 2.0.0より前のバージョンを使用する場合は、以前のキー名「spring.http.multipart.max-request-size」を使用する必要があります。

spring.servlet.multipart.enabled

Multipartリクエストを処理するSpringのデフォルト実装であるMultipartResolverを有効にするかどうかを指定します。デフォルトはtrueでMultipartResolverが有効です。
MultipartのファイルアップロードのインターフェースはRFC 1867で定められており、容易に交換が可能になっています。Springのデフォルト実装であるMultipartResolver以外の実装を使用する場合は、このプロパティーにfalseを設定します。

spring.servlet.multipart.enabled=false

このプロパティーのキー名はSpring Boot 2.0.0で変更されたものです。Spring Boot 2.0.0より前のバージョンを使用する場合は、以前のキー名「spring.http.multipart.enabled」を使用する必要があります。

spring.servlet.multipart.file-size-threshold

アップロードしたファイルがサーバーのディスクに書き込まれるファイルサイズの閾値です。このプロパティーの値以上のサイズのファイルをアップロードすると、そのファイルはサーバーのディスクに書き込まれます。このプロパティー未満のファイルサイズの場合、アップロードしたファイルはディスクには書き込まれず、メモリ上に保持されます。デフォルトは「0」です。数値の後に単位となる「MB」もしくは「KB」をつけて閾値を指定します。

spring.servlet.multipart.file-size-threshold=100MB

このプロパティーのキー名はSpring Boot 2.0.0で変更されたものです。Spring Boot 2.0.0より前のバージョンを使用する場合は、以前のキー名「spring.http.multipart.file-size-threshold」を使用する必要があります。

spring.servlet.multipart.location

アップロードしたファイルをサーバ上のディスクに中間ファイルとして書き込むときの場所(ディレクトリ)を指定します。デフォルトは未指定で、アプリケーションサーバー既定の一時ファイル用のディレクトリが使用されます。

spring.servlet.multipart.location=/upload/temp

このプロパティーのキー名はSpring Boot 2.0.0で変更されたものです。Spring Boot 2.0.0より前のバージョンを使用する場合は、以前のキー名「spring.http.multipart.location」を使用する必要があります。

spring.servlet.multipart.resolve-lazily

アップロードされたファイルの確認を遅延させるか否かを設定します。デフォルトはfalseでファイルの確認を遅延させません。
MultipartExceptionをハンドリングしたい場合にtrueを設定します。
遅延させることで、アップロードしたファイルのサイズが大きかったときに発生するMultipartExceptionの発生タイミングが変わり、コントローラーの@ExceptionHandlerで補足できるようになります。

spring.servlet.multipart.resolve-lazily=true

このプロパティーのキー名はSpring Boot 2.0.0で変更されたものです。Spring Boot 2.0.0より前のバージョンを使用する場合は、以前のキー名「spring.http.multipart.resolve-lazily」を使用する必要があります。

82
91
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
82
91