コンピュータクワガタ

かっぱのかっぱによるコンピュータ関連のサイトです

Spring MVC 4.1 No. 025 ファイルのアップロード

Java EE 6、Servlet 3.0から標準でファイルのアップロードができるようになりました。今回はServlet 3.0のファイルアップロードを行います。

ファイルアップロードする場合には、web.xmlにアップロードの設定が必要です(もしくはServletアノテーションですが、DispathcerServletなのでweb.xmlになります)。web.xmlのDispatcherServletの設定を次のようにします。

<servlet>
 <servlet-name>dispatcher</servlet-name>
 <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
 <init-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>/WEB-INF/spring/spring-context.xml</param-value>
 </init-param>
 <load-on-startup>1</load-on-startup>
 <multipart-config>
  <location>/tmp</location>
  <max-file-size>1000000</max-file-size>
  <max-request-size>1000000</max-request-size>
  <file-size-threshold>10000</file-size-threshold>
 </multipart-config>
</servlet>

また、Springの設定も必要になります。Serlvet 3.0のファイルアップロードを使用する設定になります。

<bean id="multipartResolver"
 class="org.springframework.web.multipart.support.StandardServletMultipartResolver">
</bean>

Controllerは以下のようにします。受け取る際には、@RequestPartアノテーションを付けた引数で受け取ります。

package com.example.spring.controller.c025;

import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;

@Controller
@RequestMapping("/c025")
public class C025Controller {
    @RequestMapping("/uploadForm")
    public String uploadForm() {
        return "c025/uploadForm";
    }

    @RequestMapping(value = "/uploadRecv", method = RequestMethod.POST)
    public String uploadRecv(@RequestParam String test,
            @RequestParam MultipartFile file, Model model) throws IOException {
        model.addAttribute("test", test);
        Path path = Paths.get(System.getProperty("java.io.tmpdir"),
                file.getOriginalFilename());
        file.transferTo(path.toFile());
        model.addAttribute("fileName", path);

        return "c025/uploadRecv";
    }
}

今回はファイルを一時領域に保管し、そのファイルのパスをtestパラメータと一緒にmodelに格納します。
ファイルを送信するuploadForm.jspです。

<%@page contentType="text/html; charset=utf-8" %><%--
--%><!DOCTYPE html>
<html>
 <head>
  <meta charset="utf-8">
  <title>サンプル</title>
 </head>
 <body>
  <form action="uploadRecv" method="post" enctype="multipart/form-data">
   <input type="text" name="test"><br>
   <input type="file" name="file"><br>
   <input type="submit" value="送信">
  </form>
 </body>
</html>

結果表示のuploadRecv.jspです。日本語ファイル名も問題なく表示できます。

<%@page contentType="text/html; charset=utf-8" %><%--
--%><!DOCTYPE html>
<html>
 <head>
  <meta charset="utf-8">
  <title>サンプル</title>
 </head>
 <body>
アップロードされました。<br>
ファイル名は<c:out value="${fileName}" /><br>
送信されたtestパラメータは<c:out value="${test}" />
 </body>
</html>

確認用のテストケースは次のとおりです。ファイルアップロードのテストも標準でサポートされています。

package com.example.spring.controller.c025;

import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.*;

import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.Paths;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.web.context.WebApplicationContext;

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration(locations = {
    "file:src/main/webapp/WEB-INF/spring/spring-context.xml" })
public class C025ControllerTest {
    @Autowired
    private WebApplicationContext wac;

    private MockMvc mockMvc;

    @Before
    public void setup() {
        mockMvc = webAppContextSetup(wac).build();
    }

    @Test
    public void uploadFormのGET() throws Exception {
        mockMvc.perform(get("/c025/uploadForm")).andExpect(status().isOk())
                .andExpect(view().name("c025/uploadForm"));
    }

    @Test
    public void uploadRecvのPOST() throws Exception {
        byte[] fileImage = null;
        Path path = Paths.get("src/test/resources/c025/kappa.png");
        if (Files.exists(path, LinkOption.NOFOLLOW_LINKS)) {
            fileImage = Files.readAllBytes(path);
        }

        // ローカルのファイル名もエミュレーションできる。
        String fileName = "画像.png";
        MockMultipartFile file = new MockMultipartFile("file", fileName, null,
                fileImage);
        // アップロードされるファイルのパス
        Path actualFile = Paths.get(System.getProperty("java.io.tmpdir"),
                "画像.png");

        mockMvc.perform(
                fileUpload("/c025/uploadRecv").file(file).param("test",
                        "testParam")).andExpect(status().isOk())
                .andExpect(view().name("c025/uploadRecv"))
                .andExpect(model().attribute("test", is("testParam")))
                .andExpect(model().attribute("fileName", actualFile));

        // 画像が保管されていることを確認する
        assertThat(Files.exists(actualFile, LinkOption.NOFOLLOW_LINKS),
                is(true));
        byte[] actualImage = Files.readAllBytes(actualFile);
        assertThat(actualImage, is(equalTo(fileImage)));
        // アップロードされたファイルの削除
        Files.delete(actualFile);
    }
}

ソースは https://github.com/kuwalab/spring-mvc41 にあります。タグ025が今回のサンプルです。

まとめ http://kuwalab.hatenablog.jp/entry/spring
最初 http://kuwalab.hatenablog.jp/entry/spring_mvc41/001
前回 http://kuwalab.hatenablog.jp/entry/spring_mvc41/024
次回 http://kuwalab.hatenablog.jp/entry/spring_mvc41/026