4.11. ファイルダウンロード


4.11.1. Overview

本節では、Springでクライアントにサーバからファイルをダウンロードする機能について説明する。
Spring MVCのViewで、ファイルのレンダリングを行うことを推奨する。

Note

コントローラクラスで、ファイルレンダリングのロジックを持たせることは推奨しない。

理由としては、コントローラの役割から逸脱するためである。 また、コントローラから分離することで、Viewの入れ替えが、容易にできる。

ファイルのダウンロード処理の概要を、以下に示す。

  1. DispatchServletは、コントローラへファイルダウンロードのリクエストを送信する。
  2. コントローラは、ファイル表示の情報を取得する。
  3. コントローラは、Viewを選択する。
  4. ファイルレンダリングは、Viewで行われる。
SpringベースのWebアプリケーションで、ファイルをレンダリングするため、本ガイドラインでは、カスタムビューを実装することを推奨する。
Spring フレームワークでは、カスタムビューの実装にorg.springframework.web.servlet.Viewインタフェースを提供している。

PDFファイルの場合
Springから提供されているorg.springframework.web.servlet.view.document.AbstractPdfViewクラスは、modelの情報を用いてPDFファイルをレンダリングするときに、サブクラスとして利用するクラスである。

Excelファイルの場合
Springから提供されているorg.springframework.web.servlet.view.document.AbstractXlsxViewクラスは、modelの情報を用いてExcelファイルをレンダリングするときに、サブクラスとして利用するクラスである。

Spring では上記以外にも、いろいろなViewの実装を提供している。
Viewの技術詳細は、Spring Framework Documentation -View Technologies-を参照されたい。
共通ライブラリから提供している、org.terasoluna.gfw.web.download.AbstractFileDownloadViewは、任意のファイルをダウンロードするために使用する抽象クラスである。
PDFやExcel形式以外のファイルをレンダリングする際に、本クラスをサブクラスに定義する。

Tip

ファイルダウンロード機能を提供する際には、ディレクトリトラバーサル攻撃への対策が必要な場合がある。

ディレクトリトラバーサル攻撃については、ディレクトリトラバーサル攻撃 を参照すること。


4.11.2. How to use

4.11.2.1. PDFファイルのダウンロード

PDFファイルのレンダリングには、Springから提供されている、org.springframework.web.servlet.view.document.AbstractPdfViewを継承したクラスを作成する必要がある。
コントローラでPDFダウンロードを実装するための手順は、以下で説明する。

4.11.2.1.1. カスタムViewの実装

AbstractPdfViewを継承したクラスの実装例

@Component  // (1)
public class SamplePdfView extends AbstractPdfView {  // (2)

    @Override
    protected void buildPdfDocument(Map<String, Object> model,
            Document document, PdfWriter writer, HttpServletRequest request,
            HttpServletResponse response) throws Exception {  // (3)

        document.add(new Paragraph(model.get("serverTime").toString()));
    }
}
項番 説明
(1)
本例では、@Componentアノテーションを使用して、component-scanの対象としている。
後述する、org.springframework.web.servlet.view.BeanNameViewResolverの対象とすることができる。
(2)
AbstractPdfViewを継承する。
(3)
buildPdfDocumentメソッドを実装する。
AbstractPdfViewは、PDFのレンダリングに、OpenPDFを利用している。
そのため、Mavenのpom.xmlに OpenPDFの定義を追加する必要がある。
<dependencies>
    <!-- omitted -->
    <dependency>
        <groupId>com.github.librepdf</groupId>
        <artifactId>openpdf</artifactId>
    </dependency>
</dependencies>

Note

上記設定例は、依存ライブラリのバージョンを親プロジェクトである terasoluna-gfw-parent で管理する前提であるため、pom.xmlでのバージョンの指定は不要である。


4.11.2.1.2. ViewResolverの定義

org.springframework.web.servlet.view.BeanNameViewResolverとは、Springのコンテキストで管理されたbean名を用いて実行するViewを選択するクラスである。

BeanNameViewResolverを使用する際は、通常使用するThymeleaf用のViewResolver(ThymeleafViewResolver)より先にBeanNameViewResolverが実行されるように定義する事を推奨する。

Note

Spring FrameworkはさまざまなViewResolverを提供しており、複数のViewResolverをチェーンすることができる。

そのため、特定の状況では、意図しないViewが選択されてしまうことがある。

この動作は、<mvc:view-resolvers>要素の子要素に、優先したいViewResolverを上から順に定義する事で防ぐことができる。


bean定義ファイル

<mvc:view-resolvers>
    <mvc:bean-name /> <!-- (1) (2) -->
    <bean class="org.thymeleaf.spring6.view.ThymeleafViewResolver">
        <!-- omitted -->
    </bean>
</mvc:view-resolvers>
項番 説明
(1)
<mvc:bean-name>要素を使用して、BeanNameViewResolverを定義する。
(2)
<mvc:bean-name>要素を先頭に定義し、通常使用するViewResolver(Thymeleaf用のViewResolver)より優先度を高くする。

4.11.2.1.3. コントローラでのViewの指定

BeanNameViewResolverにより、コントローラで”samplePdfView”を返却することで、Springのコンテキストで管理されたBeanIDにより、”samplePdfView”であるViewが使用される。

Javaソースコード

@GetMapping(value = "home", params= "pdf")
public String homePdf(Model model) {
    model.addAttribute("serverTime", new Date());
    return "samplePdfView";   // (1)
}
項番 説明
(1)
“samplePdfView” をメソッドの戻り値として返却することで、
Springのコンテキストで管理された、SamplePdfViewクラスが実行される。
上記の手順を実行した後、以下に示すようなPDFを開くことができる。
FILEDOWNLOAD PDF

Picture - FileDownload PDF


4.11.2.2. Excelファイルのダウンロード

EXCELファイルのレンダリングには、Springから提供されている、org.springframework.web.servlet.view.document.AbstractXlsxViewを継承したクラスを作成する必要がある。
コントローラでEXCELファイルをダウンロードさせるための実装手順は、以下で説明する。

4.11.2.2.1. カスタムViewの実装

AbstractXlsxViewを継承したクラスの実装例

@Component  // (1)
public class SampleExcelView extends AbstractXlsxView {  // (2)

    @Override
    protected void buildExcelDocument(Map<String, Object> model,
            Workbook workbook, HttpServletRequest request,
            HttpServletResponse response) throws Exception {  // (3)
        Sheet sheet;
        Cell cell;

        sheet = workbook.createSheet("Spring");
        sheet.setDefaultColumnWidth(12);

        // write a text at A1
        cell = getCell(sheet, 0, 0);
        setText(cell, "Spring-Excel test");

        cell = getCell(sheet, 2, 0);
        setText(cell, model.get("serverTime").toString());
    }

    private Cell getCell(Sheet sheet, int rowNumber, int cellNumber) {
        Row row = sheet.createRow(rowNumber);
        return row.createCell(cellNumber);
    }

    private void setText(Cell cell, String text) {
        cell.setCellValue(text);
    }
}
項番 説明
(1)
本例では、@Componentアノテーションを使用して、component-scanの対象としている。
前述した、org.springframework.web.servlet.view.BeanNameViewResolverの対象とすることができる。
(2)
AbstractXlsxViewを継承する。
(3)
buildExcelDocumentメソッドを実装する。
AbstractXlsxViewは、EXCELのレンダリングに、Apache POIを利用している。
そのため、Mavenのpom.xmlに POIの定義を追加する必要がある。
<dependencies>
    <!-- omitted -->
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi-ooxml</artifactId>
    </dependency>
</dependencies>

Note

上記設定例は、依存ライブラリのバージョンを親プロジェクトである terasoluna-gfw-parent で管理する前提であるため、pom.xmlでのバージョンの指定は不要である。

Note

xlsファイル形式をサポートしたい場合は AbstractXlsViewを使用されたい。

詳細は、AbstractXlsViewのJavaDocを参照されたい。

Note

POI 5.1.0 以降のバージョンではApache Log4j v2を依存関係に含んでおり、POIがLog4j 2を直接使用するようになった。

Macchinetta Server Framework (1.x)ではロガーのAPIにSLF4Jを使用しているが、APIの優先順位の関係でLog4j 2が有効になる可能性がある。

これを回避するため、log4j-to-slf4jを依存関係に含むことでSLF4Jへブリッジされるようになる。

<dependencies>
    <!-- omitted -->
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-to-slf4j</artifactId>
    </dependency>
</dependencies>

上記設定例は、依存ライブラリのバージョンを親プロジェクトである terasoluna-gfw-parent で管理する前提であるため、pom.xmlでのバージョンの指定は不要である。

Warning

SLF4J adapter (log4j-to-slf4j-2.0.jar) とSLF4J bridge (log4j-slf4j-impl-2.0.jar) を一緒に使用すると、SLF4J と Log4j 2の間でイベントが際限なくルーティングされてしまうため注意すること。

詳しくは、Log4j 2 to SLF4J Adapterを参照されたい。


4.11.2.2.2. ViewResolverの定義

設定は、PDFファイルをレンダリングする場合と同様である。詳しくは、ViewResolverの定義を参照されたい。

4.11.2.2.3. コントローラでのViewの指定

BeanNameViewResolverにより、コントローラで”sampleExcelView”を返却することで、Springのコンテキストで管理されたBeanIDにより、”sampleExcelView”であるViewが使用される。

Javaソース

@GetMapping(value = "home", params= "excel")
public String homeExcel(Model model) {
    model.addAttribute("serverTime", new Date());
    return "sampleExcelView";  // (1)
}
項番 説明
(1)
“sampleExcelView” をメソッドの戻り値として返却することで、
Springのコンテキストで管理された、SampleExcelViewクラスが実行される。
上記の手順を実行した後、以下に示すようなEXCELを開くことができる。
FILEDOWNLOAD EXCEL

Picture - FileDownload EXCEL


4.11.2.3. 任意のファイルのダウンロード

前述した、PDFやEXCELファイル以外のファイルのダウンロードを行う場合、共通ライブラリが提供している、org.terasoluna.gfw.web.download.AbstractFileDownloadViewを継承したクラスを実装すればよい。
他の形式にファイルレンダリングするために、AbstractFileDownloadViewでは、以下を実装する必要がある。
  1. レスポンスボディへの書き込むためのInputStreamを取得する。
  2. HTTPヘッダに情報を設定する。
コントローラでファイルダウンロードを実装するための手順は、以下で説明する。

4.11.2.3.1. カスタムViewの実装

テキストファイルをダウンロードする例を用いて、説明を行う。

AbstractFileDownloadViewを継承したクラスの実装例

@Component  // (1)
public class TextFileDownloadView extends AbstractFileDownloadView {  // (2)

    @Override
    protected InputStream getInputStream(Map<String, Object> model,
       HttpServletRequest request) throws IOException {

    @Override
    protected InputStream getInputStream(Map<String, Object> model,
          HttpServletRequest request) throws IOException {  // (3)
        Resource resource = new ClassPathResource("abc.txt");
        return resource.getInputStream();
    }

    @Override
    protected void addResponseHeader(Map<String, Object> model,
          HttpServletRequest request, HttpServletResponse response) {  // (4)
        response.setHeader("Content-Disposition",
              "attachment; filename=abc.txt");
        response.setContentType("text/plain");
    }
}
項番 説明
(1)
本例では、@Componentアノテーションを使用して、component-scanの対象としている。
前述した、org.springframework.web.servlet.view.BeanNameViewResolverの対象とすることができる。
(2)
AbstractFileDownloadViewを継承する。
(3)
getInputStreamメソッドを実装する。
ダウンロード対象の、InputStreamを返却すること。
(4)
addResponseHeaderメソッドを実装する。
ダウンロードするファイルに合わせた、 Content-Dispositionや、ContentTypeを設定する。

4.11.2.3.2. ViewResolverの定義

設定は、PDFファイルをレンダリングする場合と同様である。詳しくは、ViewResolverの定義を参照されたい。


4.11.2.3.3. コントローラでのViewの指定

BeanNameViewResolverにより、コントローラで”textFileDownloadView”を返却することで、Springのコンテキストで管理されたBeanIDにより、”textFileDownloadView”であるViewが使用される。

Javaソース

@GetMapping(value = "download")
public String download() {
    return "textFileDownloadView"; // (1)
}
項番 説明
(1)
“textFileDownloadView” をメソッドの戻り値として返却することで、
Springのコンテキストで管理された、TextFileDownloadViewクラスが実行される。

Tip

前述してきたように、SpringはModelの情報をいろいろなViewにレンダリングすることができる。Springでは、複数のレンダリングエンジンをサポートしており、さまざまなViewを返却することが可能である。

詳細は、Spring の公式ドキュメントSpring Framework Documentation -View Technologies-を参照されたい。