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」を使用する必要があります。