1.

【JasperReports】jar内のjrxml/jasperファイルを読み込む方法

▶
この記事の要点
  • IDE では動くのに jar にすると jrxml / jasper が読めないのは、jar の中身は「ファイル」ではないから
  • new File("src/main/resources/...") や Paths.get() ではなく、クラスパスから InputStream で読む
  • getClass().getResourceAsStream("/jrxml/test.jrxml") → JasperCompileManager.compileReport(InputStream)
  • コンパイル済みの .jasper は JRLoader.loadObject(InputStream) や JasperFillManager.fillReport(InputStream, ...) で読む
  • リソースは src/main/resources/ 配下に置き、パスの先頭スラッシュの有無に注意する

症状

開発環境(Eclipse や IntelliJ IDEA から実行)では普通にパス指定すれば jrxml ファイルなどのリソースファイルを読み込めるのに、本番環境にデプロイするために jar(Spring Boot の実行可能 jar を含む)にすると、ファイルが読み込めなくなることがあります。典型的には次のような例外になります。

java.io.FileNotFoundException: src/main/resources/jrxml/test.jrxml (No such file or directory)
java.nio.file.FileSystemNotFoundException
net.sf.jasperreports.engine.JRException: java.io.FileNotFoundException: ...

原因: jar の中のリソースはファイルシステム上に存在しない

IDE から実行しているときは、src/main/resources や target/classes が実際のフォルダとしてディスク上に存在するため、相対パスや File で読めてしまいます。

ところが jar にすると、リソースは zip 形式の jar ファイルの中に格納されます。jar の中身には OS のファイルパスが存在しないため、new File(...) や FileInputStream では開けません。また、getResource() で取った URL を Paths.get(url.toURI()) でファイルパスに変換しようとすると、jar:file:...!/jrxml/test.jrxml という形式の URL になるため FileSystemNotFoundException になります。

したがって、リソースファイルを指定する際は通常のファイルパスではなく、クラスパス上のリソースとして URL か InputStream で読む必要があります。

対処法: クラスパスから読み込む

以下は URL を取得して jrxml を読み込む例です。jrxml は src/main/resources/jrxml ディレクトリ内にあるものとします。

String path = "/jrxml/test.jrxml";
URL url = this.getClass().getResource(path);
JasperReport jr = JasperCompileManager.compileReport(url.openStream());

実務では、InputStream を直接取得して try-with-resources で確実に閉じる書き方がおすすめです。

import java.io.InputStream;
import net.sf.jasperreports.engine.*;

try (InputStream in = getClass().getResourceAsStream("/jrxml/test.jrxml")) {
    if (in == null) {
        throw new IllegalStateException("jrxml が見つかりません: /jrxml/test.jrxml");
    }
    JasperReport report = JasperCompileManager.compileReport(in);
    JasperPrint print = JasperFillManager.fillReport(report, params, dataSource);
    JasperExportManager.exportReportToPdfFile(print, "/tmp/out.pdf");
}

コンパイル済みの .jasper を読む場合

帳票を出すたびに jrxml をコンパイルすると時間がかかるため、事前にコンパイルした .jasper を jar に含めておく方法もよく使われます。この場合はコンパイル不要で、次のように読み込みます。

try (InputStream in = getClass().getResourceAsStream("/jasper/test.jasper")) {
    JasperReport report = (JasperReport) JRLoader.loadObject(in);
    JasperPrint print = JasperFillManager.fillReport(report, params, dataSource);
}

JasperFillManager.fillReport(InputStream, Map, ...) のように、.jasper の InputStream を直接受け取るオーバーロードもあります。JRLoader は net.sf.jasperreports.engine.util パッケージのクラスです。

パスの書き方: 先頭スラッシュの有無

書き方基準例
Class#getResource("/jrxml/a.jrxml")クラスパスのルートresources/jrxml/a.jrxml を読む
Class#getResource("jrxml/a.jrxml")そのクラスのパッケージcom/example/jrxml/a.jrxml を探してしまう
ClassLoader#getResource("jrxml/a.jrxml")クラスパスのルート(先頭スラッシュは付けない)resources/jrxml/a.jrxml を読む

先頭スラッシュを付け忘れて null が返り、NullPointerException になるのはよくあるミスです。上の例のように null チェックを入れておくと、原因がすぐに分かります。

Spring Boot での書き方

Spring を使っている場合は、ClassPathResource や ResourceLoader を使うと読みやすくなります。

import org.springframework.core.io.ClassPathResource;

ClassPathResource res = new ClassPathResource("jrxml/test.jrxml");
try (InputStream in = res.getInputStream()) {
    JasperReport report = JasperCompileManager.compileReport(in);
}

res.getFile() は jar の中では使えない(例外になる)ので、必ず getInputStream() を使ってください。

よくある落とし穴

  • リソースが jar に含まれていない: src/main/java 側に jrxml を置くと、Maven の既定設定ではビルド時にコピーされない。src/main/resources に置く
  • 大文字小文字の違い: Windows の開発環境では Test.jrxml と test.jrxml が同じ扱いになるが、jar 内や Linux では区別される
  • サブレポートや画像のパス: jrxml 内で指定するサブレポート・画像のパスも、ファイルパスのままだと同じ問題が起きる。パラメータで InputStream や JasperReport オブジェクトを渡す方法に変える
  • フォントの扱い: PDF 出力で日本語フォントを使う場合、フォント拡張の jar がクラスパスに含まれているかも確認する

確認方法

  1. jar tf app.jar | grep jrxml で、リソースが jar 内のどのパスに入っているか確認する(Spring Boot の jar では BOOT-INF/classes/ 以下がクラスパスのルート)
  2. java -jar app.jar で起動し、IDE ではなく jar の状態で帳票が出力できるか確かめる
  3. getResourceAsStream の戻り値が null でないことをログで確認する

関連

子ページ

子ページはありません

同階層のページ
  1. jar内のjrxml/jasperファイルを読み込む方法
  2. jar内のサブレポートを読み込む方法

最近更新/作成されたページ